stc8g1k08a-8pin的eeprom区擦写问题
我在用stc8g1k08a-8pin这款芯片做一个电路的定时控制装置,预计定时一年来控制这条线路的通断,程序上电后开始计时,期间可以断电,断电前将已经计时的数据保存到EEPROM中,这样下次上电,程序可以继续上次断电前保存的计时数据接着计时,直到总计时累积到总设定:一年,我的程序是按秒计时,每秒写入一次EEPROM,一年也就是三百多万次,我的问题是,这款芯片是否支持这么多次的EEPROM的擦写次数?还有就是如果不能,有没有在既保证芯片的使用寿命的前提下,又能够用其他方法解决?又或者是更改程序逻辑,亦或是说更换硬件芯片?建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比每秒写一次长寿。
或者每次换个地方写入,参考《重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08 》:
https://www.stcaimcu.com/forum.p ... =6019&extra=&page=1
一天的秒数应是:24*60*60=86400,也就是八万多秒,一年应有三千多万秒,不知你的“三百多万次”怎么来的,考虑一下。
lcwswust 发表于 2026-6-2 15:06
建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比 ...
好的,我研究研究,感谢。{:baoquan:} lcwswust 发表于 2026-6-2 15:06
建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比 ...
好的,感谢{:baoquan:} lcwswust 发表于 2026-6-2 15:06
建议在掉电时写一次就好,可利用LVD低压检测中断或自带比较器进行掉电检测。
哪怕是十秒或一分钟写一次也比 ...
感谢,我研究研究{:baoquan:} EEPROM本身不能支持百万次级别的擦写次数,有两个方法可以实现:
1、“滚动擦写”,比如使用4个扇区2048字节,写一次是4个字节,则要写512次才是一个擦写周期,官方擦写次数是10万次,这样就可以等效擦写5120万次。
2、断电保存,检测到断电,就擦除写入。 这个测试程序适用于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功能
}
我尝试采用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:}
EEPROM的使用 请 配合比较器做掉电检测 | 唯一正解,电力系统标准应用 - EEPROM/DataFlash 国芯人工智能技术交流网站 - AI32位8051交流社区
页:
[1]