
简介野火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.3VGND接板子的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说明低音3165基本用不到但保留低音5196歌曲低音区偶尔出现低音6220歌曲低音区常用1do262C42re294D43mi330E45sol392G46la440A4标准音高音1523C5高音2587D5高音3659E5高音5784G5在代码里我直接用宏定义把这些频率写死比如#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初始化后是72MHzTIM3挂载在APB1上APB1预分频为2时定时器时钟会倍频到72MHz。我要产生一个频率可变的方波思路是把计数频率固定下来然后通过修改自动重装载值ARR来改变PWM周期。我设置预分频PSC 71这样定时器计数频率 72MHz / (71 本文还有配套的精品资源点击获取