简介:野火STM32指南者开发板是嵌入式入门的常用平台,资源内含一个基于该开发板的完整工程,实现通过蜂鸣器播放《红尘情歌》,面向正在学习STM32的开发者,尤其是想掌握GPIO控制、定时器中断以及音频驱动原理的爱好者。资源包为RAR压缩包,大小约4.33MB,共177个文件,以C源文件、H头文件、编译生成的O/D目标文件和Keil工程配置为主,包含可直接打开的uvprojx工程文件,方便查看代码和重新编译。功能上支持按键KEY1启动播放、复位键重置状态,通过定时器产生不同频率驱动蜂鸣器,代码覆盖音符频率编码、中断服务程序、音乐数据循环读取等关键环节,能帮助读者理解蜂鸣器发声机制与嵌入式音频播报的基本思路。目前已有4744人浏览学习,适合作为STM32进阶练习、课程设计或项目复现的参考。资源内文件类型齐全,工程结构清晰,可以辅助快速定位到定时器配置和音乐播放函数。 把野火STM32指南者开发板拿在手里,跑通第一个蜂鸣器例程的时候,所有人听到的都是“嘀——嘀——嘀”那种死板声音。我当时总觉得哪里不对劲:蜂鸣器既然能被驱动,为什么不能像手机铃声一样响出一段旋律?后来查了一堆资料才明白,问题不在写代码,而在蜂鸣器本身。这个项目就是用指南者配合定时器PWM,让蜂鸣器一段段“唱”出《红尘情歌》,整个过程踩了不少坑,但也把STM32的定时器、PWM、引脚重映射、音乐频率换算这些知识点串起来了。如果你手头有一块STM32F103的板子,不管是不是指南者,想搞懂“单片机如何播放音乐”,这篇应该能帮你省下不少时间。
1. 让蜂鸣器开口唱歌的前提:有源和无源是两种完全不同的东西
1.1 为什么有源蜂鸣器只能“滴”不能“哆来咪”
蜂鸣器按内部结构分两类:有源蜂鸣器和无源蜂鸣器。“有源”的“源”指的是振荡源,不是电源。有源蜂鸣器内部集成了一颗多谐振荡器,只要你给它通上直流电,它自己就会以固定频率振动,一般是2.7kHz左右。这块板子一上电,你听到的“嘀”声就是它发出来的。这种蜂鸣器你不管写什么程序,声音永远是同一个音调,因为它的频率被锁死了,程序改不了。
无源蜂鸣器内部没有振荡源,它的本质就是一坨线圈加振膜,或者一片压电陶瓷。它自己不产生频率,必须由外部给它一个方波信号,方波的频率是多少,它振动出来的声音就是多少赫兹。也就是说,音高完全由软件控制,频率变了,音调就变了。这就是无源蜂鸣器能够播放《红尘情歌》的根本原因。
做个不太严谨的类比:有源蜂鸣器像一个只会吹一个调的口哨,无源蜂鸣器像一个喇叭,你给我什么信号,我就出什么声音。
1.2 判断你的蜂鸣器类型,以及指南者上的实际接法
最笨也最有效的判断方法:把蜂鸣器接到3.3V电源上,正负一碰,如果立刻响,就是有源;如果不响,就是无源。或者直接看封装底部的丝印和规格书,有源蜂鸣器一般会标“内置振荡电路”或者型号里带“Active”,无源蜂鸣器标的是“External Drive”。
野火指南者开发板上蜂鸣器的驱动引脚是PB5,经过一颗S8550三极管做电流放大。这里有个需要注意的地方:指南者不同批次的板子,板载蜂鸣器到底是有源还是无源,不完全是统一的。如果你的板载蜂鸣器是有源的,直接用它播放音乐只会听到一个固定频率的刺耳声音。我这次做的时候为了排除干扰,直接外接了一个3.3V无源蜂鸣器模块,信号线接到PB5,电源接3.3V,GND接板子的GND。PCB上的板载蜂鸣器如果冲突,可以断开或者用飞线绕过,以你自己的原理图为准。
从驱动极性来说,PB5输出高电平时三极管导通,蜂鸣器通电;输出低电平时三极管截止,蜂鸣器断电。这个“高电平导通”的电路对无源蜂鸣器播放音乐其实很合适,后面配置PWM时就按这个极性来。
2. 把《红尘情歌》拆成“频率+时长”:音乐的数据化过程
2.1 一个音高对应一个频率:十二平均律快速查表
要让STM32播放音乐,第一步是把简谱里每个音转换成具体的频率值。音乐里的标准音A4是440Hz,其他音高都通过十二平均律推出来。公式是:f = 440 × 2^(n/12),其中n是目标音与A4相差的半音数,比A4高就是正数,低就是负数。比如C4,也就是中央C,比A4低9个半音,算出来就是440 × 2^(-9/12) ≈ 261.63Hz。
实际项目中我不会每次都在单片机上算这个公式,而是直接把C调常用音高的频率算好,做成一张查表。下面这张表就是《红尘情歌》主旋律里用到的音高:
| 简谱唱名 | 频率(Hz) | 说明 |
|---|---|---|
| 低音3 | 165 | 基本用不到,但保留 |
| 低音5 | 196 | 歌曲低音区偶尔出现 |
| 低音6 | 220 | 歌曲低音区常用 |
| 1(do) | 262 | C4 |
| 2(re) | 294 | D4 |
| 3(mi) | 330 | E4 |
| 5(sol) | 392 | G4 |
| 6(la) | 440 | A4,标准音 |
| 高音1 | 523 | C5 |
| 高音2 | 587 | D5 |
| 高音3 | 659 | E5 |
| 高音5 | 784 | G5 |
在代码里我直接用宏定义把这些频率写死,比如#define NOTE_G4 392,这样乐谱数组写起来跟简谱一样直观。
2.2 一个节拍对应一个时长:4/4拍怎么量化
决定音高的是频率,决定节奏的是每个音符持续的时间。我这里用的《红尘情歌》版本是4/4拍,也就是以四分音符为一拍,每小节四拍。程序中四分音符用一个基准值Q表示,八分音符就是Q/2,二分音符就是Q×2,全音符就是Q×4。
我取Q = 500ms,也就是每分钟120拍的速度,听起来比较轻快,跟原曲那种中速的感觉差不多。如果你想更慢更有感情,把Q改成600甚至700都行。歌曲里那种“停顿”用休止符表示,频率为0,时长照常保留。
宏定义这样写就够了:
#define Q 500 // 四分音符,一拍 #define E Q / 2 // 八分音符 #define H Q * 2 // 二分音符2.3 用结构体数组描述一首歌
把频率和时长打包成一个结构体,再做成数组,一首歌就是一张“乐谱数据表”。结构体定义如下:
typedef struct { uint16_t freq; // 频率(Hz),0表示休止 uint16_t duration; // 时长(ms) } MusicNote;播放的时候,程序从头到尾遍历数组,每取到一个音符,就设置一次PWM频率,保持对应的时长,然后切到下一个。这种设计扩展性很好,想换歌只需要改数组内容,播放逻辑完全不用动。我把这个数组写成const,让它存在Flash里而不是RAM里,STM32F103的RAM本来就不大,一首歌几百个音符全塞RAM里有点浪费。
3. PB5上的PWM配置:TIM3通道2部分重映射的完整思路
3.1 为什么非要用PB5,为什么是部分重映射
指南者的板载蜂鸣器已经焊死在PB5上,所以我优先考虑把PWM信号输出到PB5。查一下STM32F103的引脚复用表,TIM3的通道2默认是PA7,但它支持部分重映射,重映射之后TIM3_CH2会跑到PB5上。这就是“部分重映射”的含义:不是所有通道都换位置,而是把一部分通道重新分配到别的引脚。
还要注意一个连带影响:启用TIM3的部分重映射后,TIM3_CH1会从默认的PA6变到PB4。如果你的项目里同时用到了PA6或者PA7的定时器功能,就要特别小心,重映射会让它们彻底失效。如果只是像我一样单纯想用PB5输出PWM,那这个重映射是划算的。另外,PB4在STM32F103上默认是JTAG的NJTRST引脚,如果哪天你想把PWM输出到PB4,必须先禁用JTAG功能。我用的是PB5,不受这个限制,但如果你照抄代码然后发现PB4不工作,原因就在这。
3.2 预分频计算与PWM输出代码
STM32F103的系统时钟经过SystemInit初始化后是72MHz,TIM3挂载在APB1上,APB1预分频为2时,定时器时钟会倍频到72MHz。我要产生一个频率可变的方波,思路是把计数频率固定下来,然后通过修改自动重装载值ARR来改变PWM周期。
我设置预分频PSC = 71,这样定时器计数频率 = 72MHz / (71 +
本文还有配套的精品资源,点击获取