#include <at89x52.h> //89s52頭文件 #define io_74hc165_SH_LD P1_0 //硬件設置 #define io_74hc165_CLK P1_1 #define io_74hc165_SDA P1_2 #define io_74hc164_SCK P1_3 #define io_74hc164_SDA P1_4 #ifndef RW_DEFINED #define RW_DEFINED void _snop_() { } #endif //Crystal at 12.0MHz 1MIPS #define I2C_SDA P3_2 // 將p3.2口模擬數據口,必須要這樣。中斷接受數據 #ifdef I2C_SCL #else #define I2C_SCL P1_6 // 將p1.1口模擬時鐘口,默認 #endif #ifdef SlaveAddress #else #define SlaveAddress 0x02 //地址 #endif #ifdef MasterAddress #else #define MasterAddress 0x01 //主機地址 #endif #define delayNOP(); {_snop_();_snop_();_snop_();_snop_();}; unsigned char s_control; unsigned char s_note; unsigned char s_svel; bit nm; bit SystemError; // 從機錯誤標志位 //-------------------------------------------------------------------------------------------------- // 函數名稱: iic_start() // 函數功能: 啟動I2C總線子程序 //-------------------------------------------------------------------------------------------------- void iic_start(void) { EA=0; //時鐘保持高,數據線從高到低一次跳變,I2C通信開始 I2C_SDA = 1; I2C_SCL = 1; delayNOP(); // 延時5us I2C_SDA = 0; delayNOP(); I2C_SCL = 0; } //-------------------------------------------------------------------------------------------------- // 函數名稱: iic_stop() // 函數功能: 停止I2C總線數據傳送子程序 //-------------------------------------------------------------------------------------------------- void iic_stop(void) { I2C_SDA = 0; //時鐘保持高,數據線從低到高一次跳變,I2C通信停止 I2C_SCL = 1; delayNOP(); I2C_SDA = 1; delayNOP(); I2C_SCL = 0; EA=1; } //-------------------------------------------------------------------------------------------------- // 函數名稱: slave_ACK // 函數功能: 從機發送應答位子程序 //-------------------------------------------------------------------------------------------------- void slave_ACK(void) { I2C_SDA = 0; I2C_SCL = 1; delayNOP(); I2C_SDA = 1; I2C_SCL = 0; } //-------------------------------------------------------------------------------------------------- // 函數名稱: slave_NOACK // 函數功能: 從機發送非應答位子程序,迫使數據傳輸過程結束 //-------------------------------------------------------------------------------------------------- void slave_NOACK(void) { I2C_SDA = 1; I2C_SCL = 1; delayNOP(); I2C_SDA = 0; I2C_SCL = 0; } //-------------------------------------------------------------------------------------------------- // 函數名稱: check_ACK // 函數功能: 主機應答位檢查子程序,迫使數據傳輸過程結束 //-------------------------------------------------------------------------------------------------- void check_ACK(void) { I2C_SDA = 1; // 將p1.0設置成輸入,必須先向端口寫1 I2C_SCL = 1; F0 = 0; if(I2C_SDA == 1) // 若I2C_SDA=1表明非應答,置位非應答標志F0 F0 = 1; I2C_SCL = 0; } //-------------------------------------------------------------------------------------------------- // 函數名稱: IICSendByte // 入口參數: ch // 函數功能: 發送一個字節 //-------------------------------------------------------------------------------------------------- void IICSendByte(unsigned char ch) { unsigned char idata n=8; // 向I2C_SDA上發送一位數據字節,共八位 while(n--) { if((ch&0x80) == 0x80) // 若要發送的數據最高位為1則發送位1 { I2C_SDA = 1; // 傳送位1 I2C_SCL = 1; delayNOP(); I2C_SDA = 0; I2C_SCL = 0; } else { I2C_SDA = 0; // 否則傳送位0 I2C_SCL = 1; delayNOP(); I2C_SCL = 0; } ch = ch<<1; // 數據左移一位 } } //-------------------------------------------------------------------------------------------------- // 函數名稱: IICreceiveByte // 返回接收的數據 // 函數功能: 接收一字節子程序 //-------------------------------------------------------------------------------------------------- unsigned char IICreceiveByte(void) { unsigned char idata n=8; // 從I2C_SDA線上讀取一上數據字節,共八位 unsigned char tdata=0; while(n--) { I2C_SDA = 1; I2C_SCL = 1; tdata = tdata<<1; // 左移一位,或_crol_(temp,1) if(I2C_SDA == 1) tdata = tdata|0x01; // 若接收到的位為1,則數據的最后一位置1 else tdata = tdata&0xfe; // 否則數據的最后一位置0 I2C_SCL=0; } return(tdata); } bit IICwaitACK() { //10us不屬于超時 unsigned char i=0; bit j=0; I2C_SDA=1;//輸入狀態 while (i!=10) { if (I2C_SCL==1) //先SCL=1 SDA=0后SCL=0 SDA=1 { j=1; break; } i++; } return j; } //-------------------------------------------------------------------------------------------------- // 函數名稱: slavesenddata // 入口參數: control,note and vel // 函數功能: 發送MIDI信息在IIC總線上 //-------------------------------------------------------------------------------------------------- //保證Simple.不使用標準I2C協議。 發送從機地址,等ACK,再發送發送3byte數據,等ACK.It 's very simple. void slavesenddata(unsigned char control,unsigned char note,unsigned char svel) { EA=0; IT0=0; //外中斷0為 下降沿觸發 設定成低電平出發的話 容易導致 誤觸發 EX0=0; //開外部中斷0 iic_start(); SystemError=1; IICSendByte(SlaveAddress);//發送地址 if (IICwaitACK()) //等主機的回應 { IICSendByte(control); IICSendByte(note); IICSendByte(svel); if (IICwaitACK()) //等主機的回應 { SystemError=0; } } iic_stop(); IT0=1; //地址不對就不去管了 EX0=1; //開中斷繼續 EA=1; } void initial_i2c() { IT0=1; //外中斷0為 下降沿觸發 設定成低電平出發的話 容易導致 誤觸發 EX0=1; //開外部中斷0 EA=1; } void recvint0() interrupt 0 using 2 { unsigned char mAddress=0; EA=0; IT0=0; // EX0=0; //關中斷防止干擾 //slave_ACK(); mAddress=IICreceiveByte(); if (mAddress==MasterAddress) { slave_ACK(); s_control=IICreceiveByte(); s_note=IICreceiveByte(); s_svel=IICreceiveByte(); nm=1; //提示主程序有新的消息,請注意查收~ } else {//延時,不能這樣退出去引發中斷 //ACK占用兩個周期,接受數據占用10個周期。延夠時了就跑 delayNOP(); delayNOP(); delayNOP(); delayNOP(); delayNOP(); delayNOP(); delayNOP(); delayNOP(); } IT0=1; //地址不對就不去管了 EX0=1; //開中斷繼續 EA=1; } //下面是引腳的連接以及相關必要的宏定義 //Crystal at 12MHz #ifdef io_74hc165_SH_LD #else #define io_74hc165_SH_LD P1_0 //默認設置 #endif #ifdef io_74hc165_CLK #else #define io_74hc165_CLK P1_1 #endif #ifdef io_74hc165_SDA #else #define io_74hc165_SDA P1_2 #endif #define io_74hc165_SH_HIGH io_74hc165_SH_LD=1; #define io_74hc165_SH_LOW io_74hc165_SH_LD=1; #ifdef io_74hc164_SCK #else #define io_74hc164_SCK P1_3 #endif #ifdef io_74hc164_SDA #else #define io_74hc164_SDA P1_4 #endif #define IO_74HC164_SCK_HIGH io_74hc164_SCK = 1 ; #define IO_74HC164_SCK_LOW io_74hc164_SCK = 0 ; #define IO_74HC164_SDA_INPUT io_74hc164_SDA //使用165來接受鍵盤數據。兩片作為164輸出。 #define uint8 unsigned char #define uchar unsigned char #define uint unsigned int uchar keytype=2; //#define keytype 2 #define blacklist_time 240 uchar kbtime=0; //記錄時間,占2個字節 uchar keynumber[2][7]; //記錄鍵位,占14個字節 uchar blacklist[7]; //uchar code seg[]={"admin"}; /*0->NoTouch Response 1->Very Soft Touch Response 2->Soft Touch Response 3->Medium Touch Response 4->Hard Touch Response 5->Very Hard Touch Response */ uchar code vel[5][251]= { {//Very Soft 127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,126,126,125,124,124,123,122,122,121,120,120,119,118,118,117,116,116,115,114,114,113,112,112,111,110,110,109,108,108,107,106,106,105,104,104,103,102,102,101,100,100,99,98,98,97,96,96,95,94,94,93,92,92,91,90,90,89,88,88,87,86,86,85,84,84,83,82,82,81,80,80,79,78,78,77, 76,76,75,74,74,73,72,72,71,70,70,69, 68,68,67,66,66,65,64,64,63,62,62,61, 60,60,59,58,58,57,56,56,55,54,54,53, 52,52,51,50,50,49,48,48,47,46,46,45, 44,44,43,42,42,41,40,40,39,38,38,37, 36,36,35,34,34,33,32,32,31,30,30,29, 28,28,27,26,26,25,24,24,23,22,22,21, 20,20,19,18,18,17,16,16,15,14,14,13, 12,12,11,10,10,9,8,8,7,6,6,5, 4,4,3,2,2,1,0 } , {//Soft 127,127,127,127,127,127,127,127,127, 127,127,127,127,127,127,127,127,127, 127,127,127,127,127,127,127,127,127, 127,127,127,127,127,127,127,127,126, 125,125,124,124,123,122,122,121,121, 120,120,119,118,118,117,117,116,115, 115,114,114,113,112,112,111,111,110, 110,109,108,108,107,107,106,105,105, 104,104,103,102,102,101,101,100,100, 99,98,98,97,97,96,95,95,94,94,93,92, 92,91,91,90,90,89,88,88,87,87,86,85, 85,84,84,83,82,82,81,81,80,80,79,78, 78,77,77,76,75,75,74,74,73,72,72,71, 71,70,70,69,68,68,67,67,66,65,65,64, 64,63,62,62,61,61,60,60,59,58,58,57, 57,56,55,55,54, 54,53,52,52,51,51,50,50,49,48,48,47, 47,46,45,45,44,44,43,42,42,41,41,40, 40,39,38,38,37,37,36,35,35,34,34,33, 32,32,31,31,30,30,29,28,28,27,27,26, 25,25,24,24,23,22,22,21,21,20,20,19, 18,18,17,17,16,15,15,14,14,13,12,12, 11,11,10,10,9,8,8,7,7,6,5,5, 4,4,3,2,2,1,1,0 } , {//Medium 127,127,127,127,127,127,127,127,127, 126,126,125,125,124,124,123,123,122, 122,121,121,120,120,119,118,118,117, 117,116,116,115,115,114,114,113,113, 112,112,111,111,110,110,109,108,108, 107,107,106,106,105,105,104,104,103, 103,102,102,101,101,100,100,99,98,98, 97,97,96,96,95,95,94,94,93,93,92,92, 91,91,90,90,89,88,88,87,87,86,86,85, 85,84,84,83,83,82,82,81,81,80,80,79, 78,78,77,77,76,76,75,75,74,74,73,73, 72,72,71,71,70,70,69,68,68,67,67,66, 66,65,65,64,64,63,63,62,62,61,61,60, 60,59,58,58,57,57,56,56,55,55,54,54,53, 53,52,52,51,51,50,50,49,48,48,47,47, 46,46,45,45,44,44,43,43,42,42,41,41, 40,40,39,38,38,37,37,36,36,35,35,34, 34,33,33,32,32,31,31,30,30,29,28,28, 27,27,26,26,25,25,24,24,23,23,22,22, 21,21,20,20,19,18,18,17,17,16,16,15, 15,14,14,13,13,12,12,11,11,10,10,9, 8,8,7,7,6,6,5,5,4,4,3,3, 2,2,1,1,0 } , {//Hard 100,99,99,98,98,98,97,97,96,96,96,95, 95,94,94,94,93,93,92,92,92,91,91,90, 90,90,89,89,88,88,88,87,87,86,86,86, 85,85,84,84,84,83,83,82,82,82,81,81, 80,80,80,79,79,78,78,78,77,77,76,76, 76,75,75,74,74,74,73,73,72,72,72,71, 71,70,70,70,69,69,68,68,68,67,67,66, 66,66,65,65,64,64,64,63,63,62,62,62, 61,61,60,60,60,59,59,58,58,58,57,57, 56,56,56,55,55,54,54,54,53,53,52,52, 52,51,51,50,50,50,49,49,48,48,48,47, 47,46,46,46,45,45,44,44,44,43,43, 42,42,42,41,41,40,40,40,39,39,38,38, 38,37,37,36,36,36,35,35,34,34,34,33, 33,32,32,32,31,31,30,30,30,29,29,28, 28,28,27,27,26,26,26,25,25,24,24,24, 23,23,22,22,22,21,21,20,20,20,19,19, 18,18,18,17,17,16,16,16,15,15,14,14, 14,13,13,12,12,12,11,11,10,10,10,9, 9,8,8,8,7,7,6,6,6,5,5,4, 4,4,3,3,2,2,2,1,1,0,0 } , {//Very Hard 92,92,91,91,91,90,90,90,89,89,88,88, 88,87,87,87,86,86,85,85,85,84,84,84, 83,83,82,82,82,81,81,81,80,80,80,79, 79,78,78,78,77,77,77,76,76,75,75,75, 74,74,74,73,73,72,72,72,71,71,71,70, 70,70,69,69,68,68,68,67,67,67,66,66, 65,65,65,64,64,64,63,63,62,62,62,61, 61,61,60,60,59,59,59,58,58,58,57,57, 57,56,56,55,55,55,54,54,54,53,53,52, 52,52,51,51,51,50,50,50,49,49,48,48, 48,47,47,47,46,46,45,45,45,44,44,44, 43,43,42,42,42,41,41,41,40,40,40,39, 39,38,38,38,37,37,37,36,36,35, 35,35,34,34,34,33,33,32,32,32,31,31, 31,30,30,29,29,29,28,28,28,27,27,27, 26,26,25,25,25,24,24,24,23,23,22,22, 22,21,21,21,20,20,20,19,19,18,18,18, 17,17,17,16,16,15,15,15,14,14,14,13, 13,12,12,12,11,11,11,10,10,10,9,9, 8,8,8,7,7,7,6,6,5,5,5,4, 4,4,3,3,2,2,2,1,1,1,0,0 } }; uchar count_begin[7][7]; //記錄按鍵時間,占49個字節 //在鋼琴上壓鍵時間大于500mS可以幾乎認為是沒有力度了啊 //VIO //VIO程序開始。 void v_74hc164WriteData_f( uint8 Dat ) //向74HC164寫一個字節的內容 { //即可并行輸出該字節 uint8 i = 0 ; uint8 SendData = Dat ; for( i = 8 ; i > 0 ; i-- ) { IO_74HC164_SCK_LOW SendData <<= 1 ; IO_74HC164_SDA_INPUT = CY ; IO_74HC164_SCK_HIGH } } uchar v_74hc165ReadData_f() { uchar i,c=0x00; for (i=0;i<8;i++) { c<=1; //0000 0010 if(io_74hc165_SDA) {c=c|0x01;} io_74hc165_CLK=0; //下降沿有效 io_74hc165_CLK=0; io_74hc165_CLK=1; //進位 } return c; } //VIO結束 /* 74HC1651 BIT1~8 KB DATA 1~8 74HC1652 BIT1~8 KB DATA 9~16 74HC5952 BIT1~8 KB CS 1~8 */ void initial_clock() { TMOD=0x00; TH0=0x10; TL0=0xC1; PCON=0x80; } /* MIDI命令簡表 命令代碼 (cc) 命令說明 數據kk含義及說明 數據vv含義及說明 8+ 通道號 關閉音符 對應的MIDI音符0-127 關閉音符的速度值 9+ 通道號 開啟音符 對應的MIDI音符0-127 壓下琴鍵的速度值(力度) A+ 通道號 觸后壓力 對應的MIDI音符0-127 對應音符的觸后壓力值 B+ 通道號 控制器 控制器號0-77 77-7F為通道模式信息 控制器值 C+ 通道號 音色切換 音色號 0-127 無該字節數據 D+ 通道號 通道壓力 該通道全部鍵盤的觸后壓力 無該字節數據 E+ 通道號 彎音輪 彎音輪低位數據 彎音輪高位數據 F 系統普通信息、實時信息、及高級信息代碼 忽略 忽略 */ void initial_keyboard() //初始化鍵盤 { v_74hc164WriteData_f(0x00);//關掉后排段選 v_74hc164WriteData_f(0x00);//關掉前排段選 } //keynumber[0]定義為第一排 //keynumber[1]定義為第二排 void scan_key() { uchar ts=0; //開始的時間 uchar kb_cs; // 段選 uchar i=0; uchar j=0; //循環變量 uchar outa=0; uchar outb=0; uchar csp; uchar lkb,hkb; uchar atime; uchar temp;//臨時 kb_cs=0x01; //00000001 00000010 while (i!=8) { ts=kbtime; v_74hc164WriteData_f(0x00);//關掉前排段選 v_74hc164WriteData_f(kb_cs);//輸出后排段選 //高在前,低在后 //以下代碼輸出下排力度檢測的信號 io_74hc165_SH_HIGH //拉高并行置數 outa=v_74hc165ReadData_f(); //讀入輸出 io_74hc165_SH_LOW //拉低并行置數 //結束,以下代碼輸出前排檢測開關的信號 initial_keyboard();//保險點 v_74hc164WriteData_f(kb_cs); //輸出前排段選 v_74hc164WriteData_f(0x00); //關掉后排段選 csp=0x01;//初始化指針 io_74hc165_SH_HIGH //拉高并行置數 outb=v_74hc165ReadData_f(); //讀入輸出 io_74hc165_SH_LOW //拉低并行置數 initial_keyboard();//全部關掉 //與原來的鍵值進行比較~ lkb=outb^keynumber[0][i]; //前排與原來的鍵值比較 hkb=outa^keynumber[1][i]; //后排與原來的鍵值比較 // 1&1=1 1&0=0 0&1=0 0&0=0 // 1^1=0 1^0=0 0^1=0 0^0=0所以用來做比較。有變化的就輸出1 keynumber[0][i]=outb; //前排,記錄下新的鍵值 keynumber[1][i]=outa; //后排 //原理:在琴鍵下面有兩個開關,順序為K1,K2。按照順序接通。只能兩個全導通/兩個全關斷,或者只有K1導通。在K1導通的時候lkb&csp發生變化并且在前排鍵上也發生變化 //所以程序會在一個2維數組中記錄閉合的時間,然后繼續掃過去。等到第二次掃到這組鍵的話 while(j!=8) { if ((lkb&csp)!=0) // 前排觸點是否有變化 { if ((outb&csp)!=0) { //前排的按鍵按下了 count_begin[i][j]=ts; } else {//前排鍵松開了就復原黑名單 temp=0x01; temp=temp<<j; if ((blacklist[i]&temp)!=0)//清白的良民的不要 { temp=0x01; temp=temp<<j; blacklist[i]=blacklist[i]^temp; //slavesenddata(0x02,((i+1)*8-j)+32,0x00); //關閉超時的琴鍵 } //else //{ //發送關音信號 //按照本人通訊協議,0x01就是開,0x02就是關 slavesenddata(0x02,((i+1)*8-j)+32,0x00); //前排鍵關閉則關音,反正最后還是要關的,前面的就省了! //} } } //掃描溢出鍵 atime=ts-count_begin[i][j]; //用當前時間減去按下時間 if (atime>blacklist_time) { //超時了 temp=0x01; temp=temp<<j; //左移j位 //然后XOR Blacklist //Because 1^1=0 0^1=1 1^0=1 0^0=0 blacklist[i]=blacklist[i]^temp;//打入黑名單 slavesenddata(0x01,((i+1)*8-j)+32,0); //呵呵,還是給主機發送一個0力度信號吧。畢竟鋼琴的弦音共振需要他。 } //地毯式掃描溢出鍵,不怕找不到 if (((hkb&csp)!=0) && ((lkb&csp)!=0)) //如果前排鍵,后排鍵有變化 { //前后排的鍵都按下了。查看時間~ /* atime=ts-count_begin[i][j]; //用當前時間減去按下時間 if (atime>blacklist_time) { //超時了 temp=0x1; temp=temp<<j; //左移j位 //然后XOR Blacklist //Because 1^1=0 0^1=1 1^0=1 0^0=0 blacklist[i]=blacklist[i]^temp;//打入黑名單 } else {//沒超時 */ if (((outa&csp)!=0) && ((outb&csp)!=0)) //并且都在現在按下了的話 { //親愛的blacklist,偶來了~ temp=0x01; temp=temp<<j; //temp=(~temp); //00100010 AND 00100000 = 1 if ((blacklist[i]&temp)==0) //防止定時器溢出后繼續發聲~,查看黑名單 { atime=ts-count_begin[i][j]; //用當前時間減去按下時間 //第28個鍵是中央C,0x01開音 slavesenddata(0x01,((i+1)*8-j)+32,vel[keytype][atime]); } } } csp=csp<<1; //指針左移 j++;//計數器+1 } kb_cs=(kb_cs<<1); //段選指針左移 i++;//計數器+1 } } void kb_initial() //初始化鍵盤函數 { initial_clock(); initial_keyboard(); } //========中斷服務程序,每2 毫秒為 kbtime 加1,============================ void timer0() interrupt 1 using 3 /*interrupt sub of timer0*/ { TH0=0x10; TL0=0xC1;//送初值 //頻率為12M時2mS中斷一次 kbtime++; } void config() { switch(s_control) //查看命令 { case 0x02: //這個是設置力度曲線的 keytype=s_note; break; default: break; //好像只有一種命令誒,感覺好浪費~ } } void main() { kb_initial();//初始化鍵盤 while (1)//死循環 { scan_key();//不停的掃描鍵盤 if (nm==1) { //有新的消息。我就去查收 nm=0;//消掉標志位 config(); } } }
全部源碼下載地址:http://www.zg4o1577.cn/f/mdid.rar
編譯后約占用1K ROM。效率夠高~
不過這個最后還要和主控ATmega8通信。
還是半成品。
做鍵盤也是要技術的。做電子琴更要技術。哇哈哈哈! 本程序的正式版本請看:http://www.zg4o1577.cn/mcu/1636.html
89S52單片機的Flash ROM為8KByte.內存為256Byte.
PS:程序本來是分塊設計的。為了發上網就用cpp處理了下。include 全部被替換掉了。