ESP8266 non-os⑴ Flash掉电记录数据读写

ESP8266 non-os SDK为3.1.0

掉电数据保存是绝大多数单片机开发绕不开的,先把方法记录下来,方便查找

Flash布局介绍

在这里插入图片描述

⽤户可以通过修改 ESP8266_NONOS_SDK/ld/eagle.app.v6.ld ⽂件来改变
eagle.irom0text.bin 的上限值,即修改⽂件中 irom0_0_seg 参数的 len 字段,如图 4-2 中红⾊标示。
不同版本 SDK 中 irom0.text ⽂件的地址也不同。⽤户必须查阅 eagle.app.v6.ld ⽂件,确保将 eagle.irom0.text.bin 下载到正确的地址。图 4-2 中蓝⾊标示即为
eagle.irom0.text.bin 的地址。
ESP8266 ⽬前系统程序区最⼤⽀持 1024 KB
在这里插入图片描述
在这里插入图片描述

扇区地址计算方法

ESP8266-12F使用的外部存储芯片为w25q32有32Mbit,容量4M。
esp8266-01s使用的是一个w25q8的存储芯片,也就是8Mbit,容量1M。
在这里插入图片描述
在ESP8266-12F里w25q32把4M容量分为了64块, 每一块又分为16个扇区, 而每个扇区占4K大小,每1K等于1204字节。由此可计算到,w25q32有 32Mbit / 8 * 1024 / 16 / 4 = 64 块 ,有64 * 16 = 1024 个扇区.
扇区号由0开始计数,所以最高为1023号扇区。

在这里插入图片描述
ESP8266-12F的扇区地址计算方法:
eagle.flash.bin 位于扇区0          地址0x00000
eagle.irom0text.bin 位于扇区16       地址0x10000
blank.bin 位于扇区1022          地址0x3FE000
esp_init_data_default.bin位于扇区1020  地址0x3FC000
4M容量的十六进制3FB000地址转换为十进制为:4173824
所在扇区为:4173824/4/1024= 1019
4M容量的十六进制3FC000地址转换为十进制为:4177920
所在扇区为:4177920/4/1024= 1020
4M容量的十六进制3FE000地址转换为十进制为:4186112
所在扇区为:4186112/4/1024= 1022

Flash操作

ESP8266-12F的Flash操作:
下列5个扇区不能占用!!!!
eagle.flash.bin 位于扇区0          地址0x00000
eagle.irom0text.bin 位于扇区16       地址0x10000
blank.bin 位于扇区1022          地址0x3FE000
esp_init_data_default.bin位于扇区1020  地址0x3FC000
RF_CAL 参数 位于扇区1019       地址0x3FB000

假如用户eagle.irom0text.bin程序小于等于480K,那么用户存储数据的安全区域起始地址为 :
1741024+4801024=561152 137扇区
把561152字节位置转换为十六进制地址0x89000
储数据的安全区域结束地址为 :1018扇区
1018
4*1024=4169728
把4169728字节位置转换为十六进制地址0x3FA000
用户存储数据的安全区域为:137扇区到1018扇区。

Flash掉电记录数据读写操作(非保护模式)

/**Flash 写操作 */
#define sec 137  //扇区号
uint32 value;
//定义数组addr_case1
uint8* addr_case1 = (uint8*)&value;
	addr_case1[0] = 10;
	addr_case1[1] = 11;
	//擦除要写入的Flash扇区
	spi_flash_erase_sector(sec);
	//写入数据,sec*4*1024就是写入起始地址,就是具体的字节地址
	spi_flash_write(sec*4*1024, (uint32*)addr_case1, sizeof(addr_case1));
	//打印
	os_printf("@xie_ru@:%d%d\r\n", addr_case1[0], addr_case1[1]);

/**Flash 读操作 */
#define sec 137  //扇区号
#define sec_pianyi 0 //偏移量
uint32 value;
//定义数组addr_case1
uint8* addr_case1 = (uint8*)&value;
	//读取flash数据,sec*4*1024就是读取起始地址,就是具体的字节地址
	spi_flash_read(sec*4*1024, (uint32*)addr_case1, sizeof(addr_case1));
	//打印
	os_printf("@du_qu@:%d%d\r\n", addr_case1[0], addr_case1[1]);

1个扇区可以储存4Kb=4*1024=4096字节数据,一般而言:
int 占据的内存大小 是4 个byte(字节)
short 占据的内存大小是2 个byte
long占据的内存大小是4 个byte
float占据的内存大小是4 个byte
double占据的内存大小是8 个byte
char占据的内存大小是1 个byte

Flash掉电记录数据读写操作(保护模式)

Espressif Flash 读写保护示例,使用 三个 sector(扇区)实现(每 sector 4KB),提供 4KB 的可靠存储空间。 将 sector 1 和 sector 2 作为数据 sector,轮流读写,始终分别存放“本次”数据和“前一次”数据, 确保了至少有一份数据存储安全;sector 3 作为 flag sector,标志最新的数据存储 sector。
在这里插入图片描述
读写过程如下:

  1. 初始上电时,数据存储在 sector 2 中,从 sector 2 中将数据读到 RAM。

  2. 第一次写数据时,将数据写入 sector 1。此时若突然掉电,sector 1写入失败,sector 2 & 3数据未改变;重新上电时,仍是从 sector 2 中 读取数据,不影响使用。

  3. 改写 sector 3,将标志置为 0,表示数据存于 sector 1。此时若突然掉电,sector 3 写入失败,sector 1 & 2 均存有一份完整数据;重新上电时,因 sector 3 无有效 flag,默认从 sector 2 中读取数据,则仍能正常使用,只是未能包含掉电前对 sector 1 写入的数据。

  4. 再一次写数据时,先从 sector 3 读取 flag,若 flag 为0,则上次数据存于 sector 1,此次应将数据写入 sector 2;若 flag 为非 0,则认为上次数据存于 sector 2,此次应将数据写入 sector 1。此时若写数据出错,请参考步骤 2、 3的说明,同理。

  5. 写入 sector 1(或 sector 2)完成后,才会写 sector 3,重置 flag。注意:只有数据扇区(sector 1或 sector 2)写完之后,才会写 flag sector(sector 3),这样即使 flag sector 写入出错,两个数据扇区都已存有完整数据内容,目前默认会读取 sector 2。

/**** 这是通过TCP连接发送命令控制读写 ****/
uint32 value;
    /**保护写 数据1***/
   if (strcmp(pdata, "a0012") == 0) {
		os_printf("\nok  012\n");
		uint8* addr_case1 = (uint8*)&value;
		addr_case1[0] = 221;
		addr_case1[1] = 145;
		addr_case1[2] = 108;
		addr_case1[3] = 26;
		//写入数据,十六进制0x8c=140,表示140扇区
		system_param_save_with_protect(0x8C, (uint32*)addr_case1, sizeof(addr_case1));
		os_printf("@@@@@@xie_ru@@@@@:%d %d %d %d\r\n", addr_case1[0], addr_case1[1], addr_case1[2], addr_case1[3]);
	}
	/**保护写 数据2***/
	if (strcmp(pdata, "a0013") == 0) {
		uint8* addr_case1 = (uint8*)&value;
		addr_case1[0] = 223;
		addr_case1[1] = 95;
		addr_case1[2] = 108;
		addr_case1[3] = 26;
		//写入数据,十六进制0x8c=140,表示140扇区
		system_param_save_with_protect(0x8C, (uint32*)addr_case1, sizeof(addr_case1));
		os_printf("@@@@@@xie_ru@@@@@:%d %d %d %d\r\n", addr_case1[0], addr_case1[1], addr_case1[2], addr_case1[3]);
	}
	/**保护读(与保护写对应) 数据***/
	if (strcmp(pdata, "a0014") == 0){
	    //读取数据,十六进制0x8c=140,表示140扇区,0 是需读取数据,在 sector 中的偏移地址
		system_param_load(0x8C, 0, (uint32*)addr_case1, sizeof(addr_case1));//读取flash值
		os_printf("@du_qu@:%d %d %d %d\r\n", addr_case1[0], addr_case1[1], addr_case1[2], addr_case1[3]);
	}
	/**用常规读取方式,读取140扇区的数据***/
	if (strcmp(pdata, "a0015") == 0){
			spi_flash_read(140*4096, (uint32*)addr_case1, sizeof(addr_case1));//读取flash值
			os_printf("@du_qu@:%d %d %d %d\r\n", addr_case1[0], addr_case1[1], addr_case1[2], addr_case1[3]);
		}
	/**用常规读取方式,读取141扇区的数据***/	
	if (strcmp(pdata, "a0016") == 0){
		spi_flash_read(141*4096, (uint32*)addr_case1, sizeof(addr_case1));//读取flash值
		os_printf("@du_qu@:%d %d %d %d\r\n", addr_case1[0], addr_case1[1], addr_case1[2], addr_case1[3]);
	}

经测验,在写入2次数据后,140,141都对应存在写入的数据1和数据2。用system_param_load读取数据,它会根据142扇区数据自动读取140或141的数据。

Flash掉电记录数据读写结构体

//Flash读写定义变量
/****************** struct 创建结构体 *******************************************************************
struct du_xie_shu_ju_01 {
	uint8 *zhang_hao[32];  //*zhang_hao[32]的指针变量(输入字符串需要)[]里需要是4的倍数,表示多少字节
	uint8 *mi_ma[64];
	uint32 id[4];
} shuju_zu_01 ;

struct du_xie_shu_ju_01 shuju_zu_02;//给结构体一个变量名shuju_zu_02,一般用在具体使用的时候

struct 关键字
du_xie_shu_ju_01 结构体名
shuju_zu_01 结构体变量1
shuju_zu_02 结构体变量2
 结构体成员的获取   结构体变量名.成员名;

 记住:指针用 = 赋值。数组用strcpy赋值
************************************* *******************************************************************/
struct du_xie_shu_ju_01 {
	uint8 *zhang_hao[32];  //字符串
	uint8 *mi_ma[64];      //字符串
	uint32 id[4];          //整形
} shuju_zu_01 ;//给结构体一个变量名shuju_zu_01,并对内容赋值

struct du_xie_shu_ju_01 shuju_zu_02;//给结构体一个变量名shuju_zu_02,一般用在多次调用的函数外,不然结果有变化(不清楚原因)

static void ICACHE_FLASH_ATTR
tcp_client_recv_cb(void *arg,char *pdata,unsigned short len){
	os_printf("tcp client receive tcp server data\r\n");//21
	os_printf("length: %d \r\ndata: %s\r\n",len,pdata);//22
	/********************指针**********************************
	&  获取变量内存地址
	*  获取内存地址的值
	   uint8* addr_case1等价于 uint8 *addr_case1
	   uint8* addr_case1  char类型的指针变量
	   &value  变量value的内存地址
	   (uint8*) 强制类型转换指针
	   记住:指针用 = 赋值。数组用strcpy赋值

	**********************************************************/	
	//strcmp比较字符串str1和str2是否相同。如果相同则返回0
	if (strcmp(pdata, "a0017") == 0){
		shuju_zu_01.zhang_hao[32] = "cccooxxx";   //结构体成员的获取和赋值,只能在函数体内
		shuju_zu_01.mi_ma[64] = "cxshn1234er567890";
		shuju_zu_01.id[4] = system_get_chip_id();
		spi_flash_erase_sector(139);
		spi_flash_write(139 * 4096, (uint32*)&shuju_zu_01, sizeof(shuju_zu_01));//写入flash
		os_printf("@@@@@@xie_ru@@@@@:%s %s %d\r\n", shuju_zu_01.zhang_hao[32], shuju_zu_01.mi_ma[64], shuju_zu_01.id[4]);
	}
	if (strcmp(pdata, "a0018") == 0) {
		//struct du_xie_shu_ju_01 shuju_zu_02;//如果声明在这里,a0020的第3个结果会出错,放在函数外就正常
		shuju_zu_02.zhang_hao[32] = "bbcxcxcx";   //结构体成员的获取和赋值,只能在函数体内;
		shuju_zu_02.mi_ma[64] = "ecxf125318090";
		shuju_zu_02.id[4] = system_get_chip_id();
		spi_flash_erase_sector(139);
		spi_flash_write(139 * 4096, (uint32*)&shuju_zu_02, sizeof(shuju_zu_02));//写入flash
		os_printf("@@@@@@xie_ru@@@@@:%s %s %d\r\n", shuju_zu_02.zhang_hao[32], shuju_zu_02.mi_ma[64], shuju_zu_02.id[4]);
	}
	if (strcmp(pdata, "a0019") == 0) {
		spi_flash_read(139 * 4096, (uint32*)&shuju_zu_01, sizeof(shuju_zu_01));//读取flash值
		os_printf("@du_qu@:%s %s %d\r\n", shuju_zu_01.zhang_hao[32], shuju_zu_01.mi_ma[64], shuju_zu_01.id[4]);
	}
	if (strcmp(pdata, "a0020") == 0) {
		//struct du_xie_shu_ju_01 shuju_zu_02;
		spi_flash_read(139 * 4096, (uint32*)&shuju_zu_02, sizeof(shuju_zu_02));//读取flash值
		os_printf("@du_qu@:%s %s %d\r\n", shuju_zu_02.zhang_hao[32], shuju_zu_02.mi_ma[64], shuju_zu_02.id[4]);
	}

现在还有个问题没弄明白,
a0017执行后,执行a0019结果正常,然后执行a0020结果错误。结果集中第3个就是芯片ID那个参数错误。
a0018执行后,执行a0020结果正常,在执行a0019同样结果正常,然后在执行a0020结果错误。结果集中第3个就是芯片ID那个参数错误。继续执行a0019结果正常。
以后在解决这个问题,现在没头绪。另外:必须先写入数据,在读取数据,如果从来没有写入过数据,直接取读取芯片会不停重启

笔记中参考并引用了下面几个大神的博文:
ESP8266 Flash的分布及其读写操作
ESP8266学习笔记(2)——内存分布及Flash读写接口
Esp8266 进阶之路24【高级篇】

发布了27 篇原创文章 · 获赞 19 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/oXingChenWuJi/article/details/102529254