趣味设计:小型电子琴
作者:凌阳大学计划网站 发布时间:2002-12-23 9:30:19
您是否尝试过自己做一个电子琴来玩玩啊!想象用自己做的电子琴来弹奏一曲,那将是一件多么舒坦的事情。本篇文章将介绍使用SPCE061A来做一个电子琴,并且提供源代码。如果您有凌阳大学计划的实验箱,那将很好完成,就是接几根线的问题,要是没有也不用着急啦,拿一块SPCE061A芯片,接个键盘和音频放大电路就可解决问题,是不是很easy!

图一 整体框图
我们知道,声音的频谱范围约在几十到几千赫兹,若能利用程序来控制单片机某个端口的“高”电平或低电平,则在该端口上就能产生一定频率的矩形波,接上喇叭就能发出一定频率的声音,若再利用延时程序控制“高”“低”电平的持续时间,就能改变输出频率,从而改变音调。乐曲中,每一音符对应着确定的频率,这个小制做是采用凌阳SPCE061A的DAC输出来实现,具体做法是,先建立一个有两百个数据的音频数据表,当按不同的按键即以不同的频率往DAC上送数据,从而达到输出不同音符的目的,为了达到电子琴的效果,当然还得在程序方面稍作修饰了,整体框图如图一所示。下面将就具体硬件电路和程序作一一说明。
硬件电路设计
键盘控制电路:
在这里采用矩阵式排列键盘,如图二所示,这样可以合理应用硬件资源,把16只按键排列成4*4矩阵形式,用一个8位I/O口控制如图所示。把键盘上的行和列分别接在IOA0~IOA3和IOA4~IOA7上。
图二 按键控制电路
先置IOA0~IOA3为带数据缓存器的高电平输出,置IOA4~IOA7为带下拉电阻的输管脚,此时若有键按下,取IOA4~IOA7的数据将得到一个值,把此值保存下来,再置IOA4~IOA7为带数据反相器的高电平输出,置IOA0~IOA3为带下拉电阻的输入管脚,此时若键仍没弹起,取IOA0~IOA3的数据将得到另一个值,把这两个值组合就可得知是哪个键按下了,再通过匹配得到键值,实际上在这个小设计中只用到了8个按键,但考虑到为广大电子爱好者自由发挥预留了八个按键,您可以自己设计加入别的音符或是别的好玩的啊。
音频放大电路:
凌阳SPCE061A单片机自带双通道DAC音频输出, DAC1、DAC2转换输出的模拟量电流信号分别通过AUD1和AUD2管脚输出, DAC输出为电流型输出,经LM396音频放大,即可驱动喇叭放音,放大电路如图三(只列出了DAC1,DAC2类似)。在DAC1、DAC2后面接一个简单的音频放大电路和喇叭就能实现语音播报功能,这为单片机的音频设计提供了极大方便,音频的具体功能主要通过程序来实现。
图三 音频放大电路
LED计数显示电路:
其实这一部分电路可以省去,不过在按下一个音符的时候,同时又能看到LED随频率像霓虹灯式的变幻,是不是视觉和听觉都得到了满足啊。具体接法是阳极接电阻排至V5(Vcc),I/O端口低电平“点亮”,如图四所示。

图四 LED计数显示电路
软件编程和源代码
为了更好地让读者了解程序,这里给出一个整体流程图,如图五所示,包括了整个程序的所有细节。对照源代码应是好理解的,并且源程序也给出了注释。

图五 程序流程图
所有源代码如下,先建立一个C程序作为调度之用,让后建立一个汇编文件,包括SPCE061A库和头文件,编译一下就好了啊。
C程序如下:
//*********************************************************************
// 名称: music
// 来源: 凌阳大学计划
// 描述: SPCE实现小型电子琴
// 日期: 2002/12/5
//**********************************************************************
#include "hardware.h"
unsigned int G_Pin=0;
//========================================================================
// 函数: main()
// 描述:主函数
//========================================================================
main()
{
F_Initial_Irq(); //调度程序初始化和中断初试化
F_Music(); //调度音符播放程序
}
//**********************************************************************************
// F_Initial_Irq(); 来自于irq.asm,程序初始化和中断初试化.
// F_F_Music( );来自于irq.asm,音符播放程序.
//main.c 结束
//**********************************************************************************
汇编程序如下:
.EXTERNAL _G_Pin;
.RAM
.VAR I_Led = 0,I_First,I_Last;
.IRAM
.VAR I_Sum,I_FuDu = 0;
S_Db_L_Up: //制作两百个据为各音符服务
.DW 0x8000,0x80a3,0x8146,0x81e9,0x828c,0x832f,0x83d2,0x8475,0x8518,0x85bb;
.DW 0x865e,0x8701,0x87a4,0x8847,0x88ea,0x898d,0x8a30,0x8ad3,0x8b76,0x8c19;
.DW 0x8cbc,0x8d5f,0x8e02,0x8ea5,0x8f48,0x8feb,0x908e,0x9131,0x91d4,0x9277;
.DW 0x931a,0x93bd,0x9460,0x9503,0x95a6,0x9649,0x9bec,0x978f,0x9832,0x98d5;
.DW 0x9978,0x9a1b,0x9abe,0x9b61,0x9c04,0x9ca7,0x9d4a,0x9ded,0x9e90,0x9f33;
.DW 0x9fd6,0xa079,0xa11c,0xa1bf,0xa262,0xa305,0xa3a8,0xa44b,0xa4ee,0xa591;
.DW 0xa634,0xa6d7,0xa77a,0xa81d,0xa8c0,0xa963,0xaa06,0xaaa9,0xab4c,0xabef;
.DW 0xac92,0xad35,0xadd8,0xae7b,0xaf1e,0xafc1,0xb064,0xb107,0xb1aa,0xb24d;
.DW 0xb2f0,0xb393,0xb436,0xb4d9,0xb57c,0xb61f,0xb6c2,0xb765,0xb808,0xb8ab;
.DW 0xb94e,0xb9f1,0xba94,0xbb37,0xbbda,0xbc7d,0xbd20,0xbdc3,0xbe66,0xbf09;
S_Db_Down:
.DW 0x8000,0x7f5d,0x7eba,0x7e17,0x7d74,0x7cd1,0x7c2e,0x7b8b,0x7ae8,0x7a45;
.DW 0x79a2,0x78ff,0x785c,0x77b9,0x7716,0x7673,0x75d0,0x752d,0x748a,0x73e7;
.DW 0x7344,0x72a1,0x71fe,0x715b,0x70b8,0x7015,0x6f27,0x6ecf,0x6e2c,0x6d89;
.DW 0x6ce6,0x6c43,0x6ba0,0x6afd,0x6a5a,0x69b7,0x6914,0x6871,0x67ce,0x672b;
.DW 0x6688,0x65e5,0x6542,0x649f,0x63fc,0x6359,0x62b6,0x6213,0x6170,0x60cd;
.DW 0x602a,0x5f87,0x5ee4,0x5e41,0x5d9e,0x5cfb,0x5c58,0x5bb5,0x5b12,0x5a6f;
.DW 0x59cc,0x5929,0x5886,0x57e3,0x5740,0x569d,0x55fa,0x5557,0x54b4,0x5411;
.DW 0x536e,0x52cb,0x5228,0x5185,0x50e2,0x503f,0x4f9c,0x4ef9,0x4e56,0x4db3;
.DW 0x4d10,0x4c6d,0x4bca,0x4b27,0x4a84,0x49e1,0x493e,0x489b,0x47f8,0x4755;
.DW 0x46b2,0x460f,0x45bc,0x44c9,0x4426,0x4383,0x42e0,0x423d,0x419a,0x40f7;
.CODE
//===================================================
//函数: F_Initial_Irq()
//语法:void F_Initial_Irq(void )
//描述:初始化子程序
//参数:无
//返回:无
//==============================================
.PUBLIC _F_Initial_Irq //初始化子程序
_F_Initial_Irq:.PROC
INT OFF
R1 = 0x0004 //开中断IRQ5_2Hz
[P_INT_CTRL] = R1
[P_INT_CTRL_NEW] = R1
R1 = 0x0000 //IOB初始化,为I_Led显示服务
[P_IOB_DATA] = R1
[P_IOB_ATTRI] = R1
R1 = 0xffff
[P_IOB_DIR] = R1
INT IRQ
.ENDP
//=======================================
//函数: F_Music()
//语法:void F_Music(void )
//描述:音符播放程序.
//参数:无
//返回:无
//======================================
.PUBLIC _F_Music;
_F_Music:.PROC
R1 = 0x0000;
[P_DAC_Ctrl] = R1
R3 = 0xffff
R1 = 0x0000;
_L_MainLoop:
R2 = 0
R1 = [I_Led]
R1 += 1
[I_Led] = R1
[P_IOB_DATA] = R1
_L_Down1:
[I_Sum] = R2
R2 = R2+[_G_Pin]
R1 = S_Db_Down
BP = R1
BP = BP+[I_Sum]
R1 = [BP]
R1 = R1+0x3f09
R1 = R1+[I_FuDu]
[P_DAC1] = R1
[P_DAC2] = R1
R3 = 100
CMP R2,R3
JB _L_Down1
R2 = 0
R1 = [I_Led]
R1+ = 1
[I_Led] = R1
[P_IOB_DATA] = R1
_L_Down:
[I_Sum] = R2
R2 = R2+[_G_Pin]
R1 = S_Db_Down
BP = R1
BP = BP+[I_Sum]
R1 = [BP]
R1 = R1-[I_FuDu]
[P_DAC1] = R1
[P_DAC2] = R1
R3 = 100
CMP R2,R3
JB _L_Down
R2 = 0
JMP _L_MainLoop1
_L_ZhongZhuan:
jmp _L_MainLoop
_L_MainLoop1:
R1 = [I_Led]
R1+ = 1
[I_Led] = R1
[P_IOB_DATA] = R1
_L_L_Up1:
[I_Sum] = R2
R2 = R2+[_G_Pin]
R1 = S_Db_L_Up
BP = R1
BP = BP+[I_Sum]
R1 = [BP]
R1 = R1-0x3f09
R1 = R1-[I_FuDu]
[P_DAC1] = R1
[P_DAC2] = R1
R3 = 100
CMP R2,R3
JB _L_L_Up1
R2 = 0
R1 = [I_Led]
R1 += 1
[I_Led] = R1
[P_IOB_DATA] = R1
_L_Up:
[I_Sum] = R2
R2 = R2+[_G_Pin]
R1 = S_Db_L_Up
BP = R1
BP = BP+[I_Sum]
R1 = [BP]
R1 = R1+[I_FuDu]
R3 = R3
[P_DAC1] = R1
[P_DAC2] = R1
R3 = 100
CMP R2,R3
JB _L_Up
JMP _L_ZhongZhuan
RETF
.ENDP
//===============================================================
//函数: F_Key()
//语法:void F_Key(int I_Key )
//描述:按键扫描
//参数:无
//返回:I_Key扫描的键值
//======================================
.PUBLIC _F_Key //键扫描程序
_F_Key:.PROC
R1 = 0x0004
[P_INT_CLEAR] = R1 //初始化扫描线和数据线
R1 = 0x00f0
[P_IOA_DIR] = R1
R1 = [PC]
R1 = 0x0000
[P_IOA_ATTRI] = R1
R1 = 0x0000
[P_IOA_DATA] = R1
R1 = [P_IOA_DATA]; //多取几次有利于防抖动
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
[I_First] = R1;
R1 = 0x000f; //扫描线和数据线换位
[P_IOA_DIR] = R1
R1 = 0x0000
[P_IOA_ATTRI] = R1
R1 = 0x0000
[P_IOA_DATA] = R1
R3 = 0x0000
R1 = [P_IOA_DATA]; //多取几次有利于防抖动
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
R1 = [P_IOA_DATA]
[I_Last] = R1;
RETF
.ENDP
.TEXT
//========================================================================
//函数: IRQ5()
//语法:void IRQ5(void )
//描述:irq5中断服务
//参数:无
//返回:无
//=========================================
.PUBLIC _IRQ5
_IRQ5:
PUSH R1,R5 TO [sp]
R1 = 0x0004
TEST R1,[P_INT_CTRL]
JNZ l_Irq_2 bsp;
l_Irq_4: //4赫兹中断不作处理
R1 = 0x0008
[P_INT_CLEAR] = R1
POP R1,r5 FROM [SP]
RETI
l_Irq_2: //2Hz程序段
CALL _F_Key;
R1 = [I_Last]
R2 = [I_First]
R3 = 0x00f1
R4 = 0x00f2
CMP R2,R3 //判哪个按键,给定相应频率
JE _L_PanKey
CMP R2,R4
JE _L_PanKey1
_L_Retrun:
POP R1,R5 FROM [sp]
RETI
_L_PanKey:
R3 = 0x001f
CMP R1,R3
JE _L_PinAdd
R3 = 0x002f
CMP R1,R3
JE _L_PinAdd2
R3 = 0x004f
CMP R1,R3
JE _L_PinAdd3
R3 = 0x008f
CMP R1,R3
JE _L_PinAdd4
_L_PinAdd:
R1 = 3
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun
_L_PinAdd2:
R1 = 4
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun
_L_PinAdd3:
R1 = 5
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun
_L_PinAdd4:
R1 = 6
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun
_L_PanKey1:
R3 = 0x001f
CMP R1,R3
JE _L_PinAdd5
R3 = 0x002f
CMP R1,R3
JE _L_PinAdd6
R3 = 0x004f
CMP R1,R3
JE _L_PinAdd7
R3 = 0x008f
CMP R1,R3
JE _L_PinAdd8
_L_PinAdd5:
R1 = 7
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun1
_L_PinAdd6:
R1 = 8
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun1
_L_PinAdd7:
R1 = 9
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun1
_L_PinAdd8:
R1 = 10
[_G_Pin] = R1
CALL _L_Delay
JMP _L_Retrun1
_L_Retrun1:
POP R1,R5 FROM [SP]
RETI
//=====================================================
//函数: L_Delay()
//语法:void L_Delay(void )
//描述:延时程序,延时1/4秒
//参数:无
//返回:无
//==============================================
.PUBLIC _L_Delay
_L_Delay: .PROC //延时子程序,有利于分辨同一音符,多次输出
R1 = 100;
_L_Loop1:
R2 = 936;
_L_Loop2:
R2- = 1
JNZ _L_Loop2
R1- = 1
JNZ _L_Loop1;
RETF;
.ENDP
于16日评论道:
这个还没做好成品,不好意思.