第一课 写入一个字节到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; }
|