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【高级篇】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容