51/AVR单片机技术驿站!  <在线翻译>     便利工具    特色网址   无弹窗、无插件的绿色站点...  英才招聘   学历查询  喜欢>>收藏我站 

当前位置:首页 > ◆本站实例 > 详细内容
单片机EEPROM掉电存储生产计数控制电路+程序
作者:admin  发布时间:2009/1/3  阅读次数:6657  字体大小: 【】 【】【

一、关于瞬间掉电存储

       瞬间掉电存储数据,是工业或民用设备一个重要的功能,比如生产流水线的自动出品计数、开关寿命检测仪器等。这些都要纪录临时数据,有些甚至不能出现一个漏掉的数量,更不允许因突然停电而丢失全部数据。
       单片机运行时的数据都存在于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

我要评论
  • 匿名发表
  • [添加到收藏夹]
  • 发表评论:(匿名发表无需登录,已登录用户可直接发表。) 登录状态:未登录
最新评论
所有评论[0]
    暂无已审核评论!

网站导航 管理登陆 ┊ 免责声明 问题反馈  友链说明
本站部分内容来自网络共享资源,如有冒犯您的权利请来信告之删除或纠正!
不得对本站进行复制、盗链或镜像,转载内容须获得同意或授权;欢迎友情链接、站务合作!

    我要报警 Alexa
 mcusy_cn#126.com (请把#改成@) 交流:522422171
本站学习交流群:138..158(高级群1-)、77930286(高级群2)、61804809(群3)
Copyright© MCUSY All Rights Reserved
本站网警备案号: WZ36040002485
  ICP备案证书号:粤ICP备09034963号