用STM32演奏《两只老虎》:PWM与定时器的创意实践
记得第一次用STM32的GPIO让蜂鸣器发出"滴"声时的兴奋吗?那种控制硬件的成就感让人上瘾。但单调的蜂鸣声很快会失去新鲜感——为什么不试试让开发板唱首歌呢?本文将带你用STM32的PWM和定时器实现《两只老虎》的演奏,从乐理到代码,完整呈现嵌入式开发的趣味实践。
1. 硬件准备与基础原理
手边的STM32开发板(正点原子或野火等常见型号)和无源蜂鸣器就是我们的"乐器"。无源蜂鸣器与有源型号的关键区别在于它没有内置振荡电路,需要外部提供方波信号才能发声。这正是PWM大显身手的地方。
PWM(脉冲宽度调制)通过快速切换高低电平来控制平均电压。对于蜂鸣器来说,PWM的频率决定了音高,而占空比则影响音量。STM32的定时器模块可以精确生成PWM信号:
- 定时器时钟:STM32F103系列通常以72MHz运行
- 预分频器:降低基准时钟频率(如分频系数为9时得到8MHz)
- 自动重装载值:与PWM频率直接相关,计算公式为
频率 = 时钟频率/(ARR+1)
// 示例:设置PWM频率为440Hz(标准A4音高) uint16_t clock_freq = 8000000; // 8MHz (72MHz/9) uint16_t arr_value = (clock_freq / 440) - 1; __HAL_TIM_SET_AUTORELOAD(&htim4, arr_value);2. 从乐谱到代码的转换艺术
《两只老虎》的简谱是我们编程的蓝图。每个音符需要转换为:
- 频率值:对应PWM的ARR设置
- 节拍时长:由定时器中断控制
建立音符频率对照表是第一步。国际标准音高A4=440Hz,其他音符按十二平均律计算:
| 音符 | 频率(Hz) | 代码常量 |
|---|---|---|
| C4 | 262 | NOTE_C4 |
| D4 | 294 | NOTE_D4 |
| E4 | 330 | NOTE_E4 |
| F4 | 349 | NOTE_F4 |
| G4 | 392 | NOTE_G4 |
| A4 | 440 | NOTE_A4 |
| B4 | 494 | NOTE_B4 |
《两只老虎》前两小节的编码示例:
uint8_t melody[] = {NOTE_C4, NOTE_D4, NOTE_E4, NOTE_C4}; // 音符序列 uint8_t rhythm[] = {4, 4, 4, 4}; // 四分音符时长3. 定时器编排音乐节奏
单独的PWM只能产生持续音,需要定时器来管理节拍切换。配置一个定时器(如TIM2)产生1ms中断,用于音符时长计数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { static uint16_t counter = 0; if (++counter >= current_note_duration) { counter = 0; play_next_note(); // 切换到下一个音符 } } }节拍时长计算要考虑曲速。假设《两只老虎》速度为120BPM(每分钟120拍),则:
- 四分音符时长 = 60000ms / 120 = 500ms
- 在代码中用定时器中断次数实现(如500次1ms中断)
4. 完整实现与优化技巧
将各个模块整合,音乐播放流程如下:
- 初始化PWM定时器(TIM4)和节拍定时器(TIM2)
- 加载《两只老虎》的音符和节拍数据
- 启动定时器中断
- 在中断服务程序中管理音符切换
高级优化技巧:
- 动态音量控制:通过调整PWM占空比实现强弱变化
void set_volume(uint8_t vol) { uint16_t pulse = (ARR_value * vol) / 100; __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_2, pulse); }- 多曲目管理:使用结构体数组存储不同歌曲
typedef struct { uint8_t *notes; uint8_t *durations; uint16_t length; uint16_t tempo; } Song; Song songs[] = { {two_tiger_notes, two_tiger_durations, sizeof(two_tiger_notes), 120}, // 添加更多歌曲... };- 按键控制:通过外部中断实现播放/暂停/切歌
实际项目中,我遇到过因中断优先级设置不当导致音乐卡顿的情况。调试发现TIM2的中断优先级低于其他外设中断,调整NVIC设置后问题解决:
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // 设置为最高优先级5. 创意扩展与实践建议
掌握了基础播放功能后,可以尝试:
- LED视觉同步:让LED随音乐节奏闪烁
- 录音功能:通过ADC采集音频并记录音符序列
- 无线控制:通过蓝牙或WiFi远程选曲
- 和弦效果:使用多个定时器同时产生不同频率
建议先用示波器观察PWM输出波形,确认频率准确后再连接蜂鸣器。不同型号蜂鸣器的最佳工作频率可能略有差异,可通过微调ARR值获得最佳音质。