2.11 歌曲演奏实例
本实例是利用单片机演奏一首生日快乐歌。
2.11.1 编程演奏器原理
1.演奏器原理
(1)通过控制单片机定时器的定时时间产生不同频率的音频脉冲,经放大后驱动蜂鸣器发出不同音节的声音。
(2)用软件延时来控制发音时间的长短,控制节拍,表2-14是各调1/4节拍的时间表。
(3)把乐谱中的音符和相应的节拍变换为定时常数和延时常数,作为数据表格存放在存储器中。由程序查表得到定时常数和延时常数,分别用来控制定时器产生的脉冲频率和发出该音频脉冲的持续时间。
(4)表2-15为单片机晶振频率为12MHz时,乐曲中的音符、频率及定时常数之间的对应关系表。
2.歌谱
3.建立步骤
(1)先把乐谱的音符找出,然后根据表2-15给出的定时值按乐谱的音符顺序建立编码表TABLE。
定时值为十六进制4位数,拆开分为两组,如5对应的定时值为FD80H,拆分为FDH和80H两组。前组装入定时器的高位TH0,后组装入定时器的低位TL0。程序中将进行两次查表来完成一个音符对应的定时初值的装入。
(2)在程序中使用定时器T0方式1来产生歌谱中各音符对应频率的音频脉冲,由 P3.4输出,再经三极管将信号放大后驱动蜂鸣器发出不同音节的声音。
(3)程序中节拍的控制是通过调用延时子程序DELAY的次数来实现的,1拍为 748 ms,即需要调用4次DELAY;3/4拍需要调用3次DELAY;2/4拍需要调用2次DELAY。
(4)节拍的控制码在表TABLE中位于音符码的后面。如第1行“DB 0FDH,80H, 03H,…”中,0FDH和80H是音符5的音符码,其后边的03H是节拍码,即3/4拍的时间。
(5)当一个音符的发音时间到时,再查下一个音符的定时常数和延时常数。依此进行下去,就可演奏出悦耳动听的乐曲。
2.11.2 程序设计
1.流程图
程序设计流程如图2-18所示。
2.程序
汇编语言编写的歌曲演奏源程序FS03.ASM代码如下:
01: ORG 00H ;主程序起始地址
02: JMP START ;跳转至主程序
03: ORG 0BH ;定时器T0中断入口
04: JMP EXT0 ;跳转至T0中断子程序
05: START: MOV TMOD,#00000001B ;设置T0方式1
06: MOV IE,#10000010B ;允许T0中断
07: MOV DPTR, #TABLE ;存表首地址
08: LOOP: CLR A ;清0
09: MOVC A,@A + DPTR ;查表
10: MOV R1, A ;定时器高8位存入R1
11: INC DPTR ;指针加1
12: CLR A ;清0
13: MOVC A,@A + DPTR ;查表
14: MOV R0, A ;定时器低8位存入R0
15: ORL A, R1 ;进行或运算
16: JZ NEXT0 ;全0为休止符
17: MOV A, R0
18: ANL A, R1 ;进行与运算
19: CJNE A, #0FFH, NEXT ;全1表示乐曲结束
20: JMP START ;从头开始循环演奏
21: NEXT: MOV TH0, R1 ;装入高位定时值
22: MOV TL0, R0 ;装入低位定时值
23: SETB TR0 ;启动定时器T0
24: JMP NEXT1 ;跳转到NEXT1处
25: NEXT0: CLR TR0 ;关闭定时器,停止发音
26: NEXT1: CLR A ;清0
27: INC DPTR ;指针加1
28: MOVC A, @A + DPTR ;查延时常数
29: MOV R2, A ;延时常数存入R2
30: LOOP1: ACALL DELAY ;调用延时子程序
31: DJNZ R2, LOOP1 ;控制延时次数
32: INC DPTR ;指针加1
33: JMP LOOP ;跳转到LOOP处
34: EXT0: MOV TH0, R1 ;重装定时值
35: MOV TL0, R0
36: CPL P3.4 ;反相输出
37: RETI ;中断子程序返回
38: DELAY: MOV R7, #02 ;延时187ms子程序
39: D2: MOV R6, #187
40: D3: MOV R5, #248
41: DJNZ R5,$
42: DJNZ R6, D3
43: DJNZ R7, D2
44: RET ;延时子程序返回
45: TABLE: DB 0FDH,80H,03H, 0FDH,80H,01H ;编码表
46: DB 0FDH,0C6H,04H, 0FDH,80H,04H
47: DB 0FEH,2AH,04H, 0FEH,02H,04H
48: DB 00H,00H,04H
49: DB 0FDH,80H,03H, 0FDH,80H,01H
50: DB 0FDH,0C6H,04H, 0FDH,80H,04H
51: DB 0FEH,5CH,04H, 0FEH,2AH,04H
52: DB 00H,00H,04H
53: DB 0FDH,80H,03H, 0FDH,80H,01H
54: DB 0FEH,0C0H,04H, 0FEH,84H,04H
55: DB 0FEH,2AH,04H, 0FEH,02H,04H
56: DB 0FDH,0C6H,04H
57: DB 0FEH,98H,03H, 0FEH,98H,01H
58: DB 0FEH,84H,04H, 0FEH,2AH,04H
59: DB 0FEH,5CH,04H, 0FEH,2AH,04H
60: DB 00H,00H,04H
61: DB 0FFH,0FFH ;结束码
62: END ;程序结束
2.11.3 代码详解
1.标号说明
START:程序开始的进入点。
LOOP:处理下一个音符的进入点。
NEXT:装入定时初值的进入点。
NEXT0:关闭定时器、停止发音的进入点。
NEXT1:查找延时常数的进入点。
LOOP1:处理节拍时间的进入点。
EXT0:中断子程序的进入点。
DELAY:延时180ms子程序的进入点。
2.寄存器使用分配情况
R0、R1、R2、R5、R6、R7:普通寄存器。其中R0存放低位定时器初值;R1存放高位定时器初值;R2存放延时常数值;R5、R6和R7在延时子程序中作计数器用。
A和DPTR:特殊功能寄存器,其中A又称累加器,DPTR为数据指针。在程序中把表TABLE的首地址存入DPTR作基础地址,A作为变址寄存器。将基址寄存器和变址寄存器的内容相加(@A + DPTR)形成操作数的地址。
TMOD:定时器工作模式控制寄存器。
TL0和TH0:定时器0的计数器,TL0为低8位,TH0为高8位。
TR0:定时器0控制寄存器TCON的一个控制位。
IE:中断允许控制寄存器。
P3.4:对内是P3寄存器的一个位,对外是输入/输出端口的一个引脚,作音频信号输出端口。
3.程序分析解释
01~04:设定入口地址。其中定时器中断是从标号EXT0处进入中断子程序。
05~07:设置定时器T0为方式1,并允许T0中断。其中07行语句将表TABLE的首地址存入数据指针寄存器DPTR中。
08~10:第一次查表,取出表中的第一个码,即乐谱中的第一个音符5的定时常数(定时初值)FD80H中的高位部分FDH,并存入寄存器R1中。
11~14:第二次查表,取出表中的第二个码,即乐谱中的第一个音符5的定时常数FD80H中的低位部分(即80H),并存入寄存器R0中。
15~20:判断、转移语句。对二次查取到的码,检查是否有休止符码,该判断是通过第15行语句“ORL A,R1”对A和R1寄存器的内容进行或运算实现的。
如果累加器A中的值是00H,R1寄存器的值也是00H,进行或运算后再存入累加器A中,此时累加器A中的值是全0,说明取到的是休止符码。通过第16行语句“JZ NEXT0”,跳转至标号NEXT0处,关闭定时器,停止发音,完成乐谱中休止符的作用。
取码时是否取到了结束码是通过第18行语句指令ANL对A和R1进行与运算来判断的。如果累加器A中的值是FFH,R1寄存器的值也是FFH,进行与运算后再存入累加器A中,此时累加器A中的值是全1,说明取到的是结束码。
第19行语句“CJNE A,#0FFH,NEXT”,如果累加器A中的值与#0FFH不相等,则跳转移到NEXT处,开始向定时器装入定时常数;如果A与#0FFH相等,说明取到的码是结束码,程序向下执行,即从头开始循环演奏。
21~23:开始向定时器装入定时常数并启动定时器工作。
24~25:处理拍节。
26~29:查取延时码并存入R2。
30~31:决定拍节时间。如果取来的码是02H,程序调用两次DELAY延时,即为2/4拍节时间;如果取来的码是04H,程序调用4次DRLAY延时,即为1拍节时间。
32~33:每次查表取码后,数据指针都要加1,以便指向下一个待查的码。第33行语句将程序运行跳转到标号LOOP处,处理下一个音符。
34~37:中断子程序。当定时器计时满后将产生中断,由标号EXT0处进入中断子程序。在中断子程序中重装定时值,并通过第36行语句在P3.4端口输出音频信号。
38~44:延时187ms子程序。
45~61:编码表TABLE。在表中每个音节由3个码组成,前两个为音符码,后一个为节拍码。如表中第1行“0FDH,80H,03H”,其中0FDH和80H即为FD80H,是音符5的发音编码;03H是节拍码,3/4节拍。
表中的最后一行“0FFH,0FFH”是结束码,表示乐曲演奏结束。
4.边用边学指令
本程序用到新的指令有ORL和JZ。
ORL:逻辑运算及位移类指令中的按位或操作指令。该指令将累加器A中的内容与源操作数所指出的内容按位进行逻辑或运算,结果存入A中。
JZ:控制转移类指令,功能是当累加器A为0时跳转。
2.11.4 模拟仿真
1.模拟仿真前注意事项
在23与24行语句之间,加一条“SETB TF0”语句,该语句使定时器T0的溢出标志位TF0为1,模拟产生中断,使程序能进入中断子程序中运行。
在39~43行语句的前边加分号,使延时子程序的延时时间缩短为2ms,缩短模拟仿真时间。
2.模拟仿真中注意事项
观察程序主要运行路线:第一次查表→第二次查表→中断子程序及返回→第三次查 表→进入延时子程序及返回→处理下一个音符(跳转到LOOP处)。
观察累加器A中值的变化。第一次查表执行第09行语句“MOVC A,@A + DPTR”后,A值为FDH。第二次查表,执行第13行语句“MOVC A,@A + DPTR”后,A值为80H。两次查表取回音符5的对应码FD80H。第三次查表,执行第28行语句“MOVC A,@A + DPTR”后,A值为03H,取回的是音符5的对应拍节码。
2.11.5 实例测试
将写入歌曲演奏程序的单片机插入实验板插座内,并检查实验板上蜂鸣器接口是否与程序中声音输出端口一致,当检查无误后接通电源,就会听到生日快乐歌。
2.11.6 经验总结
在用单片机作可编程乐曲演奏器的程序里,一般用定时器T0方式1来控制音符的频率,调用延时子程序DELAY来控制节拍。
程序采用查表的方法,将乐谱转换成控制码并制成表TABLE,然后进行三次查表。第一、第二次查表完成对音符的控制,第三次查表读取节拍码,完成对节拍的控制。
在完成第一、第二次查取音符的控制码后,程序中还设有判断语句,如果判断出取来的是休止符码,就将定时器关闭一段时间;如果判断出取来的是结束码,程序就从头开始循环。