完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
本帖最后由 jianhong_wu 于 2013-6-22 09:11 编辑
第三十七节:EEPROM篇--利用AT24C02存储数据 开场白: 一个AT24C02可以存储256个字节,地址范围是(0至255)。 利用AT24C02存储数据时,有4个地方最容易出错。 (a) 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用Delay_time(2000),读取数据时经验值用Delay_time(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。 (b) 在时序中,发送ACK确认信号时,要记得把数据线sda设置为输入的状态。对于51单片机来说,只要把sda=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接sda=1,而要直接修改方向寄存器,把它设置为输入状态,比如TRISC4=1.在本驱动程序中,鸿哥没有对ACK信号进行出错判断,因为鸿哥这么多年一直都是这样用也没出现过什么问题。 (c) 单片机跟AT24C02通讯的2根IO口都要加上一个15K左右的上拉电阻。 (d) AT24C02的WP引脚一定要接地,否则存不进数据。 (1)功能需求: 开机后两位数码管显示0到99之间的某一个数值,用一个按键可以不断的递增,用另外一个按键可以不断的递减。断电后此数据可保存,下次通电开机的时候还是从那个保存的数据开始显示。 (2)硬件原理: (a)动态扫描两位数码管的电路请参考第二十一节。 (d) 独立按键的电路请参考第二节。 (c) AT24C02的A0,A1,A2引脚都接地,芯片ID为”000”。 (3)源码适合的单片机: PIC18f4520,晶振为11.0592MHz。 (4)单片机的C语言源代码讲解如下: #include #define cnt_delay_cnt1 25 //按键去抖动延时阀值 #define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值 #define seg_0_dr LATB6 //任意7个IO口接数码管的seg引脚 #define seg_1_dr LATB5 #define seg_2_dr LATB4 #define seg_3_dr LATB3 #define seg_4_dr LATC7 #define seg_5_dr LATB0 #define seg_6_dr LATB1 #define com_left_dr LATB2 //任意2个IO口接数码管的com引脚 #define com_right_dr LATB7 #define beep_dr LATA1 //蜂鸣器输出 #define key_sr1 RD6 //独立按键输入 #define key_sr2 RD7 //独立按键输入 #define eeprom_scl_dr LATC5 //时钟线 #define eeprom_sda_dr LATC4 //数据输出线 #define eeprom_sda_sr RC4 //数据输入线 void start24(); //开始位 void ack24(); //确认位 void stop24(); //停止位 unsigned char read24(); //读取一个字节的时序 void write24(unsigned char dd); //发送一个字节的时序 unsigned char read_eeprom(unsigned int address); //从一个地址读取出一个字节数据 void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据 void initial();//初始化 void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗 void display_drive(); //数码管驱动程序,放在定时中断里 void display_seg(unsigned char seg); //编码转换程序,放在display_drive里 void Delay_time(unsigned int Delay11_MS); //延时程序 //补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中 //断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序放在中断里,别听他们,信鸿哥无坎坷。 void key_scan(); //按键扫描函数,放在定时中断里 void key_service(); //按键服务函数,放在main函数循环里 //补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名 //后缀都用_lock表示。 unsigned char key_lock1=0; //按键自锁标志 unsigned char key_lock2=0; //按键自锁标志 unsigned int delay_cnt1=0; //延时计数器的变量 unsigned int delay_cnt2=0; //延时计数器的变量 unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时 unsigned char number_left=0; //左边数码管显示的内容 unsigned char number_right=0; //右边数码管显示的内容 unsigned char dis_step=1; //扫描的步骤 unsigned char set_data; //显示,设置和保存的数 unsigned char key_sec=0; //哪个按键被触发 main() //主程序 { initial(); //初始化 while(1) { CLRWDT(); key_service(); //按键服务函数 } } //AT24C02驱动程序 void start24() //开始位 { TRISC4 = 0; // sda输出 eeprom_sda_dr=1; eeprom_scl_dr=1; Delay_time(15); eeprom_sda_dr=0; Delay_time(15); eeprom_scl_dr=0; } //在本驱动程序中,鸿哥没有对ACK信号进行出错判断,因为鸿哥这么多年一直都是这样用也没出现过什么问题。 void ack24() //确认位 { TRISC4 = 1; // sda输入 ,很关键!!! eeprom_scl_dr=1; Delay_time(15); eeprom_scl_dr=0; Delay_time(15); } void stop24() //停止位 { TRISC4 = 0; // sda输出 eeprom_sda_dr=0; eeprom_scl_dr=1; Delay_time(15); eeprom_sda_dr=1; TRISC4 = 1; // 结束后释放总线,sda输入 } unsigned char read24() //读取一个字节的时序 { unsigned char outdata,tempdata; TRISC4 = 1; // sda输入 outdata=0; asm("nop");asm("nop"); for(tempdata=0;tempdata<8;tempdata++) { eeprom_scl_dr=0; asm("nop");asm("nop"); eeprom_scl_dr=1; asm("nop");asm("nop"); outdata<<=1; if(eeprom_sda_sr==1)outdata++; asm("nop");asm("nop"); } return(outdata); } void write24(unsigned char dd) //发送一个字节的时序 { unsigned char tempdata; TRISC4 = 0; // sda输出 for(tempdata=0;tempdata<8;tempdata++) { if(dd>=0x80)eeprom_sda_dr=1; else eeprom_sda_dr=0; dd<<=1; asm("nop"); eeprom_scl_dr=1; asm("nop");asm("nop");asm("nop");asm("nop"); eeprom_scl_dr=0; } } unsigned char read_eeprom(unsigned int address) //从一个地址读取出一个字节数据 { unsigned char dd,cAddress; cAddress=address; //把低字节地址传递给一个字节变量。 GIE=0; //禁止中断 start24(); //开始 write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。 //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定 ack24(); //发送应答信号 write24(cAddress); //发送读取的存储地址(范围是0至255) ack24(); //发送应答信号 start24(); //开始 write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。 //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定 ack24(); //发送应答信号 dd=read24(); //读取一个字节 ack24(); //发送应答信号 stop24(); //停止 GIE=1; //允许中断 Delay_time(800); //此处最关键,此处的延时时间一定要,而且要足够长 return(dd); } void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据 { unsigned char cAddress; cAddress=address; //把低字节地址传递给一个字节变量。 GIE=0; //禁止中断 start24(); //开始 write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。 //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定 ack24(); //发送应答信号 write24(cAddress); //发送写入的存储地址(范围是0至255) ack24(); //发送应答信号 write24(dd); //写入存储的数据 ack24(); //发送应答信号 stop24(); //停止 GIE=1; //允许中断 Delay_time(2000); //此处最关键,此处的延时时间一定要,而且要足够长 } void key_scan() //按键扫描函数 { if(key_sr1==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock1=0; //按键自锁标志清零 delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock1==0) //有按键按下,且是第一次被按下 { ++delay_cnt1; //延时计数器 if(delay_cnt1>cnt_delay_cnt1) { delay_cnt1=0; key_lock1=1; //自锁按键置位,避免一直触发 key_sec=1; //触发1号键 } } if(key_sr2==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock2=0; //按键自锁标志清零 delay_cnt2=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock2==0) //有按键按下,且是第一次被按下 { ++delay_cnt2; //延时计数器 if(delay_cnt2>cnt_delay_cnt1) { delay_cnt2=0; key_lock2=1; //自锁按键置位,避免一直触发 key_sec=2; //触发2号键 } } } void key_service() //按键服务函数 { switch(key_sec) //按键服务状态切换 { case 1:// 1号键 加 // 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0 //时,会不断自减,一直到它为0时,自动把蜂鸣器关闭 ++set_data; if(set_data>99) { set_data=0; } write_eeprom(128,set_data); //把刚刚修改的数据保存进eeprom voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,避免一直触发 break; case 2:// 2号键 减 --set_data; if(set_data>99) //字节类型的数据,当0减去1时会变成255(0xff),当然会比99大了。 { set_data=99; } write_eeprom(128,set_data); //把刚刚修改的数据保存进eeprom voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,避免一直触发 break; } } void interrupt timer1rbint(void) //中断程序入口 { if(TMR1IE==1&&TMR1IF==1) //定时中断程序 { TMR1IF=0; TMR1ON=0; key_scan(); //按键扫描函数 if(voice_time_cnt) //控制蜂鸣器声音的长短 { beep_dr=1; //蜂鸣器响 --voice_time_cnt; //蜂鸣器响的声音长短的计数延时 } else { beep_dr=0; //蜂鸣器停止 } display_drive(); //数码管驱动程序,放在定时中断里 TMR1H=0xFF; TMR1L=0xC8; TMR1ON=1; } } void initial()//初始化 { ADCON0=0x00; ADCON1=0x0f; ADCON2=0x00; RBPU=0; TRISB=0x00; //数码管IO口设置成输出 TRISC7=0; TRISA1=0; //蜂鸣器IO口设置成输出 TRISD6=1; //独立按键IO口设置成输入 TRISD7=1; TRISC5=0; //AT24C02的时钟线设置成输出 TRISC4=1; //AT24C02的数据线设置成输入 SSPEN=0; T1CON=0x24; TMR1H=0xFE; TMR1L=0xEF; INTCON=0xC0; TMR1IF=0; TMR1IE=1; PEIE=1; //外围中断允许 GIE=1; TMR1ON=1; set_data=read_eeprom(128); //上电开机读取EEPROM数据,128是一个我任意选的一个存储地址 if(set_data>99) //说明是这个单片机是第一次烧录程序之后,内部数据是默认值,以前没操作过。 { write_eeprom(128,0); //如果从来没操作过此EEPROM,则默认写入一个0的数据 } } void display_drive() //数码管驱动程序,放在定时中断里 { if(set_data>=10) { number_left=set_data/10; //如果数据大于或者等于10,则分解出显示数据的十位去显示 } else { number_left=10; //如果数据小于10,则十位什么也不显示,显示空。 } number_right=set_data%10; //分解出显示数据的个位 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; com_left_dr=1; com_right_dr=1; //在即将更换下一位数码管时,先把让它两个什么都不显示,让显示过度更加平稳 asm("nop"); //空指令延时 asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); switch(dis_step) { case 1: //扫描左边的数码管 display_seg(number_left); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=0; //选中左数码管 com_right_dr=1; break; case 2: //扫描右边的数码管 display_seg(number_right); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=1; com_right_dr=0; //选中右数码管 break; } delay1(50); //每一位数码管显示的停留延时时间,有疑问的朋友请自己尝试改成计数延时的方式, //鸿哥认为在此种环境下,在定时中断里用死延时delay1(50)是最佳的方式 ++dis_step; //下一次中断扫描另外一位的数码管,轮流扫描 if(dis_step>2) { dis_step=1; } } //不是鸿哥不懂爱,如果不是用任意IO口,而是直接用一个并口(比如51单片机中的P1口),那么就不用那么费力, //直接用查数组(俗称查表)的方式就可以替代display_seg这个编码转换程序, void display_seg(unsigned char seg) //编码转换程序,,放在display_drive里 { switch(seg) //switch指令,单片机中的战斗机,鸿哥的最爱! { case 0: //显示"0" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=0; seg_5_dr=1; seg_6_dr=1; break; case 1: //显示"1" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; case 2: //显示"2" seg_0_dr=1; seg_1_dr=0; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 3: //显示"3" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 4: //显示"4" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=1; seg_5_dr=0; seg_6_dr=1; break; case 5: //显示"5" seg_0_dr=0; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 6: //显示"6" seg_0_dr=0; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 7: //显示"7" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=1; seg_6_dr=0; break; case 8: //显示"8" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 9: //显示"9" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 10: //什么也不显示,空 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; } } void delay1(unsigned int de) { unsigned int t; for(t=0;t void Delay_time(unsigned int Delay11_MS) { unsigned int Delay11_us; for(Delay11_us=0;Delay11_us CLRWDT(); } } (5)下集预告: EEPROM篇--利用AT24C64存储数据。 (未完待续,下节更精彩,不要走开哦!) |
|
|
|
|
|
|
|
|
|
本帖最后由 jianhong_wu 于 2013-6-22 09:12 编辑
第三十八节:EEPROM篇--利用AT24C64存储数据 开场白: 一个AT24C64可以存储8192个字节,地址范围是0至8191。 利用AT24C64存储数据时,有4个地方最容易出错。 (a) 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用Delay_time(2000),读取数据时经验值用Delay_time(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。 (b) 在时序中,发送ACK确认信号时,要记得把数据线sda设置为输入的状态。对于51单片机来说,只要把sda=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接sda=1,而要直接修改方向寄存器,把它设置为输入状态,比如TRISC4=1.在本驱动程序中,鸿哥没有对ACK信号进行出错判断,因为鸿哥这么多年一直都是这样用也没出现过什么问题。 (c) 单片机跟AT24C64通讯的2根IO口都要加上一个15K左右的上拉电阻。 (d) AT24C64的WP引脚一定要接地,否则存不进数据。 (1)功能需求: 开机后两位数码管显示0到99之间的某一个数值,用一个按键可以不断的递增,用另外一个按键可以不断的递减。断电后此数据可保存,下次通电开机的时候还是从那个保存的数据开始显示。 (2)硬件原理: (a)动态扫描两位数码管的电路请参考第二十一节。 (d) 独立按键的电路请参考第二节。 (c) AT24C64的A0,A1,A2引脚都接地,芯片ID为”000”。 (3)源码适合的单片机: PIC18f4520,晶振为11.0592MHz。 (4)单片机的C语言源代码讲解如下: #include #define cnt_delay_cnt1 25 //按键去抖动延时阀值 #define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值 #define seg_0_dr LATB6 //任意7个IO口接数码管的seg引脚 #define seg_1_dr LATB5 #define seg_2_dr LATB4 #define seg_3_dr LATB3 #define seg_4_dr LATC7 #define seg_5_dr LATB0 #define seg_6_dr LATB1 #define com_left_dr LATB2 //任意2个IO口接数码管的com引脚 #define com_right_dr LATB7 #define beep_dr LATA1 //蜂鸣器输出 #define key_sr1 RD6 //独立按键输入 #define key_sr2 RD7 //独立按键输入 #define eeprom_scl_dr LATC5 //时钟线 #define eeprom_sda_dr LATC4 //数据输出线 #define eeprom_sda_sr RC4 //数据输入线 void start24(); //开始位 void ack24(); //确认位 void stop24(); //停止位 unsigned char read24(); //读取一个字节的时序 void write24(unsigned char dd); //发送一个字节的时序 unsigned char read_eeprom(unsigned int address); //从一个地址读取出一个字节数据 void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据 void initial();//初始化 void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗 void display_drive(); //数码管驱动程序,放在定时中断里 void display_seg(unsigned char seg); //编码转换程序,放在display_drive里 void Delay_time(unsigned int Delay11_MS); //延时程序 //补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中 //断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序放在中断里,别听他们,信鸿哥无坎坷。 void key_scan(); //按键扫描函数,放在定时中断里 void key_service(); //按键服务函数,放在main函数循环里 //补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名 //后缀都用_lock表示。 unsigned char key_lock1=0; //按键自锁标志 unsigned char key_lock2=0; //按键自锁标志 unsigned int delay_cnt1=0; //延时计数器的变量 unsigned int delay_cnt2=0; //延时计数器的变量 unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时 unsigned char number_left=0; //左边数码管显示的内容 unsigned char number_right=0; //右边数码管显示的内容 unsigned char dis_step=1; //扫描的步骤 unsigned char set_data; //显示,设置和保存的数 unsigned char key_sec=0; //哪个按键被触发 main() //主程序 { initial(); //初始化 while(1) { CLRWDT(); key_service(); //按键服务函数 } } //AT24C02驱动程序 void start24() //开始位 { TRISC4 = 0; // sda输出 eeprom_sda_dr=1; eeprom_scl_dr=1; Delay_time(15); eeprom_sda_dr=0; Delay_time(15); eeprom_scl_dr=0; } //在本驱动程序中,鸿哥没有对ACK信号进行出错判断,因为鸿哥这么多年一直都是这样用也没出现过什么问题。 void ack24() //确认位 { TRISC4 = 1; // sda输入 ,很关键!!!如果是51单片机则 eeprom_sda_dr=1; eeprom_scl_dr=1; Delay_time(15); eeprom_scl_dr=0; Delay_time(15); } void stop24() //停止位 { TRISC4 = 0; // sda输出 eeprom_sda_dr=0; eeprom_scl_dr=1; Delay_time(15); eeprom_sda_dr=1; TRISC4 = 1; // 结束后释放总线,sda输入 } unsigned char read24() //读取一个字节的时序 { unsigned char outdata,tempdata; TRISC4 = 1; // sda输入 如果是51单片机则 eeprom_sda_dr=1; outdata=0; asm("nop");asm("nop"); for(tempdata=0;tempdata<8;tempdata++) { eeprom_scl_dr=0; asm("nop");asm("nop"); eeprom_scl_dr=1; asm("nop");asm("nop"); outdata<<=1; if(eeprom_sda_sr==1)outdata++; asm("nop");asm("nop"); } return(outdata); } void write24(unsigned char dd) //发送一个字节的时序 { unsigned char tempdata; TRISC4 = 0; // sda输出 for(tempdata=0;tempdata<8;tempdata++) { if(dd>=0x80)eeprom_sda_dr=1; else eeprom_sda_dr=0; dd<<=1; asm("nop"); eeprom_scl_dr=1; asm("nop");asm("nop");asm("nop");asm("nop"); eeprom_scl_dr=0; } } unsigned char read_eeprom(unsigned int address) //从一个地址读取出一个字节数据 { unsigned char dd,cAddress[2]; //0-L;1-H cAddress[0]=address; //低位地址 address>>=8; cAddress[1]=address; //高位地址 GIE=0; //禁止中断 start24(); //开始 write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。 //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定 ack24(); //发送应答信号 write24(cAddress[1]); //发送读取的高位存储地址 ack24(); //发送应答信号 write24(cAddress[0]); //发送读取的低位存储地址 ack24(); //发送应答信号 start24(); //开始 write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。 //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定 ack24(); //发送应答信号 dd=read24(); //读取一个字节 ack24(); //发送应答信号 stop24(); //停止 GIE=1; //允许中断 Delay_time(800); //此处最关键,此处的延时时间一定要,而且要足够长 return(dd); } void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据 { unsigned char cAddress[2]; //0-L;1-H cAddress[0]=address; //低位地址 address>>=8; cAddress[1]=address; //高位地址 GIE=0; //禁止中断 start24(); //开始 write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。 //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定 ack24(); //发送应答信号 write24(cAddress[1]); //发送读取的高位存储地址 ack24(); //发送应答信号 write24(cAddress[0]); //发送读取的低位存储地址 ack24(); //发送应答信号 write24(dd); //写入存储的数据 ack24(); //发送应答信号 stop24(); //停止 GIE=1; //允许中断 Delay_time(2000); //此处最关键,此处的延时时间一定要,而且要足够长 } void key_scan() //按键扫描函数 { if(key_sr1==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock1=0; //按键自锁标志清零 delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock1==0) //有按键按下,且是第一次被按下 { ++delay_cnt1; //延时计数器 if(delay_cnt1>cnt_delay_cnt1) { delay_cnt1=0; key_lock1=1; //自锁按键置位,避免一直触发 key_sec=1; //触发1号键 } } if(key_sr2==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock2=0; //按键自锁标志清零 delay_cnt2=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock2==0) //有按键按下,且是第一次被按下 { ++delay_cnt2; //延时计数器 if(delay_cnt2>cnt_delay_cnt1) { delay_cnt2=0; key_lock2=1; //自锁按键置位,避免一直触发 key_sec=2; //触发2号键 } } } void key_service() //按键服务函数 { switch(key_sec) //按键服务状态切换 { case 1:// 1号键 加 // 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0 //时,会不断自减,一直到它为0时,自动把蜂鸣器关闭 ++set_data; if(set_data>99) { set_data=0; } write_eeprom(128,set_data); //把刚刚修改的数据保存进eeprom voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,避免一直触发 break; case 2:// 2号键 减 --set_data; if(set_data>99) //字节类型的数据,当0减去1时会变成255(0xff),当然会比99大了。 { set_data=99; } write_eeprom(128,set_data); //把刚刚修改的数据保存进eeprom voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,避免一直触发 break; } } void interrupt timer1rbint(void) //中断程序入口 { if(TMR1IE==1&&TMR1IF==1) //定时中断程序 { TMR1IF=0; TMR1ON=0; key_scan(); //按键扫描函数 if(voice_time_cnt) //控制蜂鸣器声音的长短 { beep_dr=1; //蜂鸣器响 --voice_time_cnt; //蜂鸣器响的声音长短的计数延时 } else { beep_dr=0; //蜂鸣器停止 } display_drive(); //数码管驱动程序,放在定时中断里 TMR1H=0xFF; TMR1L=0xC8; TMR1ON=1; } } void initial()//初始化 { ADCON0=0x00; ADCON1=0x0f; ADCON2=0x00; RBPU=0; TRISB=0x00; //数码管IO口设置成输出 TRISC7=0; TRISA1=0; //蜂鸣器IO口设置成输出 TRISD6=1; //独立按键IO口设置成输入 TRISD7=1; TRISC5=0; //AT24C02的时钟线设置成输出 TRISC4=1; //AT24C02的数据线设置成输入 SSPEN=0; T1CON=0x24; TMR1H=0xFE; TMR1L=0xEF; INTCON=0xC0; TMR1IF=0; TMR1IE=1; PEIE=1; //外围中断允许 GIE=1; TMR1ON=1; set_data=read_eeprom(128); //上电开机读取EEPROM数据,128是一个我任意选的一个存储地址 if(set_data>99) //说明是这个单片机是第一次烧录程序之后,内部数据是默认值,以前没操作过。 { write_eeprom(128,0); //如果从来没操作过此EEPROM,则默认写入一个0的数据 } } void display_drive() //数码管驱动程序,放在定时中断里 { if(set_data>=10) { number_left=set_data/10; //如果数据大于或者等于10,则分解出显示数据的十位去显示 } else { number_left=10; //如果数据小于10,则十位什么也不显示,显示空。 } number_right=set_data%10; //分解出显示数据的个位 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; com_left_dr=1; com_right_dr=1; //在即将更换下一位数码管时,先把让它两个什么都不显示,让显示过度更加平稳 asm("nop"); //空指令延时 asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); switch(dis_step) { case 1: //扫描左边的数码管 display_seg(number_left); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=0; //选中左数码管 com_right_dr=1; break; case 2: //扫描右边的数码管 display_seg(number_right); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=1; com_right_dr=0; //选中右数码管 break; } delay1(50); //每一位数码管显示的停留延时时间,有疑问的朋友请自己尝试改成计数延时的方式, //鸿哥认为在此种环境下,在定时中断里用死延时delay1(50)是最佳的方式 ++dis_step; //下一次中断扫描另外一位的数码管,轮流扫描 if(dis_step>2) { dis_step=1; } } //不是鸿哥不懂爱,如果不是用任意IO口,而是直接用一个并口(比如51单片机中的P1口),那么就不用那么费力, //直接用查数组(俗称查表)的方式就可以替代display_seg这个编码转换程序, void display_seg(unsigned char seg) //编码转换程序,,放在display_drive里 { switch(seg) //switch指令,单片机中的战斗机,鸿哥的最爱! { case 0: //显示"0" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=0; seg_5_dr=1; seg_6_dr=1; break; case 1: //显示"1" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; case 2: //显示"2" seg_0_dr=1; seg_1_dr=0; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 3: //显示"3" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 4: //显示"4" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=1; seg_5_dr=0; seg_6_dr=1; break; case 5: //显示"5" seg_0_dr=0; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 6: //显示"6" seg_0_dr=0; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 7: //显示"7" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=1; seg_6_dr=0; break; case 8: //显示"8" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 9: //显示"9" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 10: //什么也不显示,空 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; } } void delay1(unsigned int de) { unsigned int t; for(t=0;t void Delay_time(unsigned int Delay11_MS) { unsigned int Delay11_us; for(Delay11_us=0;Delay11_us CLRWDT(); } } (5)下集预告: EEPROM篇--利用24LC1025存储数据。 (未完待续,下节更精彩,不要走开哦!) |
|
|
|
|
|
本帖最后由 jianhong_wu 于 2013-6-22 09:12 编辑
第三十九节:EEPROM篇--利用24LC1025存储数据 开场白: 一个24LC1025可以存储131072个字节,实际上它内部是分了两页来存储,每一页可以存储65536个字节,范围都是从0至65535。以下鸿哥已经把驱动的接口做好了,你只要选择在哪一页的哪个地址里存入或者读取某个数据就可以了。页地址范围是从0至1。 利用24LC1025存储数据时,有5个地方最容易出错。 (a) 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用Delay_time(4000),读取数据时经验值用Delay_time(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。 (b) 在时序中,发送ACK确认信号时,要记得把数据线sda设置为输入的状态。对于51单片机来说,只要把sda=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接sda=1,而要直接修改方向寄存器,把它设置为输入状态,比如TRISC4=1.在本驱动程序中,鸿哥没有对ACK信号进行出错判断,因为鸿哥这么多年一直都是这样用也没出现过什么问题。 (c) 单片机跟24LC1025通讯的2根IO口都要加上一个15K左右的上拉电阻。 (d) 24LC1025的WP引脚一定要接地,否则存不进数据。 (e) 24LC1025的芯片ID地址只有A0和A1两引脚。而A2引脚必须接电源正极VCC,否则芯片不工作。此处跟AT24C02的芯片有点不一样,特别要注意!!! (1)功能需求: 开机后两位数码管显示0到99之间的某一个数值,用一个按键可以不断的递增,用另外一个按键可以不断的递减。断电后此数据可保存,下次通电开机的时候还是从那个保存的数据开始显示。 (2)硬件原理: (a)动态扫描两位数码管的电路请参考第二十一节。 (d) 独立按键的电路请参考第二节。 (c) 24LC1025的A0,A1引脚都接地,芯片ID为”00”。而,A2引脚必须接电源正极VCC。 (3)源码适合的单片机: PIC18f4520,晶振为11.0592MHz。 (4)单片机的C语言源代码讲解如下: #include #define cnt_delay_cnt1 25 //按键去抖动延时阀值 #define cnt_voice_time 60 //蜂鸣器响的声音长短的延时阀值 #define seg_0_dr LATB6 //任意7个IO口接数码管的seg引脚 #define seg_1_dr LATB5 #define seg_2_dr LATB4 #define seg_3_dr LATB3 #define seg_4_dr LATC7 #define seg_5_dr LATB0 #define seg_6_dr LATB1 #define com_left_dr LATB2 //任意2个IO口接数码管的com引脚 #define com_right_dr LATB7 #define beep_dr LATA1 //蜂鸣器输出 #define key_sr1 RD6 //独立按键输入 #define key_sr2 RD7 //独立按键输入 #define eeprom_scl_dr LATC5 //时钟线 #define eeprom_sda_dr LATC4 //数据输出线 #define eeprom_sda_sr RC4 //数据输入线 void start24(); //开始位 void ack24(); //确认位 void stop24(); //停止位 unsigned char read24(); //读取一个字节的时序 void write24(unsigned char dd); //发送一个字节的时序 unsigned char read_eeprom(unsigned int address,unsigned char page); //从哪一页的哪一个地址里读取一个字节的数据 void write_eeprom(unsigned int address,unsigned char page,unsigned char dd); //往哪一页的哪一个地址里写入一个字节的数据 void initial();//初始化 void delay1(unsigned int de) ;//小延时程序,时间不宜太长,因为内部没有喂看门狗 void display_drive(); //数码管驱动程序,放在定时中断里 void display_seg(unsigned char seg); //编码转换程序,放在display_drive里 void Delay_time(unsigned int Delay11_MS); //延时程序 //补充说明:吴坚鸿程序风格是这样的,凡是按键扫描函数都放在定时中 //断里,凡是按键服务程序都是放在main函数循环里。有人说不应该把子程序放在中断里,别听他们,信鸿哥无坎坷。 void key_scan(); //按键扫描函数,放在定时中断里 void key_service(); //按键服务函数,放在main函数循环里 //补充说明:吴坚鸿程序风格是这样的,凡是按键或者感应输入的自锁变量名 //后缀都用_lock表示。 unsigned char key_lock1=0; //按键自锁标志 unsigned char key_lock2=0; //按键自锁标志 unsigned int delay_cnt1=0; //延时计数器的变量 unsigned int delay_cnt2=0; //延时计数器的变量 unsigned int voice_time_cnt; //蜂鸣器响的声音长短的计数延时 unsigned char number_left=0; //左边数码管显示的内容 unsigned char number_right=0; //右边数码管显示的内容 unsigned char dis_step=1; //扫描的步骤 unsigned char set_data; //显示,设置和保存的数 unsigned char key_sec=0; //哪个按键被触发 main() //主程序 { initial(); //初始化 while(1) { CLRWDT(); key_service(); //按键服务函数 } } //AT24C02驱动程序 void start24() //开始位 { TRISC4 = 0; // sda输出 eeprom_sda_dr=1; eeprom_scl_dr=1; Delay_time(15); eeprom_sda_dr=0; Delay_time(15); eeprom_scl_dr=0; } //在本驱动程序中,鸿哥没有对ACK信号进行出错判断,因为鸿哥这么多年一直都是这样用也没出现过什么问题。 void ack24() //确认位 { TRISC4 = 1; // sda输入 ,很关键!!!如果是51单片机则 eeprom_sda_dr=1; eeprom_scl_dr=1; Delay_time(15); eeprom_scl_dr=0; Delay_time(15); } void stop24() //停止位 { TRISC4 = 0; // sda输出 eeprom_sda_dr=0; eeprom_scl_dr=1; Delay_time(15); eeprom_sda_dr=1; TRISC4 = 1; // 结束后释放总线,sda输入 } unsigned char read24() //读取一个字节的时序 { unsigned char outdata,tempdata; TRISC4 = 1; // sda输入 如果是51单片机则 eeprom_sda_dr=1; outdata=0; asm("nop");asm("nop"); for(tempdata=0;tempdata<8;tempdata++) { eeprom_scl_dr=0; asm("nop");asm("nop"); eeprom_scl_dr=1; asm("nop");asm("nop"); outdata<<=1; if(eeprom_sda_sr==1)outdata++; asm("nop");asm("nop"); } return(outdata); } void write24(unsigned char dd) //发送一个字节的时序 { unsigned char tempdata; TRISC4 = 0; // sda输出 for(tempdata=0;tempdata<8;tempdata++) { if(dd>=0x80)eeprom_sda_dr=1; else eeprom_sda_dr=0; dd<<=1; asm("nop"); eeprom_scl_dr=1; asm("nop");asm("nop");asm("nop");asm("nop"); eeprom_scl_dr=0; } } unsigned char read_eeprom(unsigned int address,unsigned char page) //从哪一页的哪一个地址里读取一个字节的数据 { unsigned char dd,cAddress[2]; //0-L;1-H cAddress[0]=address; //低位地址 address>>=8; cAddress[1]=address; //高位地址 GIE=0; //禁止中断 start24(); //开始 if(page==0) //选择第0页的存储器 { write24(0xA0); //此字节包含页选择,写指令和芯片地址三两方面的内容。 } else //选择第1页的存储器 { write24(0xA8); //此字节包含页选择,写指令和芯片地址三两方面的内容。 } ack24(); //发送应答信号 write24(cAddress[1]); //发送读取的高位存储地址 ack24(); //发送应答信号 write24(cAddress[0]); //发送读取的低位存储地址 ack24(); //发送应答信号 start24(); //开始 if(page==0) //选择第0页的存储器 { write24(0xA1); //此字节包含页选择,读指令和芯片地址三两方面的内容。 } else //选择第1页的存储器 { write24(0xA9); //此字节包含页选择,读指令和芯片地址三两方面的内容。 } ack24(); //发送应答信号 dd=read24(); //读取一个字节 ack24(); //发送应答信号 stop24(); //停止 GIE=1; //允许中断 Delay_time(800); //此处最关键,此处的延时时间一定要,而且要足够长 return(dd); } void write_eeprom(unsigned int address,unsigned char page,unsigned char dd) //往哪一页的哪一个地址里写入一个字节的数据 { unsigned char cAddress[2]; //0-L;1-H cAddress[0]=address; //低位地址 address>>=8; cAddress[1]=address; //高位地址 GIE=0; //禁止中断 start24(); //开始 if(page==0) //选择第0页的存储器 { write24(0xA0); //此字节包含页选择,写指令和芯片地址三两方面的内容。 } else //选择第1页的存储器 { write24(0xA8); //此字节包含页选择,写指令和芯片地址三两方面的内容。 } ack24(); //发送应答信号 write24(cAddress[1]); //发送读取的高位存储地址 ack24(); //发送应答信号 write24(cAddress[0]); //发送读取的低位存储地址 ack24(); //发送应答信号 write24(dd); //写入存储的数据 ack24(); //发送应答信号 stop24(); //停止 GIE=1; //允许中断 Delay_time(4000); //此处最关键,此处的延时时间一定要,而且要足够长 } void key_scan() //按键扫描函数 { if(key_sr1==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock1=0; //按键自锁标志清零 delay_cnt1=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock1==0) //有按键按下,且是第一次被按下 { ++delay_cnt1; //延时计数器 if(delay_cnt1>cnt_delay_cnt1) { delay_cnt1=0; key_lock1=1; //自锁按键置位,避免一直触发 key_sec=1; //触发1号键 } } if(key_sr2==1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { key_lock2=0; //按键自锁标志清零 delay_cnt2=0; //按键去抖动延时计数器清零,此行非常巧妙 } else if(key_lock2==0) //有按键按下,且是第一次被按下 { ++delay_cnt2; //延时计数器 if(delay_cnt2>cnt_delay_cnt1) { delay_cnt2=0; key_lock2=1; //自锁按键置位,避免一直触发 key_sec=2; //触发2号键 } } } void key_service() //按键服务函数 { switch(key_sec) //按键服务状态切换 { case 1:// 1号键 加 // 补充说明:voice_time_cnt只要不为0蜂鸣器就会响,中断里判断voice_time_cnt不为0 //时,会不断自减,一直到它为0时,自动把蜂鸣器关闭 ++set_data; if(set_data>99) { set_data=0; } write_eeprom(128,0,set_data); //把刚刚修改的数据保存进第0页的eeprom地址 voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,避免一直触发 break; case 2:// 2号键 减 --set_data; if(set_data>99) //字节类型的数据,当0减去1时会变成255(0xff),当然会比99大了。 { set_data=99; } write_eeprom(128,0,set_data); //把刚刚修改的数据保存进第0页的eeprom地址 voice_time_cnt= cnt_voice_time; //蜂鸣器响“滴”一声就停 key_sec=0; //相应完按键处理程序之后,把按键选择变量清零,避免一直触发 break; } } void interrupt timer1rbint(void) //中断程序入口 { if(TMR1IE==1&&TMR1IF==1) //定时中断程序 { TMR1IF=0; TMR1ON=0; key_scan(); //按键扫描函数 if(voice_time_cnt) //控制蜂鸣器声音的长短 { beep_dr=1; //蜂鸣器响 --voice_time_cnt; //蜂鸣器响的声音长短的计数延时 } else { beep_dr=0; //蜂鸣器停止 } display_drive(); //数码管驱动程序,放在定时中断里 TMR1H=0xFF; TMR1L=0xC8; TMR1ON=1; } } void initial()//初始化 { ADCON0=0x00; ADCON1=0x0f; ADCON2=0x00; RBPU=0; TRISB=0x00; //数码管IO口设置成输出 TRISC7=0; TRISA1=0; //蜂鸣器IO口设置成输出 TRISD6=1; //独立按键IO口设置成输入 TRISD7=1; TRISC5=0; //AT24C02的时钟线设置成输出 TRISC4=1; //AT24C02的数据线设置成输入 SSPEN=0; T1CON=0x24; TMR1H=0xFE; TMR1L=0xEF; INTCON=0xC0; TMR1IF=0; TMR1IE=1; PEIE=1; //外围中断允许 GIE=1; TMR1ON=1; set_data=read_eeprom(128,0); //上电开机读取EEPROM数据,128是一个我任意选的一个存储地址,0代表第0页的存储器 if(set_data>99) //说明是这个单片机是第一次烧录程序之后,内部数据是默认值,以前没操作过。 { write_eeprom(128,0,0); //如果从来没操作过此EEPROM,则默认写入一个0的数据,中间的0代表第0页存储器 } } void display_drive() //数码管驱动程序,放在定时中断里 { if(set_data>=10) { number_left=set_data/10; //如果数据大于或者等于10,则分解出显示数据的十位去显示 } else { number_left=10; //如果数据小于10,则十位什么也不显示,显示空。 } number_right=set_data%10; //分解出显示数据的个位 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; com_left_dr=1; com_right_dr=1; //在即将更换下一位数码管时,先把让它两个什么都不显示,让显示过度更加平稳 asm("nop"); //空指令延时 asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); switch(dis_step) { case 1: //扫描左边的数码管 display_seg(number_left); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=0; //选中左数码管 com_right_dr=1; break; case 2: //扫描右边的数码管 display_seg(number_right); //如果不是任意IO口,可以直接用查表的方式取代此子程序 com_left_dr=1; com_right_dr=0; //选中右数码管 break; } delay1(50); //每一位数码管显示的停留延时时间,有疑问的朋友请自己尝试改成计数延时的方式, //鸿哥认为在此种环境下,在定时中断里用死延时delay1(50)是最佳的方式 ++dis_step; //下一次中断扫描另外一位的数码管,轮流扫描 if(dis_step>2) { dis_step=1; } } //不是鸿哥不懂爱,如果不是用任意IO口,而是直接用一个并口(比如51单片机中的P1口),那么就不用那么费力, //直接用查数组(俗称查表)的方式就可以替代display_seg这个编码转换程序, void display_seg(unsigned char seg) //编码转换程序,,放在display_drive里 { switch(seg) //switch指令,单片机中的战斗机,鸿哥的最爱! { case 0: //显示"0" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=0; seg_5_dr=1; seg_6_dr=1; break; case 1: //显示"1" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; case 2: //显示"2" seg_0_dr=1; seg_1_dr=0; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 3: //显示"3" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=0; break; case 4: //显示"4" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=1; seg_5_dr=0; seg_6_dr=1; break; case 5: //显示"5" seg_0_dr=0; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 6: //显示"6" seg_0_dr=0; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 7: //显示"7" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=1; seg_6_dr=0; break; case 8: //显示"8" seg_0_dr=1; seg_1_dr=1; seg_2_dr=1; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 9: //显示"9" seg_0_dr=1; seg_1_dr=1; seg_2_dr=0; seg_3_dr=1; seg_4_dr=1; seg_5_dr=1; seg_6_dr=1; break; case 10: //什么也不显示,空 seg_0_dr=0; seg_1_dr=0; seg_2_dr=0; seg_3_dr=0; seg_4_dr=0; seg_5_dr=0; seg_6_dr=0; break; } } void delay1(unsigned int de) { unsigned int t; for(t=0;t void Delay_time(unsigned int Delay11_MS) { unsigned int Delay11_us; for(Delay11_us=0;Delay11_us CLRWDT(); } } (5)下集预告: EEPROM篇--利用93C46存储数据。 (未完待续,下节更精彩,不要走开哦!) |
|
|
|
|
|
楼主你好,很感谢你的分享,你的编程思路很紧密直白,很值得学习,一直以来也希望我也能上传一些东西,可是自己现在水平和楼主还差天壤之别,只有膜拜了。在此有个问题求解,关于这一句,前面也没有传值进去,我观测了这个数组里面没有数据啊。这句到底是怎么回事。疑问中:RCREG_buf_temp[0]= RCREG_buf_temp[1]; //数据移动,方便截取关键字
RCREG_buf_temp[1]= RCREG_buf_temp[2]; RCREG_buf_temp[2]=asy_recieve(); if(RCREG_buf_temp[0]==0xeb&& RCREG_buf_temp[1]==0x00&& RCREG_buf_temp[2]==0x55) //数据头”eb 00 55”判断 |
|
|
|
|
|
lihai
|
|
|
|
|
|
还有这句,会不会丢失数据啊。unsigned char asy_recieve() //把串口缓冲区的数据一个个提取出来
{ unsigned char RCREG_dt; ++RCREG_read_total; //已经读出了多少个数据 RCREG_dt=RCREG_buf[RCREG_read_total -1]; if(RCREG_read_total >=RCREG_total) //只要把全部数据都读完,马上把缓冲区清零 { RCREG_read_total =0; RCREG_total=0; } return RCREG_dt; } |
|
|
|
|
|
|
|
|
|
只有小组成员才能发言,加入小组>>
求解外围电路实现的是4脚给持续低电平复位并正常工作,高电平不工作的原因
2086 浏览 1 评论
3630 浏览 3 评论
PIC1946程序有一个变量在运行过程中恢复初始值其他变量保持不变
2336 浏览 2 评论
2763 浏览 0 评论
PIC16F1825的RC5引脚,在主程序中操作无效,在中断中可以改变是为什么?
4029 浏览 5 评论
979浏览 0评论
用XC8编译PIC18F25K80时提示下面Error,求怎么解决这个问题
6366浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-5 04:01 , Processed in 1.122674 second(s), Total 93, Slave 82 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号