1. 程式人生 > 其它 >基於STM32F103,用蜂鳴器播放歌曲

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

技術標籤:微控制器C/C++stm32蜂鳴器播放歌曲C程式碼音調

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

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值,這個值影響的是音調準不準。具體怎麼計算的,我也沒搞明白,就是多試了幾個值,挑了個聽著舒服的。

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

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

四,新增一首歌曲


早就想新增一首歌曲了,不過真要開始新增,也還有點忐忑,得弄首自己熟悉的吧,不能弄太複雜的吧,嗯,來首兒歌吧,就選《小燕子》吧。
先上網找個譜子,仔細看看,嗯,也就是搞定兩個陣列嘛,一個音調,一個音長。
檢視樂譜,如下:

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