当前位置:网站首页>M-Arch(番外13)GD32L233评测-来点音乐
M-Arch(番外13)GD32L233评测-来点音乐
2022-06-12 10:33:00 【趣Python】
前言
开工第二天,花半个小时搞定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-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));
}视频



边栏推荐
- PHP occupies memory
- 2021-03-24
- MySQL injection load_ File common path
- 浅谈三维形状上下文特征3DSC理论及应用
- Mqtt protocol Chinese version
- PHP maximum balance method to solve the problem that the sum of the final percentages is not equal to 100
- Solution to invalid small program scroll into view
- 93. 獲得內網的所有IP地址
- Php:redis uses geospatial
- QT custom window fillets
猜你喜欢

人脸识别pip 安装dlib库失败

MYSQL——内置函数

ASP. Net core permission system practice (zero)

Common methods of string class

Machine learning is not something you can use if you want to use it

Malicious code analysis practice - lab03-03 Exe basic dynamic analysis

容器江湖的爱恨情仇

卡鱼刺别再喝醋吞米饭了!教你2招,让鱼刺安全“跑出来”

Student management system

Malicious code analysis practice -- using apatedns and inetsim to simulate network environment
随机推荐
Failed to load resource: the server responded with a status of 413 (Request Entity Too Large)
One test for twoorthree years, recording some thoughts on test exchange experience
2022京东618预售定金怎么退?京东618定金能退吗?
Solution to invalid small program scroll into view
Remote desktop cannot copy and paste solution
Pycharm view the current version of opencv
机器学习不是你想用,想用就能用
Win10 professional edition user name modification
Get array median
Building 64 bit wampserver and DVWA in win7 virtual machine
The name of a great man
Summary method of lamp environment deployment
Leetcdoe 2037. 使每位学生都有座位的最少移动次数(可以,一次过)
PHP Apple internal purchase callback processing
2022京东618预售定金怎么退?京东618定金能退吗?
Set SVG color
4. creator mode
Mqtt protocol Chinese version
On 3dsc theory and application of 3D shape context feature
MySQL injection load_ File common path