STM32CubeMX + HAL 库:基于通用定时器输出比较功能实现 PWM 的无源蜂鸣器歌曲演奏实验
本实验通过 STM32 微控制器的通用定时器输出比较功能,生成 PWM 波形 驱动无源蜂鸣器,实现乐音输出与歌曲演奏。实验展示了 PWM 信号的频率与占空比调节原理,以及如何将音乐音符映射为对应 PWM 频率进行播放。通过实验,读者能够掌握 定时器输出比较模式 的基本原理与使用方法,理解 PWM 信号与音频频率的对应关系,并掌握占空比与频率对音调和音量的控制技巧。在实践中,能够完成无源蜂鸣器驱动,
1 概述
1.1 实验目的
本实验旨在通过 STM32 微控制器的 通用定时器输出比较功能,配置并产生 PWM 波形,从而驱动无源蜂鸣器实现乐音的输出与歌曲的演奏。实验展示了定时器 PWM 信号的配置方法、频率与占空比的调节原理,以及将音乐音符转化为对应 PWM 频率的实现过程。通过完整的代码实现与参数设置,帮助读者深入理解 STM32 定时器在 PWM 信号生成中的作用,以及其在外设驱动中的应用方式。
通过本实验,读者不仅能够掌握 STM32 通用定时器输出比较模式 的基本原理和实际使用方法,还能熟悉 PWM 信号与音频频率的对应关系,理解如何通过占空比与频率控制实现音调和音量的变化。在实践过程中,读者将能够完成无源蜂鸣器的基本驱动,并进一步扩展至 乐曲演奏应用。这对于日后在嵌入式系统中实现 声音提示、音乐播放、报警提醒等功能 提供了技术积累和实践经验,具有重要的教学意义与工程应用价值。
1.2 定时器介绍
以下为STM32F103ZET6芯片的定时器介绍,如果是STM32F103C8T6的定时器有所裁剪
| 定时器类型 | 定时器 | 通道数 | 功能 | 典型应用场景 |
|---|---|---|---|---|
| 高级控制定时器 | TIM1、TIM8 | 4 + 互补输出 | 输出比较(含 PWM 模式,带死区/互补) | 电机控制(FOC)、逆变器、半桥/全桥驱动、舵机控制 |
| 刹车输入 | 电机过流/过压保护 | |||
| 编码器接口 | 高精度转速/角度检测 | |||
| 外部触发同步 | 多定时器联动控制 | |||
| 通用定时器 | TIM2、TIM3、TIM4、TIM5 | 4 | 输出比较(PWM输出) | 蜂鸣器音调、LED 呼吸灯、舵机角度控制、定时脉冲输出、 |
| 输入捕获(PWM测量) | 超声波测距、信号频率/脉宽测量 | |||
| 单脉冲模式 | 输出单次脉冲信号 | |||
| 编码器接口 | 旋转编码器测速/位置检测 | |||
| 外部时钟模式 | 外部事件计数(转速传感器) | |||
| 基本定时器 | TIM6、TIM7 | 无 | 简单定时 | 软件定时、延时 |
| DAC 触发 | DAC 周期采样、波形输出 | |||
| 定时中断 | 周期性任务触发 | |||
| 系统定时器 | SysTick | 1 | 系统时基 / 递减计数 | RTOS 心跳节拍(1ms Tick)、延时函数、任务调度 |
加粗部分是必须掌握也是最常用的功能。
1.3 输出比较原理
输出比较是 STM32 定时器的一种工作模式,通过比较计数器值(CNT)与比较寄存器值(CCR),在匹配时改变定时器通道的输出状态,实现精确的脉冲输出和 PWM 波形生成。
CNT(计数器) :定时器内部递增(或递减)的计数值,用于计时。
ARR(自动重装载寄存器) :设定计数器的最大值,计数器到达 ARR 后清零或反向计数,形成周期性计数。
CCR(比较寄存器 ):存储阈值,当 CNT = CCR 时触发事件,改变输出引脚状态。
定时器开始计数,CNT 从 0 增加到 ARR。
当 CNT < CCR 时,输出引脚保持初始状态(高或低电平)。
当 CNT = CCR 时,根据通道模式(PWM1、PWM2 或普通 OC 模式),输出引脚翻转或置高/置低。
当 CNT 达到 ARR,计数器重置,下一周期重新开始。
在 PWM 模式下,输出比较用于生成占空比可调的方波:
-
PWM 占空比:由 CCR/ARR 决定
-
PWM 频率:由 ARR 和预分频器 PSC 决定
在实际应用中,PWM 波形的生成通常有两类需求:变频定占空比和定频变占空比。在 STM32F103 系列单片机的实验中,可以通过合理配置定时器的预分频系数(PSC)和自动重装载寄存器(ARR),达到简化设置与控制的目的。
-
变频定占空比
-
适用于需要频率可变而占空比相对固定的场合。
-
一般情况下,STM32F103 的系统主频为 72 MHz。若将 PSC 设置为 71,则定时器的计数频率为 1 MHz(即 1 μs 计数一次)。
-
在此配置下,只需通过修改 ARR 的值即可直接改变 PWM 输出的频率,而无需调整占空比。
-
实验中通常将占空比固定为 50%,从而输出标准的方波信号,这样能保证输出波形能量稳定,特别适合用于 无源蜂鸣器的音调控制 或 舵机驱动 等场景。
-
-
定频变占空比
-
适用于需要频率恒定,而占空比可调的场合。
-
常见的设置方法是将 PSC 配置为 7199,ARR 配置为 99,此时输出 PWM 信号的频率为 100 Hz。
-
在该配置下,只需调节 CCR 的取值范围(0~100),即可方便地实现占空比在 0%~100% 之间的线性变化。
-
该方式常用于 LED 调光/呼吸灯 以及 直流电机转速控制 等应用。
-
1.4 蜂鸣器
在本实验中,需要注意 蜂鸣器的驱动特性:
-
单片机直接驱动限制
-
不带驱动电路的蜂鸣器,直接连接到单片机 IO 引脚是无法正常工作的,下图为无驱动电路的蜂鸣器。

-
与 LED 灯不同,LED 所需电流较小,单片机 IO 口可以直接驱动;而蜂鸣器所需电流较大,需要借助 驱动电路(如三极管、MOSFET 等)进行电流放大,才能可靠驱动蜂鸣器发声,下图为蜂鸣器模块,集成了驱动电路。

-
-
有源蜂鸣器 vs 无源蜂鸣器
-
有源蜂鸣器:接通电源即发声,断电即停止,发声频率固定,无法改变音调,因此不适合本实验播放音乐。
-
无源蜂鸣器:本实验采用无源蜂鸣器,通过 占空比为 50% 的 PWM 波 驱动。改变 PWM 频率即可改变蜂鸣器的音调,从而播放不同音高的音符,实现音乐演奏。
-
通过使用无源蜂鸣器和 PWM 控制,本实验能够演示 如何通过定时器输出比较功能调整 PWM 频率,实现音调变化和简单音乐播放。
2 STM32CubeMx配置
2.1 SYS设置

2.2 RCC设置


2.3 TIM3设置

3 VSCode
3.1 驱动代码
#include "beep.h"
#include "oled12864.h"
#include "ds3231.h"
#include "usart.h"
#include "tim.h"
// 音符频率定义(单位:Hz)
#define NOTE_C6 1046
#define NOTE_D6 1171
#define NOTE_E6 1319
#define NOTE_F6 1396
#define NOTE_G6 1567
#define NOTE_A6 1760
#define NOTE_B6 1975
#define NOTE_C7 2093
// ================= 音符时值(单位:ms) =================
#define WHOLE 1000
#define HALF 500
#define QUARTER 250
#define EIGHTH 125
#define REST 0 // 休止符
// 小星星旋律(音符频率数组)
const float melody1[] = {
NOTE_C6, NOTE_C6, NOTE_G6, NOTE_G6, NOTE_A6, NOTE_A6, NOTE_G6, // 第一小节
NOTE_F6, NOTE_F6, NOTE_E6, NOTE_E6, NOTE_D6, NOTE_D6, NOTE_C6, // 第二小节
NOTE_C6, NOTE_C6, NOTE_G6, NOTE_G6, NOTE_A6, NOTE_A6, NOTE_G6, // 第三小节
NOTE_F6, NOTE_F6, NOTE_E6, NOTE_E6, NOTE_D6, NOTE_D6, NOTE_C6 // 第四小节
};
// 音符时长数组(与 melody 对应)
const int durations1[] = {
QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, HALF, // 第一小节
QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, HALF, // 第二小节
QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, HALF, // 第三小节
QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, QUARTER, HALF // 第四小节
};
// 欢乐颂
const float melody2[] = {
NOTE_E6, NOTE_E6, NOTE_F6, NOTE_G6, // E E F G
NOTE_G6, NOTE_F6, NOTE_E6, NOTE_D6, // G F E D
NOTE_C6, NOTE_C6, NOTE_D6, NOTE_E6, // C C D E
NOTE_E6, NOTE_D6, NOTE_D6, // E D D
NOTE_E6, NOTE_E6, NOTE_F6, NOTE_G6, // E E F G
NOTE_G6, NOTE_F6, NOTE_E6, NOTE_D6, // G F E D
NOTE_C6, NOTE_C6, NOTE_D6, NOTE_E6, // C C D E
NOTE_D6, NOTE_C6, NOTE_C6 // D C C
};
// 每个音符对应的时长(四分音符为主,句尾用半拍)
const int durations2[] = {
QUARTER, QUARTER, QUARTER, QUARTER,
QUARTER, QUARTER, QUARTER, QUARTER,
QUARTER, QUARTER, QUARTER, QUARTER,
QUARTER, QUARTER, HALF,
QUARTER, QUARTER, QUARTER, QUARTER,
QUARTER, QUARTER, QUARTER, QUARTER,
QUARTER, QUARTER, QUARTER, QUARTER,
QUARTER, QUARTER, HALF
};
void PlayNote(float freq, int duration) {
if (freq == 0) { // 休止符
HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); // 停止 PWM 信号
} else {
TIM3_CH3_SetPWM(freq, 50);
}
HAL_Delay(duration); // 播放音符持续时间
// HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); // 停止当前音符
TIM3_CH3_SetPWM(freq, 0);
HAL_Delay(50); // 音符间短暂静音
// alarm_display_update();
}
void play_music(uint8_t music){
if(music == 1){
for (int i = 0; i < 28; i++) {
PlayNote(melody1[i], durations1[i]);
}
}else if (music == 2)
{
for (int i = 0; i < 30; i++) {
PlayNote(melody2[i], durations2[i]);
}
}
}
#ifndef __BEEP_H__
#define __BEEP_H__
#include "stm32f1xx_hal.h" // 根据您的STM32系列修改
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
// 函数声明
void PlayNote(float freq, int duration);
void play_music(uint8_t music);
#endif /* __BEEP_H__ */
3.2 main代码
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "beep.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
//启TIM3
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_3);
// 播放旋律
play_music(1);
play_music(2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(500);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
4 实验结论
通过以上代码,本实验演示了如何利用 STM32 通用定时器的输出比较功能(Output Compare, OC),生成 PWM 波 驱动无源蜂鸣器,实现音乐播放。
在实验过程中,学生不仅掌握了 PWM 波的频率与占空比设置方法,还能理解定时器的输出比较功能在实际应用中的作用:
-
通过调整 ARR(自动重装载寄存器) 可以改变 PWM 频率,从而控制音符高低;
-
通过调整 CCR(比较寄存器) 可以改变占空比,保证蜂鸣器方波信号稳定且音质清晰。
利用这些知识,实验实现了播放简单而优美的音乐,如《小星星》《欢乐颂》《送别》等。同时,这种技术也可以拓展到多种 预警与提醒场景:
-
可用于 闹钟提示,播放自定义音乐提醒时间;
-
可通过组合不同频率与时长的音符,实现 不同类型的预警,如警车、救护车、消防车等模拟警报。
本实验不仅提升了学生对 PWM 控制、定时器输出比较功能 的理解,还为嵌入式系统中的 声音输出与报警提示设计提供了实践基础,具有较强的工程应用价值和教学意义。
更多推荐



所有评论(0)