前述
本文在实现蜂鸣器演奏音乐时只使用了基于SysTick定时器的延时函数,最后做出来的音色效果并不是很好,目前推测的原因有很多,有可能是电路设计的问题也有可能是蜂鸣器的问题,也有可能是延时函数并不能及时翻转引脚电平的问题。有兴趣的同志可以试一试利用定时器中断实现蜂鸣器引脚电平翻转,看看效果会不会有所提升。
本文只为大家提供算法思想。
乐理知识
音阶
本人对乐理这方面不是很了解,在此只讲述一下我的理解,大家也可以去网上搜索。
通俗一点,音阶就是Do、Re、Mi、Fa、So、La、Xi即1、2、3、4、5、6、7(C、D、E、F、G、A、B),越往后音调越高即频率越高,所以我们在后续的代码中只需要将乐谱中对应的音阶转换成蜂鸣器所需要的震荡频率就可以发出我们需要的音调了。
音符
音符就是每一个音调持续的时间,如果假设正常的一个拍子为1s,那么全音符就是1s,二分音符就是0.5s,四分音符就是0.25s以此类推
OK,现在我们已经可以编程了。
代码实现
延时函数
首先初始化SysTick定时器,将SysTick定时器的时钟源设置为主频的八分频,如果主频为168那么定时器的时钟频率为,那么定时器计数1次的时间为,计时1us就需要21次,所以fac_us=定时器时钟的频率=21
因为SysTick定时器是24位的倒计时器,所以计数最大值为2^24,所以计时的最大值为
如果主频超频到240MHz那么计时的最大值就为559240us,这里我们取540000us方便计算
void SysTick_Init(uint8_t SYSCLK)
{
//将SysTick的时钟频率设置为主频的八分频,如果主频为168MHZ那么将SysTick的时钟频率为21MHZ
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8; //周期=频率的倒数,频率为21MHz,那么1us就需要震荡21次
fac_ms=fac_us*1000; //ms=us*1000
}
void Delay_Nus(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD=fac_us*nus; //装载计数值
SysTick->VAL=0x00; //当前计数值清零
SysTick->CTRL|=0x01; //启动定时器
do
{
temp=SysTick->CTRL; //获取当前计数值
}
while((temp&0x01)&&!(temp&(1<<16))); //当计时器启动且溢出标志位置1退出循环
SysTick->CTRL&=~0x01; //关闭计时器
SysTick->VAL=0x00; //当前计数值清零
}
/*
SysTick定时器为24位的倒计时器,所以最大计数值为2^24
如果主频超频到240MHz,那么可以计时的最大时间为(2^24)/(240/8)=559240us
这里取540000us方便计算
*/
void Delay_Us(uint32_t us)
{
uint32_t repeat;
uint32_t remain;
repeat=(unsigned int)(us/540000); //如果计时值为1088000=540000*2+8000us
remain=(unsigned int)(us%540000);
while(repeat--)
{
Delay_Nus(540000); //540000*2
}
if(remain!=0)
{
Delay_Nus(remain); //+8000=1088000us
}
}
GPIO初始化
#define BUZZER_BUS GPIOF
#define BUZZER_Pin GPIO_Pin_8
/*
@brief:Configure the GPIO which is connected to Buzzer.
@param:None.
@retern:None.
*/
void Buzzer_GPIO_Init(void)
{
GPIO_InitTypeDef Buzzer_GPIO_Init;
Buzzer_GPIO_Init.GPIO_Mode=GPIO_Mode_OUT;
Buzzer_GPIO_Init.GPIO_OType=GPIO_OType_PP;
Buzzer_GPIO_Init.GPIO_Pin=BUZZER_Pin;
Buzzer_GPIO_Init.GPIO_PuPd=GPIO_PuPd_DOWN;
Buzzer_GPIO_Init.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(BUZZER_BUS,&Buzzer_GPIO_Init);
}
蜂鸣器播放
下面函数的形参用的都是float型变量,这是因为形参的单位是Hz和Ms,函数内部处理时会将频率转换为周期并放大一百万倍进行处理,也就是以Us为单位进行处理。假设频率转换为周期后为5.123456……S
在函数内部处理时将转换成512 3456Us进行处理,这样产生的频率更加精准
/*
@brief:Play music with buzzer.
@param:
scale->音阶.Unit->Hz
note ->音符. Unit->ms
@retern:None.
*/
void Music_Play(float scale,float note) //这里用float型是因为形参是以Hz和Ms为单位,但我们
{ //后续用的是MHz和Us为单位,也就是将其放大了
uint32_t temp; //一百万倍,这样就产生的频率和时长就更加精确
uint32_t T;
uint32_t repeat;
T=(unsigned int)((1/scale)*1000000); //将频率转换为周期,单位为us
repeat=(unsigned int)(note*(scale/1000)); //一个周期的时间为T,那么只需要重复
//T*repeat=note即repeat=note/T*1000,单位Ms
if(scale!=0)
{
while(repeat--)
{
GPIO_SetBits(BUZZER_BUS,BUZZER_Pin);
Delay_Us(T/2);
GPIO_ResetBits(BUZZER_BUS,BUZZER_Pin);
Delay_Us(T/2);
}
}
else
{
Delay_Us(note*1000);
}
}
/*
float(*)[2]表明这个指针指向的对象是一个包含两个float类型元素的数组,MusicNotation是一个指针变量名。也就是将二维数组转换成了一位数组传递,这个一位数组每个单元有两个float型元素
*/
void Music_Player(float(* MusicNotation)[2])
{
uint32_t i=0;
while((MusicNotation[0])||(MusicNotation[1]))
{
Music_Play(MusicNotation[0],MusicNotation[1]);
i++;
}
}
音阶转换
接下来就是网上搜索,将音阶转换成对应的频率,这不是什么技术活,大家可以直接copy
/*
Quicker search :
@music Scale
Low-pitched Range->C1 C2 C3
Middle-pitched Range->C4
High-pitched Range->C5 C6 C7 C8
@Music Note
*/
/************************* Music Scale ****************************/
/* Unit->Hz */
//Low-pitched Range
#define C1 33 //Dou
#define D1 37 //Ri
#define E1 41 //Mi
#define F1 44 //Fa
#define G1 49 //Sou
#define A1 55 //La
#define B1 62 //Xi
#define C2 65
#define C_2 69
#define D2 73
#define D_2 78
#define E2 82
#define F2 87
#define F_2 93
#define G2 98
#define G_2 104
#define A2 110
#define A_2 117
#define B2 123
#define C3 131
#define C_3 139
#define D3 147
#define D_3 156
#define E3 165
#define F3 175
#define F_3 185
#define G3 196
#define G_3 208
#define A3 220
#define A_3 233
#define B3 247
//Middle-pitched Range
#define C4 262
#define C_4 277
#define D4 294
#define D_4 311
#define E4 330
#define F4 349
#define F_4 370
#define G4 392
#define G_4 415
#define A4 440
#define A_4 466
#define B4 494
//High-pitched Range
#define C5 535
#define C_5 554
#define D5 587
#define D_5 622
#define E5 659
#define F5 698
#define F_5 740
#define G5 784
#define G_5 831
#define A5 880
#define A_5 932
#define B5 988
#define C6 1047
#define C_6 1109
#define D6 1175
#define D_6 1245
#define E6 1319
#define F6 1397
#define F_6 1480
#define G6 1568
#define G_6 1661
#define A6 1760
#define A_6 1865
#define B6 1976
#define C7 2093
#define C_7 2217
#define D7 2349
#define D_7 2489
#define E7 2580
#define F7 2890
#define F_7 2960
#define G7 3136
#define G_7 3322
#define A7 3520
#define A_7 3729
#define B7 3951
#define C8 4186.0
#define C_8 4434.9
#define D8 4698.6
#define D_8 4978.0
#define E8 5274.0
#define F8 5587.7
#define F_8 5919.9
#define G8 6271.9
#define G_8 6644.9
#define A8 7040.0
#define A_8 7458.6
#define B8 7902.1
/************************* Music Note ****************************/
/* Unit->ms */
#define FullNote 1500
#define BinaryNote 750
#define QuarterNote 325
#define EighthNote 162.5
#define SixteenNote 81.25
乐谱转换
网上搜索相关音乐的乐谱,抄上去就行了,二维数组中第一个元素存放音阶即频率,第二个元素存放音符即时长
/*
Music name:两只老虎
Author :未知
*/
float TwoTigers[][2]=
{
{C4,QuarterNote},
{D4,QuarterNote},
{E4,QuarterNote},
{C4,QuarterNote},
{C4,QuarterNote},
{D4,QuarterNote},
{E4,QuarterNote},
{C4,QuarterNote},
{E4,QuarterNote},
{F4,QuarterNote},
{G4,QuarterNote},
{G4,QuarterNote},
{E4,QuarterNote},
{F4,QuarterNote},
{G4,QuarterNote},
{G4,QuarterNote},
{G4,QuarterNote},
{A4,QuarterNote},
{G4,QuarterNote},
{F4,QuarterNote},
{E4,QuarterNote},
{E4,QuarterNote},
{C4,QuarterNote},
{C4,QuarterNote},
{G4,QuarterNote},
{A4,QuarterNote},
{G4,QuarterNote},
{F4,QuarterNote},
{E4,QuarterNote},
{E4,QuarterNote},
{C4,QuarterNote},
{C4,QuarterNote},
{D4,QuarterNote},
{D4,QuarterNote},
{G3,QuarterNote},
{G3,QuarterNote},
{C4,QuarterNote},
{C4,QuarterNote},
{0,QuarterNote},
{0,QuarterNote},
{D4,QuarterNote},
{D4,QuarterNote},
{G3,QuarterNote},
{G3,QuarterNote},
{C4,QuarterNote},
{C4,QuarterNote},
{0,QuarterNote},
{0,QuarterNote},
{0,0} //Stop signal.
};
如果是变量定义在外部文件,别忘记在头文件声明变量。
extern float TwoTigers[][2];
音乐演奏
在主函数里调用之前写的那些代码。
#define TARGET_GLOBAL
#define UseHappyBirthday
#define UseTwoTigers
#include "main.h"
int main(void)
{
System_Init();
SysTick_Init(168);
Buzzer_GPIO_Init();
Music_Player(TwoTigers);
while(1)
{
}
}
源码链接
链接:https://pan.quark.cn/s/e0a803203bbb
提取码:gGDr
效果演示
可能是延时函数的原因最后得到的声音效果并不是很好,大家也可以用定时器中断试一试。
当然也有可能是其他原因
STM32蜂鸣器演奏音乐
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2402_83561731/article/details/143451210
|
|