前言

开工第二天,花半个小时搞定pwm驱动无源蜂鸣器播放音乐。

无源蜂鸣器

所谓无源,是指蜂鸣器内部不带震荡源,需要频率信号驱动。

有源蜂鸣器 无源蜂鸣器
有震荡源-频率固定 无震荡源-频率可控
管脚有方向 管脚无方向
单向有内阻,一般超过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);timer_event_software_generate(TIMER2, TIMER_EVENT_SRC_UPG);
}

其中,freq_base是prescaler之后的值,freq是声音的频率值,我们把这个接口再封装一把。

#define TIMER_FREQ_BASE     100000  // 100K
#define TIMER_PRESCALER     639     // 分频系数 * 100K = 64M
#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));
}

视频

M-Arch(番外13)GD32L233评测-来点音乐相关推荐

  1. python的类和对象——类的静态字段番外篇

    什么是静态字段 在开始之前,先上图,解释一下什么是类的静态字段(我有的时候会叫它类的静态变量,总之说的都是它.后面大多数情况可能会简称为类变量.): 我们看上面的例子,这里的money就是静态字段,首 ...

  2. [zt]数学之美番外篇:平凡而又神奇的贝叶斯方法

    数学之美番外篇:平凡而又神奇的贝叶斯方法 Tags: 数学, 机器学习与人工智能, 计算机科学 save it69 saved tags: 贝叶斯 math bayesian algorithm 数学 ...

  3. yxy和志愿者小姐姐番外篇之大宝宝123追番记(补题,淘汰赛)

    1264: yxy和志愿者小姐姐番外篇之大宝宝123追番记 时间限制: 1 Sec  内存限制: 64 MB                                               ...

  4. 转:数学之美番外篇:平凡而又神奇的贝叶斯方法 收藏

    为什么80%的码农都做不了架构师?>>>    转自:http://blog.csdn.net/pongba/archive/2008/09/21/2958094.aspx 数学之美 ...

  5. Java微信公众平台开发--番外篇,对GlobalConstants文件的补充

    转自:http://www.cuiyongzhi.com/post/63.html 之前发过一个[微信开发]系列性的文章,也引来了不少朋友观看和点评交流,可能我在写文章时有所疏忽,对部分文件给出的不是 ...

  6. Java番外篇2——jdk8新特性

    Java番外篇2--jdk8新特性 1.Lambda 1.1.无参无返回值 public class Test {interface Print{void print();}public static ...

  7. Java番外篇1——正则表达式

    Java番外篇1--正则表达式 1.什么是正则表达式 正则表达式定义了字符串的模式 正则表达式可以用来搜索.编辑或处理文本 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别 2.正则表达式 ...

  8. 【GISER Painter】矢量切片(Vector tile)番外一:Proj4js

    转载:https://www.cnblogs.com/escage/p/6406393.html 说明:番外篇是对正篇矢量切片(Vector tile)中提到的一些值得继续延伸的关注点继续进行探索和学 ...

  9. 你所能用到的数据结构之番外篇---逆袭的面向对象(一)

    对于番外篇,我深刻能明白在大多数人眼里就和电视剧的广告一样,说实话,我也不喜欢这种感觉,因为这样会让人觉得是在欺骗消费者啊~~~阿西巴~~~但是我实在发现如果不在这里对面向对象来个入门级的介绍,后面的 ...

  10. OpenCV-Python实战(番外篇)——利用 SVM 算法识别手写数字

    OpenCV-Python实战(番外篇)--利用 SVM 算法识别手写数字 前言 使用 SVM 进行手写数字识别 参数 C 和 γ 对识别手写数字精确度的影响 完整代码 相关链接 前言 支持向量机 ( ...

最新文章

  1. (转)MySQL 线程池内幕
  2. python怎么检查数据库实例能否链接_python pymysql链接数据库查询结果转为Dataframe实例...
  3. 怎样查看was的服务器信息,WAS 查看服务状态
  4. C++ 术语(C++ Primer)
  5. Django create和save方法
  6. Module build failed (from ./node_modules/babel-loader/lib/index.js) 错误解决
  7. 使用 jQuery Mobile 与 HTML5 开发 Web App (十一) —— jQuery Mobile 事件详解
  8. 【英语学习】【WOTD】pungle 释义/词源/示例
  9. Win10怎么让英伟达独立显卡成为主显卡
  10. FATA[0000] (省略) Are you trying to connect to a TLS-enabled daemon without TLS?
  11. 用DISKGEN恢复硬盘数据
  12. Redisson 配置
  13. 第十一届 蓝桥杯 省 模拟赛 试题+题解 C/C++描述
  14. ios 渐变透明背景_PPT背景常见的6种设计方法
  15. R语言一步到位求数据的均值,频数,标准差,标准误差
  16. python输入月份判断天数用函数的方法,python 月份天数
  17. iOS集成twitter分享
  18. Linux T恤设计大赛 “飞企鹅”获奖
  19. ofo 上海深圳等公司相继注销
  20. adb shell 模拟器 关闭\打开WIFI

热门文章

  1. python描述对象静态特性的数据为_The couple wanted to adopt the black boy they had been _______._学小易找答案...
  2. 太阳能热水器系统设计
  3. 【DP】【Burnside】【多项式】烷基计数
  4. 从顶级会议Interspeech 2019看语音领域大家都在弄个啥嘞
  5. vue 循环播放音乐列表 音量控制
  6. C#中的Socket
  7. 数据分析/运营——数据异常的排查方法
  8. 并发,同步,异步以及事件驱动编程的相关技术
  9. Android 加载SDCard中so库
  10. STRAIGHT特征提取算法学习