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 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> stm32蜂鸣器播放音乐 -> 正文阅读

[嵌入式]stm32蜂鸣器播放音乐

??最近学习stm32芯片,使用的是蓝桥杯的f103的旧板子,看到上面有蜂鸣器,所以就想写代码来控制蜂鸣器播放一首音乐。
??这里我参考了这篇文章基于STM32F103,用蜂鸣器播放歌曲。同这篇文章一样,我也遇到了蜂鸣器发出的声音不对。参考了这篇文章,以及查找网上的其他资料,最终完成了蜂鸣器的调试,以及歌曲的编写,文章最后会附上代码。
??先对音符这类东西进行说明吧。因为自己也不是学音乐的,一些关于音乐的知识都是网上搜集的,所以如果有讲得不对的地方,也请各位读者在评论指正,我会及时改正。
??首先在百度上搜索每个音符的频率,这里我参考的是音符与频率对照表
C调音符与频率对照表
其实对于哪个调来说,我感觉不出来什么差别(可能我没有什么艺术细胞吧),而且在代码里我的音符的频率对应图片里的音符的频率是高一个八度。【如下图】

//				0   1    2  3   4   5   6   7  8   9  10   11  12  13   14   15   
//				低7 1    2  3   4   5   6   7  高1 高2 高3  高4 高5 高6  高7	高高1
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,0};

??也许我上面说的有些迷糊,但是我们通过上面那张表就可以得到每个音符所对应的频率,这个是我们需要的。
??有了每个音符的频率还不够,我们还需要知道每个音符持续的时间,也就是音符时值。
??这里我拿我的选的一个曲子举例子。
在这里插入图片描述
?? Eb \text{Eb} Eb指的是曲子的调是Eb调,(每个调之前的区别,我个人感觉不出大差别,所以我的音符频率是按C调频率来的),后面的 4 4 \frac44 44?指的是以四分音符为一拍,一小节四拍。在后面的音符=120,指的是一分钟120拍,所以我们从这里就可以知道,一个四分音符的时值是0.5s.
那么什么是四分音符呢?
音符

??如上图,音符下面有三条横线的,就是三十二分音符,有两条横线的就是十六分音符,一条横线的就是八分音符,没有横线的就是四分音符
??这些音符对应的时值分别为(设四分音符的时值为 t t t)

  • 四分音符= t t t
  • 八分音符= t / 2 t/2 t/2
  • 十六分音符= t / 4 t/4 t/4
  • 三十二音符= t / 8 t/8 t/8

??除了上面四个音符外,有时谱子里还会遇到一个音符跟着一个小点,这种称为附点
附点
有附点的音符的时值,与附点跟随的音符有关。附点的时值为跟随的音符时值的一半。所以有附点的音符的时值为 跟随的音符的时值×1.5

例如,图中的 3 ˙ .  ̄ \underline{\dot 3.} 3˙.?的时值计算如下
设四分音符的时值为 t t t
3 ˙  ̄ \underline{\dot 3} 3˙?为八分音符,时值为 t / 2 t/2 t/2
附点的时值为跟随音符的时值的一半,在这里也就是 t / 4 t/4 t/4
因此 3 ˙ .  ̄ \underline{\dot 3.} 3˙.? 的时值为 t / 2 + t / 4 = 3 t / 4 t/2+t/4=3t/4 t/2+t/4=3t/4
//--------------------------------------------------------------------------
举一反三 6. 6. 6.的时值计算如下
设四分音符的时值为 t t t
6 6 6为四分音符,所以时值为 t t t
附点为跟随音符的时值的一半,为 t / 2 t/2 t/2
所以 6. 6. 6.的时值为 t + t / 2 = 3 t / 2 t+t/2=3t/2 t+t/2=3t/2

??关于音符的介绍差不多就到这里了,接下来就是代码相关的东西了。
【蜂鸣器的控制就不用我说了吧,一般原理图上都是用三极管驱动蜂鸣器,所以引脚出高低就能控制蜂鸣器的叫与不叫,至于引脚出高还是低,蜂鸣器响,具体的还是看自己板子上的原理图吧】
首先是定义一个音符的频率数组,至于数组中的数从哪来,可以参考我前面发的音符频率对照表,或者要是知道音符频率的关系,自己算也可以。

//				0   1    2  3   4   5   6   7  8   9  10   11  12  13   14   15   
//				低7 1    2  3   4   5   6   7  高1 高2 高3  高4 高5 高6  高7	高高1
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,0};

接下来就是写谱子的音符,这里我就截我选的曲子

蓝色线截选的音符,就是我让单片机播放的曲子片段。
相应的音符数组如下

u8 BrokenMoon[]={
	3,5,6,8,
	9,8,9,10,8,6,
	5,3,8,9,6,6,8,
	9,8,9,10,12,13,
	15,14,13,14,13,12,13,12,10,
	9,10,8,9,8,9,
	10,6,8,9,8,6,6,5,
	6,5,6,8,9,10,9,5,
	6
};

数组里对应的数字,就是tone数组的元素下标,也就是每个音符。
??然后是关于音符时值的数组【每个音符具体的时值与你选的谱子的曲速和音符种类有关,我在上面都讲解了一下,如果还是没看懂的话,可以去B站看看相关知识,大概几分钟就可以看完】

//因为我选的曲子速度是每分钟120拍,所以可以算出每个音符相对应时值
//时值 全音符2s	二分音符 1s	四分音符 0.5s 八分音符 0.25s
//	   十六分音符 0.125s 	三十二分音符 0.0625s
//比例为32:1
//全音符 64	二分音符 32	四分音符 16	八分音符 8
//十六分音符 4 三十二分音符 2


u8 scale = 16;//每个音符时值放大的比例,用于将时值数组转换为实际的时间长度
//其实scale的值应该为32,但是测试的时候,感觉播放的速度过快,所以调成16
//大家在实际操作的时候,也可以改一改,但是建议能不改scale的值就别改scale的值
u8 BrokenMoonTime[]={
	8,8,8,8,
	16,8,8,16,8,8,
	8,8,8,8,16,8,8,
	16,8,8,16,8,8,
	8,8,2,2,4,8,16,8,8,
	16,8,8,16,8,8,
	12,4,4,4,8,16,8,8,
	12,2,2,8,8,8,8,16,
	32
};

??准备好音符和时值数组之后,一个重要的函数就来了。

void BUZZER_Play(u16 frq){
	u32 time;
	if(frq){
//		time = 500000/((u32)frq);//单个脉冲的高电平持续时间(单位为us)为1*10^6/2f=500000/f	f为音符的频率
		time = 50000/((u32)frq);
		BUZZER_ON();
		delay_us(time);
		BUZZER_OFF();
		delay_us(time*9);//调节占空比
	}
	else delay_us(1000);
}

??这是用蜂鸣器播放一个脉冲的函数。函数的参数,频率frq,即单个音符对应频率。函数执行的就是蜂鸣器播放该频率下的一个脉冲。
??根据,之前的参考的文章,他们的设置高电平的占空比为50%,但我具体测试的时候,发现修改time的值虽然会有使音色改善,但也造成了播放速度过快,后面想了想,觉得蜂鸣器的占空比为50%不太合理,根据测试,选择了10%的占空比,大家具体测试的时候,如果发现音色不好,可以修改高电平的占空比。

具体方法如下:

  1. 先将time= 500000/((u32)frq);写到BUZZER_Play()函数里,然后让蜂鸣器响和不响的延时时间都相同,为time
  2. 下载代码测试音色,根据实际情况,修改time = 500000/((u32)frq)中的500000的值,直到自己一个满意的效果。然后比较修改后time的值与原来time = 500000/((u32)frq)的。将原来time值假设为 t 0 t_0 t0?,调试完之后的值为 t 1 t_1 t1?
  3. 将蜂鸣器响的延迟时间设置为 t 1 t_1 t1?,将蜂鸣器关闭的延迟时间设置为 t 0 ? t 1 t_0-t_1 t0??t1?

修改完的代码,样子如下

void BUZZER_Play(u16 frq){
	u32 time;
	if(frq){
		time = m/((u32)frq);//m根据实际测试结果进行修改
		BUZZER_ON();
		delay_us(time);
		BUZZER_OFF();
		delay_us(t_0-t_1);//t_0-t_1的值根据实际测试结果计算出来的
	}
	else delay_us(1000);
}

??写完播放一个固定频率的脉冲之后,就是写在一段时间内播放固定频率的脉冲的函数。

void MUSIC_Play(void){
	u16 i,j;
	for(i=0;i<i<sizeof(music)/sizeof(music[0]);i++)
		for(j=0;j<((u16)time[i])*tone[music[i]]/scale;j++) //time[i]*tone[music[i]]表示的是脉冲个数
			BUZZER_Play((u32)tone[music[i]]);
}

对于第一个循环语句

for(i=0;i<i<sizeof(music)/sizeof(music[0]);i++)

sizeof(music)/sizeof(music[0])的效果等价于求music数组的长度,所以这里的循环就是读取数组中的每个音符。

这个函数里最重要的一句就是

for(j=0;j<((u16)time[i])*tone[music[i]]/scale;j++) 

??前面说过time数组是music中对应音符的时值,scale是将音符时值放大的比例,之所以要放大音符的时值就是为了避免数组中存在小数,方便书写和储存。因此在计算脉冲个数的时候就需要变换回实际的时值,所以除以放大的比例。【可以发现这是整数除法,会舍弃小数,所以存在一点误差,不过问题也不大】
??所以(u16)time[i])*tone[music[i]]/scale表示的就是脉冲的个数,也就是执行BUZZER_Play()的次数。

??好了,讲完了所用的函数,我们就应该从总体上来分析,我们为什么这么写。首先蜂鸣器的发声频率不同就会产生不同的声音,因此我们控制蜂鸣器开关时间就可以控制蜂鸣器的产生不同频率的声波。

高低电平
具体就是控制高低电平的时间,产生不同的声音,产生脉冲的个数就是其持续的时间。

最后贴一个我的代码。

//				0   1    2    3   4    5   6    7   8    9   10   11  12  13   14   15   
//				低7 1    2    3   4    5   6    7   高1  高2 高3  高4 高5 高6  高7	高高1
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,0};

u8 scale = 16;//每个音符时值放大的比例,用于将时值数组转换为实际的时间长度

u8 BrokenMoon[]={
	3,5,6,8,
	9,8,9,10,8,6,
	5,3,8,9,6,6,8,
	9,8,9,10,12,13,
	15,14,13,14,13,12,13,12,10,
	9,10,8,9,8,9,
	10,6,8,9,8,6,6,5,
	6,5,6,8,9,10,9,5,
	6
};
u8 BrokenMoonTime[]={
	8,8,8,8,
	16,8,8,16,8,8,
	8,8,8,8,16,8,8,
	16,8,8,16,8,8,
	8,8,2,2,4,8,16,8,8,
	16,8,8,16,8,8,
	12,4,4,4,8,16,8,8,
	12,2,2,8,8,8,8,16,
	32
};

void BUZZER_Play(u16 frq){
	u32 time;
	if(frq){
//		time = 500000/((u32)frq);//单个脉冲的高电平持续时间(单位为us)为1*10^6/2f=500000/f	f为音符的频率
		time = 50000/((u32)frq);
		BUZZER_ON();
		delay_us(time);
		BUZZER_OFF();
		delay_us(time*9);//调节占空比
	}
	else delay_us(1000);
}

void MUSIC_Play(u8 *music,u8 *time,u16 length){
	u16 i,j;
	for(i=0;i<length;i++)
		for(j=0;j<((u16)time[i])*tone[music[i]]/scale;j++) //time[i]*tone[music[i]]表示的是脉冲个数
			BUZZER_Play((u32)tone[music[i]]);
}

void MUSIC1_Play(void){
	MUSIC_Play(BrokenMoon,BrokenMoonTime,sizeof(BrokenMoon)/sizeof(BrokenMoon[0]));
}

这里我想修改代码方便调用,就将MUSIC_Play(u8 *music,u8 *time,u16 length)封装起来方便调用,暂时还没测试可行性,等我测试完之后,看实际情况进行修改,大家如果有什么代码建议也可以发表在评论,一起讨论讨论

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 21:31:28  更:2022-10-22 21:34:35 
 
开发: 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/25 20:45:04-

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