基于STM32F103,用蜂鸣器播放歌曲
本文的内容,分为下面几部分:
1,参考网上例程;
2,移植,运行,查看,尽力理解代码;
3,调整测试参数;
4,添加一首歌曲。
下面详细描述一下过程:
一,参考网上例程
主要参考的是这一篇《使用STM32F103控制蜂鸣器发声播放音乐》,地址如下:
使用STM32F103控制蜂鸣器发声播放音乐_blue@sky的博客-CSDN博客_stm32蜂鸣器音乐代码
看起来代码比较简洁,就两个文件,就开始移植到自己的板子上。
二,移植,运行,查看代码,尽力理解代码
说明一下,我使用的芯片类型是stm32F103C8,集成开发环境用的是Keil5 MDK-ARM,仿真器使用JLINK。
查看板子上的蜂鸣器接口,相应修改下代码中的引脚。这个和硬件相关,每个人只能根据自己的硬件来调整。
然后是将这个引脚配置成推挽输出,默认是低电平,输出高电平可以驱动蜂鸣器响报警。
然后是开关的操作,Buzzer_On(),Buzzer_Off()来替换;
然后就开始运行,能听到蜂鸣声响,但是刺刺啦啦的,不像音乐。
没有办法,偷懒不行啦,就只好开始看程序了,尽力去理解代码。毕竟代码在手,任我蹂躏,哈哈!
大体理解了一遍程序,调整了几个延时相关的值,发现效果有所好转,比较像音乐了。
但是,到底对不对,对于例程中的歌曲《红尘情歌》,我没听过啊,比较不出来对不对,好不好。
如果能添加一首自己熟悉的歌曲,就能判断了。不过,先别着急,慢慢来。先来个最简单的,就是先听听“多瑞咪”吧。
u8 music[]={13,1,2,3,4,5,6,7,8};//音调 测试基础音
u8 time[] ={4, 4,4,4,4,4,4,4,4};//持续时长
例子中,歌曲由两个数组来表示,一个是音调,一个是这个音调的持续时长。
音调数组music[],里面的数字看起来就是123,与曲谱上的一样,它是怎么实现那个声音的呢?
看代码,是这么调用的:
tone[music[i]]
也就是说,是把它作为索引传给了tone[]数组,而tone[]的定义如下:
// 低7 1 2 3 4 5 6 7 高1 高2 高3 高4 高5 不发音
uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音频数据表
这个数组中的数值,又有什么用呢?
注释说是音频数据。根据蜂鸣器的原理,就是输出不同频率的PWM波,即可发出不同的声音(音符)。然后将不同的音符组合起来就是一个曲子。所以这里的数据应该是用于控制蜂鸣器的发声频率的。
看看调用的地方:
for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
Sound((u32)tone[music[i]]);
}
查看一下Sound()这个函数,这可是核心函数:
void Sound(u16 frq)
{
u32 time;
if(frq != 1000)
{
time = 500000/((u32)frq);
PBeep = 1;
delay_us(time);
PBeep = 0;
delay_us(time);
}else
delay_us(1000);
}
其实也很简单,就是打开蜂鸣器,持续一会,然后关闭蜂鸣器,持续同样时间。
然后再看看调用的地方:
for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
Sound((u32)tone[music[i]]);
}
是用时长来做控制,重复调用了许多次Sound()播放声音。这样持续起来,听起来就是一个音符了。
跑起来程序,让我们来听听“多瑞咪”!
嗯~~有点问题,有个音听起来不对劲!仔细听,数了数,对应的应该是7(第8个音),再仔细看一遍:
// 低7 1 2 3 4 5 6 7 高1 高2 高3 高4 高5 不发音
uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音频数据表
这个音对应的是294,在这个数组中有些不协调啊!
其他的数据,都是按从小到大的顺序排列的,这个数怎么比左右的数都小啊?
难道写错了?如果也按是顺序来的话,我看改为494比较协调。
那就改改呗,代码在手嘛,哈哈!
改了下,一听,还真就顺耳了,我是不是天才,哈哈!(A:天才?我看是多了两横。B:我看不光是要去掉两横,还要去掉一个“才”字。)
闲话少说,继续调试。
三,调整测试参数
到现在,大体能听出来是个音乐了,但是播放太快了(与板子上硬件相关),不太舒服,调调延时吧。
大体相关的有几个参数:
1,持续时长,就是time[]数组,但是,一旦调整,需要每个参数都修改一遍,太累了。
2,有没有修改一处,所有音符的播放时长都变的?
有!就是这个:
yanshi = 2;//10;
把它调小,循环播放的次数是增加的,也就是音符的持续时间变长了。
3,还有一个值,就是前面Sound()函数中的time值,这个值影响的是音调准不准。具体怎么计算的,我也没搞明白,就是多试了几个值,挑了个听着舒服的。
对这几个参数一顿调整后,“多瑞咪”听着就很顺畅了。
下面,最激动人心的时刻到了!
四,添加一首歌曲
早就想添加一首歌曲了,不过真要开始添加,也还有点忐忑,得弄首自己熟悉的吧,不能弄太复杂的吧,嗯,来首儿歌吧,就选《小燕子》吧。
先上网找个谱子,仔细看看,嗯,也就是搞定两个数组嘛,一个音调,一个音长。
查看乐谱,如下:
真到了转换简谱到数组的时候,这时就能发现选择儿歌的好处了,所有的音调都在tone[]数组中,也就是说,在低音7到高音5之间。让我们再看一遍这个数组:
// 低7 1 2 3 4 5 6 7 高1 高2 高3 高4 高5 不发音
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表
如果搞了一个复杂的歌曲,音调的范围超过了这个范围,就需要自己研究下那些新音符对应的是什么频率值了。
至于时长,这里是用的4表示1拍的时长。这样,半拍(一个音符下有一道横线)就是2,四分之一拍(一个音符下有两道横线)就是1了,还有两拍的,就是8。
这样,音调与时长都搞定了,是不是就ok了?
嗯,基本上可以这么说,不过,还有些小细节,例如,在两个音符中间的一个小点是什么?
我在这里就犯过错,我还以为是休止符(不知道是从那里获取的错误信息!也许真的有必要将大脑中的知识点都梳理一遍),后来查了一下,这玩意叫附点,看介绍:
附点的符号很简单,就是单纯音符后面的小圆点。附点的作用是:将前面挨着它的单纯音符的时值延长一半。
还有一个小细节,就是在一句歌词唱完之后,需要停顿一会,这时候,就用上了音频数据表中一个特殊的值:1000,前面注意看的话,在Sound()中就有对它的特殊处理,就是不开启蜂鸣器,只是一个延时。
这时再听听,就有些感觉了哈!
后面就是根据整体感觉,做点微调了,例如,我就调整了下尾部拖音的时长,本来是8(两拍),我改为6(一拍半),似乎听起来更好听了,哈哈!
下面是我修改后的代码:
#include "beep.h"
#include "stm32f10x.h"
#include "gpioHandler.h"
#include "timerHandler.h"
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能A端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOD3,6
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
void Sound(u16 frq)
{
u32 time;
if(frq != 1000)
{
// time = 500000/((u32)frq);
time = 100000/((u32)frq);
// PBeep = 1;
Buzzer_On();//打开蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置1
delay_us(time);
// PBeep = 0;
Buzzer_Off();//关闭蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置0
delay_us(time);
}else
delay_us(1000);
}
void play_music(void)
{
// 低7 1 2 3 4 5 6 7 高1 高2 高3 高4 高5 不发音
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表
//小燕子
u8 music[]={3,5,8,6,5,13,//音调
3,5,6,8,5,13,
8,10,9,8,9,8,6,8,5,13,
3,5,6,5,6,8,9,5,6,13,
3,2,1,2,13,
2,2,3,5,5,8,2,3,5,13};
u8 time[] ={2,2,2,2,6,4,//时间
2,2,2,2,6,4,
6,2,4,4,2,2,2,2,6,4,
6,2,4,2,2,4,2,2,6,4,
2,2,4,6,4,
4,2,2,4,4,4,2,2,6,4};
// u8 music[]={13,1,2,3,4,5,6,7,8};//测试基础音
// u8 time[] ={4, 4,4,4,4,4,4,4,4};
u32 yanshi;
u16 i,e;
yanshi = 2;//10; 4; 2
for(i=0;i<sizeof(music)/sizeof(music[0]);i++){
for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
Sound((u32)tone[music[i]]);
}
}
}
调用的时候,就是两句代码:
BEEP_Init();
play_music();
后记:
鉴于有网友提出疑问,所以补充了部分内容:
补充 beep.h:
#ifndef __BEEP__H
#define __BEEP__H
#include "stdlib.h"
void BEEP_Init(void);
void Sound2(short time);
void play_music(void);
void play_successful(void);
void play_failed(void);
#endif
补充几个相关函数:
void Buzzer_On(void)
{
GPIO_SetBits(BUZZER_GPIO_PORT, GPIO_Pin_5);
}
void Buzzer_Off(void)
{
GPIO_ResetBits(BUZZER_GPIO_PORT, GPIO_Pin_5);
}
void delay_us (const uint32_t usec)
{
RCC_ClocksTypeDef RCC_Clocks;
/* Configure HCLK clock as SysTick clock source */
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
RCC_GetClocksFreq(&RCC_Clocks);
// Set SysTick Reload(1us) register and Enable
// usec * (RCC_Clocks.HCLK_Frequency / 1000000) < 0xFFFFFFUL -- because of 24bit timer
SysTick_Config(usec * (RCC_Clocks.HCLK_Frequency / 1000000));//HCLK_Frequency=48M
// 72/72000000 --> 1usec
// 0.001msec = 1usec
// 1Hz = 1sec, 10Hz = 100msec, 100Hz = 10msec, 1KHz = 1msec,
// 10KHz = 0.1msec, 100Khz = 0.01msec, 1MHz = 1usec(0.001msec)
// 1usec = 1MHz
// SysTick Interrupt Disable
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk ;
// Until Tick count is 0
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
}
更详细的定时器怎么使用的说明,参见另一篇博文:
https://blog.csdn.net/lintax/article/details/84918791?spm=1001.2014.3001.5501
更多推荐
所有评论(0)