当前课程知识点:ARM微控制器与嵌入式系统 > 第五章 ARM微控制器的各种外设 > 5.5.3 ARM微控制器外设:PWM编程实例—电子音乐 > 5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
各位同学大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家继续我们的
ARM微控制器与嵌入式系统的MOOC之旅
那么这个单元呢
进入了我认为我们在这个课里
最最好玩的单元之一
那么我们真的要用一个引脚
来产生PWM的脉冲信号
来把我们的音乐给玩起来
我们前面做了很多的铺垫
PWM什么原理啊
一个引脚接在PTC8
是PWM模块的Channel4
等等怎么编程
这些基本知识都有了
最最核心内容是我们怎么把它玩起来呢
我们来看看我们的电路
之前提到我们这个引脚
是通过了一个晶体管来控制了一个蜂鸣器
这是一个频率调制的蜂鸣器
也就是说它播放出的音高
是跟我们给它的电信号的频率
在很大的一个可听到的范围内是一致的
换言之我们产生什么频率的脉冲电信号
给这个蜂鸣器
这个蜂鸣器就会得到不同频率的声音
那么我们很多同学都学过
高中物理或者大学物理
都知道声音的三要素
这个频率决定了音高对吧
当然还有响度还有音色
这个在一个简单蜂鸣器里我们就不探讨了
我们只讨论频率
放一个最简单的电子音乐
那么我们的音高怎么对应不同的频率呢
那么我们看一看这张图
显示了钢琴的这个黑白琴键
每一个因素对应的频率
我们这是一门电子学的课
当然不会回忆从巴赫的时代开始
怎么制定这个钢琴率
但是大家有一个概念
就是不同的音高肯定对应不同频率
有一些同学可能不熟的一个概念
是从1、2、3、4、5、1、6、7
到这个低音、高音的八度之间
它的频率刚好是2倍的关系
也就是高音1低音1之间的频率
正好是二分之一的关系
所以我们的中央C
C大调的1
它的频率非常好记
是261.5赫兹
那么对应的高音1
就是523赫兹
所以我们在这张表上
可以查到每一个音所应该产生的频率
都在这个百赫兹量级
我们都知道人的耳朵的听觉范围
是从几十赫兹到四万赫兹左右这个范围
所以我们要控制我们的电信号
产生这些对应的频率
去驱动我们的蜂鸣器
我们就能听到这些声音
这里要讲的是
其实有很多同学的耳朵非常的好
你会听这个音准不准
特别像曾老师这种五音不全的
我们在编程的时候又是一个计算机爱好者
我最开始的时候会觉得
哎呀这个261.5赫兹
523赫兹这种数实在是不美
太难记了
我会特别喜欢用2的整数幂
比如256赫兹
512赫兹这不挺好嘛
然后编程序
编着编着就忘了
直到有一天有个同学跟我说曾老师
你给的例程这个音实在是太不准了
所以大家可以看一看
你的PWM寄存器的值的设置
如何得到这些值
那么有的同学说
我如果不是C大调怎么办呢
音乐好的同学当然你可以去对不同的调
去查你的音所对应的频率
那么有了这些音
我们从哪里得到一个曲子呢
这当然也不是问题了
现在有很多什么钢琴爱好者网络
什么各种歌曲歌谱网
特别音乐不识五线谱的同学
曲盲的同学你可以在网上找到像这样的简谱
比如说很怀旧
这个1995年前后《仙剑奇侠传》
这个游戏的第一代
它的谱 它的曲 背景音乐
就可以在网上找到
那有的同学喜欢看韩剧
比如说你可以找这个《Kiss the Rain》
这样一首钢琴曲的简谱
那么找到这些谱
你知道了它的谱的构成
我们用一种非常
解构主义非常不美的方式
从计算机爱好者的角度来看这些东西
它就变得不那么艺术了
它是什么呢
它是一个由不同频率和不同的持续时间长度
构成的二维数组
是不是被我这么一讲
大家顿时失去了对音乐那种美好的感觉
但是我们编程的时候
确实无奈
我们一定要把它转换成
可以用程序表达的方式
好 那么问题来了
如果我们写了一个很有意思的小程序
我们做了一个很有意思的功能
希望我们的微控制器 配置蜂鸣器
加上PWM模块
前面讲的编程把音乐玩起来
大家怎么实现它
那么与之类似的还有一个问题
就有同学说曾老师你讲了很多外设
你什么时候教我们
来把这个东西给封装起来
封装成函数加以使用
所以今天的内容不是一次实验
而是我给大家讲一讲这些思想
然后带着大家把它给实现
那么在这样一种编程实现里
我觉得思维上
可能会有四个步骤要加以考虑
第一个步骤我们PWM模块会有基础的时钟
这个时钟是20.97兆除以128
得到一个频率大约是百K量级
所以我把它define成了一个宏
在我们的代码的第一句
然后第二个概念呢
我们以这个时钟为单元进行计数
我们会设置一个计数器的值
决定当前的这个引脚
所对应这个模块的周期
周期被寄存器决定了
反过来就决定了频率
所以其实我们可以计算我们给寄存器
所应该设的值应该是
我们的系统时钟TPM模块所使用的时钟
也就这百K赫兹这个时钟
与我们目标频率做除法得到的商
大家想想这个概念
所以我们用我们已有的输入时钟频率
除以我们想要得到的比如说523赫兹
或者261.5赫兹
这个音乐的频率
这两者一除得到的值 取整
应该是我们给这个周期寄存器
这个MOD寄存器未来将要设置的值
它会决定PWM模块输出信号的频率
所以我们提前可以把我们要的不同音的
音高的这些目标值
这些寄存器将要设的值
像曾老师给的代码这样
尝试都定义成一个一个的宏
那么我会使用L1到L7
M1到M7
还有这个高音的1到高音的7
来表示三个8度里头的
1、2、3、4、5、6、7 对不对
然后当然我还会00这样的值来表达休止符
暂时不放音乐
那么一旦完成了这一系列的宏定义
我们未来对于这个寄存器设置的时候
只用设置这些宏
这些比较形象的宏
M3、M0
并能把它配置我们要的音高
使我们的代码
进行了第一次的封装 变得简化
第二个层面呢
就是对于我们所学到的PWM模块
去做串口的时候
大家已经开始有这种感觉
我们会尝试把它封装成有限个函数
来方便我们的主程序或者别的程序
在代码复用的情况下灵活的调用
那么在我们播放音乐
这样一个特别简单的功能里
我们可以把它封装如右所示的这两个函数
一个是我们TPM或者PWM模块的
init函数初始化
那么这个里头其实就完成了上节课
我给大家讲过的那几个初始化的步骤
配置时钟打开
配置时钟源
配置引脚给PWM模块用
配置TPM模块的初始值寄存器
那么我下面把它封装成了一个特别小的函数
来设置当前PWM模块这个通道
这个模块所产生的频率
那么目前这个函数的写法我只给了一个参数
这个参数是什么呢
就是前面我们每个音高的宏定义
对应的要给
周期的那个MOD寄存器
将要赋予的值
我作为参数给这个函数
所以这个函数一调用
我给了一个参数给他
他就把我们当前的PWM模块
配置为我将要要的那个频率
改变频率
然后里头我有一句话
第二句话大家注意读一读
是将这个C4V
也就是我们通道4的
这个占空比的这个寄存器
设置为了
我们周期这个值的一半
也就使我们的占空比永远是50%
频率高 频率低
设置的周期值大 周期值小
它总是它的一半
那么我们这里留了个引子
大家思考一下
如果我们改这个占空比
可能会有什么效果
声音的三要素
频率 响度和音色
所以愿意动脑筋同学你可以联想
他可能会对声音的音量产生影响
所以这个尾巴留给大家以后自己尝试改
我们可以尝试把这个函数
改成两个参数的函数进一步的封装
同时设置频率又设置音量
这个留给大家以后
那么第二个问题已经讲完
就是有了这两个函数
我们完成了对于这个模块的
一个封装 对不对
我们有了对于音高
以一种宏的方式表达出来
我们有了模块封装成初始化函数
和改变频率的函数
接下来是什么呢
接下来还有两个问题
在下一页我会给大家讲
一个就是如何把我们刚才查简谱的
那种曲子表征为C语言计算机编程
可以呈现和记录的数据结构
如何设计一种程序的机制
让这些函数
这些宏定义和这个数据结构
有机的运行起来
成为一种音乐的播放
是不是还有这两个问题
我来带着大家理清
如何把一个程序表达出来
那么对于数据结构
我刚才说过
其实我是故意说的
一个音乐从一个非常不美的
编程的角度去看
它不是美妙的艺术和旋律
而是一个由音高和音长组成的
二维的一个数组
或者叫每一个源都是一个二维的向量
那么这样一个音高和一个音长
一一对应
我们用一个数据结构来表征它
我们可以有很多种灵活的方法
比如说有的同学学C语言学的好一点的
我会用联合体
我们会用结构体
我会用更复杂的公式
但是根据曾老师多年的教学经验我会发现
学我们这样一门课的入门的同学
往往你最熟悉的数据结构也就仅限于数组
那OK数组就数组
我们用两个数组
来一一对应记录我们的简谱
比如说我这里放了一个简谱
是《玛丽的小羊》对不对
那么大家会发现有了我上一页所讲的
宏定义这张谱
是不是变得非常可读啊
我们以M3、M2、M1
是不是就很容易看出我们标记的是
3212这样的简谱构成了一个数组
每一个源对应一个音高
那么与之一一对应的
我们用另外一个构建的数组
来记录每个音的音长
我们以0X01为单元记录一个八分之一音符
这样把半拍、一拍
分别用不同的音长加以记录
是不是我们就成功的
将一个最最简单的曲谱
表征为了两个数组对吧
然后我在这两个数组的尾部
都留了一个00和0FF这样的特殊字符
来表征告诉我们的程序
这是曲子的结尾部分
便于我们待会编程来使用
那好如果我们不看曾老师给出的代码
大家想一想这些东西都有了
你自己怎么写一个程序把它的音乐放出来
可能绝大多数同学最容易想到的
还是在for循环里头
在main函数里头我写个delay函数
把delay的时间调调调调到这个
最小的音乐的时间长度节拍单元
然后每过一个delay单元
去查一查是不是要放下一个音
然后从数组分别取出下一个源进行播放
然后播放再到一个delay的时候呢
看看这个音的音长有没有播放完
我们可以拿这个音的长度函数做减减运算
减到0为止
这些有些同学可能能想到
然后当这个音的长度播放完了
再在数组里各取下一个源
切换到下一个音的长度
和这个音高来进行播放
在这个过程当中
调用我们刚才封装好的PWM函数的
改变频率的那个接口函数
是不是一种编程思想
那么这个思想如果能想到这一步
其实已经非常不错了
那么在这里我给出的例程呢
是跟我们上一个单元直接关联的
曾老师课非常有意思
就是我会把知识一个一个串起来
每一个单元模块去嵌套着使用前面的知识
大家一个单元都不要落
你就会发现你的本领越来越大
那么在这里我们要使用的是什么呢
是我们最最简单的那个
SysTick时钟中断函数
我们配置好一个时钟中断函数
调一调时钟中断发生的周期频率
比如说我这个SYSTICK_Init里头
我把它调成了八分之一秒发生一次中断
来对应一个这个八分之一音符对吧
那么我们这个时钟中断
每八分之一秒发生一次
我的时钟中断的SysTick_Handler
这个中断响应函数是不是就如我这样所写
里头的函数提示
其实就是我刚才所讲的这个思想 代码
大家可以停下来仔细读一读
就是依次取这个数组里头的源
然后根据这个数组里头的音高
调整PWM里面播放的音高
调用PWM接口函数
同时把这个音
所要播放的音长记录下来
做减减运算
然后根据进入中断的次数
来决定这个音是否播放完了
一旦播放完了用一个指针变量
去寻找下一个源
把这个数组里对应的
下一个源取出来 进行播放
直到遇到数组里头特殊字符表征的
这个曲子的尾部
当然我们在程序上可以灵活
决定我们把程序写成这个曲子
滚动播放 还是播放一遍就停
那么这样就实现了
用中断模式驱动下的一个
音乐的播放
那么我多一个问题
这样一种方式编写
我反复给大家加深对于中断的概念
跟我们最开始说的用main函数里头
用delay来做
有什么好处呢
给大家想几秒钟
大家会发现这样子写了函数以后
我们的main其实仍然是空的
我们播放音乐整个功能
只是在main函数里
增加了一个中断的初始函数
在时钟中断的服务函数里
完成了音乐的播放
他完全没有改变我们原有main函数的
那些结构和功能
所以这样一种模式
使我们在做完了音乐功能里
我们非常容易并行的
去开发我们程序所希望完成的其他功能
也非常容易在一个
已经有完善功能的程序里
添加音乐播放功能
多多少少大家会发现
中断这样一种前后台的模式
帮助我们把我们的代码给剥离开
使我们的任务
使我们的程序
变得更加的清晰 易懂
那么贯穿这样一种思想里头
我们实现了一个非常好玩的音乐播放
而我更希望大家的是去读一读这些代码
你能不能有更好的主意
更巧妙的代码来实现音乐的播放
把我们这个板子从现在开始
越来越有意思的给玩起来
那就是这节课的
这个单元的内容
到这儿结束
-1.1 课程概览
--1.1 课程概览
-1.2 如何学好嵌入式系统
-2.1 计算机的基本概念、发展历史
-2.2 从晶体管到CPU
-2.3 概念CPU、微控制器MCU和嵌入式系统
-2.4 八卦计算机史
-2.5 不同领域、不同系列的嵌入式系统
-2.6 ARM历史与MKL25Z128 MCU
--2.6 ARM历史与MKL25Z128 MCU【习题】
-3.1 CPU的基本结构和运行机制
-3.2.1 堆栈的概念
--3.2.1 堆栈的概念【习题】
-3.2.2 堆栈的概念-头脑体操
-3.3.1 ARM的体系结构
--3.3.1 ARM的体系结构【习题】
-3.3.2 ARM的体系结构-头脑体操
-3.4.1 中断的概念和机制
-3.4.2 中断子程的概念和编程
-3.5 复位、时钟、存储器和总线
--3.5 复位、时钟、存储器和总线【习题】
-3.6 小结:MCU的总体结构和程序运行机制
--3.6 小结:MCU的总体结构和程序运行机制【习题】
-4.1 第一种外设:IO
-4.2 IO外设的编程实操-点亮LED
-4.3 IO外设的进阶知识
-4.4 嵌入式开发的基本概念与工具链
-4.5 嵌入式开发的进阶知识
-4.6 嵌入式开发中的C语言(上)
--4.6 嵌入式开发中的C语言(上)【习题】
-4.7 嵌入式开发中的C语言(下)
--4.7 嵌入式开发中的C语言(下)【习题】
-E0.1 实验零 开发板的初步认识与工具链的安装
-E0.2 实验零 体验一个例程的编译与下载
-E0.3 实验零 编写第一个程序:点亮核心板LED
-E1 实验一 点灯秘籍
-5.1 ARM微控制器外设学习概述
-5.2.1 ARM微控制器外设:通讯
-5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)【习题】
-5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器的外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)【习题】
-5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
--5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
-5.2.5 ARM微控制器外设:UART的寄存器编程(上)
--5.2.5 ARM微控制器外设:UART的寄存器编程(上)
-5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)【习题】
-E2 实验二 UART编程实操
-5.3.1 ARM微控制器外设:IO的中断编程(上)
-5.3.2 ARM微控制器外设:IO的中断编程(下)
-5.4.1 ARM微控制器外设:定时器的原理
-5.4.2 ARM微控制器外设:定时器的编程
--5.4.2 ARM微控制器外设:定时器的编程【习题】
-E3 实验三 定时器中断编程实操
-5.5.1 ARM微控制器外设:PWM的原理
-5.5.2 ARM微控制器外设:PWM寄存器与编程
-5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
--5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
-E4 实验四 数码管显示编程实操
-5.6.1 ARM微控制器外设:SPI通讯简介
--5.6.1 ARM微控制器外设:SPI通讯简介【习题】
-5.6.2 ARM微控制器外设:SPI寄存器与编程
-5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
--5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
-5.7.1 ARM微控制器外设:I2C通讯简介
-5.7.2 ARM微控制器外设:I2C的通讯协议
-5.7.3 ARM微控制器外设:I2C寄存器与编程
--5.7.3 ARM微控制器外设:I2C寄存器与编程【习题】
-5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
--5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
-5.8.1 ARM微控制器外设:ADC简介
-5.8.2 ARM微控制器外设:ADC基础
-5.8.3 ARM微控制器外设:ADC寄存器与编程
-E5 实验五 ADC编程实操
-E6 挑战实验
--E6 挑战实验
-6.1 嵌入式系统的接口与设计
-6.2 嵌入式系统的实例