前言
回顾下之前的章节:
第一章节中我们描述了整个框架的核心设计思路以及主要的文件架构
第二章节中我们基于一个简单的定时器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-7
????441,??495,??556,??589,??661,??742,??833,????///<?普通音1-7
????882,??990,?1112,?1178,?1322,?1484,?1665?????///<?高音1-7
};
const?uint16_t?freq_B[]?=?{
????0,
????248,??278,??294,??330,??371,??416,??467,????///<?低音1-7
????495,??556,??589,??661,??742,??833,??935,????///<?普通音1-7
????990,?1112,?1178,?1322,?1484,?1665,?1869?????///<?高音1-7
};
const?uint16_t?freq_C[]?=?{
????0,
????131,??147,??165,??175,??196,??221,??248,????///<?低音1-7
????262,??294,??330,??350,??393,??441,??495,????///<?普通音1-7
????525,??589,??661,??700,??786,??882,??990?????///<?高音1-7
};
const?uint16_t?freq_D[]?=?{
????0,
????147,??165,??175,??196,??221,??248,??278,????///<?低音1-7
????294,??330,??350,??393,??441,??495,??556,????///<?普通音1-7
????589,??661,??700,??786,??882,??990,?1112?????///<?高音1-7
};
const?uint16_t?freq_E[]?=?{
????0,
????165,??175,??196,??221,??248,??278,??312,????///<?低音1-7
????330,??350,??393,??441,??495,??556,??624,????///<?普通音1-7
????661,??700,??786,??882,??990,?1112,?1248?????///<?高音1-7
};
const?uint16_t?freq_F[]?=?{
????0,
????175,??196,??221,??234,??262,??294,??330,????///<?低音1-7
????350,??393,??441,??495,??556,??624,??661,????///<?普通音1-7
????700,??786,??882,??935,?1049,?1178,?1322?????///<?高音1-7
};
const?uint16_t?freq_G[]?=?{
????0,
????196,??221,??234,??262,??294,??330,??371,????///<?低音1-7
????393,??441,??495,??556,??624,??661,??742,????///<?普通音1-7
????786,??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--
例行求粉,谢谢!
求粉