最近再用stc15f2K60s2做项目,其中用到了stc内部的EEPROM操作(大小1K,两个扇区,详细如上图),翻看其器件手册借鉴其例程写了一个测试程序。但是发现了一个问题,在第一次测试程序时好用,再在原地址重新写别的数据时就不好用了。因为之前没怎么用过EEPROM,所以使我比较迷惑。
当我改变其写数据的地址,再写数据时,发现好用,但是再次在这个地址写其他数据竟然又不好使了。我就开始考虑是不是EEPROM每个地址数据只能写入一次(那就太悲催了,写错一次就要换一块单片机),经过查阅资料发现并非如此(放下心来)。
经过多次测试发现,每次想向已有数据的地址写其他数据时需清除此地址的数据后才可再次写入。然而stc15f2K60s2只有扇区数据清除操作,没有地址数据清除指令,所以每次写数据时就应将整个扇区的数据清除,加上stc15f2K60s2只有两个扇区,这就产生了另一个令人头疼的问题:当此扇区存有几个或多个不同数据,而只想改变其中一个的数据时,就要把整个扇区的数据清除,相应的其他数据也就丢失,需重新写入。
为了解决此问题,许多大神就想出来了许多的解决办法,最常见的有:
1.掉电瞬间保存数据(最优)
在单片机上电时,先读取EEPROM内的数据,并保存下来,再清除扇区数据,程序执行期间不向EEPROM写数据,只改变保存的。当单片机掉电时,通过相应的硬件手段,保证单片机再保持短暂的有电状态,再此短暂的时间内单片机将需要保存的数据存入EEPROM内,再掉电。
2.实时保存(不建议用)
在单片机运行期间就不停的执行扇区擦除,重新写入的操作
最后贴上我根据stc官方手册整理的EEPROM驱动代码
EEPROM.c
#include "stc15f2k60s2.h"
#include "intrins.h"
#include "eeprom.h"
/*************************************此文件只针对stc15f2k60s2编写,其他型号不通用**********************************************************/
#define CMD_IDLE 0 //空闲模式
#define CMD_READ 1 //IAP字节读命令
#define CMD_PROGRAM 2 //IAP字节编程命令
#define CMD_ERASE 3 //IAP扇区擦除命令
//#define ENABLE_IAP 0x80 //如果系统时钟<30MHz选这个
//#define ENABLE_IAP 0x81 //如果系统时钟<24MHz选这个
#define ENABLE_IAP 0x82 //如果系统时钟<20MHz选这个
//#define ENABLE_IAP 0x83 //如果系统时钟<12MHz选这个
//#define ENABLE_IAP 0x84 //如果系统时钟<6MHz选这个
//#define ENABLE_IAP 0x85 //如果系统时钟<3MHz选这个
//#define ENABLE_IAP 0x86 //如果系统时钟<2MHz选这个
//#define ENABLE_IAP 0x87 //如果系统时钟<1MHz选这个
//关闭IAP函数(禁止对EEPROM操作)
void IapIdle(void)
{
IAP_CONTR = 0 ; //关闭IAP功能
IAP_CMD = 0 ; //清除命令寄存器
IAP_TRIG = 0 ; //清除触发寄存器
IAP_ADDRH = 0x80 ; //将地址设置到非IAP地址区域
IAP_ADDRL = 0 ;
}
//从ISP/IAP/EEPROM区域读取1字节
unsigned char IapReadByte(unsigned int addr)
{
unsigned char dat;
IAP_CONTR = ENABLE_IAP; //使能IAP
IAP_CMD = CMD_READ; //设置IAP命令为“读”
IAP_ADDRL = addr ; //设置IAP低位地址
IAP_ADDRH = addr>>8 ; //设置IAP高位地址
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)[触发命令必须先送0x5a 再送0xa5才能成功触发]
_nop_(); //等待ISP/IAP/EEPROM操作完成
dat = IAP_DATA; //读取ISP/IAP/EEPROM数据
IapIdle(); //关闭IAP功能
return dat;
}
//写1字节数据到从ISP/IAP/EEPROM区域
void IapProgramByte(unsigned int addr,unsigned char dat)
{
IAP_CONTR = ENABLE_IAP; //使能IAP
IAP_CMD = CMD_PROGRAM; //设置IAP命令为“写”
IAP_ADDRL = addr ; //设置IAP低位地址
IAP_ADDRH = addr>>8 ; //设置IAP高位地址
IAP_DATA = dat; //写ISP/IAP/EEPROM数据
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)[触发命令必须先送0x5a 再送0xa5才能成功触发]
_nop_(); //等待ISP/IAP/EEPROM操作完成
IapIdle(); //关闭IAP功能
}
//扇区擦除(使用擦除扇区命令擦除整个扇区,stc15f2k60s2总共两个扇区,0x0000-0x01FF和0x0200-0x03FF,故addr=0x0000 或 0x0200)
//注意!在扇区地址存有数据后不可再次向此地址写数据,需全部清空所在扇区数据才可重新向此地址写入数据
void IapEraseSector(unsigned int addr)
{
IAP_CONTR = ENABLE_IAP; //使能IAP
IAP_CMD = CMD_ERASE; //设置IAP命令为“擦除”
IAP_ADDRL = addr ; //设置IAP低位地址
IAP_ADDRH = addr>>8 ; //设置IAP高位地址
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)[触发命令必须先送0x5a 再送0xa5才能成功触发]
_nop_(); //等待ISP/IAP/EEPROM操作完成
IapIdle(); //关闭IAP功能
}
EEPROM.h
#ifndef __EEPROM__
#define __EEPROM__
void IapIdle(void); //关闭IAP函数(禁止对EEPROM操作)
unsigned char IapReadByte(unsigned int addr); //从ISP/IAP/EEPROM区域读取1字节
void IapProgramByte(unsigned int addr,unsigned char dat); //写1字节数据到从ISP/IAP/EEPROM区域
void IapEraseSector(unsigned int addr); //扇区擦除
#endif