ESP8266-12E/F相关内存

两款模组在使用功能和内存功能都一样

模组中主要部分的芯片是乐鑫的esp8266,flash使用w25q32(4MB)

w25q 系列生产的加工的商家很多,但是里面的分布和命名规则都是一样的。比如华邦的w25q64,spi通讯接口,64就是指 64Mbit 也就是 8M 的容量。而我们平时的8266-12f的 32Mbit 就是 4M 容量。

以 w25q32 为例,里面的存储分布。w25q32把4M容量分为了 64 块,每一块又分为 16 个扇区,而每个扇区占 4K 大小。由此可计算到,w25q32有 32Mbit / 8 * 1024 / 16 / 4 = 64 块 ,有 64 * 16 = 1024 个扇区。

注:1B=8 Bit ,1KB=1024B ,1MB=1024KB

内存介绍

esp8266主要有内部芯片内存和外部Flash内存

  • 程序区:代码编译生成的 bin 文件,烧录到 Flash 占用的区域,请勿改写
  • 系统参数区: esp_iot_sdk 中底层用于存放系统参数的区域,请勿改写
  • 用户参数区:上层应用程序存储用户参数的区域。开发者请根据实际使用的 Flash size 设置,可 以参考文档“2A-ESP8266__IOT_SDK_User_Manual” 中的 “Flash Map” 一章。

芯片内存

esp8266 sdk 默认将 iRAM 中 0x40108000 开始的 32KB 空间用作 cache,sdk 启动后会将对应的 spi flash 空间映射到 cache 空间。
esp8266 sdk 中,支持若干种 flash map 方式,有 256KB + 256KB、512KB + 512KB、1024KB + 1024KB。
在 256KB + 256KB 和 512KB + 512KB 两种 map 方式中,user1.bin 和 user2.bin 均在第一个 1MB 空间,因此映射到同一个 1MB 空间。

w25q32内存分布
注意擦除或写入都是以扇区为最小单位。

Non-FOTA

FOTA

更多图查看:

链接:https://blog.csdn.net/k7arm/article/details/51812021

ESP8266和w25q32内存分布说明

ESP826612F/E里面使用w25q32作为了flash存储.

提前说下哈,bit代表位 也就是 0 1 0 1 , Bit代表字节 ,一字节就是8位

w25q32的容量是32Mbit 也就是 32/8 = 4MB字节 = 4*1024 = 4096KB字节

然后 w25q32 这个芯片规定每 64KB字节作为一个块

所以呢w25q32总共分成了 4096/64 = 64个块,不要问我,块是神么…..就是一块一块的区域,所以就是块…

然后 w25q32 这个芯片还规定每 4KB字节作为一个扇区.每256字节作为一页.

所以所有的扇区个数是 64*16 = 1024个

ESP8266内存规定

芯片是4096KB字节 = 4096*1024 = 4194304字节 = 0x400000

eagle.flash.bin 从flash的最开始的地址开始存储

eagle.irom0text.bin (0x10000 = 65536) 偏移了65536字节即64KB

esp_init_data_default.bin (0x3FC000) 从倒数第4个扇区开始存储 (注:0x400000 - 4096-4096-4096-4096 = 0x3FC000)

blank.bin (0x3FE000)从倒数第2个扇区开始存储 (注:0x400000 - 4096-4096 = 0x3FE000)

注意,未使用区域是变化的…咱编译完程序会显示eagle.irom0text.bin 大小

随着程序量的增加eagle.irom0text.bin 大小也在增加

其实呢咱使用上面的未使用区域的时候一般可以从0x3FC000地址往前推,

(提醒:以一个扇区4KB作为最小使用哈,因为擦除的时候最小擦除是4KB)

假设咱感觉存储咱自己的数据只使用4KB就可以了,那么就是在 0x3EB000地址开始存储咱自己的数据.

0x3FC000 - 4KB = 0x3EB000 即从倒数第5个扇区开始存储数据

其实从0x3FC000 到 0x3FE000 总共有8KB,所有中间有两个扇区

因为esp_init_data_default.bin 的大小是固定的哈不会超过4KB,

所以0x3FE000 左面的那个4KB的扇区也是可以使用的

开始的地址是 0x3FD000 即从倒数第三个扇区存储数据

提供的API函数

  1. 擦除某个扇区

总共1024个扇区,扇区号从0开始,所以是 0 - 1023

列如擦除上面的倒数第5个扇区 就是 spi_flash_erase_sector (1019)

  1. 往扇区里面写数据
  1. 从flash里面读取数据
  1. SDK还封装了一套交替存储API

就是使用3个扇区保存数据,第一个扇区和第三个扇区来回的保存数据

第二个扇区只保存一个标志位,标志当前数据是存储在第一个扇区还是第二个扇区(程序内部实现)

API函数的第二个参数假设是 1017

那么就是使用第1017和1018扇区来回的保存数据,1018扇区保存标志位

注意:关于4字节对齐

如果你存储数据,存储的数据个数是4的倍数就可以.

一般咱都会把数据放到一个数组里面,所以咱把数组长度定义为4的倍数就可以

我上面说的是char型的数组

如果是u16型的数组,数组长度定义为2的倍数就可以

如果是u32的就随意啦….

开始实践(普通)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//设置扇区地址
#define flash_save_sector 1019
//设置写入flash的起始地址
#define flash_save_addr flash_save_sector*4*1024 //(该扇区的起始地址)
//存储的数据
char flash_data[16] = "test flash data";
/**
* @brief system_done
* @param None
* @param None
* @retval None
* @warning None
* @example
**/
void system_done(void)
{
char temp[16];
//擦除扇区
spi_flash_erase_sector (flash_save_sector);
//写入数据
spi_flash_write(flash_save_addr,(uint32 *)flash_data,sizeof(flash_data));
//读取数据
spi_flash_read(flash_save_addr,(uint32 *)temp,sizeof(temp));

os_printf("\r\n save data =%s \r\n",temp);
}


void ICACHE_FLASH_ATTR user_init(void){
uart_init_2(BIT_RATE_115200,BIT_RATE_115200);
//注册系统初始化完成回调函数
system_init_done_cb(system_done);
}
  • Flash读写接口

SPI Flash 接口位于 /ESP8266_NONOS_SDK/include/spi_flash.h

system_param_xxx 接口位于 /ESP8266_NONOS_SDK/include/user_interface.h

  1. spi_flash_erase_sector

  2. spi_flash_write

    注意:

    • Flash 请先擦再写。

    • Flash 读写必须 4 字节对齐。

      示例代码:

      1
      2
      3
      4
      5
      6
      7
      #define N 0x7C

      uint32 data[M];

      spi_flash_erase_sector (N);

      spi_flash_write (N*4*1024, data, M*4);
  3. spi_flash_read

  4. system_param_save_with_protect

  5. system_param_load

    示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/***************Flash 读写结构体声明***************/

struct esp_platform_saved_param {

// 服务器参数
uint8 devkey[40];
uint8 token[40];
uint8 activeflag;
char server_domain[64];
ip_addr_t server_ip;
int server_port;

// AP参数
uint8 ssid[32];
uint8 password[64];
int authmode;
uint8 ssid_hidden;

// 填充
uint8 pad[2];
};

/***************Flash 读写结构体定义***************/
struct esp_platform_saved_param esp_param;

/***************Flash 写入数据***************/
system_param_save_with_protect(0x7D, &esp_param, sizeof(esp_param));

/***************Flash 读出数据***************/
system_param_load(0x7D, 0, &esp_param, sizeof(esp_param));

开始实践(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。

软件示例

IOT_Demo 中,使用 0x3C000 开始的 4 个 sector(每 sector 4KB),作为用户参数存储区。 其中 0x3D000、 0x3E000、 0x3F000 这 3 个 sector 实现了读写保护的功能,并存储了应用级参数 esp_platform_saved_param

图中“有读写保护的存储区”, IOT_Demo 中建议调用 system_param_loadsystem_param_save_with_protect 进行读写。

system_param_load - 读 Flash 用户参数区数据

system_param_save_with_protect - 写 Flash 用户参数区数据

参数 struct esp_platform_saved_param 定义了目前乐鑫存储于 Flash 的用户应用级数据,用户只需将自己要存储的数据添加到结构体 struct esp_platform_saved_param 后面,调用上述两个函数进行 Flash 读写即可。

Flash读写保护参考一

方法: “轮流写入”+“首部记数”+“尾部校验”

占用空间: 2 个 sector,共计 8KB;提供 4KB 的带数据保护存储空间。

原理:

仍然 采用两个数据 sector 轮流写入来做备份数据保护,只是不再专门设立 flag sector。 记一个 counter,写入数据 sector 的首部,每次写入时计数加一,用记数比较来判别下一次应写入哪个 sector;在数据尾部加入校验码(CRC、checksum 等任一种校验方式),用以验证数据的完整性。

(1) 假设初次上电,数据存储在 sector A, sector A 的记数为初始值 0xFF,从 sector A 将数据读入 RAM。

(2) 第一次数据写入 sector B,则在 sector B 首部信息中记录 counter 为 1,尾部加入校验码。

(3) 再次写入数据时,先分别读取 sector A/B 的 counter 值进行比较,此次应当将数据写入 sector A, sector A 首部记录 counter 为 2,尾部加入校验码。

(4) 若发生突然掉电,当前正在写入的 sector 数据丢失,重新上电时,先比较 sector A/B 的 counter 值,读取 counter 值较大的完整 sector,根据 sector 尾部的校验码进行校验,当前 sector 数据是否可靠,若校验通过,则继续执行;若校验失败,则读取另一个 sector 的数据,校验,并执行。

Flash读写保护参考二

方法: “备份扇区”+“尾部校验”

占用空间: 2 个 sector,共计 8KB;提供 4KB 的带数据保护存储空间。

原理:

始终往 sector A 读写数据,每次写入时,同样写一遍 sector B 作为 sector A 的备份扇区,每个 sector 尾部均加入校验码(CRC、checksum等任一种校验方式)。

(1) 从 sector A 读取数据,并进行校验。

(2) 数据写入 sector A,尾部为校验码。

(3) sector A 写入完成后,同样的数据也写入 sector B 进行备份。

(4) 若发生突然掉电,当前正在写入的 sector 数据丢失,重新上电时,先从 sector A 读取数据,根据尾部的校验码进行校验,sector A 数据是否可靠,若校验通过,则继续执行;若校验失败,则读取 sector B 的数据,校验,并执行。

• 由 Leung 写于 2018 年 9 月 14 日

• 参考:《ESP8266 Flash 读写说明》[25l1]

《ESP8266 Non-OS SDK IoT_Demo 指南》[q878]

《ESP8266 Non-OS SDK API 参考》[7qq6]

《Esp8266 进阶之路24》

《ESP8266 Flash》

其他方法

  1. 官方给了例子

注意 priv_param_start_sec 参数是这个参数

咱使用的时候也这样就可以

esp8266 flash地址规划

选取的是esp8266-12f 4096KB 4M flash内存

1.如何让确定为4M内存的?

原因:由于是从同事手上接项目,所以并无芯片规格书等物件。

查看工具编译选项,esp_init_data_default.bin: 0X3FC000 blank.bin :0X3FE000

备份系统程序地址是0x101000,绿色位置就是对应选择。

2.扇区应该如何划分?

esp8266 以4k为一个扇区,16k为一个块。4M内存的8266有1024个扇区。

OTA flash内存分布图

ESP8266-12F的扇区地址计算方法: blank.bin 位于扇区1022          地址0x3FE000 esp_init_data_default.bin位于扇区1020  地址0x3FC000

4M容量的十六进制3FC000地址转换为十进制为:4177920 所在扇区为:4177920/4/1024= 1020

4M容量的十六进制3FE000地址转换为十进制为:4186112 所在扇区为:4186112/4/1024= 1022

即system_param位置

Flash操作 ESP8266-12F的Flash操作: 下列扇区不能占用: blank.bin 位于扇区1022          地址0x3FE000 esp_init_data_default.bin位于扇区1020  地址0x3FC000

用户数据可存储的位置扇区号: (1024 - 16) /4 = 252 之后的四个扇区 在之前选位置也可以。只要不在用户程序处使用flash地址就可以。


相关链接(侵删)

  1. ESP8266-12E/F 内存分配
  2. ESP8266 SDK开发: 外设篇-内存分布说明及Flash读写
  3. esp8266 flash地址规划
  4. ESP8266学习笔记(2)——内存分布及Flash读写接口
  5. ESP8266 Flash

=================我是分割线=================

欢迎到公众号来唠嗑: