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扇区
10184*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。
读写过程如下:
初始上电时,数据存储在 sector 2 中,从 sector 2 中将数据读到 RAM。
第一次写数据时,将数据写入 sector 1。此时若突然掉电,sector 1写入失败,sector 2 & 3数据未改变;重新上电时,仍是从 sector 2 中 读取数据,不影响使用。
改写 sector 3,将标志置为 0,表示数据存于 sector 1。此时若突然掉电,sector 3 写入失败,sector 1 & 2 均存有一份完整数据;重新上电时,因 sector 3 无有效 flag,默认从 sector 2 中读取数据,则仍能正常使用,只是未能包含掉电前对 sector 1 写入的数据。
再一次写数据时,先从 sector 3 读取 flag,若 flag 为0,则上次数据存于 sector 1,此次应将数据写入 sector 2;若 flag 为非 0,则认为上次数据存于 sector 2,此次应将数据写入 sector 1。此时若写数据出错,请参考步骤 2、 3的说明,同理。
写入 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【高级篇】