## 引言 无源蜂鸣器(Buzzer)内部不含振荡源,需外部提供方波信号才能发声。通过调节方波频率,即可产生不同音调。STM32 的定时器 PWM 输出能精确产生所需频率,配合简单的音符映射,就能实现类似 MIDI 的音乐播放。本文以 STM32F103 为例,讲解如何用 PWM 驱动无源蜂鸣器,并给出可复用的代码框架。 ## 原理讲解 ### 1. PWM 与蜂鸣器 PWM(脉冲宽度调制)输出固定频率的方波,其频率由定时器的预分频器(PSC)和自动重载值(ARR)决定: `PWM频率 = 定时器时钟 / ((PSC+1) * (ARR+1))` 无源蜂鸣器的音调由方波频率决定,占空比影响音量(通常 50% 左右)。因此,要播放不同音符,只需改变 ARR 值(或 PSC)来调整频率。 ### 2. 音符频率计算 音乐中,标准音 A4(中央 C 上方的 A)频率为 440Hz。其他音符频率可通过公式计算: `f = 440 * 2^((n-69)/12)`,其中 n 为 MIDI 音符编号(A4=69)。 实际应用中,可预定义常用音符频率表,或动态计算。为简化,本文使用静态数组存储从 C4 到 B5 的频率值。 ### 3. 简易 MIDI 播放 MIDI 文件本质是音符事件序列(如 note-on/off)。我们可定义一个结构体数组,包含音符编号和持续时间,模拟 MIDI 播放。通过定时器中断或延时控制每个音符的时长。 ## 硬件连接 - 无源蜂鸣器正极接 STM32 的 PWM 输出引脚(如 PA0,对应 TIM2_CH1) - 蜂鸣器负极接 GND(若蜂鸣器工作电压为 5V,需通过三极管或驱动芯片,但本例假设 3.3V 可直接驱动) ## 配置步骤 ### 1. 使能定时器时钟 使用 TIM2,其时钟来自 APB1,频率为 72MHz(若系统时钟 72MHz)。 ### 2. 配置 GPIO 为复用推挽输出 PA0 映射到 TIM2_CH1,需配置为 AF_PP。 ### 3. 配置定时器 PWM 模式 - 设置 PSC 和 ARR 以产生目标频率 - 设置 PWM 模式 1,占空比 50%(CCR = ARR/2) - 使能输出比较通道和定时器 ### 4. 编写音符播放函数 根据音符频率计算 ARR 值,并更新定时器。 ## 代码实现 以下代码基于 STM32 标准外设库,使用 TIM2 PWM 输出。 ```c #include "stm32f10x.h" // 音符频率表(C4~B5,MIDI编号60~71) const uint16_t note_freq[] = { 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494 }; // 简易MIDI事件:音符编号(0~11),时长(ms) typedef struct { uint8_t note; // 0~11,对应频率表索引 uint16_t dur; // 时长ms } midi_event; // 示例旋律:小星星 const midi_event melody[] = { {0, 400}, {0, 400}, {4, 400}, {4, 400}, {5, 400}, {5, 400}, {4, 800}, {2, 400}, {2, 400}, {3, 400}, {3, 400}, {4, 400}, {4, 400}, {2, 800}, {0, 400}, {0, 400}, {4, 400}, {4, 400}, {5, 400}, {5, 400}, {4, 800}, {2, 400}, {2, 400}, {3, 400}, {3, 400}, {4, 400}, {4, 400}, {2, 800} }; void TIM2_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 配置 PA0 为复用推挽 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 定时器时基:72MHz/(PSC+1) = 1MHz,即1us计数一次 TIM_TimeBaseStructure.TIM_Period = 1000; // ARR,初始值,后续更新 TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC = 71 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // PWM 模式1,占空比50% TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 500; // CCR = ARR/2 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_Cmd(TIM2, ENABLE); } // 设置音符频率:根据音符索引更新ARR void Set_Note(uint8_t note_index) { uint16_t freq = note_freq[note_index]; // 定时器时钟1MHz,ARR = 1MHz / freq - 1 uint16_t arr = 1000000 / freq - 1; TIM_SetAutoreload(TIM2, arr); // 更新占空比(50%) TIM_SetCompare1(TIM2, arr / 2); } // 播放旋律 void Play_Melody(void) { uint16_t i; for (i = 0; i < sizeof(melody)/sizeof(melody[0]); i++) { Set_Note(melody[i].note); Delay_ms(melody[i].dur); // 停止发声:设置ARR为0或关闭输出 TIM_SetCompare1(TIM2, 0); Delay_ms(20); // 音符间隔 } } // 简单延时函数(需实现) void Delay_ms(uint32_t ms) { // 使用SysTick或循环实现 } int main(void) { TIM2_PWM_Init(); while (1) { Play_Melody(); Delay_ms(1000); } } ``` ## 注意事项 - **频率精度**:ARR 计算时取整,可能产生微小误差,但人耳可接受。若需更精确,可调整 PSC 或使用更高分辨率定时器。 - **占空比**:50% 占空比方波音色较纯,但实际蜂鸣器可能对占空比敏感,可适当调整。 - **停止发声**:将 CCR 设为 0 可关闭输出,但定时器仍在运行。也可禁用 TIM 输出,但简单置零更高效。 - **驱动能力**:STM32 GPIO 输出电流有限,若蜂鸣器功率较大,需外接三极管或驱动芯片。 - **扩展 MIDI**:可解析标准 MIDI 文件,提取音符事件,替换 melody 数组。注意时基转换。 - **实时性**:使用延时播放会阻塞 CPU,若需多任务,可改用定时器中断或 DMA。 ## 总结 本文通过 STM32 定时器 PWM 驱动无源蜂鸣器,实现了基于音符频率表的简易 MIDI 播放。核心在于理解 PWM 频率与音符的关系,以及灵活配置定时器。此方法可轻松扩展至更复杂的音乐播放,甚至结合按键交互。希望本文能为你开发蜂鸣器应用提供参考。