ESP-8266读写外部Flash

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

本笔记为ESP8266和外部Flash w25q32芯片通讯学习笔记,参考文献《W25Q16Flash存储芯片数据手册》

一.W25Q32-Flash

1.官方说明

W25980 (8M-bit), w25Q16 (16M-bit)和W25932 (32M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行Flash存储器。259系列比普通的串行Flash存储器更灵活,性能更优越。基于双倍/四倍的SPI,它们能够可以立即完成提供数据给RAM,包括存储声音、文本和数据。芯片支持的工作电压2. 7V到3.6V,正常工作时电流小于5mA,掉电时低于1uA,所有芯片提供标准的封装.
W25980/16/32由每页256字节,总共4, 096/8, 192/16, 384页组成。每页的256字节用一次页编程指令即可完成。每次擦除16页(扇区)、128页(32KB块),256页(64KB块)和全片擦除。W25980/16/32有各自的256/512/1024个可擦除扇区和16/32/64个可擦除块。最小4KB扇区允许更灵活的应用去要求数据和参数保存(见图2)
W25080/16/32支持标准串行外围接口(SPI),和高速的双倍/四倍输出,双倍/四倍用的引脚:串行时钟、片选端、串行数据1/00 (DI)、1/01 (DO)、1/02 (WP)和1/03 (HOLD), SPI最高支持80MHz,当用快读双倍/四倍指令时,相当于双倍输出时最高速率160MHz,四倍输出时最高速率320MHz。这个传输速率比得上8位和16位的并行Flash存储器。
HOLD引脚和写保护引脚可编程写保护。此外,芯片支持JEDEC标准,具有唯一的64位识别序列号。

2.引脚排列

ESP-8266读写外部Flash
ESP-8266读写外部Flash
*1 IO0和IO1用在双倍/四倍传输中
*2 IO0-IO3用在四倍传输中
ESP-8266读写外部Flash

3.特殊引脚说明

1.串行数输入输出和IOS (DI DO和IO0, IO1,IO2,IO3)

W25q80, W25q16和W25q32支持标准SPI、双倍SPI和四倍SPI

标准的SPI传输用单向的DI(输入)引脚连续的写命令、地址或者数据在串行时钟(CLK)的上升沿时写入到芯片内。标准的. SPI用单向的DO
(输出)在CLK的下降沿从芯片内读出数据或状态。

意思就是标准模式下,数据输入用一条线(DI),数据输出用另一条线(DO)

双倍和四倍SPI指令用双向的10引脚在CLK的上升沿来连续的写指令、地址或者数据到芯片内,在CLK的下降沿从芯片内读出数据或者状态。四倍SPI指令操作时要求在状态寄存器2中的四倍使能位(QE)一直是置位状态。当QE-1时/WP引脚变为I02,/HOLD引脚变为IO3

这个模式就厉害了:可以将DI和DO变成双向IO口 ,既可以输入也可以当输出数据使用,四倍模式下,另外加2条 /WP 和/HOLD 作为双向IO口使用,达到四倍速传输,变成IO口,需要配置特殊寄存器

2.写保护(/WP)

低电平有效,接入低电平 数据不可被写入 如果状态寄存器被配置为IO口,写保护功能失效

3.保持端(/HOLD)

当引脚为低电平,允许芯片暂停工作。如果状态寄存器被配置为IO口,此功能失效

4.存储结构讲解(W25Q32)

W25032共32M-bit
(4MB字节),它可划分为64块,每块64KB;每块又可划分为16个扇区,每个扇区4KB;每个扇区又可划分16页,每页256B,块的划分如下图1所示:
(擦除必须整扇擦)
在往某个地址写之前必须确保这个地址上的值是OxFF,否则说明这个地址以前被写过数据,还没有被擦除。w25Q32擦除的最小单位是Sector也就是4k个字节,也就是说如果要想往某个地址写一个值,如果这个地址上的值不是OxFF,那么就要把整个扇区都擦除,然后再写。
给W25Q32开辟一个4k的缓存,比如定义一个4k的数组,然后在写数据之前先判断如果这个地址上的数据不是OxFF,就先把这个地址所在的Sector里的数据全部保存在4k缓存中,再擦除这个扇区,再把缓存中对应的地址上的数据更新,再把这个4k缓存区的所有数据一次性的写入到这个Sector中。

如果把一块Flash存储芯片比作一本书,那么这本书一共有3200万字(32000000bit),一共有16384页,每页有2048bit(256字节)。这本书的内容分为64章节(块),每章节分为16小节(扇区),每个小节有16页(页),每页有256字(字节/2048bit)。

flash4MB 块1 扇区1 扇区2 扇区... 扇区16 块2 ... 块64 页1 页2 页... 页16 256字节

字节地址: 00 00 00H ~ 3F FF FFH
页地址: 00 00 H ~ 3F FF H(总16384页)
扇地址: 00 0 H ~ 3F F H(扇地址=页地址/16,总1024扇)
块地址: 00 H ~ 3F H

1.存储器划分:(字节地址)

1.存储器块区划分

ESP-8266读写外部Flash

2.扇区划分

ESP-8266读写外部Flash

3.页区划分

ESP-8266读写外部Flash

二:ESP8266读写Flash

1.ESP8266Flash分布

ESP8266提供了读写Flash的接口,操作很简单,但不能随便找个地方就开始擦除然后写入自己的数据,因为内部有系统文件
ESP-8266读写外部Flash
ESP-8266读写外部Flash

不支持云端升级(即NON-FOTA)的用户数据在eagle.irom0text.bin之后,而改文件的大小和保存地址在指南里有:
ESP-8266读写外部Flash
ESP-8266读写外部Flash

以W25Q32为例,计算出用户数据区开始地址:0X10000+7681024=0xD0000;
注意768后面单位是KB,所以要✖1024不是1000;
实际上8266擦除是以扇区来计算,所以0xD0000对应的扇区为:0xD0000/256
16=0xD0,
除了首地址,还有结束地址,那么0x3FB000就是结束地址。

2.注意问题:

  • Flash擦除的最小单位为一个扇区(4KB) ,当存储在某个扇区的数据需要改写时,流程是先擦掉"整个扇区,再将该扇区的数据写回去。
  • Flash 请先擦再写。
  • Flash 读写必须 4 字节对⻬。

2.接口函数

SPI Flash接口位于/ESP8266 NONOS SDK/include/spi_flash.h.
system_param_xxx接口位于/ESP8266 NONOS-SDK/include/user
_interface.h.

1.擦除扇区

ESP-8266读写外部Flash


	//…………………………………………………………………………………………………
	spi_flash_erase_sector(0xD0);	// 擦除0xD0扇区		参数==【扇区编号】

2.写入扇区数据

// 向Flash写数据(参数1=【字节地址】、参数2=写入数据的指针、参数3=数据长度)//0xD0*256*16=扇区排号×每个扇区内字节数(256×16)=字节地址
	//------------------------------------------------------------------------
	spi_flash_write(0xD0*256*16, (uint32 *)A_W_Data, sizeof(A_W_Data));
	

3.读出扇区数据

// 从【0xD0 000】地址起,读出16个数据(每个数据占4字节)
	//---------------------------------------------------------------------
	spi_flash_read(0xD0*4096, (uint32 *)A_R_Data, sizeof(A_W_Data));

4.程序


#include "user_config.h"		// 用户配置
#include "driver/uart.h"  		// 串口

//#include "at_custom.h"
#include "c_types.h"			// 变量类型
#include "eagle_soc.h"			// GPIO函数、宏定义
#include "ip_addr.h"			// 被"espconn.h"使用
#include "espconn.h"			// TCP/UDP接口
//#include "espnow.h"
#include "ets_sys.h"			// 回调函数
//#include "gpio.h"
#include "mem.h"				// 内存申请等函数
#include "os_type.h"			// os_XXX
#include "osapi.h"  			// os_XXX、软件定时器
//#include "ping.h"
//#include "pwm.h"
//#include "queue.h"
//#include "smartconfig.h"
//#include "sntp.h"
//#include "spi_flash.h"
//#include "upgrade.h"
#include "user_interface.h" 	// 系统接口、system_param_xxx接口、WIFI、RateContro
//==================================================================================

// 宏定义
//==================================================================================
#define		ProjectName			"Flash"		// 工程名宏定义

#define		SPI_FLASH_SEC_SIZE	4096		// Flash扇区大小
//==================================================================================

// 全局变量
//==================================================================================
u16 N_Data_FLASH_SEC = 0x77;	// 存储数据的扇区编号

u32 A_W_Data[11] = {1,8,1,4,9,0,7,2,1,0,1};	// 写入Flash的数据

u32 A_R_Data[11] = {0};			// 缓存读Flash的数据
//==================================================================================


// 毫秒延时函数
//===========================================
void ICACHE_FLASH_ATTR delay_ms(u32 C_time)
{	for(;C_time>0;C_time--)
		os_delay_us(1000);
}
//===========================================


// user_init:entry of user application, init user function here
//==========================================================================
void ICACHE_FLASH_ATTR user_init(void)
{
	u8 C_loop = 0;
	uart_init(115200,115200);	// 初始化串口波特率
	os_delay_us(10000);			// 等待串口稳定
	os_printf("\r\n=================================================\r\n");
	os_printf("\t Project:\t%s\r\n", ProjectName);
	os_printf("\t SDK version:\t%s", system_get_sdk_version());
	os_printf("\r\n=================================================\r\n");


	// 向【0xD0 000】地址起,写入16个数据(每个数据占4字节)
	//…………………………………………………………………………………………………
	spi_flash_erase_sector(0xD0);	// 擦除0xD0扇区		参数==【扇区编号】

	// 向Flash写数据(参数1=【字节地址】、参数2=写入数据的指针、参数3=数据长度)//0xD0*256*16=扇区排号×每个扇区内字节数(256×16)=字节地址
	//------------------------------------------------------------------------
	spi_flash_write(0xD0*256*16, (uint32 *)A_W_Data, sizeof(A_W_Data));

	os_printf("\r\n---------- Write Flash Data OVER ----------\r\n");
	//…………………………………………………………………………………………………


	// 从【0xD0 000】地址起,读出16个数据(每个数据占4字节)
	//---------------------------------------------------------------------
	spi_flash_read(0xD0*4096, (uint32 *)A_R_Data, sizeof(A_W_Data));


	// 串口打印读出的数据
	//-----------------------------------------------------
	for(C_loop=0; C_loop<11; C_loop++)
	{
		os_printf("Read Data = %d \r\n",A_R_Data[C_loop]);

		delay_ms(10);
	}

	os_printf("\r\n\r\n------------ user_init OVER ------------\r\n\r\n");
}
//==========================================================================



uint32 ICACHE_FLASH_ATTR user_rf_cal_sector_set(void)
{
    enum flash_size_map size_map = system_get_flash_size_map();
    uint32 rf_cal_sec = 0;

    switch (size_map) {
        case FLASH_SIZE_4M_MAP_256_256:
            rf_cal_sec = 128 - 5;
            break;

        case FLASH_SIZE_8M_MAP_512_512:
            rf_cal_sec = 256 - 5;
            break;

        case FLASH_SIZE_16M_MAP_512_512:
        case FLASH_SIZE_16M_MAP_1024_1024:
            rf_cal_sec = 512 - 5;
            break;

        case FLASH_SIZE_32M_MAP_512_512:
        case FLASH_SIZE_32M_MAP_1024_1024:
            rf_cal_sec = 1024 - 5;
            break;

        case FLASH_SIZE_64M_MAP_1024_1024:
            rf_cal_sec = 2048 - 5;
            break;
        case FLASH_SIZE_128M_MAP_1024_1024:
            rf_cal_sec = 4096 - 5;
            break;
        default:
            rf_cal_sec = 0;
            break;
    }

    return rf_cal_sec;
}

void ICACHE_FLASH_ATTR user_rf_pre_init(void){}

5.完成效果

ESP-8266读写外部Flash

上一篇:c++内存管理


下一篇:ESP32扫描环境中的所有WiFi并且通过串口选择需要连接的WiFi