第一課 寫入一個字節到24c02 中 24c02 是一個非揮發eeprom 存儲器器件,采用的IIC 總線技術。24c02 在許多試驗 中都有出現。24c02 的應用,主要在存儲一些掉電后還要保存數據的場合,在上次運行 時,保存的數據,在下一次運行時還能夠調出。24c02 采用的IIC 總線,是一種2 線 總線,我們在試驗中用IO 來模擬這種總線,至于總線的時序和原理,請參考相關資料。 如果您不想研究, 也沒有關系, 我們在程序中已經為你寫好了,現在和今后您都可以只調用 就是,不必花時間和精力去研究。一塊24c02 中有256 個字節的存儲空間。我們將 24c02 的兩條總線接在了P26 和P27 上,因此,必須先定義: sbit SCL=P2^7; sbit SDA=P2^6; 在這個試驗中,我們寫入了一個字節數值0x88 到24c02 的0x02 的位置。 寫入完成后, P10 燈會亮起,我們再在下一顆來讀出這個字節來驗證結果。 ――――――――――――― #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文件 // 本課試驗寫入一個字節到24c02 中 char code dx516[3] _at_0x003b;// 這是為了仿真設置的 #define WriteDeviceAddress0xa0 // 定義器件在IIC 總線中的地址 #define ReadDviceAddress0xa1 sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; // 定時函數 void DelayMs(uint number) { uchar temp; for(;number!=0;number--) { for(temp=112;temp!=0;temp--); } } // 開始總線 void Start() { SDA=1; SCL=1; SDA=0; SCL=0; } // 結束總線 void Stop() { SCL=0; SDA=0; SCL=1; SDA=1; } // 測試ACK bit TestAck() { bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA; SCL=0; return(ErrorBit); } // 寫入8 個bit 到24c02 Write8Bit(uchar input) { uchar temp; for(temp=8;temp!=0;temp--) { SDA=(bit)(input&0x80); SCL=1; SCL=0; input=input<<1; } } // 寫入一個字節到24c02 中 void Write24c02(uchar ch,uchar address) { Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Write8Bit(ch); TestAck(); Stop(); DelayMs(10); } void main(void) // 主程序 { Write24c02(0x88,0x02);// 將0x88 寫入到24c02 的第2 個地址空間 P10=0; // 指示運行完畢 while(1); // 程序掛起 } ――――――――――――――――― 【24C02放不下的情結繞不過的坎】 第二課 寫入一個字節到24c02 并讀出來 本課的程序已經包含了上一顆的內容,增加了讀24c02 的函數,請看程序: ――――――――――――――――――――――――――――― #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文件 char code dx516[3] _at_0x003b;// 這是為了仿真設置的 #define WriteDeviceAddress0xa0 // 定義器件在IIC 總線中的地址 #define ReadDviceAddress0xa1 sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; // 定時函數 void DelayMs(unsigned intnumber) { unsigned char temp; for(;number!=0;number--) { for(temp=112;temp!=0;temp--); } } // 開始總線 void Start() { SDA=1; SCL=1; SDA=0; SCL=0; } // 結束總線 void Stop() { SCL=0; SDA=0; SCL=1; SDA=1; } // 發ACK0 void NoAck() { SDA=1; SCL=1; SCL=0; } // 測試ACK bit TestAck() { bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA; SCL=0; return(ErrorBit); } // 寫入8 個bit 到24c02 Write8Bit(unsigned charinput) { unsigned char temp; for(temp=8;temp!=0;temp--) { SDA=(bit)(input&0x80); SCL=1; SCL=0; input=input<<1; } } // 寫入一個字節到24c02 中 void Write24c02(uchar ch,uchar address) { Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Write8Bit(ch); TestAck(); Stop(); DelayMs(10); } // 從24c02 中讀出8 個bit uchar Read8Bit() { unsigned char temp,rbyte=0; for(temp=8;temp!=0;temp--) { SCL=1; rbyte=rbyte<<1; rbyte=rbyte|((unsignedchar)(SDA)) SCL=0; } return(rbyte); } // 從24c02 中讀出1 個字節 uchar Read24c02(uchar address) { uchar ch; Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Start(); Write8Bit(ReadDviceAddress); TestAck(); ch=Read8Bit(); NoAck(); Stop(); return(ch); } void main(void) // 主程序 { uchar c1,c2; c1=Read24c02(0x02); Write24c02(0x99,0x03); c2=Read24c02(0x03); P10=0; while(1); // 程序掛起 } ―――――――――――――――― 在主程序中,我們將上一課寫入的0x02 位置的數據讀出來放在c1 中,新寫了一個數 據0x99 在0x03 位置中,并立即將它讀出來放在c2 中。 【24C02放不下的情結繞不過的坎】 第三課 寫入按鍵次數到24c02 ,并讀出來顯示在4 個LED 上 #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文件 char code dx516[3] _at_0x003b;// 這是為了仿真設置的 #define WriteDeviceAddress0xa0 // 定義器件在IIC 總線中的地址 #define ReadDviceAddress0xa1 sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; sbit K1=P3^2; // 定時函數 void DelayMs(unsigned intnumber) { unsigned char temp; for(;number!=0;number--) { for(temp=112;temp!=0;temp--); } } // 開始總線 void Start() { SDA=1; SCL=1; SDA=0; SCL=0; } // 結束總線 void Stop() { SCL=0; SDA=0; SCL=1; SDA=1; } // 發ACK0 void NoAck() { SDA=1; SCL=1; SCL=0; } // 測試ACK bit TestAck() { bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA; SCL=0; return(ErrorBit); } // 寫入8 個bit 到24c02 Write8Bit(unsigned charinput) { unsigned char temp; for(temp=8;temp!=0;temp--) { SDA=(bit)(input&0x80); SCL=1; SCL=0; input=input<<1; } } // 寫入一個字節到24c02 中 void Write24c02(uchar ch,uchar address) { Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Write8Bit(ch); TestAck(); Stop(); DelayMs(10); } // 從24c02 中讀出8 個bit uchar Read8Bit() { unsigned char temp,rbyte=0; for(temp=8;temp!=0;temp--) { SCL=1; rbyte=rbyte<<1; rbyte=rbyte|((unsignedchar)(SDA)); SCL=0; } return(rbyte); } // 從24c02 中讀出1 個字節 uchar Read24c02(uchar address) { uchar ch; Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Start(); Write8Bit(ReadDviceAddress); TestAck(); ch=Read8Bit(); NoAck(); Stop(); return(ch); } void main(void) // 主程序 { uchar c1,c2; while(1) { c1=Read24c02(0x01); // 讀出24c02 第一個地址數據 P1=c1; // 顯示在P1 口的4 個LED 上 if(!K1) // 按鍵處理 { c1++; // 值加1 Write24c02(c1,0x01); // 重新寫入24c02 while(!K1); // 等待按鍵松開 for(c2=0;c2<250;c2++);// 松開按鍵去抖 } } } 【24C02放不下的情結繞不過的坎】 第四課 把樂譜全部寫入到了24c02 中 前面我們只學習過寫入一個字節到24c02 中。在這一課中,我們將一首樂譜全部寫入 到24c02 中。在下一課時,我們將會讀出24c02 中的樂譜播放。 這種工作方式,可以比作一個簡單的mp3 ,將樂曲寫在可以重復寫入的存儲器中,播 放時,讀出來再播放。這樣,只要換一個存有新的音樂的存儲器,或者從外部將新的樂曲寫 入到存儲器中,就可以播放新的樂曲,而單片機的程序并沒有改變。 ―――――――――――――――――――――――――――― #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文件 // 本課試驗寫入一個字節到24c02 中 char code dx516[3] _at_0x003b;// 這是為了仿真設置的 #define WriteDeviceAddress0xa0 // 定義器件在IIC 總線中的地址 #define ReadDviceAddress0xa1 sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; // 定時函數 void DelayMs(uint number) { uchar temp; for(;number!=0;number--) { for(temp=112;temp!=0;temp--); } } // 開始總線 void Start() { SDA=1; SCL=1; SDA=0; SCL=0; } // 結束總線 void Stop() { SCL=0; SDA=0; SCL=1; SDA=1; } // 測試ACK bit TestAck() { bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA; SCL=0; return(ErrorBit); } // 寫入8 個bit 到24c02 Write8Bit(uchar input) { uchar temp; for(temp=8;temp!=0;temp--) { SDA=(bit)(input&0x80); SCL=1; SCL=0; input=input<<1; } } // 寫入一個字節到24c02 中 void Write24c02(uchar ch,uchar address) { Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Write8Bit(ch); TestAck(); Stop(); DelayMs(10); } // 老鼠愛大米 uchar code dami[]={ "321-|3.2_1-|3231|3_6.5-|3_5_665|65-3_2_|122_3_2|12--|" }; // 本課試驗寫老鼠愛大米的樂譜到24c02 中 void main(void) // 主程序 { uchar i; for(i=0;dami!=0;i++) { Write24c02(dami,i);// 逐個寫入到24c02 的中 } Write24c02(0x00,i); // 寫入最后一個0 P10=0; // 指示運行完畢 while(1); // 程序掛起 } ―――――――――――――――――――――――― 通過一個for 循環,我們就把樂譜全部寫入到了24c02 中。看到for 循環的條件是 dami!=0 ,這是因為字符串的最后一個字符是0,可以作為結束的判斷,這個方法在前面 的音樂播放等例子中已經見到。 【24C02放不下的情結繞不過的坎】 第五課 從24c02 中讀出音樂來并播放音樂 這一課,我們學會從24C02 中讀出音樂,并實現單片機播放音樂。 main.c ――――――――――――――――――――――― #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文件 extern uchar Read24c02(uchar address); // 聲明外部的讀24c02 函數 extern void play(uchar*songdata); // 聲明外部的音樂播放函數 char code dx516[3] _at_0x003b;// 這是為了仿真設置的 sbit P10=P1^0; sbit K1= P3^2; sbit K2=P3^5; sbit K3= P2^4; sbit K4= P2^5; void main(void) // 主程序 { uchar i; uchar xdata yinyue[256]; //設立一個緩沖區 TMOD = 0x01; // 使用定時器0 的16 位工作模式 TR0 = 0; ET0 = 1; // 定時器0 中斷 EA = 1; // 打開總中斷 for(i=0;yinyue!=0;i++) // 讀出音樂來放到緩沖中 { yinyue=Read24c02(i); } yinyue=0; while(1) { play(yinyue); // 播放音樂 } } ―――――――――――――――――――――――――― 24c02.c ――――――――――――――――――――――― #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文件 #define WriteDeviceAddress0xa0 // 定義器件在IIC 總線中的地址 #define ReadDviceAddress0xa1 sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; /* // 定時函數 void DelayMs(unsigned intnumber) { unsigned char temp; for(;number!=0;number--) { for(temp=112;temp!=0;temp--); } } */ // 開始總線 void Start() { SDA=1; SCL=1; SDA=0; SCL=0; } // 結束總線 void Stop() { SCL=0; SDA=0; SCL=1; SDA=1; } // 發ACK0 void NoAck() { SDA=1; SCL=1; SCL=0; } // 測試ACK bit TestAck() { bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA; SCL=0; return(ErrorBit); } // 寫入8 個bit 到24c02 Write8Bit(unsigned charinput) { unsigned char temp; for(temp=8;temp!=0;temp--) { SDA=(bit)(input&0x80); SCL=1; SCL=0; input=input<<1; } } /* // 寫入一個字節到24c02 中 void Write24c02(uchar ch,uchar address) { Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Write8Bit(ch); TestAck(); Stop(); DelayMs(10); } */ // 從24c02 中讀出8 個bit uchar Read8Bit() { unsigned char temp,rbyte=0; for(temp=8;temp!=0;temp--) { SCL=1; rbyte=rbyte<<1; rbyte=rbyte|((unsignedchar)(SDA)); SCL=0; } return(rbyte); } // 從24c02 中讀出1 個字節 uchar Read24c02(uchar address) { uchar ch; Start(); Write8Bit(WriteDeviceAddress); TestAck(); Write8Bit(address); TestAck(); Start(); Write8Bit(ReadDviceAddress); TestAck(); ch=Read8Bit(); NoAck(); Stop(); return(ch); } ―――――――――――――――――――――――――― music.c ―――――――――――――――――― #define uchar unsigned char// 定義一下方便使用 #define uint unsigned int #define ulong unsigned long #include <reg52.h> //包括一個52 標準內核的頭文 sbit BEEP=P1^7; // 喇叭輸出腳 sbit K1= P3^2; sbit K2=P3^5; sbit K3= P2^4; sbit K4= P2^5; uchar th0_f; // 在中斷中裝載的T0 的值高8 位 uchar tl0_f; // 在中斷中裝載的T0 的值低8 位 //T0 的值,及輸出頻率對照表 uchar code freq[36*2]={ 0xA9,0xEF,//00220HZ ,1 //0 0x93,0xF0,//00233HZ ,1# 0x73,0xF1,//00247HZ ,2 0x49,0xF2,//00262HZ ,2# 0x07,0xF3,//00277HZ ,3 0xC8,0xF3,//00294HZ ,4 0x73,0xF4,//00311HZ ,4# 0x1E,0xF5,//00330HZ ,5 0xB6,0xF5,//00349HZ ,5# 0x4C,0xF6,//00370HZ ,6 0xD7,0xF6,//00392HZ ,6# 0x5A,0xF7,//00415HZ ,7 0xD8,0xF7,//00440HZ 1 //12 0x4D,0xF8,//00466HZ 1# //13 0xBD,0xF8,//00494HZ 2 //14 0x24,0xF9,//00523HZ 2# //15 0x87,0xF9,//00554HZ 3 //16 0xE4,0xF9,//00587HZ 4 //17 0x3D,0xFA,//00622HZ 4# //18 0x90,0xFA,//00659HZ 5 //19 0xDE,0xFA,//00698HZ 5# //20 0x29,0xFB,//00740HZ 6 //21 0x6F,0xFB,//00784HZ 6# //22 0xB1,0xFB,//00831HZ 7 //23 0xEF,0xFB,//00880HZ `1 0x2A,0xFC,//00932HZ `1# 0x62,0xFC,//00988HZ `2 0x95,0xFC,//01046HZ `2# 0xC7,0xFC,//01109HZ `3 0xF6,0xFC,//01175HZ `4 0x22,0xFD,//01244HZ `4# 0x4B,0xFD,//01318HZ `5 0x73,0xFD,//01397HZ `5# 0x98,0xFD,//01480HZ `6 0xBB,0xFD,//01568HZ `6# 0xDC,0xFD,//01661HZ `7 //35 }; // 定時中斷0, 用于產生唱歌頻率 timer0() interrupt 1 { TL0=tl0_f;TH0=th0_f; // 調入預定時值 BEEP=~BEEP; // 取反音樂輸出IO } //****************************** // 音樂符號串解釋函數 // 入口: 要解釋的音樂符號串, 輸出的音調串,輸出的時長串 changedata(uchar*song,uchar *diao,uchar *jie) { uchar i,i1,j; char gaodi; // 高低+/-12 音階 uchar banyin;// 有沒有半個升音階 uchar yinchang;// 音長 uchar codejie7[8]={0,12,14,16,17,19,21,23}; //C 調的 *diao=*song; for(i=0,i1=0;;) { gaodi=0; // 高低=0 banyin=0;// 半音=0 yinchang=4;// 音長1 拍 if((*(song+i)=='|') ||(*(song+i)==' ')) i++; // 拍子間隔和一個空格過濾 switch(*(song+i)) { case ',': gaodi=-12;i++;// 低音 break; case '`': gaodi=12;i++; // 高音 break; } if(*(song+i)==0) // 遇到0 結束 { *(diao+i1)=0; // 加入結束標志0 *(jie+i1)=0; return; } j=*(song+i)-0x30; i++; // 取出基準音 j=jie7[j]+gaodi; // 加上高低音 yinc: switch(*(song+i)) { case '#': // 有半音j 加一個音階 i++;j++; goto yinc; case '-': // 有一個音節加長 yinchang+=4; i++; goto yinc; case '_': // 有一個音節縮短 yinchang/=2; i++; goto yinc; case '.': // 有一個加半拍 yinchang=yinchang+yinchang/2; i++; goto yinc; } *(diao+i1)=j; // 記錄音符 *(jie+i1)=yinchang; // 記錄音長 i1++; } } //****************************************** // 奏樂函數 // 入口: 要演奏的音樂符號串 void play(uchar *songdata) { uchar i,c,j=0; uint n; uchar xdata diaodata[112];// 音調緩沖 uchar xdata jiedata[112];// 音長緩沖 changedata(songdata,diaodata,jiedata);// 解釋音樂符號串 TR0=1; for(i=0;diaodata!=0;i++) //逐個符號演奏 { tl0_f=freq[diaodata*2]; // 取出對應的定時值送給T0 th0_f=freq[diaodata*2+1]; for(c=0;c<jiedata;c++)// 按照音長延時 { for(n=0;n<32000;n++); if((!K1)||(!K2)||(!K3)||(!K4))//發現按鍵,立即退出播放 { TR0=0; return; } } TR0=0; for(n=0;n<500;n++); // 音符間延時 TR0=1; } TR0=0; }
|