您是否嘗試過自己做一個電子琴來玩玩。∠胂笥米约鹤龅碾娮忧賮韽椬嘁磺,那將是一件多么舒坦的事情。本篇文章將介紹使用SPCE061A來做一個電子琴,并且提供源代碼。如果您有凌陽大學計劃的實驗箱,那將很好完成,就是接幾根線的問題,要是沒有也不用著急啦,拿一塊SPCE061A芯片,接個鍵盤和音頻放大電路就可解決問題,是不是很easy!

圖一 整體框圖
我們知道,聲音的頻譜范圍約在幾十到幾千赫茲,若能利用程序來控制單片機某個端口的“高”電平或低電平,則在該端口上就能產(chǎn)生一定頻率的矩形波,接上喇叭就能發(fā)出一定頻率的聲音,若再利用延時程序控制“高”“低”電平的持續(xù)時間,就能改變輸出頻率,從而改變音調(diào)。樂曲中,每一音符對應著確定的頻率,這個小制做是采用凌陽SPCE061A的DAC輸出來實現(xiàn),具體做法是,先建立一個有兩百個數(shù)據(jù)的音頻數(shù)據(jù)表,當按不同的按鍵即以不同的頻率往DAC上送數(shù)據(jù),從而達到輸出不同音符的目的,為了達到電子琴的效果,當然還得在程序方面稍作修飾了,整體框圖如圖一所示。下面將就具體硬件電路和程序作一一說明。
硬件電路設計
鍵盤控制電路:
在這里采用矩陣式排列鍵盤,如圖二所示,這樣可以合理應用硬件資源,把16只按鍵排列成4*4矩陣形式,用一個8位I/O口控制如圖所示。把鍵盤上的行和列分別接在IOA0~IOA3和IOA4~IOA7上。

圖二 按鍵控制電路
先置IOA0~IOA3為帶數(shù)據(jù)緩存器的高電平輸出,置IOA4~IOA7為帶下拉電阻的輸管腳,此時若有鍵按下,取IOA4~IOA7的數(shù)據(jù)將得到一個值,把此值保存下來,再置IOA4~IOA7為帶數(shù)據(jù)反相器的高電平輸出,置IOA0~IOA3為帶下拉電阻的輸入管腳,此時若鍵仍沒彈起,取IOA0~IOA3的數(shù)據(jù)將得到另一個值,把這兩個值組合就可得知是哪個鍵按下了,再通過匹配得到鍵值,實際上在這個小設計中只用到了8個按鍵,但考慮到為廣大電子愛好者自由發(fā)揮預留了八個按鍵,您可以自己設計加入別的音符或是別的好玩的啊。
音頻放大電路:
凌陽SPCE061A單片機自帶雙通道DAC音頻輸出, DAC1、DAC2轉(zhuǎn)換輸出的模擬量電流信號分別通過AUD1和AUD2管腳輸出, DAC輸出為電流型輸出,經(jīng)LM396音頻放大,即可驅(qū)動喇叭放音,放大電路如圖三(只列出了DAC1,DAC2類似)。在DAC1、DAC2后面接一個簡單的音頻放大電路和喇叭就能實現(xiàn)語音播報功能,這為單片機的音頻設計提供了極大方便,音頻的具體功能主要通過程序來實現(xiàn)。
圖三 音頻放大電路
LED計數(shù)顯示電路:
其實這一部分電路可以省去,不過在按下一個音符的時候,同時又能看到LED隨頻率像霓虹燈式的變幻,是不是視覺和聽覺都得到了滿足啊。具體接法是陽極接電阻排至V5(Vcc),I/O端口低電平“點亮”,如圖四所示。

圖四 LED計數(shù)顯示電路
軟件編程和源代碼
為了更好地讓讀者了解程序,這里給出一個整體流程圖,如圖五所示,包括了整個程序的所有細節(jié)。對照源代碼應是好理解的,并且源程序也給出了注釋。

圖五 程序流程圖
所有源代碼如下,先建立一個C程序作為調(diào)度之用,讓后建立一個匯編文件,包括SPCE061A庫和頭文件,編譯一下就好了啊。
C程序如下:
//*********************************************************************
// 名稱: music
// 來源: 凌陽大學計劃
// 描述: SPCE實現(xiàn)小型電子琴
// 日期: 2002/12/5
//**********************************************************************
#include "hardware.h"
unsigned int G_Pin=0;
//========================================================================
// 函數(shù): main()
// 描述:主函數(shù)
//========================================================================
main()
{
F_Initial_Irq(); //調(diào)度程序初始化和中斷初試化
F_Music(); //調(diào)度音符播放程序
}
//**********************************************************************************
// F_Initial_Irq(); 來自于irq.asm,程序初始化和中斷初試化.
// F_F_Music( );來自于irq.asm,音符播放程序.
//main.c 結(jié)束
//**********************************************************************************
匯編程序如下:
.EXTERNAL _G_Pin;
.RAM
.VAR I_Led = 0,I_First,I_Last;
.IRAM
.VAR I_Sum,I_FuDu = 0;
S_Db_L_Up: //制作兩百個據(jù)為各音符服務
.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
//===================================================
//函數(shù): F_Initial_Irq()
//語法:void F_Initial_Irq(void )
//描述:初始化子程序
//參數(shù):無
//返回:無
//==============================================
.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
//=======================================
//函數(shù): F_Music()
//語法:void F_Music(void )
//描述:音符播放程序.
//參數(shù):無
//返回:無
//======================================
.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
//===============================================================
//函數(shù): F_Key()
//語法:void F_Key(int I_Key )
//描述:按鍵掃描
//參數(shù):無
//返回:I_Key掃描的鍵值
//======================================
.PUBLIC _F_Key //鍵掃描程序
_F_Key:.PROC
R1 = 0x0004
[P_INT_CLEAR] = R1 //初始化掃描線和數(shù)據(jù)線
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; //掃描線和數(shù)據(jù)線換位
[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
//========================================================================
//函數(shù): IRQ5()
//語法:void IRQ5(void )
//描述:irq5中斷服務
//參數(shù):無
//返回:無
//=========================================
.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
//=====================================================
//函數(shù): L_Delay()
//語法:void L_Delay(void )
//描述:延時程序,延時1/4秒
//參數(shù):無
//返回:無
//==============================================
.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