精华内容
下载资源
问答
  •  用法拉电容可从容实现单片机掉电检测与数据掉电保存。电路见下图。这里首先用6V供电(如7806),为什么用6V不用5V是显而易见的.电路中的二极管们一般都起两个作用,一是起钳位作用,钳去0.6V,保证使
  • 本文将介绍用法拉电容实现单片机掉电数据保存
  • STC89C52单片机掉电数据保存程序,代码清晰易懂!
  • 以下的电路,是一个可靠的简单的掉电检测、法拉电容能量储存等完整硬件电路和相应的软件细节,是笔者在产品上一个成熟的可靠的自诩经典电路和心血,在这里完全公开地提供给大家以供大家一起来批判赏析借鉴和改进.
  • 单片机在正常工作时,因... 用法拉电容可从容实现单片机掉电检测与数据掉电保存。电路见下图。这里首先用6V供电(如7806),为什么用6V不用5V是显而易见的.电路中的二极管们一般都起两个作用,一是起钳位作用,钳去0.6V,保
  • 单片机在正常工作时,因... 用法拉电容可从容实现单片机掉电检测与数据掉电保存。电路见下图。这里首先用6V供电(如7806),为什么用6V不用5V是显而易见的.电路中的二极管们一般都起两个作用,一是起钳位作用,钳去0.6V,保
  • 单片机在正常工作时,因某种原因造成突然掉电,将会丢失数据存储器(RAM)里的数据。在某些应用场合如测量、控制等领域,单片机正常...为此,通常做法是在这些系统中加入单片机掉电检测电路与单片机掉电数据保存。用法...

    单片机在正常工作时,因某种原因造成突然掉电,将会丢失数据存储器(RAM)里的数据。在某些应用场合如测量、控制等领域,单片机正常工作中采集和运算出一些重要数据,待下次上电后需要恢复这些重要数据。因此,在一些没有后备供电系统的单片机应用系统中,有必要在系统完全断电之前,把这些采集到的或计算出的重要数据存在在 EEPROM 中。为此,通常做法是在这些系统中加入单片机掉电检测电路与单片机掉电数据保存。

    用法拉电容可从容实现单片机掉电检测与数据掉电保存。电路见下图。这里首先用 6V 供电(如 7806),为什么用 6V 不用 5V 是显而易见的。电路中的二极管们一般都起两个作用,一是起钳位作用,钳去 0.6V,保证使大多数 51 系列的单片机都能在 4.5V--5.5V 之间的标称工作电压下工作。而 4.5-5.5 间这 1V 电压在 0.47F 电容的电荷流失时间就是我们将来在单片机掉电检测报警后我们可以规划的预警回旋时间。二是利用单向导电性保证向储能电容 0.47F/5.5V 单向冲电。

    两只 47 欧电阻作用:第一,对单片机供电限流。一般地单片机电源直接接 7805 上,这是个不保险的做法,为什么?因为 7805 可提供高达 2A 的供电电流,异常时足够把单片机芯片内部烧毁。有这个 47 欧姆电阻保护,即使把芯片或者极性插反也不会烧单片机和三端稳压器,但这个电阻也不能太大,上限不要超过 220 欧,否则对单片机内部编程时,会编程失败(其实是电源不足)。第二,和 47UF 和 0.01UF 电容一起用于加强电源滤波。第三,对 0.47F/5.5V 储能电容,串入的这只 47 欧电阻消除“巨量法拉电容”的上电浪涌。实现冲电电流削峰。

    35673e8ef55f73d38b54ba754baeefcf.png

    现在我们算一算要充满 0.47F 电容到 5.5V,即使用 5.5A 恒流对 0.47F 电容冲电,也需要 0.47 秒才能冲到 5.5V,因此我们可以知道:

    1. 如果没有 47 欧姆电阻限流,上电瞬间三端稳压器必然因强大过电流而进入自保。

    2. 长达 0.47 秒(如果真有 5.5A 恒流充电的话)缓慢上电,如此缓慢的上电速率,将使得以微分(RC 电路)为复位电路的 51 单片机因为上电太慢无法实现上电复位。(其实要充满 0.47UF 电容常常需要几分种)。

    3. 正因为上电时间太慢,将无法和今天大多数主流的以在线写入(ISP)类单片机与写片上位计算机软件上预留的等待应答时间严重不匹配(一般都不大于 500MS),从而造成应答失步,故总是提示“通信失败”。

    知道这个道理你就不难理解这个电路最上面的二极管和电阻串联起来就是必须要有上电加速电路。这里还用了一只(内部空心不带蓝色的)肖特基二极管(1N5819)从法拉电容向单片机 VCC 放电,还同时阻断法拉电容对上电加速电路的旁路作用,用肖特基二极管是基于其在小电流下导通电压只有 0.2V 左右考虑的,目的是尽量减少法拉电容在单片机掉电时的电压损失。多留掉点维持时间。

    三极管 9014 和钳制位二极管分压电阻垫位电阻(470 欧姆)等构成基极上发射极双端输入比较器,实现单片机掉电检测和发出最高优先级的掉电中断,单片机掉电保存程执行。这部分电路相当于半只比较器 LM393,但电路更简单耗电更省(掉电时耗电小于 0.15MA)。

    47K 电阻和 470 欧姆二极管 1N4148 一道构成嵌位电路,保证基极电位大约在 0.65V 左右(可这样计算 0.6(二极管导通电压)+5*0.47/47),这样如果 9014 发射极电压为 0(此时就是外部掉电),三极管 9014 正好导通,而且因为 51 单片机 P3.2 高电平为弱上拉(大约 50UA),此时 9014 一定是导通且弱电流饱和的,这样就向单片机内部发出最高硬件优先级的 INX0 掉电检测中断。

    而在平时正常供电时,因发射极上也大约有 6*0.22/2.2=0.6V 电压上顶,不难发现三极管 9014 一定处于截止状态,而使 P3.2 维持高电平的,单片机掉电保存中断程序不被触发。

    最后还有两个重要软件和硬件 note:

    软件上:首先 INX0 在硬件上(设计)是处于最高优先级的,这里还必须要在软件保证最高级别的优先。从而确保单片机掉电时外部中断 0 能打断其他任何进程,最高优先地被检测和执行。其次在 INX0 的掉电保存写入子程序模块入口,还要用:

    MOVP1,#00H

    MOVP2,#00H

    MOVP3,#00H

    MOVP0,#00H

    SJMP 掉电保存

    来阻断法拉电容的电荷通过单片机口线外泄和随后跳转掉电保存写入子程序模块。(见硬件要点)

    硬件上:凡是驱动单片机外部口线等的以输出高电平驱动外部设备,其电源不能和电片机的供电电压 VCC 去争抢(例如上拉电阻供电不取自单片机 VCC)。而应直接接在电源前方,图中 4.7K 电阻和口线 PX.Y 就是一个典型示例,接其它口线 PX.Y‘和负载也雷同。这里与上拉 4.7K 电阻相串联二极管也有两个作用:1、钳去 0.6V 电压以便与单片机工作电压相匹配,防止口线向单片机内部反推电。造成单片机口线功能紊乱 .2、利用二极管单向供电特性,防止掉电后单片机通过口线向电源和外部设备反供电。

    上面的单片机掉电检测电路,在与掉电保存写入子程序模块结合起来就可以保证在单片机掉电期间,不会因法拉电容上的积累电荷为已经掉电的外部电路无谓供电和向电源反供电造成电容能量泄放缩短掉电维持时间。

    有了这些基础,我们来计算 0.47UF 的电容从 5.5V 跌落到 4.5V(甚至可以下到 3.6V)所能维持的单片机掉电工作时间。这里假设设单片机工作电流为 20MA(外设驱动电流已经被屏蔽)不难算出:

    T=1V*0.47*1000(1000 是因为工作电流为豪安)/20=23.5 秒!

    展开全文
  • 求STC89C52单片机关电源之后能把程序中的某个变量(比如说a)保存下来,下次启动之后还是变成上次的数据。比如第一次用的时候a=100而关电源时是a=200,;那么下次开机时a=200....一次类推。希望要纯软件,不希望...
  • 《电子报》2001年第51期第十二版介绍过陈先生的《在掉电瞬间将数据存入E2PROM的方法》,很实用。但这种对电源波形进行扫描的方法要求市电...本文向读者推荐一种对市电无严格要求的单片机掉电数据保存方法。
  • 用结构体的方式来操作单片机内eeprom进行数据掉电保存.pdf
  • 由于客户在请人设计开发一设备,但是设备用户处总停电,造成设备及其周边耗材损耗严重,因此请我司在现有STC为主要芯片的基础上做掉电瞬间EEPROM里的20个参数保存,上电后通过读取EEPROM中的参数回到掉电的状态,...

    由于客户在请人设计开发一设备,但是设备用户处总停电,造成设备及其周边耗材损耗严重,因此请我司在现有STC为主要芯片的基础上做掉电瞬间EEPROM里的20个参数保存,上电后通过读取EEPROM中的参数回到掉电的状态,基于此,荣致电子科技做了大量的工作,并选用了很多种方案:

    1、 通过1法拉大电容做掉电临时备份电池。

    2、 通过备份电池或者DS1302里的31个字节ram+电池方案。

    3、 通过MAX813L检测掉电,然后通过普通STC10某个引脚读取MAX813L的第五脚高低电平状态。

    4、 购买铁电芯片来不停的存储用户数据。

    5、采用单片机的ADC来检测掉电,电压低于某个值时,操作EEPROM

    6、采用STC15W的CMP+和CMP-来做比较电压中断,然后操作EEPROM.


    =========================        以下为参考资料       ======================


    方案3经济、安全、可靠,并且存储20个参数无任何问题,具体实施细节如下:

    一、割掉单片机单独供电VCC线路.

    二、外部进电源VCC接IN5819二极管后单独给单片机供电.

    三、VCC5V通过4.7K与2K电阻分压给MAX813L第四脚,高于1.25V即可。

    四、MAX813L第五脚连接至STC10单片机某个引脚,如:P2.0。

    五、通过软件来检测P2.0引脚的电平状态,如果为低,立刻关闭所有的外部输出,然后进行EEPROM的写操作,通过延时约100ms后即可很好的保存在掉电时的动态参数。

    通过各类验证,在断电几十次的情况下,所有的参数及执行程序未发生丢失及错误的情况,说明可用。具体图纸如下:

     

    转载于:https://www.cnblogs.com/bytebee/p/8421225.html

    展开全文
  • main.c ---- @Edit: ZHQ ---- @Version: V1.0 ---- @CreationTime: 20200721 ---- @ModifiedTime: 20200721 ---- @Description: 实现功能: ---- 4个被更改的参数断电不丢失,数据可以保存,断电再上电后还是上...

    一、使用proteus绘制简单的电路图,用于后续仿真

    关于IIC的读写:

    二、编写程序

    /********************************************************************************************************************
    ----	@Project:	AT24C02
    ----	@File:	main.c
    ----	@Edit:	ZHQ
    ----	@Version:	V1.0
    ----	@CreationTime:	20200721
    ----	@ModifiedTime:	20200721
    ----	@Description:	实现功能:
    ----	4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。
    ----	一共有4个窗口。每个窗口显示一个参数。
    ----	第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
    ----	第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。
    ----	
    ----	单片机:AT89C52
    ********************************************************************************************************************/
    #include "reg52.h"
    /*——————宏定义——————*/
    #define FOSC 11059200L
    #define BAUD 9600
    #define T1MS (65536-FOSC/12/500)   /*0.5ms timer calculation method in 12Tmode*/
    
    #define	OP_READ	0xa1		/*器件地址以及读取操作,0xa1即为1010 0001B*/
    #define	OP_WRITE 0xa0		/*器件地址以及写入操作,0xa0即为1010 0000B*/
    
    #define	const_key_time1 9	/*按键去抖动延时的时间*/
    #define	const_key_time2 9	/*按键去抖动延时的时间*/
    #define	const_key_time3 9	/*按键去抖动延时的时间*/
    
    #define const_voice_short 20	/*蜂鸣器短叫的持续时间*/
    
    /*——————变量函数定义及声明——————*/
    /*蜂鸣器的驱动IO口*/
    sbit BEEP = P2^7;
    /*LED*/
    sbit LED = P3^5;
    
    /*按键*/
    sbit Key_S1 = P0^0;	/*对应S1键,加键*/
    sbit Key_S2 = P0^1;	/*对应S5键,减键*/
    sbit Key_S3 = P0^2;	/*对应S9键,切换窗口*/
    sbit Key_S4 = P0^3;	/*对应S13键,复位*/
    sbit Key_Gnd = P0^4;
    
    /*数码管*/
    sbit Dig_Hc595_Sh = P2^0;
    sbit Dig_Hc595_St = P2^1;
    sbit Dig_Hc595_Ds = P2^2;
    
    /*EEPROM*/
    sbit eeprom_scl_dr = P3^7;	/*时钟线*/
    sbit eeprom_sda_dr_sr = P3^6;	/*数据的输出线和输入线*/
    
    unsigned char ucKeySec = 0; /*被触发的按键编号*/
    unsigned int uiKeyTimeCnt1 = 0; /*按键去抖动延时计数器*/
    unsigned char ucKeyLock1 = 0;   /*按键触发后自锁的变量标志*/
    unsigned int uiKeyTimeCnt2 = 0; /*按键去抖动延时计数器*/
    unsigned char ucKeyLock2 = 0;   /*按键触发后自锁的变量标志*/
    unsigned int uiKeyTimeCnt3 = 0; /*按键去抖动延时计数器*/
    unsigned char ucKeyLock3 = 0;   /*按键触发后自锁的变量标志*/
    
    unsigned int uiVoiceCnt = 0;    /*蜂鸣器鸣叫的持续时间计数器*/
    unsigned char ucVoiceLock = 0;  /*蜂鸣器鸣叫的原子锁*/
    
    unsigned char ucDigShow8;   /*第8位数码管要显示的内容*/
    unsigned char ucDigShow7;   /*第7位数码管要显示的内容*/
    unsigned char ucDigShow6;   /*第6位数码管要显示的内容*/
    unsigned char ucDigShow5;   /*第5位数码管要显示的内容*/
    unsigned char ucDigShow4;   /*第4位数码管要显示的内容*/
    unsigned char ucDigShow3;   /*第3位数码管要显示的内容*/
    unsigned char ucDigShow2;   /*第2位数码管要显示的内容*/
    unsigned char ucDigShow1;   /*第1位数码管要显示的内容*/
    
    unsigned char ucDigDot8;   /*数码管8的小数点是否显示的标志*/
    unsigned char ucDigDot7;   /*数码管7的小数点是否显示的标志*/
    unsigned char ucDigDot6;   /*数码管6的小数点是否显示的标志*/
    unsigned char ucDigDot5;   /*数码管5的小数点是否显示的标志*/
    unsigned char ucDigDot4;   /*数码管4的小数点是否显示的标志*/
    unsigned char ucDigDot3;   /*数码管3的小数点是否显示的标志*/
    unsigned char ucDigDot2;   /*数码管2的小数点是否显示的标志*/
    unsigned char ucDigDot1;   /*数码管1的小数点是否显示的标志*/
    
    unsigned char ucDigShowTemp = 0;	/*临时中间变量*/
    unsigned char ucDisplayDriveStep = 1; /*动态扫描数码管的步骤变量*/
    
    unsigned char ucWd1Update = 1;	/*窗口1更新显示标志*/
    unsigned char ucWd2Update = 0;	/*窗口2更新显示标志*/
    unsigned char ucWd3Update = 0;	/*窗口3更新显示标志*/
    unsigned char ucWd4Update = 0;	/*窗口4更新显示标志*/
    unsigned char ucWd = 1;	/*本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。*/
    unsigned int uiSetData1 = 0;	/*本程序中需要被设置的参数1*/
    unsigned int uiSetData2 = 0;	/*本程序中需要被设置的参数2*/
    unsigned int uiSetData3 = 0;	/*本程序中需要被设置的参数3*/
    unsigned int uiSetData4 = 0;	/*本程序中需要被设置的参数4*/
    
    unsigned char ucTemp1 = 0;	/*中间过渡变量*/
    unsigned char ucTemp2 = 0;	/*中间过渡变量*/
    unsigned char ucTemp3 = 0;	/*中间过渡变量*/
    unsigned char ucTemp4 = 0;	/*中间过渡变量*/
    
    void Dig_Hc595_Drive(unsigned char, unsigned char);
    
    /*根据原理图得出的共阴数码管字模表*/
    code unsigned char Dig_Table[] =
    {
    	0x3f,  /*0       序号0*/
    	0x06,  /*1       序号1*/
    	0x5b,  /*2       序号2*/
    	0x4f,  /*3       序号3*/
    	0x66,  /*4       序号4*/
    	0x6d,  /*5       序号5*/
    	0x7d,  /*6       序号6*/
    	0x07,  /*7       序号7*/
    	0x7f,  /*8       序号8*/
    	0x6f,  /*9       序号9*/
    	0x00,  /*不显示  序号10*/
    	0x40,  /*-		 序号11*/
    	0x73,  /*P       序号12*/	
    };
    
    /**
    * @brief  延时函数
    * @param  无
    * @retval 无
    **/
    void Delay_Long(unsigned int uiDelayLong)
    {
       unsigned int i;
       unsigned int j;
       for(i=0;i<uiDelayLong;i++)
       {
          for(j=0;j<500;j++)  /*内嵌循环的空指令数量*/
              {
                 ; /*一个分号相当于执行一条空语句*/
              }
       }
    }
    /**
    * @brief  延时函数
    * @param  无
    * @retval 无
    **/
    void delay_short(unsigned int uiDelayShort)
    {
      unsigned int i;
      for(i=0;i<uiDelayShort;i++)
      {
    		 ; /*一个分号相当于执行一条空语句*/
      }
    }
    
    /**
    * @brief  开始数据传送函数
    * @param  无
    * @retval 开始位
    **/
    void start24(void)
    {
    	eeprom_sda_dr_sr = 1;
    	eeprom_scl_dr = 1;
    	delay_short(15);
    	eeprom_sda_dr_sr = 0;
    	delay_short(15);
    	eeprom_scl_dr = 0;
    }
    
    /**
    * @brief  结束数据传送函数
    * @param  无
    * @retval 结束位
    **/
    void stop24(void)
    {
    	eeprom_sda_dr_sr = 0;
    	eeprom_scl_dr = 1;
    	delay_short(15);
    	eeprom_sda_dr_sr = 1;
    }
    
    /**
    * @brief  确认位时序函数
    * @param  无
    * @retval 检测应答
    **/
    void ack24(void)
    {
    	eeprom_sda_dr_sr = 1;
    	eeprom_scl_dr = 1;
    	delay_short(15);
    	eeprom_scl_dr = 0;
    	delay_short(15);
    }
    
    /**
    * @brief  读函数
    * @param  无
    * @retval 读取一个字节的时序
    **/
    unsigned char read24(void)
    {
    	unsigned char outdata, tempdata;
    	outdata = 0;
    	eeprom_sda_dr_sr = 1;	/*51单片机的IO口在读取数据之前要先置一,表示数据输入*/
    	delay_short(2);
    	for(tempdata = 0; tempdata < 8; tempdata ++)
    	{
    		eeprom_scl_dr = 0;
    		delay_short(2);
    		eeprom_scl_dr = 1;
    		delay_short(2);
    		outdata = outdata << 1;
    		if(eeprom_sda_dr_sr == 1)
    		{
    			outdata ++;
    		}
    		eeprom_sda_dr_sr = 1;
    		delay_short(2);
    	}
    	return(outdata);
    }
    
    /**
    * @brief  写函数
    * @param  dd
    * @retval 发送一个字节的时序
    **/
    void write24(unsigned char dd)
    {
    	unsigned char tempdata;
    	for(tempdata = 0; tempdata < 8; tempdata ++)
    	{
    		if(dd >= 0x80)
    		{
    			eeprom_sda_dr_sr = 1;
    		}
    		else
    		{
    			eeprom_sda_dr_sr = 0;
    		}
    		dd = dd <<1;
    		delay_short(2);
    		eeprom_scl_dr = 1;
    		delay_short(4);
    		eeprom_scl_dr = 0;
    	}
    }
    
    /**
    * @brief  读函数
    * @param  address
    * @retval 从一个地址读取出一个字节数据
    **/
    unsigned char read_eeprom(unsigned char address)
    {
    	unsigned char dd, cAddress;
    	cAddress = address;	/*把低字节地址传递给一个字节变量。*/
    
    	EA = 0;	/*禁止中断*/
    	start24();	/*IIC通讯开始*/
    	write24(OP_WRITE);	/*此字节包含读写指令和芯片地址两方面的内容。*/
    						/*指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定*/
    	ack24();	/*发送应答信号*/
    	write24(cAddress);	/*发送读取的存储地址(范围是0至255)*/
    	ack24();	/*发送应答信号*/
    
    	start24();
    	write24(OP_READ);	/*此字节包含读写指令和芯片地址两方面的内容。*/
    						/*指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定*/	
    	ack24();	/*发送应答信号*/	
    	dd = read24();	/*读取一个字节*/
    	ack24();	/*发送应答信号*/
    	stop24();	/*停止*/
    /* 
    * 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,
    * 写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。
    * 否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,
    * 应该适当继续把这个时间延长,尤其是在写入数据时。
    */	
    	delay_short(800);	/*此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因*/
    	EA = 1;	/*允许中断*/
    	return(dd);
    }
    
    /**
    * @brief  写函数
    * @param  address, dd
    * @retval 往一个地址存入一个字节数据
    **/
    void write_eeprom(unsigned char address, unsigned char dd)
    {
    	unsigned char cAddress;	
    	cAddress = address;	/*把低字节地址传递给一个字节变量。*/
    
    	EA = 0;	/*禁止中断*/
    	start24();	/*IIC通讯开始*/
    	write24(OP_WRITE);	/*此字节包含读写指令和芯片地址两方面的内容。*/
    						/*指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定*/
    	ack24();	/*发送应答信号*/
    	write24(cAddress);	/*发送读取的存储地址(范围是0至255)*/
    	ack24();	/*发送应答信号*/
    	write24(dd);	/*写入存储的数据*/
    	ack24();	/*发送应答信号*/
    	stop24();	/*停止*/	
    	delay_short(2000);	/*此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因*/
    	EA = 1;	/*允许中断*/
    }
    
    /**
    * @brief  读函数
    * @param  address
    * @retval 从一个地址读取出一个int类型的数据
    **/
    unsigned int read_eeprom_int(unsigned int address)
    {
    	unsigned char ucReadDataH;
    	unsigned char ucReadDataL;
    	unsigned int uiReadDate;
    
    	ucReadDataH = read_eeprom(address);	/*读取高字节*/
    	ucReadDataL = read_eeprom(address + 1);	/*读取低字节*/
    
    	uiReadDate = ucReadDataH;	/*把两个字节合并成一个int类型数据*/
    	uiReadDate = uiReadDate <<8;
    	uiReadDate = uiReadDate + ucReadDataL;
    
    	return(uiReadDate);
    }
    
    /**
    * @brief  写函数
    * @param  address, uiWriteData
    * @retval 往一个地址存入一个int类型的数据
    **/
    void write_eeprom_int(unsigned int address, unsigned int uiWriteData)
    {
    	unsigned char ucWriteDataH;
    	unsigned char ucWriteDataL;
    
    	ucWriteDataH = uiWriteData >>8;
    	ucWriteDataL = uiWriteData;
    
    	write_eeprom(address, ucWriteDataH);	/*存入高字节*/
    	write_eeprom((address + 1), ucWriteDataL);	/*存入低字节*/
    }
    
    /**
    * @brief  定时器0初始化函数
    * @param  无
    * @retval 初始化T0
    **/
    void Init_T0(void)
    {
    	TMOD = 0x01;                    /*set timer0 as mode1 (16-bit)*/
    	TL0 = T1MS;                     /*initial timer0 low byte*/
    	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
    }
    
    /**
    * @brief  外围初始化函数
    * @param  无
    * @retval 初始化外围
    * 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
    * 只要更改以下对应变量的内容,就可以显示你想显示的数字。
    **/
    void Init_Peripheral(void)
    {
    	ucDigDot8 = 0;  
    	ucDigDot7 = 0; 
    	ucDigDot6 = 0; 
    	ucDigDot5 = 0;   
    	ucDigDot4 = 0;
    	ucDigDot3 = 0;   
    	ucDigDot2 = 0;  
    	ucDigDot1 = 0; 
    	ET0 = 1;/*允许定时中断*/
    	TR0 = 1;/*启动定时中断*/
    	TR1 = 1;
    	ES = 1;	/*允许串口中断*/
    	EA = 1;/*开总中断*/  
    
    /* 
    * 如何初始化EEPROM数据的方法。在使用EEPROM时,这一步初始化很关键!
    * 第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。
    * 这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。另外,
    * 由于int类型数据占用2个字节,所以以下4个数据挨着的地址是0,2,4,6.
    */
    	uiSetData1 = read_eeprom_int(0);	/*读取uiSetData1,内部占用2个字节地址*/
    	if(uiSetData1 > 9999)	/*不在范围内*/
    	{
    		uiSetData1 = 0;	/*填入一个初始化数据*/
    		write_eeprom_int(0, uiSetData1);	/*存入uiSetData1,内部占用2个字节地址*/
    	}
    	uiSetData2 = read_eeprom_int(2);	/*读取uiSetData2,内部占用2个字节地址*/
    	if(uiSetData2 > 9999)	/*不在范围内*/
    	{
    		uiSetData2 = 0;	/*填入一个初始化数据*/
    		write_eeprom_int(2, uiSetData2);	/*存入uiSetData2,内部占用2个字节地址*/
    	}
    	uiSetData3 = read_eeprom_int(4);	/*读取uiSetData3,内部占用2个字节地址*/
    	if(uiSetData3 > 9999)	/*不在范围内*/
    	{
    		uiSetData3 = 0;	/*填入一个初始化数据*/
    		write_eeprom_int(4, uiSetData3);	/*存入uiSetData3,内部占用2个字节地址*/
    	}
    	uiSetData4 = read_eeprom_int(6);	/*读取uiSetData4,内部占用2个字节地址*/
    	if(uiSetData4 > 9999)	/*不在范围内*/
    	{
    		uiSetData4 = 0;	/*填入一个初始化数据*/
    		write_eeprom_int(6, uiSetData4);	/*存入uiSetData4,内部占用2个字节地址*/
    	}
    }
    
    /**
    * @brief  初始化函数
    * @param  无
    * @retval 初始化单片机
    **/
    void Init(void)
    {
    	LED  = 0;
    	BEEP = 1;
    	Key_Gnd = 0;
    	Dig_Hc595_Drive(0x00, 0x00);	/*关闭所有经过另外两个74HC595驱动的LED灯*/
    	Init_T0();
    }
    
    
    /**
    * @brief  显示数码管字模的驱动函数
    * @param  无
    * @retval	动态驱动数码管的原理
    * 在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
    * 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
    * 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
    * 数码管公共位com的引脚。
    **/
    void Display_Drive(void)
    {
    	switch(ucDisplayDriveStep)
    	{
    		case 1:	/*显示第1位*/
    			ucDigShowTemp = Dig_Table[ucDigShow1];
    			if(ucDigDot1 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xfe);
    		break;
    		case 2:	/*显示第2位*/
    			ucDigShowTemp = Dig_Table[ucDigShow2];
    			if(ucDigDot2 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xfd);
    		break;
    		case 3:	/*显示第3位*/
    			ucDigShowTemp = Dig_Table[ucDigShow3];
    			if(ucDigDot3 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xfb);
    		break;
    		case 4:	/*显示第4位*/
    			ucDigShowTemp = Dig_Table[ucDigShow4];
    			if(ucDigDot4 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xf7);
    		break;
    		case 5:	/*显示第5位*/
    			ucDigShowTemp = Dig_Table[ucDigShow5];
    			if(ucDigDot5 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xef);
    		break;
    		case 6:	/*显示第6位*/
    			ucDigShowTemp = Dig_Table[ucDigShow6];
    			if(ucDigDot6 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xdf);
    		break;
    		case 7:	/*显示第7位*/
    			ucDigShowTemp = Dig_Table[ucDigShow7];
    			if(ucDigDot7 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0xbf);
    		break;
    		case 8:	/*显示第8位*/
    			ucDigShowTemp = Dig_Table[ucDigShow8];
    			if(ucDigDot8 == 1)
    			{
    				ucDigShowTemp = ucDigShowTemp | 0x80;	/*显示小数点*/
    			}
    			Dig_Hc595_Drive(ucDigShowTemp, 0x7f);
    		break;
    	}
    	ucDisplayDriveStep ++;	/*逐位显示*/
    	if(ucDisplayDriveStep > 8)	/*扫描完8个数码管后,重新从第一个开始扫描*/
    	{
    		ucDisplayDriveStep = 1;
    	}
    }
    /**
    * @brief  数码管的595驱动函数
    * @param  无
    * @retval 
    * 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
    * 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
    * 位的数码管界面,这样可以增加显示的效果。但是,由于是间接经过74HC595驱动数码管的,
    * 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
    * 因此这里不再需要加delay延时函数或者计数延时。
    **/
    void Dig_HC595_Drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01)
    {
    	unsigned char i;
    	unsigned char ucTempData;
    	Dig_Hc595_Sh = 0;
    	Dig_Hc595_St = 0;	
    	
    	ucTempData = ucDigStatusTemp16_09;	/*先送高8位*/
    	for(i = 0; i < 8; i ++)
    	{
    		if(ucTempData >= 0x80)
    		{
    			Dig_Hc595_Ds = 1;
    		}
    		else
    		{
    			Dig_Hc595_Ds = 0;
    		}
    		/*注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。*/
    		Dig_Hc595_Sh = 0;	/*SH引脚的上升沿把数据送入寄存器*/
    		delay_short(1); 
    		Dig_Hc595_Sh = 1;
    		delay_short(1); 	
    			
    		ucTempData = ucTempData <<1;
    	}
    	ucTempData = ucDigStatusTemp08_01;	/*再先送低8位*/
    	for(i = 0; i < 8; i ++)
    	{
    		if(ucTempData >= 0x80)
    		{
    			Dig_Hc595_Ds = 1;
    		}
    		else
    		{
    			Dig_Hc595_Ds = 0;
    		}
    		Dig_Hc595_Sh = 0;	/*SH引脚的上升沿把数据送入寄存器*/
    		delay_short(1); 
    		Dig_Hc595_Sh = 1;
    		delay_short(1); 	
    			
    		ucTempData = ucTempData <<1;
    	}
    	
    	Dig_Hc595_St = 0;	/*ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来*/
    	delay_short(1);
    	Dig_Hc595_St = 1;
    	delay_short(1);
    	
    	Dig_Hc595_Sh = 0;	/*拉低,抗干扰就增强*/
    	Dig_Hc595_St = 0;
    	Dig_Hc595_Ds = 0;
    }
    /**
    * @brief  显示的窗口菜单服务程序
    * @param  无
    * @retval 
    *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
    *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
    *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
    *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
    **/
    void Display_Service(void)	/*显示的窗口菜单服务程序*/
    {
    		switch(ucWd)
    		{
    			case 1:	/*显示P--1窗口的数据*/
    				if(ucWd1Update == 1)	/*窗口1要全部更新显示*/
    				{
    					ucWd1Update = 0;	/*及时清零标志,避免一直进来扫描*/
    					ucDigShow8 = 12;	/*第8位数码管显示P*/
    					ucDigShow7 = 11;	/*第7位数码管显示-*/
    					ucDigShow6 = 1;		/*第6位数码管显示1*/
    					ucDigShow5 = 10;	/*第5位数码管不显示*/
    /* 
    * 此处为什么要多加4个中间过渡变量ucTemp?是因为uiSetData1分解数据的时候
    * 需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时
    * 了一会。我们的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,
    * 当uiSetData1还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致
    * 显示的数据瞬间产生不完整,影响显示效果。因此,为了把需要显示的数据过渡最快,
    * 所以采取了先分解,再过渡显示的方法。
    */
    					/*先分解数据*/
    					ucTemp4 = uiSetData1 / 1000;
    					ucTemp3 = uiSetData1 % 1000 / 100;
    					ucTemp2 = uiSetData1 % 100 / 10;
    					ucTemp1 = uiSetData1 %10;
    					/*再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好*/
    					/*就是在这里略作修改,把高位为0的去掉不显示。*/
    					if(uiSetData1 < 1000)
    					{
    						ucDigShow4 = 10;
    					}
    					else
    					{
    						ucDigShow4 = ucTemp4;	/*第4位数码管要显示的内容*/						
    					}
    					if(uiSetData1 < 100)
    					{
    						ucDigShow3 = 10;
    					}
    					else
    					{
    						ucDigShow3 = ucTemp3;	/*第3位数码管要显示的内容*/						
    					}
    					if(uiSetData1 < 10)
    					{
    						ucDigShow2 = 10;
    					}
    					else
    					{
    						ucDigShow2 = ucTemp2;	/*第2位数码管要显示的内容*/						
    					}
    					ucDigShow1 = ucTemp1;	/*第1位数码管要显示的内容*/					
    				}
    			break;
    				
    			case 2:	/*显示P--2窗口的数据*/
    				if(ucWd2Update == 1)	/*窗口2要全部更新显示*/
    				{
    					ucWd2Update = 0;	/*及时清零标志,避免一直进来扫描*/
    					ucDigShow8 = 12;	/*第8位数码管显示P*/
    					ucDigShow7 = 11;	/*第7位数码管显示-*/
    					ucDigShow6 = 2;		/*第6位数码管显示2*/
    					ucDigShow5 = 10;	/*第5位数码管不显示*/
    					/*先分解数据*/
    					ucTemp4 = uiSetData2 / 1000;
    					ucTemp3 = uiSetData2 % 1000 / 100;
    					ucTemp2 = uiSetData2 % 100 / 10;
    					ucTemp1 = uiSetData2 %10;
    					if(uiSetData2 < 1000)
    					{
    						ucDigShow4 = 10;
    					}
    					else
    					{
    						ucDigShow4 = ucTemp4;	/*第4位数码管要显示的内容*/						
    					}
    					if(uiSetData2 < 100)
    					{
    						ucDigShow3 = 10;
    					}
    					else
    					{
    						ucDigShow3 = ucTemp3;	/*第3位数码管要显示的内容*/						
    					}
    					if(uiSetData2 < 10)
    					{
    						ucDigShow2 = 10;
    					}
    					else
    					{
    						ucDigShow2 = ucTemp2;	/*第2位数码管要显示的内容*/						
    					}
    					ucDigShow1 = ucTemp1;	/*第1位数码管要显示的内容*/									
    				}
    			break;	
    
    			case 3:	/*显示P--3窗口的数据*/
    				if(ucWd3Update == 1)	/*窗口3要全部更新显示*/
    				{
    					ucWd3Update = 0;	/*及时清零标志,避免一直进来扫描*/
    					ucDigShow8 = 12;	/*第8位数码管显示P*/
    					ucDigShow7 = 11;	/*第7位数码管显示-*/
    					ucDigShow6 = 3;		/*第6位数码管显示3*/
    					ucDigShow5 = 10;	/*第5位数码管不显示*/
    					/*先分解数据*/
    					ucTemp4 = uiSetData3 / 1000;
    					ucTemp3 = uiSetData3 % 1000 / 100;
    					ucTemp2 = uiSetData3 % 100 / 10;
    					ucTemp1 = uiSetData3 %10;
    					if(uiSetData3 < 1000)
    					{
    						ucDigShow4 = 10;
    					}
    					else
    					{
    						ucDigShow4 = ucTemp4;	/*第4位数码管要显示的内容*/						
    					}
    					if(uiSetData3 < 100)
    					{
    						ucDigShow3 = 10;
    					}
    					else
    					{
    						ucDigShow3 = ucTemp3;	/*第3位数码管要显示的内容*/						
    					}
    					if(uiSetData3 < 10)
    					{
    						ucDigShow2 = 10;
    					}
    					else
    					{
    						ucDigShow2 = ucTemp2;	/*第2位数码管要显示的内容*/						
    					}
    					ucDigShow1 = ucTemp1;	/*第1位数码管要显示的内容*/							
    				}
    			break;			
    
    			case 4:	/*显示P--4窗口的数据*/
    				if(ucWd4Update == 1)	/*窗口4要全部更新显示*/
    				{
    					ucWd4Update = 0;	/*及时清零标志,避免一直进来扫描*/
    					ucDigShow8 = 12;	/*第8位数码管显示P*/
    					ucDigShow7 = 11;	/*第7位数码管显示-*/
    					ucDigShow6 = 4;		/*第6位数码管显示4*/
    					ucDigShow5 = 10;	/*第5位数码管不显示*/
    					/*先分解数据*/
    					ucTemp4 = uiSetData4 / 1000;
    					ucTemp3 = uiSetData4 % 1000 / 100;
    					ucTemp2 = uiSetData4 % 100 / 10;
    					ucTemp1 = uiSetData4 %10;
    					if(uiSetData4 < 1000)
    					{
    						ucDigShow4 = 10;
    					}
    					else
    					{
    						ucDigShow4 = ucTemp4;	/*第4位数码管要显示的内容*/						
    					}
    					if(uiSetData4 < 100)
    					{
    						ucDigShow3 = 10;
    					}
    					else
    					{
    						ucDigShow3 = ucTemp3;	/*第3位数码管要显示的内容*/						
    					}
    					if(uiSetData4 < 10)
    					{
    						ucDigShow2 = 10;
    					}
    					else
    					{
    						ucDigShow2 = ucTemp2;	/*第2位数码管要显示的内容*/						
    					}
    					ucDigShow1 = ucTemp1;	/*第1位数码管要显示的内容*/							
    				}
    			break;					
    		}
    }
    
    /**
    * @brief  扫描按键
    * @param  无
    * @retval 放在定时中断里
    **/
    void key_scan(void)
    {
    	if(Key_S1 == 1)	/*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/	
    	{
    		ucKeyLock1 = 0;	/*按键自锁标志清零*/
    		uiKeyTimeCnt1 = 0;	/*按键去抖动延时计数器清零*/
    	}
    	else if(ucKeyLock1 == 0)	/*有按键按下,且是第一次被按下*/
    	{
    		uiKeyTimeCnt1 ++;	/*累加定时中断次数*/
    		if(uiKeyTimeCnt1 > const_key_time1)
    		{
    			uiKeyTimeCnt1 = 0;
    			ucKeyLock1 = 1;
    			ucKeySec = 1;	/*触发1号键*/
    		}
    	}
    
    	if(Key_S2 == 1)	/*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/	
    	{
    		ucKeyLock2 = 0;	/*按键自锁标志清零*/
    		uiKeyTimeCnt2 = 0;	/*按键去抖动延时计数器清零*/
    	}
    	else if(ucKeyLock2 == 0)	/*有按键按下,且是第一次被按下*/
    	{
    		uiKeyTimeCnt2 ++;	/*累加定时中断次数*/
    		if(uiKeyTimeCnt2 > const_key_time2)
    		{
    			uiKeyTimeCnt2 = 0;
    			ucKeyLock2 = 1;
    			ucKeySec = 2;	/*触发2号键*/
    		}
    	}
    
    	if(Key_S3 == 1)	/*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/	
    	{
    		ucKeyLock3 = 0;	/*按键自锁标志清零*/
    		uiKeyTimeCnt3 = 0;	/*按键去抖动延时计数器清零*/
    	}
    	else if(ucKeyLock3 == 0)	/*有按键按下,且是第一次被按下*/
    	{
    		uiKeyTimeCnt3 ++;	/*累加定时中断次数*/
    		if(uiKeyTimeCnt3 > const_key_time3)
    		{
    			uiKeyTimeCnt3 = 0;
    			ucKeyLock3 = 1;
    			ucKeySec = 3;	/*触发3号键*/
    		}
    	}	
    }
    
    /**
    * @brief  按键服务的应用程序
    * @param  无
    * @retval 无
    **/
    void key_service(void)
    {
    	switch(ucKeySec)	/*按键服务状态切换*/
    	{
    		case 1:	/*加按键*/
    			switch(ucWd)	/*在不同的窗口下,设置不同的参数*/
    			{
    				case 1:
    					uiSetData1 ++;
    					if(uiSetData1 > 9999)
    					{
    						uiSetData1 = 9999;
    					}
    					write_eeprom_int(0, uiSetData1);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd1Update = 1;	/*窗口1更新显示*/
    					break;
    				case 2:
    					uiSetData2 ++;
    					if(uiSetData2 > 9999)
    					{
    						uiSetData2 = 9999;
    					}
    					write_eeprom_int(2, uiSetData2);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd2Update = 1;	/*窗口1更新显示*/
    					break;
    				case 3:
    					uiSetData3 ++;
    					if(uiSetData3 > 9999)
    					{
    						uiSetData3 = 9999;
    					}
    					write_eeprom_int(4, uiSetData3);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd3Update = 1;	/*窗口1更新显示*/
    					break;
    				case 4:
    					uiSetData4 ++;
    					if(uiSetData4 > 9999)
    					{
    						uiSetData4 = 9999;
    					}
    					write_eeprom_int(6, uiSetData4);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd4Update = 1;	/*窗口1更新显示*/
    					break;				
    			}
    			ucVoiceLock = 1;
    			uiVoiceCnt = const_voice_short;
    			ucVoiceLock = 0;
    			ucKeySec = 0;
    			break;
    
    		case 2:	/*减按键*/
    			switch(ucWd)	/*在不同的窗口下,设置不同的参数*/
    			{
    				case 1:
    					uiSetData1 --;
    					if(uiSetData1 > 9999)
    					{
    						uiSetData1 = 0;
    					}
    					write_eeprom_int(0, uiSetData1);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd1Update = 1;	/*窗口1更新显示*/
    					break;
    				case 2:
    					uiSetData2 --;
    					if(uiSetData2 > 9999)
    					{
    						uiSetData2 = 0;
    					}
    					write_eeprom_int(2, uiSetData2);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd2Update = 1;	/*窗口1更新显示*/
    					break;
    				case 3:
    					uiSetData3 --;
    					if(uiSetData3 > 9999)
    					{
    						uiSetData3 = 0;
    					}
    					write_eeprom_int(4, uiSetData3);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd3Update = 1;	/*窗口1更新显示*/
    					break;
    				case 4:
    					uiSetData4 --;
    					if(uiSetData4 > 9999)
    					{
    						uiSetData4 = 0;
    					}
    					write_eeprom_int(6, uiSetData4);	/*存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁*/
    					ucWd4Update = 1;	/*窗口1更新显示*/
    					break;				
    			}
    			ucVoiceLock = 1;
    			uiVoiceCnt = const_voice_short;
    			ucVoiceLock = 0;
    			ucKeySec = 0;		
    			break;
    		
    		case 3:	/*切换窗口按键*/
    			ucWd ++;	/*切换窗口*/
    			if(ucWd > 4)
    			{
    				ucWd = 1;
    			}
    			switch(ucWd)
    			{
    				case 1:
    					ucWd1Update = 1;
    					break;
    				case 2:
    					ucWd2Update = 1;
    					break;
    				case 3:
    					ucWd3Update = 1;
    					break;
    				case 4:
    					ucWd4Update = 1;
    					break;
    			}
    			ucVoiceLock = 1;
    			uiVoiceCnt = const_voice_short;
    			ucVoiceLock = 0;	
    			ucKeySec = 0;	
    			break;	
    	
    	}
    }
    
    
    /**
    * @brief  定时器0中断函数
    * @param  无
    * @retval 无
    **/
    void ISR_T0(void)	interrupt 1
    {
    	TF0 = 0;  /*清除中断标志*/
    	TR0 = 0; /*关中断*/
    	/* 
    	* 此处多增加一个原子锁,作为中断与主函数共享数据的保护
    	*/
    	if(ucVoiceLock == 0)	/*原子锁判断*/
    	{
    		if(uiVoiceCnt != 0)
    		{
    			uiVoiceCnt --;
    			BEEP = 0;
    		}
    		else
    		{
    			;
    			BEEP = 1;
    		}		
    	}
    	key_scan();
    	Display_Drive();	/*数码管字模的驱动函数*/
    	TL0 = T1MS;                     /*initial timer0 low byte*/
    	TH0 = T1MS >> 8;                /*initial timer0 high byte*/
      	TR0 = 1; /*开中断*/	
    }
    
    
    /*————————————主函数————————————*/
    /**
    * @brief  主函数
    * @param  无
    * @retval 实现LED灯闪烁
    **/
    void main()
    {
    	/*单片机初始化*/
    	Init();
    	/*延时,延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定*/
    	Delay_Long(100);
    	/*单片机外围初始化*/	
    	Init_Peripheral();
    	while(1)
    	{
    		key_service();	/*按键服务的应用程序*/   
    		Display_Service();	/*显示的窗口菜单服务程序*/        
    	}
    }
    
    

    三、仿真实现

    51单片机实现利用AT24C02进行掉电后的数据保存

     

    展开全文
  • 本篇博客主要讲授华大半导(STM32、C51等单片机均可适用)复位(以看门狗复位为例)变量数据保存的方法。 这里将用到__not_init属性,其用于变量声明,可禁止系统启动时变量的初始化,有了__not_init属性,编译器...

    目录

    1、理论

    2、实践


    1、理论

    众所周知,单片机复位后变量数值会自动初始化,以华大半导体HC32L136为例,具有 7 个复位信号来源,每个复位信号都可以让 CPU 重新运行,绝大多数寄存器会被复位到复位值,程序会从复位向量处开始执行。

    • 数字区域上电掉电复位 POR
    • 外部 Reset PAD,低电平为复位信号
    • WDT 复位
    • PCA 复位
    • LVD 低电压复位
    • Cortex-M0+ SYSRESETREQ 软件复位
    • Cortex-M0+ LOCKUP 硬件复位

    每个复位源由相应的复位标志进行指示,复位标志均由硬件置位,需要用户软件清零。

    华大半导体各区域的复位来源如下图所示:

    本篇博客主要讲授华大半导(STM32、C51等单片机均可适用)复位(以看门狗复位为例)后变量数据保存的方法。

    这里将用到__not_init属性,其用于变量声明,可禁止系统启动时变量的初始化,有了__not_init属性,编译器只给指定变量分配空间,不会再初始化。

    __not_init的两种定义方式如下所示:

        方式1:不指定存储位置,由编译器分配
        __no_init 类型 变量名;        ///< 例如:__no_init uint8_t cou_num;
        方式2:指定存储位置
        __no_init 类型 变量名 @地址;  ///< 例如:__no_init uint8_t cou_num @0x20000000;

    2、实践

    实践描述:使用__no_init属性创建一个变量cou_num,其将数据存储在SRAM中,每隔300毫秒自加1并通过串口打印输出数值,当检测到上电复位和按键复位后,变量cou_num数值置为0,在看门狗复位下变量cou_num数值不变。

    第1步:配置串口引脚、串口使能和串口中断,代码如下所示:

    ///< 串口引脚配置
    static void App_PortInit(void)
    {
        stc_gpio_cfg_t stcGpioCfg;
    
        DDL_ZERO_STRUCT(stcGpioCfg);
        ///< 使能GPIO模块时钟
        Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); 
    
        ///< 配置PA02端口为URART1_TX
        stcGpioCfg.enDir = GpioDirOut;
        Gpio_Init(GpioPortA, GpioPin2, &stcGpioCfg);
        Gpio_SetAfMode(GpioPortA, GpioPin2, GpioAf1);            
    }
    
    ///< 串口配置
    static void App_UartCfg(void)
    {
        stc_uart_cfg_t    stcCfg;
    
        DDL_ZERO_STRUCT(stcCfg);
    
        ///< 开启UART1外设时钟
        Sysctrl_SetPeripheralGate(SysctrlPeripheralUart1,TRUE);
    
        ///< UART1初始化
        stcCfg.enRunMode        = UartMskMode3;           ///< 模式3
        stcCfg.enStopBit        = UartMsk1bit;            ///< 1bit停止位
        stcCfg.enMmdorCk        = UartMskEven;            ///< 偶检验
        stcCfg.stcBaud.u32Baud  = 9600;                   ///< 波特率9600  注意误差
        stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div;        ///< 通道采样分频配置
        stcCfg.stcBaud.u32Pclk  = Sysctrl_GetPClkFreq();  ///< 获得外设时钟(PCLK)频率值
        Uart_Init(M0P_UART1, &stcCfg);                    ///< 串口初始化
    
        ///< UART1中断使能
        Uart_ClrStatus(M0P_UART1,UartTC);                 ///< 清发送请求
        Uart_EnableIrq(M0P_UART1,UartTxIrq);              ///< 使能串口发送中断
        EnableNvic(UART1_IRQn, IrqLevel3, TRUE);          ///< 系统中断使能
    }
    
    ///< UART1中断函数
    void Uart1_IRQHandler(void)
    {
        ///< UART1数据发送
        if(Uart_GetStatus(M0P_UART1, UartTC))         
        {
            ///< 清中断状态位
            Uart_ClrStatus(M0P_UART1, UartTC);  
        }
    }

    第2步:配置看门狗复位,每隔820毫秒若没有喂狗,则复位,代码如下所示:

    ///< WDT初始化配置
    static void App_WdtInit(void)
    {
        ///< 开启WDT外设时钟
        Sysctrl_SetPeripheralGate(SysctrlPeripheralWdt,TRUE);
        ///< WDT 初始化,喂狗时间:820ms
        Wdt_Init(WdtResetEn, WdtT820ms);
    }

    第3步:使用__no_init属性定义cou_num变量,将数组存储在SRAM寄存器0x20001000中,代码如下所示:

    __no_init uint8_t cou_num @ 0x20001000;

     第4步:添加上电复位源和RESET脚复位源检测,当检测到其中之一个复位的时候,cou_num置为0,代码如下所示:

    int32_t main(void)
    {
        char * data_buf = (char *)malloc(sizeof(char) * 19);
        
        ///< 串口引脚配置
        App_PortInit();
    
        ///< 串口配置
        App_UartCfg();
        
        ///< WDT初始化
        App_WdtInit();
       
        ///< 启动 WDT
        Wdt_Start();
        
        ///< 当上电复位或者RESET脚复位后cou_num为0,看门狗复位数值不变
        if((Reset_GetFlag(ResetFlagMskPor5V) == 1) || (Reset_GetFlag(ResetFlagMskRstb) == 1))
        {
          cou_num = 0;
    
          Reset_ClearFlag(ResetFlagMskPor5V);
          Reset_ClearFlag(ResetFlagMskRstb);
        }
       
        while (1)
        {
            cou_num = cou_num + 1;
            
            delay1ms(300);
            
            ///< 开启喂狗后,将不会产生复位
            //Wdt_Feed(); 
            
            sprintf(data_buf,"numerical value:%d\n",cou_num);
            
            for(int8_t i = 0;i < 19;i++)
            {
              Uart_SendDataIt(M0P_UART1,data_buf[i]); 
              delay1ms(5);
            }
        }
    }
    

    运行效果如下所示:

    可见虽然看门狗每隔820毫秒复位一次,但是cou_num数值不收影响,但是也可以看出cou_num数值中间存在丢失,例如没有打印输出数值3,主要原因是运行到此数时,恰巧看门狗复位,所以串口未来得及打印,但是不影响cou_num计数。

    展开全文
  • 基于延长射频系统中的单片机掉电时间的目的,设计了一种...通过与其他掉电保存的方式对比测试,证明该系统能够满足物料搬运行业的准则要求,在电时能够维持50 ms以上,并且能让电波形更为平滑,并减少电压抖动。
  • 在测量、控制等领域的应用中,常要求单片机内部和外部RAM中的数据在电源掉电时不丢失,重新加时,RAM中的数据能够保存完好,这就要求对单片机系统加接掉电保护电路。掉电保护通常可采用以下三种方法:一是加接不...
  • AVR单片机掉电保护

    2020-08-03 23:58:11
    我想在掉电保存数据(3个字节)到EEPROM中,用BOD掉电检测,不知怎样使用。望高手指点: 1。在BOOT区设置好BODEN,BODLEVEL,软件还要怎样设置? 2。掉电中断是否是产生复位?我的写EEPROM程序应该放在什么...
  • 单片机I方C掉电重启数据保持C语言代码!
  • 关于掉电保存数据的思考

    千次阅读 2014-07-02 19:57:22
    我是搞仪表的,基本工作简单说就是弄个人机界面把一些参数...二是掉电掉电中断,把你要保存数据保存下来。 我们以前都是走第一条路的,走的还不错。主要的问题就是不同的时间点掉电数据可能没有完全修改完,
  • 51内部EEPROM掉电保存

    2012-06-13 13:03:06
    用51单片机内部EEPROM实现掉电保存数据,不需要外部电路,如24C02等芯片,更加方便。
  • SDRAM的数据掉电后可以保存多久?

    千次阅读 2015-02-10 15:12:29
    你电脑上的内存SDRAM条取下来,多久以后,内存的数据会消失? 大多数的人认为是几秒钟.
  • E2PROM、FLASHMEMORY属于可在线修改的ROM器件,它解决了应用系统中实时参数掉电保存的难题,但这类芯片写入速度慢(ms级),擦写次数有限(万次级),有些器件擦写次数虽达百万次,对某些应用系统而言,其写入次数...
  • 应用非易失性存储芯片24C16 与单片机相连接,设计了一种基于掉电数据存储的耐压绝缘测试系统,详细说明了掉电数据存储的耐压绝缘测试系统的设计思路和硬件电路结构,编写了相应的读写程序。这种串行非易失性存储技术...
  • 三是采用EEPROM来保存数据。由于第一种方法体积大、成本高,对单片机系统来说,不宜采用。第二种方法是根据实际需要,掉电时保存一些必要的数据,使系统在电源恢复,能够继续执行程序,因而经济实用,故大量采用[1...
  • 三是采用EEPROM来保存数据。由于第一种方法体积大、成本高,对单片机系统来说,不宜采用。第二种方法是根据实际需要,掉电时保存一些必要的数据,使系统在电源恢复,能够继续执行程序,因而经济实用,故大量采用[1...
  • 单片机电后内部发生的事情

    千次阅读 2016-01-07 11:17:21
    单片机电后,如果晶振正常起震的话,cpu就会在晶振的驱动下开始工作,cpu的工作就是在每个机器周期到指定的地方提取指令,然后解析并执行,51单片机只有一个时钟源所以51单片机永远只能以一种时钟频率工作,单片机...
  • 利用51单片机作为主控芯片 利用12864液晶显示 来实现密码锁功能,具有修改密码 密码掉电保存以及温度显示等功能……
  • E2PROM、 FLASHMEMORY属于可在线修改的ROM器件,它解决了应用系统中实时参数掉电保存的难题,但这类芯片写入速度慢(ms级),擦写次数有限(万次级),有些器件擦写次数虽达百万次,对某些应用系统而言,其写入次数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,348
精华内容 3,739
关键字:

单片机掉电后保存数据