M-Arch(12)第十一个示例:如何用无源蜂鸣器播放音乐
前言
回顾下之前的章节:
第一章节中我们描述了整个框架的核心设计思路以及主要的文件架构
第二章节中我们基于一个简单的定时器OS实现了串口的数据打印,并完成了通用crc模块的设计和测试
第三章节中我们给出了真随机数和伪随机数的概念和代码示例,并在架构上对接口进行了重构
第四章节中我们回顾了FMC的基本知识,并给出了示例,后面我们将在设计IAP的时候再次使用到FMC
第五章节中我们使用ADC和DMA搭建了一个通用的采样框架,并通过串口给出了采样的数据示例
第六章节中我们总结了DAC的基本使用方法,并通过DAC生成了任意频率的正弦波,三角波和方波
第七章节中我们总结下时钟的概念,并给出了获取系统中各模块的时钟频率的代码
第八章节中我们介绍了如何通过串口的DMA来实现串口数据的收发
第九章节中我们介绍了定时器的使用,以及如何产生普通占空比PWM以及互补带死区的PWM,这在控制中十分重要
第十章节中我们介绍了SPI的概念,以及用三线SPI的时序驱动DS1302时钟芯片的方法,进一步掌握了SPI的使用
第十一章节中我们介绍了通过PWM控制无源蜂鸣器的方法
本文我们将介绍通过PWM控制无源蜂鸣器播放音乐的方法
关键字:STM32,GD32,PWM,无源蜂鸣器
蜂鸣器
蜂鸣器有2种:无源蜂鸣器和有源蜂鸣器,所谓有源,是指蜂鸣器内部带有震荡源,通电就可以响;所谓无源,是指蜂鸣器内部不带震荡源,需要频率信号驱动。
有源蜂鸣器 | 无源蜂鸣器 |
---|---|
有震荡源-频率固定 | 无震荡源-频率可控 |
管脚有方向 | 管脚无方向 |
单向有内阻,一般超过1KΩ | 双向有内阻,一般几百Ω |
有电路板 | 无电路板 |
通电即发声 | 频率信号驱动 |
贵 | 便宜 |
由于无源蜂鸣器由频率信号控制,我们可以通过调整控制频率的方式来播放音乐。
人耳能够感受到的声音频率范围为20HZ ~ 20KHZ,音乐的频率一般是几百HZ,不超过2KHZ。
假设我们的定时器频率是100MHZ,我们需要通过分频的方式把PWM的频率降到音乐的频率范围之内:把prescaler值设置为999,频率降为100KHZ,通过调整period使输出频率在声音的范围内。
例如,C调音阶1的频率是262HZ,那么period值就是:100K/262≈382
我们把接口进行封装(代码GD32):
void timer3_freq_change(uint32_t freq_base, uint16_t freq)
{uint16_t period = freq_base / freq;timer_autoreload_value_config(TIMER2, period);
}
其中,freq_base是prescaler之后的值,freq是声音的频率值,我们把这个接口再封装一把。
#define TIMER_FREQ_BASE 100000 // 100K
#define TIMER_PRESCALER 999 // 分频系数 * 100K = 100M
#define PLAY_SOUND(note) timer3_freq_change(TIMER_FREQ_BASE, note)
到这里,播放声音的接口就搞定了。
一点乐理
上面我们已经搞定了播放声音的方式,那么在乐理中,我们还需要知道音乐的调子和节拍。
看一段简谱。
简谱的左上角一般标示了这首歌的调子,节拍数和BPM。
《祝你生日快乐》是C调,3/4表示4分音符为1拍,每小节3拍,小蝌蚪=100表示每分钟100拍。
有了这几个数据,我们就可以算出来每拍音符的时长=60s/100=600ms。
关于简谱中的记法:
音符的下点表示低音,上点表示高音,-表示延长音,一个下划线表示时长减半,N个下划线表示时长缩小2^N倍。
()表示间奏或者过门,‖::‖表示重复,上括号表示连音。
如上划横线部分,表示:
前一节:7播放600ms,-表示7继续播放600ms,5下划线播放300ms,5下划线播放300ms,共播放1800ms。
后一节:6播放600ms,5播放600ms,2上加点播放600ms,共播放1800ms。
代码部分
音符
我们把音符的频率用数组枚举出来备用:
const uint16_t freq_A[] = {0,221, 248, 278, 294, 330, 371, 416, ///< 低音1-7441, 495, 556, 589, 661, 742, 833, ///< 普通音1-7882, 990, 1112, 1178, 1322, 1484, 1665 ///< 高音1-7
};const uint16_t freq_B[] = {0,248, 278, 294, 330, 371, 416, 467, ///< 低音1-7495, 556, 589, 661, 742, 833, 935, ///< 普通音1-7990, 1112, 1178, 1322, 1484, 1665, 1869 ///< 高音1-7
};const uint16_t freq_C[] = {0,131, 147, 165, 175, 196, 221, 248, ///< 低音1-7262, 294, 330, 350, 393, 441, 495, ///< 普通音1-7525, 589, 661, 700, 786, 882, 990 ///< 高音1-7
};const uint16_t freq_D[] = {0,147, 165, 175, 196, 221, 248, 278, ///< 低音1-7294, 330, 350, 393, 441, 495, 556, ///< 普通音1-7589, 661, 700, 786, 882, 990, 1112 ///< 高音1-7
};const uint16_t freq_E[] = {0,165, 175, 196, 221, 248, 278, 312, ///< 低音1-7330, 350, 393, 441, 495, 556, 624, ///< 普通音1-7661, 700, 786, 882, 990, 1112, 1248 ///< 高音1-7
};const uint16_t freq_F[] = {0,175, 196, 221, 234, 262, 294, 330, ///< 低音1-7350, 393, 441, 495, 556, 624, 661, ///< 普通音1-7700, 786, 882, 935, 1049, 1178, 1322 ///< 高音1-7
};const uint16_t freq_G[] = {0,196, 221, 234, 262, 294, 330, 371, ///< 低音1-7393, 441, 495, 556, 624, 661, 742, ///< 普通音1-7786, 882, 935, 1049, 1178, 1322, 1484 ///< 高音1-7
};
这里用一个数组把它们组织起来方便调用:
const uint16_t *FREQS[] = {freq_A, freq_B, freq_C, freq_E, freq_F, freq_G};
同时对外开放一个枚举量方便外部调用(外部只需要知道调号就可以用,并不需要关心具体的频率值):
typedef enum
{TONE_A,TONE_B,TONE_C,TONE_D,TONE_E,TONE_F,TONE_G,
}tone_e;
制谱
从上面的推断来看,实际上单个音符的播放时间只跟bpm有关系,在编程上我们需要关心的只有一个点:
如何表示不同播放时长的音符?
这里,我们需要用点数学或者编程的技巧。
我们用有规律的数字来表示不同的播放时长,用一个表来说明一下:
音符 | 数学表示方法 |
---|---|
0(空) | 0 |
低音1-7 | 1-7 |
普通1-7 | 8-14 |
高音1-7 | 15-21 |
--- | --- |
低音1-7(1个下划线) | 31-37 |
普通1-7(1个下划线) | 38-44 |
高音1-7(1个下划线) | 45-51 |
--- | --- |
低音1-7(2个下划线) | 61-67 |
普通1-7(2个下划线) | 68-74 |
高音1-7(2个下划线) | 75-81 |
--- | --- |
低音1-7(3个下划线) | 91-97 |
普通1-7(3个下划线) | 98-104 |
高音1-7(3个下划线) | 105-111 |
--- | --- |
低音1-7(4个下划线) | 121-127 |
普通1-7(4个下划线) | 128-134 |
高音1-7(4个下划线) | 135-141 |
这里用到了线性的表示方法,我们在写程序的时候,就可以很愉快的用if else搞定了。
void play_music(uint16_t bpm, tone_e tone, uint16_t *data, uint16_t len)
{uint16_t delay = 60000/bpm;uint8_t index;const uint16_t *freq = FREQS[tone];for (index = 0; index < len; index++){if (data[index] <= HIGH7){/* 基本音阶 */PLAY_SOUND(freq[data[index]]);delay_ms(delay);}else if (data[index] <= HIGH7_){/* 1个_ */PLAY_SOUND(freq[data[index]-30]);delay_ms(delay/2);}else if (data[index] <= HIGH7_2){/* 2个_ */PLAY_SOUND(freq[data[index]-60]);delay_ms(delay/4);}else if (data[index] <= HIGH7_3){/* 3个_ */PLAY_SOUND(freq[data[index]-90]);delay_ms(delay/8);}else if (data[index] <= HIGH7_4){/* 4个_ */PLAY_SOUND(freq[data[index]-120]);delay_ms(delay/16);}}
}
在上面的程序中,我们再次用到了编程技巧,用简单的宏定义替换数字,方便我们制谱。
0 = BASE0
1低音 = LOW1
1 = BASE1
1高音 = HIGH1
1一个下划线 = BASE1_
1两个下划线 = BASE1_2
1三个下划线 = BASE1_3
1四个下划线 = BASE1_4
有了这个关系,我们就可以很轻松的制谱了。
例如《祝你生日快乐》中的划横线部分,表示出来就是:
BASE7, BASE7, BASE5_, BASE5_
BASE6, BASE5, HIGH2
到这里,我们就可以很轻松的翻译一首歌了。
我们把宏定义也贴一把:
#define BASE0 0
#define LOW1 1
#define LOW2 2
#define LOW3 3
#define LOW4 4
#define LOW5 5
#define LOW6 6
#define LOW7 7
#define BASE1 8
#define BASE2 9
#define BASE3 10
#define BASE4 11
#define BASE5 12
#define BASE6 13
#define BASE7 14
#define HIGH1 15
#define HIGH2 16
#define HIGH3 17
#define HIGH4 18
#define HIGH5 19
#define HIGH6 20
#define HIGH7 21#define LOW1_ (30+LOW1)
#define LOW2_ (30+LOW2)
#define LOW3_ (30+LOW3)
#define LOW4_ (30+LOW4)
#define LOW5_ (30+LOW5)
#define LOW6_ (30+LOW6)
#define LOW7_ (30+LOW7)
#define BASE1_ (30+BASE1)
#define BASE2_ (30+BASE2)
#define BASE3_ (30+BASE3)
#define BASE4_ (30+BASE4)
#define BASE5_ (30+BASE5)
#define BASE6_ (30+BASE6)
#define BASE7_ (30+BASE7)
#define HIGH1_ (30+HIGH1)
#define HIGH2_ (30+HIGH2)
#define HIGH3_ (30+HIGH3)
#define HIGH4_ (30+HIGH4)
#define HIGH5_ (30+HIGH5)
#define HIGH6_ (30+HIGH6)
#define HIGH7_ (30+HIGH7)#define LOW1_2 (60+LOW1)
#define LOW2_2 (60+LOW2)
#define LOW3_2 (60+LOW3)
#define LOW4_2 (60+LOW4)
#define LOW5_2 (60+LOW5)
#define LOW6_2 (60+LOW6)
#define LOW7_2 (60+LOW7)
#define BASE1_2 (60+BASE1)
#define BASE2_2 (60+BASE2)
#define BASE3_2 (60+BASE3)
#define BASE4_2 (60+BASE4)
#define BASE5_2 (60+BASE5)
#define BASE6_2 (60+BASE6)
#define BASE7_2 (60+BASE7)
#define HIGH1_2 (60+HIGH1)
#define HIGH2_2 (60+HIGH2)
#define HIGH3_2 (60+HIGH3)
#define HIGH4_2 (60+HIGH4)
#define HIGH5_2 (60+HIGH5)
#define HIGH6_2 (60+HIGH6)
#define HIGH7_2 (60+HIGH7)#define LOW1_3 (90+LOW1)
#define LOW2_3 (90+LOW2)
#define LOW3_3 (90+LOW3)
#define LOW4_3 (90+LOW4)
#define LOW5_3 (90+LOW5)
#define LOW6_3 (90+LOW6)
#define LOW7_3 (90+LOW7)
#define BASE1_3 (90+BASE1)
#define BASE2_3 (90+BASE2)
#define BASE3_3 (90+BASE3)
#define BASE4_3 (90+BASE4)
#define BASE5_3 (90+BASE5)
#define BASE6_3 (90+BASE6)
#define BASE7_3 (90+BASE7)
#define HIGH1_3 (90+HIGH1)
#define HIGH2_3 (90+HIGH2)
#define HIGH3_3 (90+HIGH3)
#define HIGH4_3 (90+HIGH4)
#define HIGH5_3 (90+HIGH5)
#define HIGH6_3 (90+HIGH6)
#define HIGH7_3 (90+HIGH7)#define LOW1_4 (120+LOW1)
#define LOW2_4 (120+LOW2)
#define LOW3_4 (120+LOW3)
#define LOW4_4 (120+LOW4)
#define LOW5_4 (120+LOW5)
#define LOW6_4 (120+LOW6)
#define LOW7_4 (120+LOW7)
#define BASE1_4 (120+BASE1)
#define BASE2_4 (120+BASE2)
#define BASE3_4 (120+BASE3)
#define BASE4_4 (120+BASE4)
#define BASE5_4 (120+BASE5)
#define BASE6_4 (120+BASE6)
#define BASE7_4 (120+BASE7)
#define HIGH1_4 (120+HIGH1)
#define HIGH2_4 (120+HIGH2)
#define HIGH3_4 (120+HIGH3)
#define HIGH4_4 (120+HIGH4)
#define HIGH5_4 (120+HIGH5)
#define HIGH6_4 (120+HIGH6)
#define HIGH7_4 (120+HIGH7)
SHOW TIME
祝你生日快乐
代码:
void play_sheng_ri_kuai_le(void)
{uint16_t bpm = 100;tone_e tone = TONE_C;uint16_t gc[] = {/* 间奏 */BASE5, BASE5,HIGH5, HIGH3, HIGH1,BASE7, BASE6, BASE6,BASE0, HIGH4_, HIGH4_,HIGH3, HIGH1, HIGH2,HIGH1, HIGH1,/* 第一段 */BASE5_, BASE5_, // - 祝你BASE6, BASE5, HIGH1,BASE7, BASE7, BASE5_, BASE5_, // 乐,祝你BASE6, BASE5, HIGH2,HIGH1, HIGH1, BASE5_, BASE5_, // - 祝你HIGH5, HIGH3, HIGH1,BASE7, BASE6, BASE6,BASE0, HIGH4_, HIGH4_, // - 祝你HIGH3, HIGH1, HIGH2,HIGH1, HIGH1,/* 第二段 */BASE5_, BASE5_, // - 祝你BASE6, BASE5, HIGH1,BASE7, BASE7, BASE5_, BASE5_, // 乐,祝你BASE6, BASE5, HIGH2,HIGH1, HIGH1, BASE5_, BASE5_, // - 祝你HIGH5, HIGH3, HIGH1,BASE7, BASE6, BASE6,BASE0, HIGH4_, HIGH4_, // - 祝你HIGH3, HIGH1, HIGH2,HIGH1, HIGH1,/* 结束 */BASE5_, BASE5_, // - 祝你HIGH5, HIGH3, HIGH1,BASE7, BASE6, BASE6,BASE0, HIGH4_, HIGH4_, // - 祝你HIGH3, HIGH1, HIGH2,HIGH1, HIGH1, HIGH1};play_music(bpm, tone, gc, ARRAY_NUM(gc));
}
沧海一声笑
代码:
void play_cang_hai_yi_sheng_xiao(void)
{uint16_t bpm = 66;tone_e tone = TONE_A;uint16_t gz0[] = {/* 间奏 */BASE5_2, BASE6_2, HIGH1_2, HIGH2_2, HIGH5_, HIGH2_,BASE5_2, BASE6_2, HIGH1_2, HIGH2_2, HIGH5_, HIGH2_};uint16_t gc123[] = {/* 第一 二 三段 沧海笑 苍天笑 江山笑 */BASE6_, BASE6_2, BASE5_2, BASE3_, BASE2_2, BASE1, BASE1,BASE3_, BASE2_2, BASE1_, LOW6_2, LOW5_2, LOW5, LOW5,LOW5_, LOW6_2, LOW5_, LOW6_2, LOW1_, LOW2_2, BASE3_, BASE5_,LOW6_, LOW5_2, BASE3_2, BASE2_2, BASE1_, BASE2};uint16_t gc45[] = {/* 第四段 清风笑 苍生笑 */BASE6_, BASE6_2, BASE5_2, BASE3_, BASE2_2, BASE1, BASE1,BASE3_, BASE2_2, BASE1_, LOW6_2, LOW5_2, LOW5, LOW5,LOW5_, LOW6_2, LOW5_, LOW6_2, LOW1_, LOW2_2, BASE3_, BASE5_,LOW6_, LOW5_2, BASE3_2, BASE2_2, BASE1_, BASE1, BASE1,};uint16_t gz1[] = {/* 间奏 */LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,LOW6_2, BASE1_2, BASE2_2, BASE3_2,BASE6_, BASE6_2, BASE5_2, BASE3_, BASE2_, BASE1, BASE1,BASE3_2, BASE5_2, BASE3_2, BASE2_2, BASE1_, LOW6_, LOW5, LOW5,LOW5_, LOW6_2, LOW5_, LOW6_2, LOW1_, LOW2_2, BASE3_, BASE5_2,BASE6_, BASE5_2, BASE5_2, BASE5_2, BASE3_2, BASE2_2, BASE1_2, BASE2, BASE2};uint16_t last[] = {/* 结束 */BASE6_, BASE6_2, BASE5_2, BASE3_, BASE2_2, BASE1, BASE1,BASE3_, BASE2_2, BASE1_, LOW6_2, LOW5, LOW5,LOW5_, LOW6_2, LOW5_, LOW6_2, BASE1_, BASE2_2, BASE3_, BASE5_2,BASE6_, BASE5_2, BASE5_2, BASE5_2, BASE3_2, BASE2_2, BASE1_2, BASE2, BASE2};play_music(bpm, tone, gz0, ARRAY_NUM(gz0));play_music(bpm, tone, gc123, ARRAY_NUM(gc123));play_music(bpm, tone, gc123, ARRAY_NUM(gc123));play_music(bpm, tone, gc123, ARRAY_NUM(gc123));play_music(bpm, tone, gc45, ARRAY_NUM(gc45));play_music(bpm, tone, gz1, ARRAY_NUM(gz1));play_music(bpm, tone, gc45, ARRAY_NUM(gc45));play_music(bpm, tone, last, ARRAY_NUM(last));play_music(bpm, tone, last, ARRAY_NUM(last));
}
--EOF--
例行求粉,谢谢!
M-Arch(12)第十一个示例:如何用无源蜂鸣器播放音乐相关推荐
- 2020年12月31日flash禁用后网页如何播放rtmp视频流
2020年12月31日flash禁用后网页如何播放rtmp视频流 众所周知,2020年底flash将不再被各大浏览器支持,那么以前利用flash技术做的产品该怎么办呢?我就遇到了这样的问题. 公司有一 ...
- 12.13记录//QQDemo示例程序源代码
笔记的完整版pdf文档下载地址: https://www.evernote.com/shard/s227/sh/ac692160-68c7-4149-83ea-0db5385e28b0/5742995 ...
- html禁止自动播放音乐代码,HTML Audio autoplay用法及代码示例
音频自动播放属性用于设置或返回音频是否应在加载后立即开始播放.它可以用来指定音频在加载后应立即自动开始播放. 用法: 返回自动播放属性:audioObject.autoplay 设置自动播放属性:au ...
- 【sketchup 2021】草图大师的高级工具使用4【截面工具的使用与示例、场景工具的使用与示例、动画创建与播放的使用与示例、相机工具的使用与示例】【重要】
文章目录 不透明度高级使用.填充材质高级使用 材质贴图应用 山体与全景天空.图层工具.标记管理器.视图与样式 截面工具 基础功能 创建剖切面 创建说明 创建剖解面并移动 创建剖解面并旋转 多面剖切创建 ...
- 【Android】入门级连接网络示例: 网页浏览和播放网络MP3
前提:使用可以联网的模拟器或者手机调试 一,更改xml文件 <?xml version="1.0" encoding="utf-8"?> <L ...
- jquer each 遍历的结果不显示 null_SpringBoot系列(三十一)- Thymeleaf如何用th:each 做条件遍历
步骤1:基于前面的知识点步骤2:先运行,看到效果,再学习步骤3:模仿和排错步骤4:TestController步骤5:普通遍历步骤6:带状态的遍历步骤7:结合 select步骤8:结合 单选框步骤9: ...
- 播放音乐的html语言,Html5音频和视频播放示例详解
html5中的音频和视频 codebase="swflash.cab#version=6,0,10,0"> 您的浏览器不支持video! var video = docume ...
- android wms 窗口,Android6.0 WMS(十一) WMS窗口动画生成及播放
上一篇我们我们分析到有VSync信号过来,最后会调用WindowAnimator的animateLocked函数来生成和播放动画,这篇我们我们主要从这个函数开始分析. animateLocked函数 ...
- 12.【最详细】如何用命令行cmd运行java程序
0.前置知识 1.从IDEA中使用命令行的方法 (1)直接在IDEA中使用: (2)从IDEA到cmd: 第一步:从IDEA中打开代码文件所在目录 第二步:从资源管理器中进入cmd的方法 在文件地址栏 ...
- 野火A7学习第十一次(驱动无源蜂鸣器)
1 理论学习 2 实战演练 任务:驱动do ra mi fa - 的发声,每个音调0.5s,占空比50% 2.1 设计规划 2.2 波形绘制 在duo一个周期内的波形变化,计数值小于占空比计数值的时候 ...
最新文章
- Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream
- JavaHelp软件的一个定制实用程序类
- builds error
- ML之NB:基于NB朴素贝叶斯算法训练20类新闻文本数据集进行多分类预测
- TOMCAT报错:HTTP Status 404 -
- CentOS笔记:yum使用说明
- java8 例外网站_Java8兰巴达斯和例外
- 一篇总结的很好的Spring data jpa 文章,里面包含多种查询方式,可以结合api使用
- 最小连通-(代码、分析、汇编)
- 简洁版即时聊天---I/O多路复用使用
- 如何做一个国产数据库(三)
- 3 	2012年 	毕节市 	工业废水排放量 	5466 	万吨	中国城市统计年鉴2013 	335-341
- 公司新来了个软件测试工程师,一副毛头小子的样儿,哪想到是新一代卷王...
- UE4材质(二):PBR材质
- Oracle计算分组分位数
- Python教程:批量合成PDF
- 三极管基础分类, 参数选择及常见型号对比
- 达梦数据库恢复到指定时间点
- 【单片机仿真项目】外部中断0控制8个发光二极管闪烁
- iptables nat及端口映射(转载)