- 打卡等级:偶尔看看III
- 打卡总天数:53
- 最近打卡:2025-11-02 23:04:14
已绑定手机
中级会员
- 积分
- 241
|
发表于 2025-10-17 16:38:09
|
显示全部楼层
第二十一集 Flash模拟EEPROM,学习笔记
一. FLASH和EEPROM是什么
通常,单片机里的Flash都用于存放运行代码,在运行过程中不能改;EEPROM是用来保存用户数据,运行过程中可以改变,比如我们在程序运行中需要保存密码等参数,就需要存在EEPROM里。但现在单片机没EEPROM了,该怎么存储我们的掉电保存数据呢?
关于 IAP 技术,做过 bootloader 的想必很熟悉 (IAP全称 In Application Programming,即应用编程),和 ISP (全称 In System Programming,即系统编程)不同,ISP 一般都是通过专业的调试器或者下载器对单片机内部的 Flash 存储器进程编程(如JTAG等),而 IAP 技术是从结构上将 Flash 储存器映射分为两个或者多个分区,在一个分区中对其他分区进行编程,这个分区通常称为 bootloader。
注意事项:
IAP_TPS,22.1184MHz注意事项:
1.擦除必须整个扇区
2.只能往1写0,不能往0写1
3.数据必须按扇区写入
4.在电源稳定时操作
5.避免频繁读写
二.内部EEPROM介绍
IAP_TPS,22.1184MHz,取值23
同一时间只能有一个操作
三.内部EEPROM的简单使用
EEPROM
数据寄存器IAP_DATA,读写数据
地址寄存器IAP_ADDR(E,H,L),数据地址,高中低
命令寄存器IAP_CMD:001读,010写,011擦除扇区(512字节)
触发寄存器IAP_TRIG:触发命令,先写入5A A5,相当于解锁
控制寄存器IAP_CONRE:先写入0X80,IAPEN:0禁止,1使能。CMD_FAIL:0正确,1失败
时间控制寄存器IAP_TPS:工作频率/1M,向上进位
SWBS/SWBS2:00复位后从用户程序区执行,IAP_CONTR = 0x20
01复位后从用户系统区执行,IAP_CONTR = 0x28
1x复位后从系统ISP区执行,IAP_CONTR = 0x60
SWRST:为1触发软件复位
示例代码里也有三个非常经典的案例
任务1:实现手册的EEPROM的基本操作,并用数码管显示当前的数值。并实现每次重新开机数值+1
生成两个文件
eeprom.c和eeprom.h
在eeprom.c中输入
#include "eeprom.h"
#define MAIN_Fosc 24000000UL
#define IAP_STANDBY() IAP_CMD = 0 //IAP空闭命令(禁止)
#define IAP_READ() IAP_CMD = 1 //IAP读出命令
#define IAP_WRITE() IAP_CMD = 2 //IAP写入命令
#define IAP_ERASE() IAP_CMD = 3 //IAP擦除命令
#define IAP_ENABLE() IAP_CONTR = IAP_EN; IAP_TPS = MAIN_Fosc / 1000000
#define IAP_DISABLE() IAP_CONTR = 0; IAP_CMD = 0; IAP_TRIG = 0; IAP_ADDRE = 0xff; IAP_ADDRH = 0xff; IAP_ADDRL = 0xff
#define IAP_EN (1<<7)
#define IAP_SWBS (1<<6)
#define IAP_SWRST (1<<5)
#define IAP_CMD_FAIL (1<<4)
//========================================================================
// 函数: void DisableEEPROM(void)
// 描述:禁止EEPROM.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void DisableEEPROM(void) //禁止访问EEPROM
{
IAP_CONTR = 0; //关闭 IAP 功能
IAP_CMD = 0; //清除命令寄存器
IAP_TRIG = 0; //清除触发寄存器
IAP_ADDRE = 0xff; //将地址设置到非IAP 区域
IAP_ADDRH = 0xff; //将地址设置到非IAP 区域
IAP_ADDRL = 0xff;
}
//========================================================================
// 函数: void EEPROM_Trig(void)
// 描述: 触发EEPROM操作
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_Trig(void)
{
F0 = EA; //保存全局中断
EA = 0; //禁止中断,避免触发命令无效
IAP_TRIG = 0x5A;
IAP_TRIG = 0xA5; //先送5AH,再送A5H到IAP触发寄存器,每次都需要这样
//送完A5后,IAP命令立即被触发启动
//CPU等待IAP完成后,都会继续执行程序
_nop_(); //多级流水线的,触发后建议加四个NOP,保证IAP_DATA的数据完成准备
_nop_();
_nop_();
_nop_();
EA = F0; //恢复全局中断
}
//========================================================================
//函数: void EEPROM_SectorErase(u32 EE_address)
//描述: 擦除一个扇区
//参数: EE_address:要擦除的EEPROM的扇区中的一个字节地址
//返回: none.
//版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_SectorErase(u32 EE_address)
{
IAP_ENABLE(); //设置等待时间,允许IAP操作,送一次就够
IAP_ERASE(); //宏调用,送扇区操作命令,命令不需改变时,不需重送命令
//只有扇区擦除,没有字节擦除,512字节/扇区
//扇区中任意一个字节地址都是扇区地址
IAP_ADDRE = (u8)(EE_address >> 16); //送扇区地址高字节(仅地址需要改变时才需重送地址)
IAP_ADDRH = (u8)(EE_address >> 8); //送扇区地址中字节(仅地址需要改变时才需重送地址)
IAP_ADDRL = (u8)EE_address; //送扇区地址低字节(仅地址需要改变时才需重送地址)
EEPROM_Trig(); //触发EEPROM操作
DisableEEPROM(); //禁止EEPROM操作
}
//========================================================================
//函数: void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 lenth)
//描述:读N个字节函数
//参数: EE_address: 要读出的EEPROM地址
// DataAddress: 要读出的数据指针
// length: 要读出的数据长度
//返回: 0: 写入正确 1: 写入长度为0错误. 2: 写入数据错误
//版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 length)
{
IAP_ENABLE(); //设置等待时间,允许IAP操作,送一次就够
IAP_READ(); //送字节读命令,命令不需改变时,不需重送命令
do
{
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址要变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8); //送地址中字节(地址要变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址要变时才需重新送地址)
EEPROM_Trig(); //触发EEPROM操作
*DataAddress = IAP_DATA; //读出的数据送往
EE_address++;
DataAddress++;
}while(--length);
DisableEEPROM();
}
//========================================================================
//函数: u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
//描述:写N个字节函数
//参数: EE_address: 要写入的EEPROM的首地址.
// DataAddress: 要写入数据的指针
// length: 要写入的长度
//返回: 0:写入正确 1:写入长度为0错误 2:写入数据错误
//版本: V1.0, 2014-6-30
//========================================================================
u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length)
{
u8 i;
u16 j;
u8 *p;
if(length == 0) return 1; //长度为0错误
IAP_ENABLE(); //设置等待时间,允许IAP操作
i = length;
j = EE_address;
p = DataAddress;
IAP_WRITE(); //宏调用,送字节写命令
do
{
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址要变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8); //送地址中字节(地址要变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址要变时才需重新送地址)
IAP_DATA = *DataAddress; //送数据到IAP DATA,只有数据改变时才需重新送
EEPROM_Trig(); //触发EEPROM操作
EE_address++; //下一个地址
DataAddress++; //下一个数据
}while(--length); //直到结束
EE_address = j;
length = i;
DataAddress = p;
i = 0;
IAP_READ(); //送N个字节并比较
do
{
IAP_ADDRE = (u8)(EE_address >> 16); //送地址高字节(地址要变时才需重新送地址)
IAP_ADDRH = (u8)(EE_address >> 8); //送地址中字节(地址要变时才需重新送地址)
IAP_ADDRL = (u8)EE_address; //送地址低字节(地址要变时才需重新送地址)
EEPROM_Trig(); //´触发EEPROM操作
if(*DataAddress != IAP_DATA) //读出的数据与源数据比较
{
i = 2;
break;
}
EE_address++;
DataAddress++;
}while(--length);
DisableEEPROM();
return i;
}
#define E_ADDR 10
#define E_PWR 0XFE
u8 SYS_Run_times = 0;
//任务1:实现手册的EEPROM的基本操作,并用数码管显示当前的数值,并实现每次重新开机数值+1
void Parm_Init(void)
{
u8 dat[5]; //0:操作码 1:开机次数 2:校验码(累加校验)
EEPROM_read_n( E_ADDR,dat,3 );
if( dat[0] != E_PWR ) //如果读取的数据不为操作码,就是第一次上电,此时可以初始化
{
EEPROM_SectorErase(E_ADDR); //清空地址里的数据,擦除扇区
dat[0] = E_PWR;
dat[1] = 0;
dat[2] = (dat[0]+dat[1]);
EEPROM_write_n(E_ADDR,dat,3);
SYS_Run_times = 0;
}
else //第二次及其之后的上电
{
if( dat[2] == (u8)(dat[0]+dat[1]) ) //校验通过,数据有效
{
SYS_Run_times = dat[1];
SYS_Run_times +=1;
EEPROM_SectorErase(E_ADDR); //清空地址里的数据,擦除扇区
dat[0] = E_PWR;
dat[1] = SYS_Run_times;
dat[2] = (dat[0]+dat[1]);
EEPROM_write_n(E_ADDR,dat,3);
}
else
{
//如果数据校验出错是,执行什么
}
}
}
在eeprom.h中输入
#ifndef __EEPROM_H
#define __EEPROM_H
#include "config.h" //调用头文件
extern u8 SYS_Run_times ;
void DisableEEPROM(void); //禁止访问EEPROM
void EEPROM_Trig(void);
void EEPROM_SectorErase(u32 EE_address);
void EEPROM_read_n(u32 EE_address,u8 *DataAddress,u8 length);
u8 EEPROM_write_n(u32 EE_address,u8 *DataAddress,u8 length);
void Parm_Init(void);
#endif
在main.c中写入
#include "eeprom.h"
Parm_Init(); //在USB配置之后
SEG_Show_U32(SYS_Run_time); //在while中
在task.c中
{0,1,1, SEG_Task}, //显示数码管
编译0错0警告,下载运行,正确。
课后小练
密码锁
1.没有输入时,显示“- - - - - - - -”
2.有输入时,按下一个按键,开始按顺序写入
例如,第一个按下1,显示“1 - - - - - - -”
例如,第二个按下3,显示“1 3 - - - - - -”
3.当按下的密码为“ 1 2 3 4 5 6 7 0”时,数码管显示open的字 符,否则,还是显示“- - - - - - - -”
4.看门狗,超时1秒自动复位
5.增加开机版本号,开机显示三秒的U 1.00 版本号
6.增加手动复位,P33按钮按下时重启(方便查看版本号和清除密码)
新增:
1.开锁后长按“#” 持续3秒后进入管理员模式,可以修改密码,输入完成后再次按下保存,之后需要输入这个修改后的密码才能开锁!
|
|