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

ESP8266 non-os SDK为3.1.0

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

Flash布局介绍

asdwasdasd.jpg

⽤户可以通过修改 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【高级篇】

你可能感兴趣的:(ESP8266 non-os Flash掉电记录数据读写)