天天看點

基于STM32F103,用蜂鳴器播放歌曲 

本文的内容,分為下面幾部分:

1,參考網上例程;

2,移植,運作,檢視,盡力了解代碼;

3,調整測試參數;

4,添加一首歌曲。

下面較長的描述一下過程:

一,參考網上例程

主要參考的是這一篇《使用STM32F103控制蜂鳴器發聲播放音樂》,位址如下:

https://blog.csdn.net/qq_36355662/article/details/80606753

看起來代碼比較簡潔,就兩個檔案,就開始移植到自己的闆子上。

二,移植,運作,檢視代碼,盡力了解代碼

說明一下,我使用的晶片類型是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值,這個值影響的是音調準不準。具體怎麼計算的,我也沒搞明白,就是多試了幾個值,挑了個聽着舒服的。

對這幾個參數一頓調整後,“多瑞咪”聽着就很順暢了。

下面,最激動人心的時刻到了!

四,添加一首歌曲

早就想添加一首歌曲了,不過真要開始添加,也還有點忐忑,得弄首自己熟悉的吧,不能弄太複雜的吧,嗯,來首兒歌吧,就選《小燕子》吧。

先上網找個譜子,仔細看看,嗯,也就是搞定兩個數組嘛,一個音調,一個音長。

檢視樂譜,如下:

基于STM32F103,用蜂鳴器播放歌曲 

真到了轉換簡譜到數組的時候,這時就能發現選擇兒歌的好處了,所有的音調都在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();