IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> M-Arch(12)第十一个示例:如何用无源蜂鸣器播放音乐 -> 正文阅读

[嵌入式]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)

到这里,播放声音的接口就搞定了。

一点乐理

上面我们已经搞定了播放声音的方式,那么在乐理中,我们还需要知道音乐的调子和节拍。

看一段简谱。

5e07c1dd2555a5426505ad113a0cf91e.png
祝你生日快乐

简谱的左上角一般标示了这首歌的调子,节拍数和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-71-7
普通1-78-14
高音1-715-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));
}

沧海一声笑

7c1edeef74903116c11a4ed076735a37.png
沧海一声笑简谱

代码:

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--

例行求粉,谢谢!

5f97e3df0dea0a540337b953ac28a618.png
求粉
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-11-18 11:20:34  更:2021-11-18 11:21:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 8:50:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码