本文源码:https://github.com/Philon/rpi-drivers/tree/master/05-pwm_musicbox
上一篇用LED呼吸灯的方式,基本介绍了PWM原理以及在树莓派上的驱动开发,但总感觉意犹未尽,所以再写一篇PWM的应用场景——PWM+蜂鸣器,实现一个简易的音乐盒。
说明一下: 本篇纯粹是“玩”,并不涉及任何新的知识点,如果不感兴趣可以掠过。
先来看看我实现的效果:《保卫黄河》、《灌篮高手》、《欢乐斗地主》。这些谱子全是我从网上扒下来的,并根据蜂鸣器的效果修改过,自己不是专业搞音乐的,所以难免会有错误的地方。(反正我耳朵里听着没问题就行😁)
1 | philon@rpi:~ $ cd modules/ |
再来看看我是怎么实现的:
- 实现PWM蜂鸣器驱动
musicbox
:通过write
音符给设备节点,播放不同的声音;通过ioctl
控制节拍 - 实现应用层
player_test
,负责读取歌曲的乐谱 - 编写乐谱,其实就是文本简谱,类似下面这首
1 | C 3/4 |
好,具体实现且听我慢慢道来~
PWM蜂鸣器驱动
有关蜂鸣器硬件原理、有源、无源这里不展开讨论。总之本文采用的是树莓派上的PWM0+一个无源蜂鸣器。接线如下图所示:
根据上一篇《PWM呼吸灯》的学习,基本知道PWM对脉冲的控制主要有占空比
和脉冲周期
两部分。用来控制LED的时,占空比可以调节灯光的强弱,在脉冲周期似乎没什么乱用。
对于蜂鸣器声用作声乐,有三个基本要素:音调、节拍、音量大小。
- 音调:由震动频率决定,对应PWM的脉冲周期
- 音量:同样的频率,PWM占空比越高,声音越大
- 节拍,声音的持续时长,和PWM毛关系都没有,做个定时开关即可
综上,其实在蜂鸣器驱动musicbox
里重点实现两个接口:
write
: 解析用户层写入的字符串,例如音调do的高中低音分别为’1`
‘和’1
‘和’1.
‘,然后换算出对应的频率
即可。ioctl
: 解析用户层发来的指令,有节拍、音调、音量等控制。
不同音调的蜂鸣器频率
注意:此部分涉及的乐理知识我不是很懂,基本是从网上抄来的,但我发现F和B调的发音不是很准,估计频率不对。
下表分别是Do Re Mi Fa So La Ti
对应的蜂鸣器震动频率。
wait-!7个音符,怎么会干出13种频率呢?
因为其中涵盖了A-G不同曲调,一首曲子可以由多个调子来演奏,比如我们经常听到的C小调,D大调之类的。其中的乐理只是更为复杂,这里只需要记住:
以C调的Do
为基准,其他调子做相应偏移。例如E调的Do相当于C调的Mi,而A调的Do相当于C调的La。
音域 | 1 | 2 | 3 | 4 | 5 | 6 | C7 | D7 | E7 | F7 | G7 | A7 | B7 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
低音 | 131 | 147 | 165 | 175 | 196 | 221 | 248 | 278 | 312 | 330 | 371 | 416 | 467 |
中音 | 262 | 294 | 330 | 350 | 393 | 441 | 495 | 556 | 624 | 661 | 742 | 883 | 935 |
高音 | 525 | 589 | 661 | 700 | 786 | 882 | 900 | 1112 | 1248 | 1322 | 1484 | 1665 | 1869 |
如何计算PWM的周期
有了不同音符的震动频率,也就得到了PWM的脉冲周期。举个例子,50Hz相当于1秒钟震动50次,那PWM的脉冲周期就应该为1s/50=0.02秒。因此周期的计算公式为:
period = 1s / freq
其中的freq就是音符表中的频率,而1s可以由Linux中的HZ
变量表示。
如何计算PWM占空比
有了脉冲周期,才能计算占空比。一个周期内高电平所占时间越大,输出声音也就越大。所以我们可以通过百分比来决定占空比大小。
假设现在要输出高音3`
,它对应的频率为661,并根据前面的公式求得脉冲周期为12345,而音量为75%,那占空比应该为12345*75/100 = 9528。因此占空比的计算公式为:
duty = period * volume / 100
如何计算节拍
所谓节拍,如2/4拍,表示以4分音符为一拍,每小节有两拍。
但在程序里,节拍即每个音符输出的时长,这一点我并没有在驱动层实现,但做的做法非常简单。并没有引入“小节”和“动次打次”的概念。就是强制一个小节为4秒,如果是2/4拍,就相当于4000/4/2 = 500毫秒,即每个音符默认响0.5秒。如果存在半拍的情况(就是音符下有画线),那时间再减半。
程序中把半拍用圆括号()
表示,遇到左括号就减半时间,遇到右括号就加倍时间,就是那么粗暴。
驱动程序实现
当加载以下驱动后,可以通过命令行echo 1 > /dev/musicbox
来测试是否会响。
1 |
|
应用层加载乐谱
应用层player_test
程序的业务逻辑就简单得多了:
- 加载指定的乐谱文件
- 配置音乐盒的节拍、音调
- 按行读取文件内容(跳过注释行和空行)
- 提取每一行的音符、括号
- 将音符、节拍写入驱动
- 重复第3-5步,直至文件末尾
1 |
|
小结
由于本章没有新的知识点,就不做知识总结了,说说感受。
当蜂鸣器按照我的预期演奏音乐是还是挺开心的,仿佛一下子把我拉回了大学的那个暑假,一个人默默在宿舍鼓弄51单片机的日子。时光荏苒,尽管做的是同一件事,但我现在的软件架构、编程基础不可同日而语。或许我重拾底层技术的同时,也重拾了当年学习的热情吧😊。