STM32 控制蜂鸣器播放音乐的原理和实例
本文通过将乐谱里的每个音符的声音频率和声音时长保存在两个数组里面。
1.使用通用定时器TIM4实现无中断的微秒级延时函数,控制每个音符的发声时长。
2.使用系统滴答时钟Systick实现带有中断的输出控制,在中断函数里实现蜂鸣器端口输出电平反转,并且根据当前播放音符的频率重新设置中断产生时间。
一、播放的原理
播放的乐谱:

1.1 C音调乐谱对应的音频(Hz):

根据乐谱的基础知识可知,低音的下面加点,高音的上面加点,普通的不加点。
//用枚举定义,记录所有的音频。
enum Low_frequency{l_dao=262,l_re=286,l_mi=311,l_fa=349,l_sao=392,l_la=440,l_xi=494};
enum Normal_frequency{dao=523,re=587,mi=659,fa=698,sao=784,la=880,xi=987};
enum High_frequency{h_dao=1046,h_re=1174,h_mi=1318,h_fa=1396,h_sao=1567,h_la=1760,h_xi=1975};
1.2 乐谱对应的节拍-音长:
本次乐谱的节拍为每分钟72拍,可以算出每个节拍的时长:

然后看乐谱的第一小节:






最后将整个乐谱的音频和音长记录在两个数组里。
。
二、播放音乐的具体实现
2.1 无中断的毫秒延时函数
//TIM_4初始化函数
void TIM_4(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
TIM_TimeBaseStructure.TIM_Period = 1;
TIM_TimeBaseStructure.TIM_Prescaler=(72-1);
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Down;
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
}
//延时n个us
void delay_us(unsigned int nus)
{
TIM4->CNT=nus-1;
TIM4->CR1|=TIM_CR1_CEN;
while((TIM4->SR & TIM_FLAG_Update) != SET)
;
TIM4->CR1&=(~TIM_CR1_CEN);
TIM4->SR &=~(TIM_FLAG_Update);
}
//延时n个毫秒
void delay_ms(unsigned int nms)
{
int count;
for(count=0;count<nms;count++)
delay_us(1000);
}
2.2 Systick的中断初始化和中断函数
中断初始化:
void SysTick_Init(int n)
{
/* SystemFrequency /1000 1ms 中断一次
* SystemFrequency / 100000 10us 中断一次
* SystemFrequency / 1000000 1us 中断一次
*/
if (SysTick_Config(SystemCoreClock/1000000*n)) {
/* Capture error */
while(1)
;
}
}
中断函数:
//stm32f10x_it.c,官方库函数文件里的中断函数
//以下是需要添加的代码
#define Buzzer_TOGGLE {GPIOC->ODR ^=GPIO_Pin_0;} //PC0是连接蜂鸣器的引脚
extern int C;
void SysTick_Init(int n);
//在 SysTick_Handler函数里面添加的代码
void SysTick_Handler(void)
{
Buzzer_TOGGLE;
SysTick_Init(C);
}
2.3 main.c文件的代码
#include "stm32f10x.h"
#define Buzzer_TOGGLE {GPIOC->ODR ^=GPIO_Pin_0;}//PC0连接的是蜂鸣器,可以根据自己的实际情况修改端口
int C;
void key_init(void);
void TIM_4(void);
void Buzzer_init(void);//PC0连接的是蜂鸣器,可以根据自己的实际情况修改端口
void SysTick_Init(int n);
void delay_us(unsigned int nus);
void delay_ms(unsigned int nms);
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
enum Low_frequency{l_dao=262,l_re=286,l_mi=311,l_fa=349,l_sao=392,l_la=440,l_xi=494};
enum Normal_frequency{dao=523,re=587,mi=659,fa=698,sao=784,la=880,xi=987};
enum High_frequency{h_dao=1046,h_re=1174,h_mi=1318,h_fa=1396,h_sao=1567,h_la=1760,h_xi=1975};
int j;
int i_f;
//以下是《渴望》片头曲的一段简谱“好人一生平安”
unsigned int f[]={re,mi,re,dao,l_la,dao,l_la,//每行对应一小节音调
l_sao,l_mi,l_sao,l_la,dao,
l_la,dao,sao,la,mi,sao,
re,
mi,re,mi,sao,mi,
l_sao,l_mi,l_sao,l_la,dao,
l_la,l_la,dao,l_la,l_sao,l_re,l_mi,
l_sao,
re,re,sao,la,sao,
fa,mi,sao,mi,
la,sao,mi,re,mi,l_la,dao,
re,
mi,re,mi,sao,mi,
l_sao,l_mi,l_sao,l_la,dao,
l_la,dao,re,l_la,dao,re,mi,
re,
l_la,dao,re,l_la,dao,re,mi,
re,
0xff};//以0xff作为音调的结束标志
//以下是简谱中每个音调的节拍
//“4”对应4个延时单位,“2”对应两个延时单位,“1”对应1个延时单位
unsigned char JP[]={4,1,1,4,1,1,2,//每行对应一小节音调的节拍
2,2,2,2,8,
4,2,3,1,2,2,
10,
4,2,2,4,4,
2,2,2,2,4,
2,2,2,2,2,2,2,
10,
4,4,4,2,2,
4,2,4,4,
4,2,2,2,2,2,2,
10,
4,2,2,4,4,
2,2,2,2,6,
4,2,2,4,1,1,4,
10,
4,2,2,4,1,1,4,
10};
// key_init();//按键1的初始化函数,可以不用。
TIM_4();
// while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0)//等待按键按下,执行播放音乐
// delay_ms(5);
Buzzer_init();
while(1)
{
i_f=0;
while(f[i_f]!=0xff)
{
C=(1000*1000/2)/f[i_f]; //ms->us:1000,s->ms:1000,方波半周期翻转:1/2
SysTick_Init(C); //翻转时长更新,重新配置翻转时长
for(j=0;j<JP[i_f];j++)
delay_ms(208);
i_f++;
}
}
}
void key_init(void)
{
GPIO_InitTypeDef keyInit;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
keyInit.GPIO_Pin=GPIO_Pin_0;
keyInit.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&keyInit);
}
void TIM_4(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
TIM_TimeBaseStructure.TIM_Period = 1;
TIM_TimeBaseStructure.TIM_Prescaler=(72-1);
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Down;
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
}
void Buzzer_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void SysTick_Init(int n)
{
/* SystemFrequency /1000 1ms 中断一次
* SystemFrequency / 100000 10us 中断一次
* SystemFrequency / 1000000 1us 中断一次
*/
if (SysTick_Config(SystemCoreClock/1000000*n)) {
/* Capture error */
while(1)
;
}
}
void delay_us(unsigned int nus)
{
TIM4->CNT=nus-1;
TIM4->CR1|=TIM_CR1_CEN;
while((TIM4->SR & TIM_FLAG_Update) != SET)
;
TIM4->CR1&=(~TIM_CR1_CEN);
TIM4->SR &=~(TIM_FLAG_Update);
}
void delay_ms(unsigned int nms)
{
int count;
for(count=0;count<nms;count++)
delay_us(1000);
}
/*********************************************END OF FILE**********************/
最后,就可以听到蜂鸣器播放的音乐了。。。。。。
三、参考书籍
《单片机C语言应用100例》,王东锋等,6.3.4 实例47:用定时器T0的模式0控制播放《好人一生平安》
本文介绍了如何使用STM32单片机通过C语言控制蜂鸣器播放音乐,包括理解音乐播放原理,如乐谱对应音频和节拍,以及具体实现步骤,如使用TIM4实现毫秒延时和Systick中断控制蜂鸣器输出。
1622





