陈默大将军 发表于 2026-6-2 14:03:20

stc8g1k08a-8pin的eeprom区擦写问题

我在用stc8g1k08a-8pin这款芯片做一个电路的定时控制装置,预计定时一年来控制这条线路的通断,程序上电后开始计时,期间可以断电,断电前将已经计时的数据保存到EEPROM中,这样下次上电,程序可以继续上次断电前保存的计时数据接着计时,直到总计时累积到总设定:一年,我的程序是按秒计时,每秒写入一次EEPROM,一年也就是三百多万次,我的问题是,这款芯片是否支持这么多次的EEPROM的擦写次数?还有就是如果不能,有没有在既保证芯片的使用寿命的前提下,又能够用其他方法解决?又或者是更改程序逻辑,亦或是说更换硬件芯片?

lcwswust 发表于 2026-6-2 15:06:04

建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比每秒写一次长寿。

或者每次换个地方写入,参考《重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08 》:
https://www.stcaimcu.com/forum.p ... =6019&extra=&page=1

一天的秒数应是:24*60*60=86400,也就是八万多秒,一年应有三千多万秒,不知你的“三百多万次”怎么来的,考虑一下。



陈默大将军 发表于 2026-6-2 15:28:51

lcwswust 发表于 2026-6-2 15:06
建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比 ...

好的,我研究研究,感谢。{:baoquan:}

陈默大将军 发表于 2026-6-2 15:29:25

lcwswust 发表于 2026-6-2 15:06
建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比 ...

好的,感谢{:baoquan:}

陈默大将军 发表于 2026-6-2 15:29:58

lcwswust 发表于 2026-6-2 15:06
建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比 ...

感谢,我研究研究{:baoquan:}

梁工 发表于 2026-6-2 17:21:14

EEPROM本身不能支持百万次级别的擦写次数,有两个方法可以实现:
1、“滚动擦写”,比如使用4个扇区2048字节,写一次是4个字节,则要写512次才是一个擦写周期,官方擦写次数是10万次,这样就可以等效擦写5120万次。
2、断电保存,检测到断电,就擦除写入。

wulin 发表于 2026-6-2 17:22:24

这个测试程序适用于STC8系列

//测试条件:STC8H实验板,MCU型号STC8H3K48S2
//注意:测试本示例时,需在ISP下载时将【允许低压复位(禁止低压中断)】关闭
#include <STC8H.H>
#include <intrins.h>                                //库头文件
#define uint unsigned int                         //宏定义数据类型uint
#define uchar unsigned char                 //宏定义数据类型uchar

#define IAP_ADDRESS 0x0000                        //测试地址

//顺序共阴极数码管段码表,段码a-h顺序接PX0-PX7
uchar code table[]={//共阴数码管段码"0~f-."
                0x3f,0x06,0x5b,0x4f,
                0x66,0x6d,0x7d,0x07,
                0x7f,0x6f,0x77,0x7c,
                0x39,0x5e,0x79,0x71,0x40,0x80};
uchar data dis_buf;            //缓存数组
uintnum,sec;
uchar i;
uintsign;
voidTimer0Init();               //定时器初始化声明
voidIapIdle();                  //关闭IAP/EEPROM
uchar IapRead(uint addr);         //读取EEPROM数据
voidIapProgram(uint addr, uchar dat);//写入EEPROM数据
voidIapErase(uint addr);          //擦除EEPROM扇区

//初始化单片机端口
void McuInit()
{
        P0M0 = 0xff; P0M1 = 0x00;
        P1M0 = 0x00; P1M1 = 0x00;
        P2M0 = 0x00; P2M1 = 0x00;
        P3M0 = 0x00; P3M1 = 0x00;
        P4M0 = 0x00; P4M1 = 0x00;
        P5M0 = 0x00; P5M1 = 0x00;
        P6M0 = 0x00; P6M1 = 0x00;
        P7M0 = 0x00; P7M1 = 0x00;
}
//关闭单片机端口
void McuSleep()
{
        P0M0 = 0x00; P0M1 = 0xff;
        P1M0 = 0x00; P1M1 = 0xff;
        P2M0 = 0x00; P2M1 = 0xff;
        P3M0 = 0x00; P3M1 = 0xff;
        P4M0 = 0x00; P4M1 = 0xff;
        P5M0 = 0x00; P5M1 = 0xff;
        P6M0 = 0x00; P6M1 = 0xff;
        P7M0 = 0x00; P7M1 = 0xff;
}

void main()                                                       
{
        McuInit();
        if(IapRead(IAP_ADDRESS)==0xff)//如果没有保存过数据
        {
                IapProgram(IAP_ADDRESS, 0);//扇区首地址写0
                sec=0;
                sign=1;
        }
        else
        {
//                for(i=1;i<12;i++)         //测试写10次
                for(i=1;i<511;i++)      //测试写满510个字节
                {
                        if(IapRead(IAP_ADDRESS+i)==0xff)//如果遇到没有保存数据的单元
                        {
                                sec=IapRead(IAP_ADDRESS+i-1);//读取前一个字节保存的数据
                                sign=i;         //地址缓存
                                break;            //跳出循环
                        }
                }
        }
//        if(sign==11)                  //测试写10次
        if(sign==510)               //如果写满510
        {
                IapErase(IAP_ADDRESS);    //擦除扇区
                IapProgram(IAP_ADDRESS, 0);//首地址写0
                sign=1;
        }
        PCON &= 0xDF;               //清0掉电标志
        ELVD = 1;                     //开低压中断
        EA   = 1;                     //开总中断

        Timer0Init();               //初始化定时器

        while(1)
        {
                if(TF0)                   //查询T0中断请求标志
                {               
                        TF0=0;                //T0中断请求标志清0
                        if(++num>=1000)       //1秒
                        {
                                num=0;                               
                                sec=++sec%250;
                        }
                        dis_buf=table;
                        dis_buf=table;
                        dis_buf=table;
                        P0=0x00;            //段消隐
                        P2=~(0x01<<i);      //送位码
                        P0=dis_buf;      //送段码
                        i=++i%3;            //循环计数
                }//耗时569us
        }
}

void PowerLost() interrupt 6      //低压中断
{
        EA = 0;                     //关闭总中断
        McuSleep();                   //关闭所有端口(停止所有耗电电路)
        IapProgram(IAP_ADDRESS+sign,sec);//写数据到EEPROM
        while((PCON & 0x20) != 0)   //复查低压标志
        {
                PCON &= 0xDF;             //清除低压标志
                _nop_();               
                _nop_();                  //坐等掉电
        }
        IAP_CONTR = 0x20;             //发现是误报,重启单片机,恢复正常工作
}

void Timer0Init(void)             //1毫秒@11.0592MHz
{
        AUXR |= 0x80;               //定时器时钟1T模式
        TMOD &= 0xF0;               //设置定时器模式
        TL0 = 0xCD;                   //设置定时初始值
        TH0 = 0xD4;                   //设置定时初始值
        TF0 = 0;                      //清除TF0标志
        TR0 = 1;                      //定时器0开始计时
}
//关闭IAP/EEPROM
void IapIdle()
{
    IAP_CONTR = 0;                //关闭IAP功能
    IAP_CMD = 0;                  //清除命令寄存器
    IAP_TRIG = 0;               //清除触发寄存器
    IAP_ADDRH = 0x80;             //将地址设置到非IAP区域
    IAP_ADDRL = 0;
}
//读取EEPROM数据
uchar IapRead(uint addr)
{
    uchar dat;

    IAP_CONTR = 0x80;             //使能IAP
    IAP_TPS = 12;               //设置等待参数12MHz
    IAP_CMD = 1;                  //设置IAP读命令
    IAP_ADDRL = addr;             //设置IAP低地址
    IAP_ADDRH = addr >> 8;      //设置IAP高地址
    IAP_TRIG = 0x5a;            //写触发命令(0x5a)
    IAP_TRIG = 0xa5;            //写触发命令(0xa5)
    _nop_();
    dat = IAP_DATA;               //读IAP数据
    IapIdle();                  //关闭IAP功能

    return dat;
}
//写入EEPROM数据
void IapProgram(uint addr, uchar dat)
{
    IAP_CONTR = 0x80;             //使能IAP
    IAP_TPS = 12;               //设置等待参数12MHz
    IAP_CMD = 2;                  //设置IAP写命令
    IAP_ADDRL = addr;             //设置IAP低地址
    IAP_ADDRH = addr >> 8;      //设置IAP高地址
    IAP_DATA = dat;               //写IAP数据
    IAP_TRIG = 0x5a;            //写触发命令(0x5a)
    IAP_TRIG = 0xa5;            //写触发命令(0xa5)
    _nop_();
    IapIdle();                  //关闭IAP功能
}

void IapErase(uint addr)
{
    IAP_CONTR = 0x80;             //使能IAP
    IAP_TPS = 12;               //设置等待参数12MHz
    IAP_CMD = 3;                  //设置IAP擦除命令
    IAP_ADDRL = addr;             //设置IAP低地址
    IAP_ADDRH = addr >> 8;      //设置IAP高地址
    IAP_TRIG = 0x5a;            //写触发命令(0x5a)
    IAP_TRIG = 0xa5;            //写触发命令(0xa5)
    _nop_();                      //
    IapIdle();                  //关闭IAP功能
}



陈默大将军 发表于 2026-6-5 17:50:32

我尝试采用LVD低压检测,在断电前一刻将定时的数据进行保存,虽然实现了,随时断电再上电都能够接着计时,但是我将定时时间改的久一点,却发现在程序运行途中,会发生继电器突然关闭,但计时还在继续的情况,这是什么原因造成的?就好像电路被干扰了,不知道是程序方面原因还是硬件方面,我硬件方面采用USB转TTL给单片机供5V电,继电器外部12V供电,然后再用一根线连接二者供电的GND,这样就能够通过STC8G1K08A-8PIN来控制继电器1从而控制这条线路的输出。但现在就是测试时它会继电器突然断开,而计时仍在继续。这是我的代码:
#include <STC8G.H>
#include <INTRINS.H>

// ========== 配置参数 ==========
#define TOTAL_TIMEOUT   36000UL
#define DELAY_START   5
#define PROGRAM_VER   0x01

#define EE_BASE         0x0000
#define FLAG_VALID      0xA5

#define ST_WAIT         0
#define ST_RUN          1
#define ST_DONE         2

sbit RELAY = P5^5;

unsigned char sys_state;
unsigned long total_sec;
unsigned intwait_cnt;
unsigned long ee_last_sec;
bit sector_erased;
bit lvd_triggered;

// ========== 基础串口函数 ==========
void UartChar(unsigned char c)
{
    SBUF = c;
    while(!TI);
    TI = 0;
}

void UartStr(const char *str)
{
    while(*str) UartChar(*str++);
}

void UartNum(unsigned long n)
{
    unsigned char buf, i = 0, j;
    if(n == 0) { UartChar('0'); return; }
    while(n > 0) { buf = '0' + (n % 10); n /= 10; }
    for(j = i; j > 0; j--) UartChar(buf);
}

// ========== IAP底层驱动 ==========
void IAP_Idle(void)
{
    IAP_CONTR = 0; IAP_CMD = 0; IAP_TRIG = 0;
    IAP_ADDRH = 0x80; IAP_ADDRL = 0;
}

unsigned char IAP_Read(int addr)
{
    unsigned char dat;
    IAP_TPS = 11;
    IAP_CONTR = 0x80; IAP_CMD = 1;
    IAP_ADDRL = addr & 0xFF;
    IAP_ADDRH = (addr >> 8) & 0xFF;
    IAP_TRIG = 0x5A; IAP_TRIG = 0xA5;
    _nop_(); _nop_();
    dat = IAP_DATA;
    IAP_Idle();
    return dat;
}

void IAP_Erase(int addr)
{
    IAP_TPS = 11;
    IAP_CONTR = 0x80; IAP_CMD = 3;
    IAP_ADDRL = addr & 0xFF;
    IAP_ADDRH = (addr >> 8) & 0xFF;
    IAP_TRIG = 0x5A; IAP_TRIG = 0xA5;
    _nop_(); _nop_();
    IAP_Idle();
}

void IAP_Write(int addr, unsigned char dat)
{
    IAP_TPS = 11;
    IAP_CONTR = 0x80; IAP_CMD = 2;
    IAP_ADDRL = addr & 0xFF;
    IAP_ADDRH = (addr >> 8) & 0xFF;
    IAP_DATA = dat;
    IAP_TRIG = 0x5A; IAP_TRIG = 0xA5;
    _nop_(); _nop_();
    IAP_Idle();
}

// ========== EEPROM: 主计时数据 ==========
unsigned char CalcChk(unsigned long s)
{
    unsigned char c = PROGRAM_VER ^ FLAG_VALID;
    c ^= (unsigned char)s;
    c ^= (unsigned char)(s >> 8);
    c ^= (unsigned char)(s >> 16);
    c ^= (unsigned char)(s >> 24);
    return c;
}

unsigned long EE_Read(void)
{
    unsigned long s;
    unsigned char v, f, c;

    v = IAP_Read(EE_BASE);
    f = IAP_Read(EE_BASE + 1);
    s= (unsigned long)IAP_Read(EE_BASE + 2);
    s |= (unsigned long)IAP_Read(EE_BASE + 3) << 8;
    s |= (unsigned long)IAP_Read(EE_BASE + 4) << 16;
    s |= (unsigned long)IAP_Read(EE_BASE + 5) << 24;
    c = IAP_Read(EE_BASE + 6);

    if(v != PROGRAM_VER || f != FLAG_VALID || c != CalcChk(s) || s > TOTAL_TIMEOUT)
      return 0xFFFFFFFF;

    ee_last_sec = s;
    return s;
}

void EE_Write_Fast(unsigned long s)
{
    IAP_Write(EE_BASE, PROGRAM_VER);
    IAP_Write(EE_BASE + 1, FLAG_VALID);
    IAP_Write(EE_BASE + 2, (unsigned char)s);
    IAP_Write(EE_BASE + 3, (unsigned char)(s >> 8));
    IAP_Write(EE_BASE + 4, (unsigned char)(s >> 16));
    IAP_Write(EE_BASE + 5, (unsigned char)(s >> 24));
    IAP_Write(EE_BASE + 6, CalcChk(s));
}

// ========== 系统初始化 ==========
void SysInit(void)
{
    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;

    SCON = 0x50;
    AUXR = 0x00;
    TMOD = 0x20;
    TH1 = 0xFD; TL1 = 0xFD; TR1 = 1;
    ES = 1;

    TMOD &= 0xF0; TMOD |= 0x01;
    AUXR &= 0x7F;
    TL0 = 0x00; TH0 = 0x4C;
    TF0 = 0; ET0 = 1; EA = 1;
}

// ========== 主函数 ==========
void main(void)
{
    unsigned long loaded_sec, last_sec;

    SysInit();
    RELAY = 0;
    sys_state = ST_WAIT;
    wait_cnt = 0;
    last_sec = 0;
    sector_erased = 0;
    lvd_triggered = 0;

    // 初始化低压检测
    PCON &= 0xDF;
    ELVD = 1;
    EA = 1;

    // 读取主计时
    loaded_sec = EE_Read();
    if(loaded_sec == 0xFFFFFFFF)
    {
      UartStr("\r\n=== Cherokee Timer ===\r\n");
      UartStr(" New chip.\r\n");
      IAP_Erase(EE_BASE);
      EE_Write_Fast(0);
      loaded_sec = 0;
    }

    total_sec = loaded_sec;

    UartStr("\r\n=== Cherokee Timer ===\r\n");
    UartStr(" ");
    UartNum(total_sec);
    UartStr("s\r\n");

    if(total_sec >= TOTAL_TIMEOUT)
    {
      sys_state = ST_DONE;
      RELAY = 0;
      UartStr("\r\n");
    }
    else
    {
      sys_state = ST_WAIT;
      RELAY = 0;
      UartStr("\r\n");
      TF0 = 0; TR0 = 1;
    }

    // 主循环
    while(1)
    {
      // 主循环守护:如果运行时继电器意外断开,立刻重新吸合
      if(sys_state == ST_RUN && RELAY == 0)
      {
            RELAY = 1;
      }

      // 如果LVD被触发过且系统还在运行,说明是干扰,重新擦除扇区
      if(lvd_triggered && sys_state == ST_RUN)
      {
            lvd_triggered = 0;
            sector_erased = 0;
      }

      // 提前擦除扇区,为掉电时的快速写入做准备
      if(sys_state == ST_RUN && sector_erased == 0)
      {
            IAP_Erase(EE_BASE);
            sector_erased = 1;
      }

      // 每秒输出计时信息
      if(sys_state == ST_RUN && total_sec != last_sec)
      {
            last_sec = total_sec;
            UartStr("T:");
            UartNum(total_sec);
            UartStr("/");
            UartNum(TOTAL_TIMEOUT);
            UartStr(" R:");
            UartNum(TOTAL_TIMEOUT - total_sec);
            UartStr("s\r\n");
      }
    }
}

// ========== 定时器0中断(50ms) ==========
void T0_ISR(void) interrupt 1
{
    static unsigned char tick = 0;

    TL0 = 0x00; TH0 = 0x4C;
    tick++;
    if(tick < 20) return;
    tick = 0;

    if(sys_state == ST_WAIT)
    {
      wait_cnt++;
      if(wait_cnt >= DELAY_START)
      {
            sys_state = ST_RUN;
            RELAY = 1;
            UartStr("\r\n");
      }
    }
    else if(sys_state == ST_RUN)
    {
      total_sec++;
      if(total_sec >= TOTAL_TIMEOUT)
      {
            sys_state = ST_DONE;
            RELAY = 0;
            TR0 = 0;
            IAP_Erase(EE_BASE);
            EE_Write_Fast(total_sec);
            UartStr("\r\n");
      }
    }
}

// ========== 低压检测中断(掉电保护) ==========
void LVD_ISR(void) interrupt 6
{
    unsigned char i;

    RELAY = 1;

    // 冷静期确认:连续3次检测到低压标志才认为是真掉电
    for(i = 0; i < 3; i++)
    {
      if(!(PCON & 0x20))
      {
            // 低压标志消失,说明是干扰
            lvd_triggered = 1;
            PCON &= 0xDF;
            EA = 1;
            return;
      }
      PCON &= 0xDF;
    }

    // 确认是真掉电,执行紧急保存
    EA = 0;
    if(sys_state == ST_RUN)
    {
      EE_Write_Fast(total_sec);
    }

    // 等待彻底掉电或电压恢复
    while((PCON & 0x20) != 0)
    {
      PCON &= 0xDF;
      _nop_(); _nop_();
    }
    IAP_CONTR = 0x20;
}

// ========== 串口中断 ==========
void UART_ISR(void) interrupt 4
{
    if(RI) RI = 0;
    if(TI) TI = 0;
}
虽然增加了一些保护机制,但还是没用,请大佬们帮我看看是问题出在了哪里?是程序上还是硬件上需要改进,我询问AI,它说可能继电器线圈出现电磁干扰导致触发LVD低压检测,是让我在单片机VCC引脚和GND引脚之间增加电容来排除电路中电压异常。求大佬们多指点指点,感谢。{:baoquan:}

神农鼎 发表于 2026-6-22 23:12:26



EEPROM的使用 请 配合比较器做掉电检测 | 唯一正解,电力系统标准应用 - EEPROM/DataFlash 国芯人工智能技术交流网站 - AI32位8051交流社区
页: [1]
查看完整版本: stc8g1k08a-8pin的eeprom区擦写问题