本文的内容,分为下面几部分:

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
 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐