一、关于瞬间掉电存储
瞬间掉电存储数据,是工业或民用设备一个重要的功能,比如生产流水线的自动出品计数、开关寿命检测仪器等。这些都要纪录临时数据,有些甚至不能出现一个漏掉的数量,更不允许因突然停电而丢失全部数据。 单片机运行时的数据都存在于RAM(随机存储器)中,在掉电后RAM 中的数据是无法保留的,那么怎样使数据在掉电后不丢失呢?这就需要使用EEPROM 或FLASHROM 等存储器来实现。在传统的单片机系统中,一般是在片外扩展存储器,单片机与存储器之间通过IIC 或SPI 等接口来进行数据通信。这样不光会增加开发成本,同时在程序开发上也要花更多的心思。 而在51系列单片机中,STC系列单片机大都有eeprom(其实是采用IAP 技术读写内部FLASH来实现EEPROM),不再像以前用AT89C5X单片机那样只能依懒AT24XX或其它存储器,而且STC自身带的eeprom比外部存储器读写的速度更快、使用更方便。以下就是笔者采用STC89C52单片机,制作成功并已经应用到厂家自动生产线上,用于控制流水线和计数(电路见下图)。电路加了一个掉电检测触发中断在几十毫秒时间内完成eeprom的实时数据写入。 有的朋友就说了,根本不需要什么掉电检测,只需每计到一个数就让单片机写入eeprom更新数据就行了... 这种想法是不对的,一般初学者或只是玩玩单片机的人可能会这样说。 比如:生产线出品的商品每天有成千上万个数量,你能计一个数就插写一次eeprom?要知道eeprom的插写寿命官方公布是10万次(有的朋友说测试有几百万次)。如果每天数千次,单片机连续工作最多几月eeprom寿命就game over了 。类似在程序中循环反复读写eeprom只能适合开关机次数等频率不高的场合。 所以,对于计数频率较高的场合,我们只需做到掉电存储(或者说关机会自动储存)就行了,这样单片机每天只需进行一二次插写,您就不用关心寿命问题了。这样,我需要加一个掉电检测电路和一个大容量的电容即可。部分朋友用到电压监控/复位IC-IMP810等-只有3个管脚、简单,我个人没用过,不过下面是我用358做比较器-经测试和现应用到设备上的,已经工作了一年多。
二、电路原理简介 1) 工作中T0(P3.4)用作外部计数、LCD1602用作显示计数值; 2) 当因掉电(或关机)7805输出低于3.5V时(比较值R4可调),此时单片机和LM358由C7短时供电,供电时间数十毫秒或以上即可(当然C7容量越大供电时间越充足)。这样LM358才有足够时间触发INT0去写eeprom。eeprom读一个字节、编程一个字节、擦除一个扇区分别用时10us、60us、10ms。 注:STC89C52RC官方标注最低工作电压为3.4V 3) P3.3启动/停止按键:按一下---启动马达/T0开始计数、生产流水线运转;再按一次---马达停止/T0停止计数。 4) P3.5显示清零键:用于清除存储的数据和LCD显示。
三、电源设计提示: 在电路中,7805经2个4007后变成约4.3V、并分为两个方向:+5是提供单片机、LM358电路的和C7电容充电,VCC则是提供单片机外围功耗电路。由于二极管隔离,这样电容C7(2200uF)不会因其它电路快速消耗储存电力(比如LCD电流较大)。也就是说C7是作为掉电短时提供电力的。此电路经实测,C7能提供单片机+LM358工作电压在不低于3.5V能维持0.2s左右时间,足够时间完成写eeprom 。
四、 功能说明: 正在计数 按下了暂停键 完成1万个(自动停止马达和计数,等待重来) 1. 按下P3.3(复合键)启动马达流水线运转和T0外部对产品计数; 2. 再次按下P3.3(复合键)停止马达和计数; (当停止时计数值保持并执行EEPROM写操作,以防突然停电丢失临时数据) 3. 当自动计满10000个(可修改)时自动停止马达和计数,存储数据LCD保持显示并提示 已完成;当再次按下P3.3时马达流水线又开始运转;这时重新从0开始计数。 5. P3.5键为临时清零键,用于清除显示的数字。 6. 当然突然断掉(或关机)时,INT0触发中断写EEPROM,下次通电会读回参数。 >>适合自动生产流水线产品计数,可确保临时或掉电数据万无一失 >>适合比如某厂家生产线以100、1000、10000个或其它量为单位进行包装!
五、参考程序: //=========================================================== // 程序名称:T0外部计数0~10000-1602显示+EEPROM掉电存储 // 实验对象:XY900_USB型单片机开发系统板 // 程序设计:月夜听风 注明出处:www.mcusy.cn // 单 片 机:STC89C52RC //===========================================================
#include <reg52.h> #include<absacc.h> #include<intrins.h> #define uchar unsigned char #define uint unsigned int //--------------------------------------------- #define DataPort P0 //LCD数据口 sbit RS = P2^5; sbit RW = P2^6; sbit EN = P2^7; uint count; //定义计数/整型 //--------------------------------------------- //sbit cs = P1^0; //测试用 sbit start_stop = P3^3;//启动/停止复合键 sbit Clear = P3^5; //显示清零键 sbit MOTO = P2^0; //马达控制 sbit Warning = P2^4; //计满信号 //--------------------------------------------- uchar code tab0[] = {" MODEL : XY900 "};//显示控制板型号 uchar code tab1[] = {" WWW.MCUSY.COM "};//显示开发网址 uchar code tab2[] = {" Products Count "};//产品计数标题 uchar code tab3[] = {" Q =00000 Pause!"};//显示暂停/等待 uchar code tab4[] = {" Q =00000 Run.. "};//显示计数中 uchar code tab5[] = {" Q =10000 Done!!"};//计满一万个数为包装单位 /////以下EEPROM操作部分摘选了一个网上的///// /*======================== 定义操作命令 ========================*/ #define RdCommand 0x01 //定义ISP操作 #define PrgCommand 0x02 #define EraseCommand 0x03 #define Error 1 #define Ok 0 #define WaitTime 0x01 //定义CPU的等待时间 sfr ISP_DATA = 0xe2; //声明寄存器 sfr ISP_ADDRH = 0xe3; sfr ISP_ADDRL = 0xe4; sfr ISP_Order = 0xe5; sfr ISP_TRIG = 0xe6; sfr ISP_CONTR = 0xe7; /*---------------------打开 ISP,IAP 功能 ----------------------*/ void ISP_IAP_enable(void) { EA = 0; /*关中断*/ ISP_CONTR = ISP_CONTR & 0x18; ISP_CONTR = ISP_CONTR | WaitTime; ISP_CONTR = ISP_CONTR | 0x80; /*ISPEN=1*/ } /*--------------------- 关闭 ISP,IAP 功能 ---------------------*/ void ISP_IAP_disable(void) { ISP_CONTR = ISP_CONTR & 0x7f; /*ISPEN = 0*/ ISP_TRIG = 0x00; EA = 1; /*开中断*/ } /*----------------------- 公用的触发代码 ----------------------*/ void ISPgoon(void) { ISP_IAP_enable(); /*打开 ISP,IAP 功能*/ ISP_TRIG = 0x46; /*触发ISP_IAP命令字节1*/ ISP_TRIG = 0xb9; /*触发ISP_IAP命令字节2*/ _nop_(); } /*-------------------------- 字节读 ---------------------------*/ unsigned char byte_read(unsigned int byte_addr) { ISP_ADDRH = (unsigned char)(byte_addr>>8);/*地址赋值*/ ISP_ADDRL = (unsigned char)(byte_addr & 0x00ff); ISP_Order = ISP_Order & 0xf8; /*清除低3位*/ ISP_Order = ISP_Order | RdCommand; /*写入读命令*/ ISPgoon(); /* 触发执行*/ ISP_IAP_disable(); /*关闭ISP,IAP功能*/ return (ISP_DATA); /*返回读到的数据*/ } //--------------------- 函数名:EEPROM操作 --------------------// /* -----扇区擦除----- */ void SectorErase(unsigned int sector_addr) { unsigned int iSectorAddr; iSectorAddr = (sector_addr & 0xfe00); /*取扇区地址*/ ISP_ADDRH = (unsigned char)(iSectorAddr>>8); ISP_ADDRL = 0x00; ISP_Order = ISP_Order & 0xf8; /*清空低3位*/ ISP_Order = ISP_Order | EraseCommand; /* 擦除命令3*/ ISPgoon(); /*触发执行*/ ISP_IAP_disable(); /*关闭ISP,IAP功能*/ } /* -----字节写----- */ void byte_write(unsigned int byte_addr, unsigned char original_data) { ISP_ADDRH = (unsigned char)(byte_addr>>8); /*取地址*/ ISP_ADDRL = (unsigned char)(byte_addr & 0x00ff); ISP_Order = ISP_Order & 0xf8; /*清低3位*/ ISP_Order = ISP_Order | PrgCommand; /*写命令2*/ ISP_DATA = original_data; /*写入数据准备*/ ISPgoon(); /*触发执行*/ ISP_IAP_disable(); /*关闭IAP功能*/ } ////////以下是LCD1602操作函数//////// //==================函数名:LCD检测忙碌信号======================// void WaitEnable(void) { DataPort = 0xff; RS = 0; RW = 1; _nop_(); EN=1; _nop_(); _nop_(); while(DataPort&0x80); EN=0; } //--------------------- 函数名:写命令到LCD ---------------------// void WriteCommand(uchar Order,uchar Attribc) { if(Attribc)WaitEnable(); RS = 0; RW = 0; _nop_(); DataPort=Order; _nop_(); EN = 1; _nop_(); _nop_(); EN = 0; } //--------------------- 函数名: LCD写数据 ----------------------// void WriteData(uchar dataW) { WaitEnable(); RS = 1; RW = 0; _nop_(); DataPort=dataW; _nop_(); EN = 1; _nop_(); _nop_(); EN = 0; } //--------------------- 函数名: LCD初始化 ----------------------// void InitLcd() { WriteCommand(0x38,1); //显示为16*2 WriteCommand(0x08,1); WriteCommand(0x01,1); WriteCommand(0x06,1); WriteCommand(0x0c,1); } //--------------- 函数名:显示指定坐标的一个字符 ----------------// void DisplayOne(uchar X,uchar Y,uchar DData) { Y&=1; X&=15; if(Y)X|= 0x40; X|= 0x80; WriteCommand(X,0); WriteData(DData); } //--------------- 函数名:显示指定坐标的一串字符 ----------------// void DisplayList(uchar X,uchar Y,uchar code *DData) { uchar ListLength=0; Y&= 0x1; X&= 0xF; while(X<=15) { DisplayOne(X,Y,DData[ListLength]); ListLength++; X++; } } //================= 函数名:T0和INT0初始化中断函数 ===============// void InitTime(void) { TMOD=0X05;//T0为工作方式1-16位/对外部计数 TH0 = 0; TL0 = 0; //至65536最大 IE = 0; EX0 = 1;//启用INT0中断 ET0 = 1;//启动TF0中断功能 IT0 = 0;//INT0采用低态触发 EA = 1; }
//==================== 函数名:LCD显示函数 =======================// void display() { uchar w,q,b,s,g; w=count/10000+0x30; //万位数 q=count/1000%10+0x30; //千 b=count/100%10+0x30; //百 s=count/10%10+0x30;//个 g=count%10+0x30; DisplayOne(4,1,w); //万位显示位 DisplayOne(5,1,q); DisplayOne(6,1,b); DisplayOne(7,1,s); DisplayOne(8,1,g); } //==========================延时程序==============================// void delay(uint i) { uchar j; while(i--)//变量i由实际参数传入一个值 for(j=0;j<205;j++); } //========================函数名:主函数==========================// void main(void) { char i; delay(500); InitLcd(); //调用LCD初始化设置 InitTime(); //调用定时计数器中断函数 for(i=15;i>=0;i--) { //以下显示自定义工控板型号信息 DisplayList(i,0,tab0);//第一行 DisplayList(i,1,tab1);//第二行 delay(100); } delay(2000); for(i=16;i>=0;i--) { //以下显示等待启动 DisplayList(i,0,tab2);//第一行 DisplayList(i,1,tab3);//第二行 } TL0=byte_read(0x2000);TH0=byte_read(0x2200);//读回掉电时数据 delay(100);
//-----------以下程序在正常计数时不会执行EEPROM插写操作------------ while(1) { //-----开关操作1----- if(start_stop!=1) //如果启动/停止复合键被按下 { delay(10);while(start_stop!=1);//防抖;有无放开按键? if(MOTO!=0) //马达有无动作(回读)? { MOTO=0;TR0 = 1;//没有就启动马达和T0 for(i=16;i>=0;i--) {DisplayList(i,1,tab4);}//第二行显示正在计数中 } else { //在按下停止时执行写操作(防突然停电) MOTO=1;TR0 = 0;//否则马达停止,关闭T0 for(i=16;i>=0;i--) {DisplayList(i,1,tab3);}//第二行显示暂停 SectorErase(0x2000);SectorErase(0x2200); byte_write(0x2000,TL0);byte_write(0x2200,TH0);//写入临时数据 } } //-----开关操作2----- if(Clear==0)//如果清零键被按下 {TL0=TH0=0;}//计数值临时清零 //-----事件处理----- if(count==10000) //如果计满了10000个产品数 { SectorErase(0x2000);SectorErase(0x2200); byte_write(0x2000,TL0);byte_write(0x2200,TH0);//写入临时数据 MOTO=1; TR0=0; count=TL0=TH0=0;//马达停止/T0停止/寄存器均清零 for(i=16;i>=0;i--) {DisplayList(i,1,tab5);}//第二行显示已完成预设数 Warning = 0; //蜂鸣器或LED提示! while(start_stop!=0); //重启键 {delay(10);while(start_stop!=1);}//是否放开按键? Warning = 1; delay(100);
} //-----循环主体----- // cs=0;delay(10);cs=1;//模仿外部脉冲测试,实际应用请取消 count = TH0*256+TL0; //计数值赋值count display(); //调用显示函数 } }
//=================(掉电或关机触发)INT0中断写入EEPROM======================= //此函数需要掉电检测电路执行操作 void INT_0(void) interrupt 0 { SectorErase(0x2000); SectorErase(0x2200); byte_write(0x2000,TL0); byte_write(0x2200,TH0);//写入临时数据 while(1); //什么事也不做 }
程序出处:微控实验网 www.mcusy.cn
|