實驗名稱:基於51單片機音樂播放器
實驗目的:
聲音的音調不同是因爲聲音的頻率不同造成的。那麼,就可以通過單片機發送不同頻率的脈衝信號給蜂鳴器,來達到讓蜂鳴器發出不同音調聲音的目的。不同頻率的脈衝信號就意外着每個脈衝之間必須有着不一樣的時差,這可以通過延時或者定時計數器定時的方式來實現。
我們便是通過這個目的,去通過對於單片機的操作,蜂鳴器變調,使蜂鳴器完成對於歌曲樂譜的播放,再通過LCD1602把我們想顯示的漢字顯示在液晶屏幕上。製作成一個建議的基於單片機的音樂播放器。
實驗環境:
Keil4編寫代碼,Proteus仿真程序
LCD1602液晶顯示漢字:
這個程序代碼關聯性不強,所以我分成3個模塊爲大家講解程序,首先爲大家講解比較重要的1602。1602是一種工業字符型液晶,全稱LCD1602,能夠同時顯示16x02即32個字符。LCD1602液晶顯示的原理是利用液晶的物理特性,通過電壓對其顯示區域進行控制,有電就有顯示,這樣即可以顯示出圖形。
對其編程需要看時序圖,根據時序圖寫出與1602通信代碼。我們先來看1602引腳定義:
第1腳:VSS爲電源地
第2腳:VCC接5V電源正極
第3腳:V0爲液晶顯示器對比度調整端,接正電源時對比度最弱,接地電源時對比度最高(對比度過高時會 產生“鬼影”,使用時可以通過一個10K的電位器調整對比度)。
第4腳:RS爲寄存器選擇,高電平1時選擇數據寄存器、低電平0時選擇指令寄存器。
第5腳:RW爲讀寫信號線,高電平(1)時進行讀操作,低電平(0)時進行寫操作。
第6腳:E(或EN)端爲使能(enable)端,高電平(1)時讀取信息,負跳變時執行指令。
第7~14腳:D0~D7爲8位雙向數據端。
第15~16腳:空腳或背燈電源。15腳背光正極,16腳背光負極
我們可以看到除了雙向數據端,我們需要用到的有三個,RS(4),RW(5),E(6)這三個。
下面我們上時序圖,我們來看寫時序:
通過時序圖,我們可以看出需要進行寫操作時,我們RW引腳要爲低(0),RS引腳選擇是寫數據還是寫命令(0/1),E引腳的下降沿即完成程序寫入操作,上程序:
void write_command(uchar com)
{
check_busy();
E=0;
RS=0;
RW=0;
out=com;
E=1;
delay(2);
E=0;
delay(2);
}
void write_date(uchar dat)
{
check_busy();
E=0;
RS=1;
RW=0;
out=dat;
E=1;
delay(2);
E=0;
delay(2);
}
Out即爲整個雙向數據端,根據我們每個人的接線去操作即可。完成了寫函數以後,我們就可以根據指令集對1602進行初始化,這裏我們不在對指令集和初始化進行介紹。
對於1602寫漢字我們需要下一番功夫,因爲1602內置的CGROM裏存儲了常見的192個字符,但是沒有中文,我們想顯示中文就必須字節創建一個字庫。查看1602數據手冊,我們可以看到1602有一個CGRAM區,作爲用戶自定義區,大小爲64字節,每8個字節爲一組顯示一個字符,總共可以顯示8種自定義字符。每種字符顯示都有自己的顯示編碼從第一種字符到第八個字符,依次是:{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}。
現在的問題就是建字庫,我查看數據手冊發現1602其內部數據矩陣並非是8 * 8的,而是5 * 8的只用右邊區域進行顯示,知道這點我們就直接上字模提取軟件,且只用右邊位置即可。
總得來說,就是我們先建一個字庫,然後設定好一個顯示地址,然後在每個地址寫入每個字符的顯示編碼,即可在對應的區域顯示我們的字符了。
我們這裏放個字庫截圖吧:
table2就是我們建的字庫,table3是每個字庫顯示編碼,table4即直接用了CGROM的字符。想知道我們建的庫長啥樣,見文末。。
顯示函數我們列一下:
void lcd1602xianshi(void)
{
uchar i;
init_1602();
write_command(0x40);//向用戶自定義RAM區寫入我們定義好的字庫
for(i=0;i<64;i++)
{
write_date(table2[i]);
delay(1);
}
write_command(0x80+0x03);//把填充好的字庫顯示在第一行第三個字符位置後面
for(i=0;i<8;i++)
{
write_date(table3[i]);
delay(100);
}
write_command(0xc0+0x02);//把happy birthday顯示到第二行第二個字符後面
for(i=0;i<14;i++)
{
write_date(table4[i]);
delay(100);
}
a=2;
}
很簡單的我們就完成了在1602上顯示漢字的騷操作了,效果見文末。
PWM控制揚聲器/蜂鳴器演奏音樂:
首先我們來看硬件連接,很簡單
我們在硬件上只是簡單的通過一個NPN三極管去做成了一個放大電路從而驅動有源蜂鳴器,在軟件上因爲89C51不像STM32有自己PWM硬件外設,所以我們只能去模擬PWM波形的產生,從而控制蜂鳴器的聲調。
模擬PWM其實很簡單,我們可以想象有一個單片機引腳PA0我們設置成輸出爲0,還有兩個數字a ,b, 這個a初始值爲0,b的初始值爲300,我們在定時器裏面設定每進一次定時器中斷,a就自增,a = b時,我們把PA0設置成輸出爲1,當a = 1000時,我們讓a = 0,PA0=0。這樣我們就輸出了佔空比爲70%的方波。驅動蜂鳴器也只是改變這個佔空比而以,換句話說就是改變b的值!
一首曲子肯定要有7個音調,哆來咪發嗦啦西,只有一個聲部肯定不夠,要分高聲部和低聲部,這樣我們就要設置14個音調,每個音調持續的時間也不一定,有的音持續時間長,有的音持續時間短。所以我們還需要一個可以標誌音持續時間的數字。
綜上所述,我們還是直接上截圖:
Sszymmh[]這個數組裏的數據每3個爲一組,前兩個數確定PWM佔空比,後一個確定佔空比持續時間。
我們這裏上一下音樂播放函數:
void t0int() interrupt 1
{
TR0=0;
speaker=!speaker;
TH0=timer0h;
TL0=timer0l;
TR0=1;
}
void song()
{
TH0=timer0h;
TL0=timer0l;
TR0=1;
delayms(time);
}
void music(void)
{
unsigned char k,i;
TMOD=1;
EA=1;
ET0=1;
while(1)
{
i=0;
while(i<75){
k=sszymmh[i]+7*sszymmh[i+1]-1;
timer0h=FREQH[k];
timer0l=FREQL[k];
time=sszymmh[i+2];
i=i+3;
song();
}
}
a=3;
}
函數還是比較容易理解的,我們這裏就不囉嗦了。
8*8矩陣
我們在程序裏面加入了8*8矩陣作爲一個趣味性的元器件,這個矩陣會形成一個箭頭,逐漸往上,等到箭頭全部上升完畢,1602顯示內容,蜂鳴器播放音樂。
矩陣還是比較簡單的,雖然用的引腳好多。。就是8個引腳來掃描,8個引腳搞顯示,其實用74hc595會更省事兒,兩個引腳就能解決問題,懶得弄了。直接上程序把:
void juzhen()
{
uint n,i;
uint k;
for(i=0;i<8;i++)
{ k=0;
while(k<100)
{for(n=0;n<8;n++)
{
P3=~table1[n];
P1=table[n+i*8];
delay(1);
}
k++;
}
}
a=1;
}
實物效果展示
這是基本的電路設計,因爲是仿真,沒有加入晶振,復位等電路。
接下來看運行以後的程序:
至此,這個簡單的項目已經介紹完畢,感興趣的同學可以給個贊。下面放完整視頻:
B站 51音樂播放器