單片機音樂編程程序實驗
來源: 作者:
// 單片機音樂編程實驗
在很多兒童玩具或一些需要音樂提示(如報警儀、定時鬧鐘、電子寵物等)的場合,需要用到單片機音樂編程的功能。原理就是利用單片機的引腳發出某一定頻率的信號驅動外部發聲設備發出聲音,或者放出美妙的音樂。
在這個例程中,音樂曲調的原始編碼已做好,只要按照曲譜給定參數即可實現播放不同的樂曲,使實驗生動有趣。參數兩個:前一個按照曲譜對照編碼表給定,第二個,為節拍數,8->1拍,4->半拍。
musich equ 40h ;音樂定時高位數據
musicl equ 41h ;音樂定時低位數據
musicstart equ 42h ;音樂開始首地址; ************************ 預定義結束 *******************
org 00h ;程序開始
jmp main ;跳轉到主程序org 1bh ;定時器T1中斷入口地址
jmp timer1 ;定時器服務程序org 030h ;主程序代碼開始
main:mov sp,#30h ;設置堆棧
lcall music ;調用音樂程序
jmp main ;重新開始musdelay:mov r0,#20 ;短時間延時
d:djnz r0,d ;延時時間 20uS
ret ;返回
; ************ 定時器 服務程序 *******************
timer1:clr tr1 ;停止定時器工作
mov th1,musich ;重新設置定時初值
mov tl1,musicl ;
cpl beep ;從蜂鳴器輸出聲音
setb tr1 ;開始定時工作
reti ;中斷返回
;****************************************************
;************ 音樂播放程序 *********************
music:mov tmod,#10h ;設置定時器T1工作方式1
setb ea ;總的中斷
setb et1 ;定時器T1中斷允許
mov musicstart,#00h ;音樂開始地址
mov musich,#0ffh ;音樂定時常數高位
mov musicl,#0ffh ;音樂定時常數低位
loopm:mov dptr,#mu_tab ;得到音符表頭
mov a,musicstart ;開始查表
movc a,@a+dptr ;獲得音樂數據
jz musend ;結束符 00H,返回
rl a ;A*2
mov b,a ;暫存地址
mov dptr,#mustab ;獲得音符表頭
movc a,@a+dptr ;根據音樂數據查得定時常數
mov musich,a ;保存定時常數高位
mov th1,a ;設置定時器高位常數
mov a,b ;開始找低位數據
inc a ;指向下一個數據
mov dptr,#mustab ;得到音符表頭
movc a,@a+dptr ;查得低位數據
mov musicl,a ;保存數據
mov tl1,a ;設置定時低位常數
setb tr1 ;定時器T1開始工作
mov dptr,#mu_tab ;得到音樂表
inc musicstart ;指向下一個單元(音符節拍)
mov a,musicstart ;開始查找
movc a,@a+dptr ;找到數據
lcall delay1 ;根據參數延時,發出持續音樂
inc musicstart ;指向下一個音
clr tr1 ;停止發聲
lcall delay
jmp loopm ;重復,直到遇到結束符號 00H.
musend:clr tr1 ;樂曲播放完畢,關閉定時器
clr ea ;禁止中斷
clr et1 ;關閉定時器T1中斷
clr beep ;禁止蜂鳴器
mov a,#20
lcall delay1
ret
;*********************
;************ 節拍發生器 ,產生音樂節拍 *********************************
delay1:mov r0,#00h ;節拍發生器,用來發生節拍
mov r1,#00h ;
mov r2,a ;這是節拍發生器的參數,當它為8
dlay1:djnz r0,dlay1 ;表示1拍
mov r0,#00h
djnz r1,dlay1
mov r0,#00h
mov r1,#00h
djnz r2,dlay1
ret
; ————————————————————
; ******** 延時 產生休止符 *****************delay:mov r0,#00h ;延時 10ms
mov r1,#100
dlay:djnz r0,dlay
mov r0,#100
djnz r1,dlay
ret
; ************************
; ************************曲譜編碼,這是定義的曲譜表,對應如下 **************************************************
; 1 低音 DO 2 低音 RE 3 低音ME 4 低音 FA 5 低音 SO 6 低音LA 7 低音SI
mustab: db 'h','j', 0f8h,8bh, 0f9h,5bh, 0fah,14h, 0fah,0ch, 0fbh,03h, 0fbh,8fh, 0fch,0bh
; 8 中音 DO 9 中音 RE 0ah 中音ME 0bh 中音 FA 0ch 中音 SO 0dh 中音LA 0eh 中音SI
db 0fch,43h, 0fch,0abh, 0fdh,08h, 0fdh,33h, 0fdh,81h, 0fdh,0c7h, 0feh,05h
; 0fh 高音 DO 10h 高音 RE 11h 高音ME 12h 高音 FA 13h 高音 SO 14h 高音LA 15h 高音SI
db 0feh,21h, 0feh,55h, 0feh,84h, 0fdh,99h, 0feh,0c0h, 0feh,0e3h, 0ffh,02h;以下為音樂樂譜代碼,前兩位表示曲譜,后一位表示節拍
;8為 1 拍,4 ->半拍mu_tab: db 6,4,8,4,10,4,1,4, 9,8,8,4,7,4,10,8,9,8,6,16
db 8,4,10,4,12,4,12,4, 13,8,12,4,11,4,10,16
db 00h
end
;c語言
;*******************************************
// 音樂編程
//通過蜂鳴器發出訊響
//只要通過修改 后面的音樂程序里面的碼
//表就可以自己譜曲唱歌!
//前一個參數為按照編碼表給定,第二個參數為節拍數#include <80d51.h>
#define uchar unsigned char
#define key1 P3_4 //鍵盤定義
#define key2 P3_5
#define key3 P3_6#define beep P3_7 //蜂鳴器定義
uchar timer_h;
uchar timer_l;//****************************************************************************
void reset();
void delay(uchar times);
void dems(); //休止符延時
void display(uchar disseg,uchar disdata);
uchar keyb();// 這里定義的是數碼管對應的字符字根
code uchar disbuf_u[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,
0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e};
// 這里定義音樂音符表
code uchar musictabl[]={0xf0,0xf0,
0xf8,0x8b,0xf9,0x5b,0xfa,0x14,0xfa,0x0c,0xfb,0x03,0xfb,0x8f,0xfc,0x0b,0xfc,0x43,0xfc,0xab,0xfd,0x08,0xfd,0x33,0xfd,0x81,0xfd,0xc7,0xfe,0x05,
0xfe,0x32,0xfe,0x55,0xfe,0x84,0xfe,0x99,0xfe,0xc0,0xfe,0xe3,0xff,0x02};
//這里定義音樂曲譜表格
code uchar musicdata[]={6,4, 8,4,10,4, 1,4, 9,8, 8,4, 7,4,10,8,9,8,6,16,
8,4,10,4,12,4,12,4,13,8,12,4,11,4,10,16,0};// ***************************************************************************
void ClockIrqHandler (void) interrupt 1 using 3; //定義定時器T0中斷服務程序
//下面是定時器T0的中斷服務程序
void ClockIrqHandler (void) interrupt 1 using 3 {
TR0 = 0; //關閉定時器
TL0 = timer_l;
TH0 = timer_h;
beep = ~beep;
TR0 = 1; //打開定時器
}
// ***************** 定時器程序 **********************************uchar keybuf;
void main()
{
uchar counts,mustmp,tmp,keym = 0; //鍵盤返回結果的緩沖區
reset(); //調用初始化程序while(1) //設置一個無限制循環
{
for(counts=0;musicdata[counts]!=0;counts+=2)
{
mustmp = musicdata[counts];
TR0 = 0;tmp = musictabl[mustmp];
timer_h = tmp;tmp = musictabl[mustmp+1];
timer_l = tmp;TR0 = 1;
display(0,musicdata[counts+1]);
delay(musicdata[counts+1]);
TR0 = 0;
dems();
}
// display(250,0);
// delay(255);
// delay(255);
// display(keybuf-1,keybuf); //在相應的位置顯示返回的結果
}
}void reset()
{keybuf = 0;
P1 = 255; //關閉LED顯示
P0 = 0; //關閉數碼顯示
P2 = 255; //
//********* 上面是一些基礎定義 **************TL0 = 0xff; //初值
TH0 = 0xff; //初值
ET0=1; // 允許定時器T0中斷
EA =1; // 中斷總允許
}
//*****************************************************************************
//下面是延時程序。具體的延時時間不能通過表面程序看出,(為什么?)
//如果我們需要一個精密的延時程序,那么我們可以采用內嵌匯編代碼的方式
void delay(uchar times)
{
int t=24000; //延時倍數
uchar i=times;
for(;i!=0;i--)
{for(;t!=0;t--){}}
}void dems() //休止符延時
{
_asm
mov R0,#0
llp: mov R1,#100
llk: djnz R1,llk
djnz R0,llp
_endasm;
}//這里是顯示子程序,入口參數為
// disseg -> 位選 可選范圍 0-7 一共8個數碼管
// disdata -> 段選 可選范圍 0-31 一個32個字符
// 段選 0-15 16個字符 為 "0"->"F"
// 段選 16-31 16個字符 為 "0."->"F."void display(uchar disseg,uchar disdata)
{
uchar dataf;
if(disseg < 8) //只有當要顯示的位數有效,才顯示.否則,不顯示
{
dataf = 1;
while(disseg)
{
dataf <<= 1;
disseg--;
}
P0 = dataf;
P2 = disbuf_u[disdata];
}
else{P0=0,P2=255;} //關閉數碼管顯示
}uchar keyb()
{
uchar key,keytmp;key1 = 1; //將輸出線拉高
key2 = 1;
key3 = 1;key = P3 ; //讀回來
key = key & 0xf0; //獲得鍵盤結果
if(key == 112 ) return 0; //如果用戶沒有按鍵返回 0
else
{
keytmp = key;
delay(1); //判斷是不是干擾
key = P3 & 0xf0;
if (key != keytmp ) return 0; //是干擾,返回 0
else //不是干擾,等待用戶釋放按鍵
{
do{
key1 = 1; //輸出拉高
key2 = 1;
key3 = 1;
key = P3 & 0xf0; //讀回來
P1_0 = ~P1_0; //如果用戶不釋放,閃爍 p1.0
}while(key != 112 ); //等待用戶釋放
P1_0 = 1; //用戶釋放以后,清除p1.0指示燈
switch(keytmp)
{
case 96: return 3;//返回用戶按鍵結果
case 80: return 2;
case 48: return 1;
}
}
}
}
//*****************************