零之前言
最近想用無源蜂鳴器來播放曲子,但是看了好多博客講的都是馬馬虎虎,沒有講的太清楚,所以我只好自己重新學習了一下,音樂發聲的原理(因爲硬件基礎夠啦QAQ)和簡譜。
一.發聲原理
原理就是這個:人之所以能聽見聲音,是因爲聲音在震動。那麼不同的震動頻率帶給我們的就是不同的聲調。所以我們只需要知道每個音調的發聲頻率就可以用單片機模擬出它的音調。
二.頻率與簡譜
1.頻率
這是一張標準的音高與頻率的關係對照表:
我們只需要記住這一點,其中的列
就是八度音階
,也就是我們的XX調
,比如你熟悉的G大調
。行
就是我們的音級
,也就是我們熟悉的do re mi
。
我們可以簡單的理解爲:有很多很多臺階,我們認爲規定8個臺階爲一層。
我們在演奏的時候會規定基準調,就類似於零重力勢能點、零電位、零電勢點的選擇。假設我們規定了某個調是基準調,那麼這個簡譜裏的1就是這個調,然後234依次提高。比如我們規定了440Hz是基準調,那麼這個譜子裏的2就是466Hz、3就是493Hz……
2.簡譜
如何認識簡譜纔是最難的。建議大家百度仔細瞭解,如果來不及仔細瞭解就看這個20多分鐘的視頻:【零基礎學樂理】第一課:什麼是簡譜。如果更來不及就看我簡單說說吧。
放出一張我要用來講解的簡譜:
①音調
音調如同上圖,假設我們的1上面有一個點,那它就比沒有點的1高八階(一度)。如果下面有一個點,那它就低一度。比如假設我們的的C調do 1 是261.6Hz 那麼 它頭上有個點就是523.2Hz,它下面一個點就是130.8Hz。兩個點就再高(低)一度。
②調號
我們簡譜左上角會有一個類似於1=D
或1=C
這樣的東西是用來確定基準調的。
假設我們的1=C,那麼我們的基準調就是261Hz 。1=D 就是294Hz。所以我們不懂樂理的外行人需要根據左上角的標號百度一下我們的基準調
。而且有些特殊的基準調對於某些音符需要升降音,所以建議百度。
③節拍+音符
節拍數就是我們左上角"分數",比如2/4我們讀作"以一個4分音符爲1拍,每個小節兩拍"。
每一節結束後因該是有一段小豎線作爲我們的小節線。
音符就是每一個數字,但是數字的標記不同,就代表他發的音的長短不同。例如:1 這是一個4分音符,假設它發1秒的音;1就是8分音符,它發0.5秒的音;1-就是2分音符,它發2秒的音。根據一個音符下的-或者 的數量不同,依次加倍減半。
例如小紅帽:它是2/4拍的。我們每一個小節算出來應該是2個4分音符。所以我們來算算?
1 2 3 4 | 5 3 1 | 你看滿不滿足這個規則?
三.寫代碼
1.代碼思路:
一秒鐘震動很多次,那麼一次我們需要的時間就是1/頻率。那麼我們就算出了其週期。那麼我們讓一半週期高電平,一半週期低電平,我們就得到了其一次震動。我們再用週期x頻率=1s就可以控制每個調的時間。
for(i = 0; i < hz; i++) //這是一秒一個音 此時整乘除hz可以擴大縮小一拍的時間
{
u32 time = 1000000 / 2 / hz;
buzzer(); //use the buzzer
delay_us(time);
not_buzzer(); // not use the buzzer
delay_us(time);
}
2.扒譜
我們需要:一個音符的音調,的一個音符的時間,這個音調的頻率。
還是以小紅帽的前五小節爲例:
它是 1 = D 那麼我們按1 2 3 4 5 6 7 i來記錄頻率:494 523 587 659 698 784 880 988
時間:==尤其是時間,我們雖然是4分音符爲1拍,但是我們裏面有8分音符,所以我們因該找全曲最快的音符。==此小段那就是8分音符爲1,4分音符就爲2了唄。
三要素找齊了:
包括音調的所有頻率:
293 330 350 392 440 494 523 587(1-8)
樂譜音調:
1 2 3 4 5 3 1 8 6 4 5 5 3 1 2 3 4
音調對應時間(與上面一一對應)
1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1
3.真代碼
#include "stm32f10x.h"
#include "sys.h"
u32 play_hz[] = {0, 293, 330, 350, 392, 440, 494, 523, 587};
u32 play_tone[] = {1, 2, 3, 4, 5, 3, 1, 8, 6, 4, 5, 5, 3, 1, 2, 3, 4};
u32 play_time[] = {1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1};
void play(u32 sta) // 這是播放函數
{
u32 hz, tone;
u32 i, j;
tone = play_tone[sta]; //由當前播放的位置獲取聲調
hz = play_hz[tone]; //由聲調獲取頻率
for(j = 0; j < play_time[sta]; j++)
{
//通過控制下面的i < hz 右側的值來控制單個音節的播放時間
for(i = 0; i < hz / 3; i++) // 循環體內運行一次的週期=1s÷頻率,所以整個循環體就是1s,就是一個音節1s
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
delay_us(500000 / hz);
GPIO_SetBits(GPIOA, GPIO_Pin_4);
delay_us(500000 / hz);
}
}
}
int main(void)
{
u32 a = 0;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
delay_init();//這個函數自備
while(1)
{
for(a = 0; a < sizeof(play_tone) / sizeof(play_tone[0]); a++)
{
play(a);
}
}
}