您的位置:首页 > 其它

详解51单片机播放音乐、电子琴、快进

2013-07-14 23:33 246 查看
这是在学习完51之后做的一个实验,现在再来看有些寄存器都不记得了。不过我还是想要把它记录下来——因为我热爱音乐



1.效果

先来看看实验的仿真图吧:



从按键就可以看到,这个实验可以实现播放音乐,暂停音乐,快进音乐,弹奏电子琴。

接下来就来看看怎么来做这个实验。

2.声音

声音是由物体振动产生。我们听到人唱歌,听到手机播放音乐,都是这样一个道理,只是声源不同罢了。声音有以下三个主要特性:响度、音调和音色。响度是由声源的振幅和与声源的距离决定的;音调是由振动的频率决定的;而音色是由振动的波形决定的。可以这么认为,音乐的乐谱是由不同音调的音符组成的。只要我们能够发出不同音调的声音,组合起来就成了优美动听的音乐。

3.蜂鸣器

蜂鸣器可以作为一种声源,当有不同频率的电流流过的时候就能发出不一样的声音——不同音调。

所以,可以用51单片机的一个IO口接到蜂鸣器上写这么一段驱动程序(注意到让P3.0反相是关键的,因为要输出的是频率信号。):

MAIN:
MOV R1,#10
LOP1:MOV R2,#50
LOP2:DJNZ R2,LOP2
DJNZ R1,LOP1
CPL P3.0;反相(P3.0接到蜂鸣器)
LJMP MAIN
END
但是这个程序有三个缺点:音调(频率)固定、一直响、占用CPU。为此,我们需要使用51的定时器来解放CPU。

4.确定音符的频率

有了上面的知识,我们知道要想让蜂鸣器播放出音乐,首先要做的是让蜂鸣器发出不同音符的声音。

我们先来看音符频率表:



DO(中)的频率为523Hz,其中523Hz=1/523 S=1912uS,又1912uS/2=956uS。因此只要令单片机(假设该单片机的晶振是12MHz的)的计数器计时956uS/1us=956(次)并在每计数956次时就将I/O口输出反相,就可让蜂鸣器发出中音DO(532Hz)。由于使用的是16位的加法计数器,只要再求65535-956=64579.将它赋给计数器并计数就可以了。

利用上面的方法计算其它音符得出一张表:



这时候,我们的程序变成了这样:

SPEAKER BIT P3.0

ORG 0000H
LJMP MAIN
ORG 000BH
LJMP TIMER0
MAIN:
MOV TMOD,#01H;计数器工作在MODE1,16位计数器
SETB ET0;开定时/计数器0允许
SETB EA;允许单片机总中断
MOV DPTR,#TAB
MOV A,#0
MOVC A,@A+DPTR
MOV TH0,A
MOV A,#1
MOVC A,@A+DPTR
MOV TL0,A
SETB TR0
LOOP:
JMP LOOP

TIMER0:
MOV A,#0
MOVC A,@A+DPTR
MOV TH0,A
MOV A,#1
MOVC A,@A+DPTR
MOV TL0,A
CPL SPEAKER
RETI

TAB:
DW 64580,64684,64777,64820
DW 64898,64968,65030,65058
DW 64260,64400,64524,65217
DW 65252,65283,65300,65330

END
这个程序把一些常用音符的计数初值存入了单片机的ROM里,这样要想发出哪种频率的声音只要取出表中的值就可以了(但是这个程序只发出表中第一个音符的声音)。

另外,这个程序还解放了CPU,我们可以在LOOP里实现其它功能。

4.乐谱

现在,我们可以利用蜂鸣器发出不同音符的声音,那就找自己喜欢的乐谱进行编码吧!



这是《东风破》的简谱。大家应该知道数字表示的是音符吧?


《东风破》是一首4/4拍的歌曲——4分音符为一拍,每小节4拍,可以有4个4分音符。在这个实验里节拍只是一个时间的概念,它规定了某个音符应该响多久。

简单来看这张谱,用横线划在一起的是一小节,05、55、67、65、33、32、2、05、55、32……这些分别是一个小节。假定1/4小节(1拍)要响的时间为1s,那么对于第一个小节来说,0是2拍,5也是2拍,他们分别要响2s。而对于第三个小节67,6要响3拍3s,而7只响1拍1s。

这样,我们得出了音乐表,以一个字节表示一个音符,高4位表示音符的频率,低4位表示这个音符应该响的节拍。

SONG:
DB 02H,52H,52H,52H,63H,71H,62H,52H
DB 32H,32H,32H,22H,24H
DB 02H,52H,52H,52H,32H,22H,22H,12H
DB 22H,12H,22H,32H,34H
DB 02H,0B2H,12H,22H,32H,22H,32H,52H
DB 32H,22H,12H,22H,0A4H,04H
DB 02H,12H,12H,22H,32H,22H,32H,52H
DB 62H,52H,52H,32H,32H,32H,32H,22H
按照这种编码方式,你也可以弄一首自己喜欢的曲子。

现在,我们用SONG表来表示想要播放的曲子,在程序里不断取出SONG表的每一个字节,然后根据节拍码来决定延时时间Tp,在Tp时间里,又根据频率码来决定播放的声音。这样美妙动听的歌曲就组合起来了。

5.快进

上面提到了1/4拍的时间,具体是多少没有确定。所以我们可以控制这个值来控制播放的快慢。

6.电子琴

在知道怎么让蜂鸣器发出不同频率的声音的时候电子琴功能就完成了一大半,无非就是差在矩阵键盘的识别上,而矩阵键盘的识别按键的方法有多种。

最后,列出源程序,不明白的地方看注释。源程序和proteus仿真图在此下载:http://download.csdn.net/detail/songxueyu/5753669

SPEAKE BIT P3.0;接到蜂鸣器
MAINLED BIT P3.1
AUTOSING BIT P2.0
SINGLED BIT P3.2
AUTOSTOP BIT P2.1
PLAYSING BIT P2.2
PLAYLED BIT P3.3
PLAYSTOP BIT P2.3
FAST BIT P2.4
FASTLED BIT P3.4
FASTFLAG BIT 00H

ORG 0000H
LJMP MAIN

ORG 000BH
LJMP TIM0;TIMER0的中断服务程序

ORG 1000H
MAIN:
MOV SP,#60H;初始化堆栈
MOV TMOD,#01H;计数器工作在MODE1,16位计数器
SETB ET0;开定时/计数器0允许
SETB EA;允许单片机总中断
MOV P3,#0FCH;让除了MAIN以外的灯灭
MOV 40H,#83;设置初始速度
MOV 30H,#00H;初始化偏移量

START0:
JB AUTOSING,PLAY1/*若按下p2.0,表示想要自动播放歌曲*/
SETB MAINLED
CLR SINGLED
JMP SONG1
PLAY1:
JB PLAYSING,START0/*若按下PLAYSING表示想要弹琴*/
SETB MAINLED
CLR PLAYLED
LJMP PLAY2

;播放歌曲的程序段
SONG1:
MOV A,30H;将偏移量传入A
MOV DPTR,#SONG;取得歌曲表的首地址
MOVC A,@A+DPTR;取得相应音符
JZ OVER;若8位全为0表示播放歌曲结束
MOV R2,A
ANL A,#0FH;从低4位取出节拍
MOV R5,A;存入R5,用于定时器的中断服务程序
MOV A,R2;再导入R2
SWAP A
ANL A,#0FH;取出频率向量
JNZ SING;若为0表示无声
CLR TR0 ;不记数
JMP D1
;取出频率(计数值)的程序段
SING:
DEC A;减1,因为要从0位置开始
RL A;在计数值表中以字存储,乘2才得到正确位置
MOV 22H,A;将位置暂存
MOV DPTR,#TAB1;计数值表
MOVC A,@A+DPTR
MOV TH0,A;取到的高位放入TH0
MOV 21H,A;暂存到21H,用于中断程序
MOV A,22H
INC A;取低位地址
MOVC A,@A+DPTR
MOV TL0,A;放入TL0
MOV 20H,A;暂存到20H,用于中断程序
SETB TR0;计数器开始计数
D1:
ACALL DELAY
INC 30H;播放完一个音符后将偏移量加1
JMP SONG1
OVER:
SETB SINGLED
CLR MAINLED
LJMP MAIN
;以下是电子琴的程序段
PLAY2:
MOV A,#0FH  ;先给p1的高4位置1,低4位置0,判断是哪行
MOV P1,A
MOV R3,#00H
MOV R4,#00H
PLAY3:
ACALL DELAY20;加上这个就不会有串音的现象了
JB PLAYSTOP,PL0;若按下退出电子琴键则跳回
SETB PLAYLED
CLR MAINLED
LJMP START0
;以下是判断矩阵键盘的按键
;判断是哪行按下
PL0:
MOV A,P1
JB ACC.0,PL1
MOV R3,#0
SJMP LINE
PL1:
JB ACC.1,PL2
MOV R3,#1
SJMP LINE
PL2:
JB ACC.2,PL3
MOV R3,#2
SJMP LINE
PL3:
JB ACC.3,PLAY3
MOV R3,#3
;判断是哪列
LINE:
MOV A,#0F0H;这次给低位置1,高位置0
MOV P1,A
MOV A,P1
LINE4:
JB ACC.4,LINE5
MOV R4,#0
SJMP PLAY4
LINE5:
JB ACC.5,LINE6
MOV R4,#4
SJMP PLAY4
LINE6:
JB ACC.6,LINE7
MOV R4,#8
SJMP PLAY4
LINE7:
JB ACC.7,PLAY3
MOV R4,#12
;这样就可以用R4+R3得出是哪个键被按下
PLAY4:
MOV A,R3
ADD A,R4
ACALL YOUPLAY;得出按键后就调出频率发声就行了
LOOP:;将这个地方做做修改
MOV A,P1
ORL A,#0FH
CPL A
JZ OFF
JMP LOOP
OFF:CLR TR0
LJMP PLAY2

;定时计数器0中断服务程序
TIM0:
PUSH ACC
PUSH PSW
MOV TL0,20H;将暂存的计数值再放进去
MOV TH0,21H
CPL SPEAKE;取反
POP PSW
POP ACC
RETI

;弹琴的子程序
YOUPLAY:
RL A
MOV 22H,A;将位置暂存
MOV DPTR,#TAB1
MOVC A,@A+DPTR
MOV 21H,A
INC 22H
MOV A,22H
MOVC A,@A+DPTR
MOV 20H,A
MOV TH0,21H
MOV TL0,20H
SETB TR0
RET

;延时20ms,用于防抖
DELAY20:
mov r1,#50h
del0:mov r2,#7dh
del1:djnz r2,del1
djnz r1,del0
RET

;节拍延时函数,用于产生每个音符的节拍,延时187ms
DELAY:
MOV R7,#02
D2:MOV R4,#187
D3:MOV R3,40H
TEST:
JB FAST,TEST1;若没有按下快进键直接去检测是否按下暂停键
JB FASTFLAG,FASTSTOP;FAST为0表示可以快进,FAST为1表示已经在快进状态
SETB FASTFLAG
CLR FASTLED
MOV 40H,#30;用30使播放速度变快
LJMP DELAY
FASTSTOP:
CLR FASTFLAG
SETB FASTLED
MOV 40H,#83
LJMP DELAY
TEST1:
JB AUTOSTOP,GO_ON
CLR TR0
SETB SINGLED
CLR MAINLED
LJMP START0
GO_ON:
DJNZ R3,TEST
DJNZ R4,D3
DJNZ R7,D2
DJNZ R5,DELAY
RET
;计数表
TAB1:
DW 64580,64684,64777,64820
DW 64898,64968,65030,65058
DW 64260,64400,64524,65217
DW 65252,65283,65300,65330
;歌曲表,高位是频率,低位是节拍
SONG:
;洋娃娃和小熊跳舞
/*
DB 12H,22H,32H,42H,52H,52H,51H,41H,32H
DB 42H,42H,41H,31H,22H,12H,32H,54H
DB 12H,22H,32H,42H,52H,52H,51H,41H,32H
DB 42H,42H,41H,31H,22H,12H,32H,14H
DB 62H,62H,61H,51H,42H,52H,52H,51H,41H,32H
DB 42H,42H,41H,31H,22H,12H,32H,54H
DB 62H,62H,61H,51H,42H,52H,52H,51H,41H,32H
DB 42H,42H,41H,31H,22H,12H,32H,14H,00H
*/
/*
;东风破
;一个小节是一行;9,A,B
DB 22H,12H,22H,33H,21H,22H,12H
DB 22H,12H,12H,0A2H,28H
DB 02H,22H,12H,22H,33H,21H,21H,11H,12H
DB 22H,12H,11H,0A1H,0A1H,91H,98H
DB 02H,52H,52H,52H,32H,32H,32H,42H
DB 53H,41H,42H,4AH
DB 02H,22H,22H,32H,21H,11H,12H,0A2H,12H
DB 62H,62H,62H,52H,58H
;反复记号后面
DB 02H,32H,32H,32H,33H,21H,22H,12H
DB 22H,32H,22H,12H,26H,32H
DB 02H,22H,0A2H,12H,23H,11H,12H,22H
DB 32H,52H,52H,32H,58H
DB 02H,52H,52H,52H,63H,51H,32H,42H
DB 54H,62H,4AH
DB 02H,22H,22H,12H,32H,22H,12H,0A2H
DB 22H,12H,22H,1AH
;括号里面的
DB 61H,31H,51H,21H,31H,21H,11H,0A1H,91H,0A1H,11H,21H,31H,51H,61H,81H
*/
;括号后面的反复记号
DB 02H,52H,52H,52H,63H,71H,62H,52H
DB 32H,32H,32H,22H,24H
DB 02H,52H,52H,52H,32H,22H,22H,12H
DB 22H,12H,22H,32H,34H
DB 02H,0B2H,12H,22H,32H,22H,32H,52H
DB 32H,22H,12H,22H,0A4H,04H
DB 02H,12H,12H,22H,32H,22H,32H,52H
DB 62H,52H,52H,32H,32H,32H,32H,22H
;重复
DB 02H,52H,52H,52H,63H,71H,62H,52H
DB 32H,32H,32H,22H,24H
DB 02H,52H,52H,52H,32H,22H,22H,12H
DB 22H,12H,22H,32H,34H
DB 02H,0B2H,12H,22H,32H,22H,32H,52H
DB 62H,52H,52H,32H,54H
DB 02H,62H,62H,52H,33H,51H,32H,22H
DB 02H,22H,22H,12H,32H,22H,12H,0A2H
DB 22H,1EH
;第二个括号里面
DB 3AH,22H,32H,0A2H
DB 16H,21H,31H,54H
DB 02H,32H,32H,32H,32H,61H,51H,33H,21H
DB 32H,51H,3DH
DB 6AH,32H,23H,31H
DB 21H,11H,0A6H,0A6H,21H,31H
DB 24H,22H,11H,0A1H,92H,0A1H,11H,14H,14H
DB 00H
END
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: