提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
本笔记为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.引脚排列
*1 IO0和IO1用在双倍/四倍传输中 *2 IO0-IO3用在四倍传输中
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.存储器块区划分
2.扇区划分
3.页区划分
二:ESP8266读写Flash
1.ESP8266Flash分布
ESP8266提供了读写Flash的接口,操作很简单,但不能随便找个地方就开始擦除然后写入自己的数据,因为内部有系统文件
不支持云端升级(即NON-FOTA)的用户数据在eagle.irom0text.bin之后,而改文件的大小和保存地址在指南里有:
以W25Q32为例,计算出用户数据区开始地址:0X10000+7681024=0xD0000; 注意768后面单位是KB,所以要?1024不是1000; 实际上8266擦除是以扇区来计算,所以0xD0000对应的扇区为:0xD0000/25616=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.擦除扇区
//…………………………………………………………………………………………………
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.完成效果
|