Arduino应用开发——spi flash(以esp32和w25qxx为例)
Arduino应用开发——spi flash
目录
- Arduino应用开发——spi flash
- 前言
- 1 硬件介绍
- 1.1 模块简介
- 1.2 硬件连接
- 2 软件开发
- 2.1 寄存器介绍
- 2.2 编程讲解
- 3 运行测试
- 4 读写速度测试
- 结束语
前言
flash是我们在做嵌入式开发时一定会用到的,因为MCU本身就要使用flash来存储代码,flash的好处是掉电不会丢数据,只是一般MCU本身flash的容量都不大,如果我们需要存储大量的数据,就需要外接flash。
flash常用spi接口的,与传感器,电源IC这些芯片不同,不同型号和厂商的flash芯片在通讯协议和内部寄存器这些方面很统一,这对我们开发而言有着很大的好处,这意味着我们的驱动代码可以兼容各种各样的flash,比如W25Q64,W25Q128,GD25Q64等等都是通用的,可以在不改代码的基础上直接替换。
不过让我觉得奇怪的是,Arduino好像并没有单纯使用外置flash的库,大部分都是在存放代码的flash里面分区,做文件系统或者其他用途,实在没找到合适的库,于是我就自己写了一个。
1 硬件介绍
1.1 模块简介
本文基于ESP32-S2测试了W25Q128和GD32Q64两种FLASH。
注:ESP32和ESP32-S2读写flash是完全一样的,只有SPI的接口引脚号有区别。而ESP8266的硬件SPI库则有略微区别,需要稍做修改。
硬件配置如下:
模块 | 型号 | 说明 |
---|---|---|
ESP32-S2 | ESP32-S2-WROVER | 这是乐鑫的一款模组,内部主要是用乐鑫的ESP32-S2再加上一个4M FLASH和2M PSRAM组成,开发板用的是乐鑫的ESP32-S2-SAOLA |
W25Q128 | W25Q128 | W25QXX是很常用的型号, 这里不具体介绍了 |
GD25Q64 | GD25Q64 | 跟W25Q64是兼容的,可以直接替代 |
因为篇幅问题,本文主要讲解软件部分开发,关于硬件就不在这里具体介绍了,不懂的同学可以网上自行查阅资料,也可以翻一下我之前发布的博客。
1.2 硬件连接
ESP32-S2 | W25Q128\GD25Q64 | 说明 |
---|---|---|
VCC | VCC | 电源正,3.3V |
GND | GND | 电源负 |
GPIO12\SPI_CLK | CLK | SPI时钟线 |
GPIO11\SPI_MOSI | DI | SPI数据线,esp32s2输出,flash输入 |
GPIO13\SPI_MISO | DO | SPI数据线,esp32s2输入,flash输出 |
GPIO10\SPI_CS | CS | SPI片选 |
GPIO9 | HOLD |
保持引脚,可以用GPIO控制也可以直接硬件拉高 当HOLD引脚拉低时,DO将处于高阻抗,DI和CLK针上的信号将被忽略 当HOLD引脚拉高时,设备允许操作 |
GPIO14 | WP | 写入保护引脚,低电有效,可以用GPIO控制也可以直接硬件拉高 |
2 软件开发
2.1 寄存器介绍
以W25QXX为例,列举部分常用的寄存器。
指令名称 | 数值 |
---|---|
制造商设备ID | 90h |
JEDEC ID | 9Fh |
写状态寄存器 | 01h |
读状态寄存器1 | 05h |
读状态寄存器2 | 35h |
读数据 | 03h |
写使能 | 06h |
写失能 | 04h |
扇区擦除(4KB) | 20h |
全片擦除 | C7h |
页编程 | 02h |
2.2 编程讲解
我们要使用SPI和flash通讯,通过读写flash内部的寄存器达到数据存储和读取的目的,因此,我们第一步是要搞定SPI的驱动代码。
可以使用硬件SPI也可以用软件模拟,硬件SPI跟你选用的MCU有直接关联,比如ESP32和ESP8266,硬件SPI这部分代码一般都是有现成的库,这个在各自的开发板库就有,但需要注意的是不用的MCU用的库不同,因此SPI相关的API可能会有差异。
所以,同样是使用Arduino的SPI,代码却不一定一样,不过一般都是大同小异的,相互之间是可以参考的。我下面会以ESP32-S2为例编写驱动代码。
Arduino官方SPI可以参考:
http://arduino.cc/en/Tutorial/BarometricPressureSensor
http://arduino.cc/en/Tutorial/SPIDigitalPot
FLASH驱动示例代码:
我这里以ESP32-S2为例测试了硬件SPI和软件SPI,可以通过宏定义HARDWARE_SPI和SOFTWARE_SPI切换,另外测试的时候可以打开uart debug的宏,方便在遇到问题时排查,实际使用时建议关闭,因为在读写大量数据的时候串口会频繁输出数据,影响读写速度。
#include <SPI.h>// #define NORFLASH_DEBUG_ENABLE // uart debug
#define HARDWARE_SPI // use hardware spi
// #define SOFTWARE_SPI // use software spi#define NORFLASH_CS_PIN 10
#define NORFLASH_CLK_PIN 12
#define NORFLASH_MOSI_PIN 11
#define NORFLASH_MISO_PIN 13
// #define NORFLASH_HOLD_PIN 9 // hold pin no connect 3.3V
// #define NORFLASH_WP_PIN 14 // hold pin no connect 3.3V
#define NORFLASH_HOLD_PIN -1 // hold pin connect 3.3V
#define NORFLASH_WP_PIN -1 // wp pin connect 3.3V#define ManufactDeviceID_CMD 0x90
#define READ_JEDEC_ID_CMD 0x9F
#define WRITE_STATUS 0x01
#define READ_STATU_REGISTER_1 0x05
#define READ_STATU_REGISTER_2 0x35
#define READ_DATA_CMD 0x03
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04
#define SECTOR_ERASE_CMD 0x20
#define CHIP_ERASE_CMD 0xC7
#define PAGE_PROGRAM_CMD 0x02#define ONE_PAGE_SIZE 256
#define SPI_FREQUENCY 40 * 1000000#define FLASH_TEST_ENABLE/* Norflash spi init */
void norflash_spi_init()
{// gpio initpinMode(NORFLASH_HOLD_PIN, OUTPUT); pinMode(NORFLASH_WP_PIN, OUTPUT); digitalWrite(NORFLASH_HOLD_PIN, HIGH); digitalWrite(NORFLASH_WP_PIN, HIGH); pinMode(NORFLASH_CS_PIN, OUTPUT); digitalWrite(NORFLASH_CS_PIN, HIGH);#ifdef HARDWARE_SPISPI.begin(NORFLASH_CLK_PIN, NORFLASH_MISO_PIN, NORFLASH_MOSI_PIN, NORFLASH_CS_PIN);SPI.setDataMode(SPI_MODE0);SPI.setBitOrder(MSBFIRST);SPI.setFrequency(SPI_FREQUENCY);
#elsepinMode(NORFLASH_CLK_PIN, OUTPUT); pinMode(NORFLASH_MOSI_PIN, OUTPUT); pinMode(NORFLASH_MISO_PIN, INPUT); digitalWrite(NORFLASH_CLK_PIN, LOW);delay(1);
#endif// check write enable statusuint8_t data = 0;write_enable(); data = read_status();
#ifdef NORFLASH_DEBUG_ENABLESerial.printf("norflash write enable status:");Serial.println(data, BIN);
#endif// read device iduint16_t device_id = 0;device_id = read_norflash_id();
#ifdef NORFLASH_DEBUG_ENABLESerial.printf("norflash device id: 0x%04X", device_id);
#endif
}/* Norflash write one byte */
void write_byte(uint8_t data)
{#ifdef HARDWARE_SPI// SPI.transfer(data);SPI.write(data);
#else if SOFTWARE_SPIfor(uint8_t i = 0; i < 8; i++){ uint8_t status;status = data & (0x80 >> i);digitalWrite(NORFLASH_MOSI_PIN, status);digitalWrite(NORFLASH_CLK_PIN, LOW);digitalWrite(NORFLASH_CLK_PIN, HIGH); }
#endif
}/* Norflash read one byte */
uint8_t read_byte(uint8_t tx_data)
{
#ifdef HARDWARE_SPIuint8_t data = 0;data = SPI.transfer(tx_data);return data;
#else if SOFTWARE_SPIuint8_t i = 0, data = 0;for(i = 0; i < 8; i++){ digitalWrite(NORFLASH_CLK_PIN, HIGH);digitalWrite(NORFLASH_CLK_PIN, LOW);if(digitalRead(NORFLASH_MISO_PIN)) {data |= 0x80 >> i;}}return data;
#endif
}/* Norflash write enable */
void write_enable()
{digitalWrite(NORFLASH_CS_PIN, LOW);write_byte(WRITE_ENABLE_CMD);digitalWrite(NORFLASH_CS_PIN, HIGH);
}/* Norflash write disable */
void write_disable()
{digitalWrite(NORFLASH_CS_PIN, LOW);write_byte(WRITE_DISABLE_CMD);digitalWrite(NORFLASH_CS_PIN, HIGH);
}/* Read norflash status */
uint8_t read_status()
{uint8_t status = 0; digitalWrite(NORFLASH_CS_PIN, LOW);write_byte(READ_STATU_REGISTER_1); status = read_byte(0); digitalWrite(NORFLASH_CS_PIN, HIGH); return status;
}/* check norflash busy status */
void check_busy(char *str)
{while(read_status() & 0x01){#ifdef NORFLASH_DEBUG_ENABLESerial.printf("status = 0, flash is busy of %s\n", str); #endif }
}/* Write less than oneblock(256 byte) data */
void write_one_block_data(uint32_t addr, uint8_t * pbuf, uint16_t len)
{uint16_t i; check_busy("write_one_block_data");write_enable(); digitalWrite(NORFLASH_CS_PIN, LOW); write_byte(PAGE_PROGRAM_CMD); write_byte((uint8_t)(addr >> 16)); write_byte((uint8_t)(addr >> 8)); write_byte((uint8_t)addr); for(i = 0; i < len; i++) {write_byte(*pbuf++); }digitalWrite(NORFLASH_CS_PIN, HIGH); check_busy("write_one_block_data");
} /* Write less than one sector(4096 byte) length data */
void write_one_sector_data(uint32_t addr, uint8_t * pbuf, uint16_t len)
{ uint16_t free_space, head, page, remain; free_space = ONE_PAGE_SIZE - addr % ONE_PAGE_SIZE; if(len <= free_space) {head = len;page = 0; remain = 0; }if(len > free_space) {head = free_space;page = (len - free_space) / ONE_PAGE_SIZE; remain = (len - free_space) % ONE_PAGE_SIZE;}if(head != 0){#ifdef NORFLASH_DEBUG_ENABLESerial.print("head:");Serial.println(head);#endifwrite_one_block_data(addr, pbuf, head);pbuf = pbuf + head;addr = addr + head;}if(page != 0) {#ifdef NORFLASH_DEBUG_ENABLESerial.print("page:");Serial.println(page);#endiffor(uint16_t i = 0; i < page; i++) {write_one_block_data(addr, pbuf, ONE_PAGE_SIZE);pbuf = pbuf + ONE_PAGE_SIZE;addr = addr + ONE_PAGE_SIZE;}}if(remain != 0) {#ifdef NORFLASH_DEBUG_ENABLESerial.print("remain:");Serial.println(remain);#endifwrite_one_block_data(addr, pbuf, remain);}
}/* Write arbitrary length data */
void write_arbitrary_data(uint32_t addr, uint8_t* pbuf, uint32_t len)
{ uint32_t secpos;uint16_t secoff;uint16_t secremain; uint16_t i; uint8_t *write_buf = pbuf; uint8_t save_buffer[4096]; // save sector original data and add new data secpos = addr / 4096; // sector number secoff = addr % 4096; // sector offsetsecremain = 4096 - secoff; // sector remaining space if(len <= secremain){secremain = len; // sector remaining space less than 4096}while(1) { read_data(secpos * 4096, save_buffer, 4096); // read sector datafor(i = 0; i < secremain; i++){// check data, if all data is 0xFF no need erase sectorif(save_buffer[secoff + i] != 0XFF){// need erase sectorbreak; }}if(i < secremain){// erase sector and write datasector_erase(secpos);for(i = 0; i < secremain; i++) {save_buffer[i + secoff] = write_buf[i]; // add new data }write_one_sector_data(secpos * 4096, save_buffer, 4096); // write sector}else {// no need erase sectorwrite_one_sector_data(addr, write_buf, secremain); } if(len == secremain){// write donebreak; }else{// continue writesecpos ++; // sector number + 1secoff = 0; // sector offset = 0 write_buf += secremain; // write buff offsetaddr += secremain; // addr offset len -= secremain; // remaining data lenif(len > 4096){// remaining data more than one sector(4096 byte)secremain = 4096; }else {// remaining data less than one sector(4096 byte)secremain = len; }} }
}/* Read arbitrary length data */
void read_data(uint32_t addr24, uint8_t *pbuf, uint32_t len)
{check_busy("read_data");digitalWrite(NORFLASH_CS_PIN, LOW);write_byte(READ_DATA_CMD);write_byte((uint8_t)(addr24 >> 16)); write_byte((uint8_t)(addr24 >> 8)); write_byte((uint8_t)addr24); for(uint32_t i = 0; i < len; i++){*pbuf = read_byte(0xFF);pbuf ++;}digitalWrite(NORFLASH_CS_PIN, HIGH);
}/* Erase sector */
void sector_erase(uint32_t addr24)
{addr24 *= 4096; check_busy("sector_erase");write_enable(); digitalWrite(NORFLASH_CS_PIN, LOW);write_byte(SECTOR_ERASE_CMD); write_byte((uint8_t)(addr24 >> 16)); write_byte((uint8_t)(addr24 >> 8)); write_byte((uint8_t)addr24); digitalWrite(NORFLASH_CS_PIN, HIGH);check_busy("sector_erase");
} /* Read norflash id */
uint16_t read_norflash_id()
{uint8_t data = 0;uint16_t device_id = 0;digitalWrite(NORFLASH_CS_PIN, LOW);write_byte(ManufactDeviceID_CMD);write_byte(0x00);write_byte(0x00);write_byte(0x00);data = read_byte(0);device_id |= data; // low bytedata = read_byte(0);device_id |= (data << 8); // high bytedigitalWrite(NORFLASH_CS_PIN, HIGH);return device_id;
}void setup() {Serial.begin(115200);norflash_spi_init(); #ifdef FLASH_TEST_ENABLE/* readwrite test */int g = 0;uint8_t str[1280];memset(str, 0, sizeof(str));unsigned int j = 0; for(int k=0; k < 5; k++){for(int i = 0; i < 256; i++){str[j] = i;j++;}}Serial.println("");Serial.println("-----write data-------");sector_erase(0x00);write_one_sector_data(0x10, str, 256);memset(str, 0, sizeof(str));read_data(0x00, str, 512);Serial.println("str:");for(int k = 0; k < 512; k++){if(g == 16){Serial.println("|");if(k % 256 == 0) Serial.println("---------------");{g = 1;}} else {g++;}Serial.printf("%02X ", str[k]);}
#endif
}void loop() {}
3 运行测试
运行结果如下:
设备启动之后先读取了flash的制造商设备ID信息,这个数值仅供参考,因为这个是跟具体的flash芯片有关的。不过,只要这个数值符合芯片datasheet所描述的值就能说明spi的通讯是正常的,同理,如果读出来的值是00或者FF,不符合datasheet的值,那就是异常的。
读取了设备ID之后在0x10的位置开始写入了256个字节的数据,然后再从0x00的位置开始读取512个字节。
从串口打印的信息可以看出,flash相应的位置都已经写入了对应的数据,没有操作的地址都是FF,与代码一致。
4 读写速度测试
测试代码示例如下:
我这里是外扩了2M PSRAM的,因此可以直接分配1MB的动态内存用来测试,测试数据量越大应该是越准确的,但是如果是普通的ESP8266或者ESP32,是没有这么多RAM的,要测试的话只能改小一点。
uint32_t test_size = 1024 * 1024;long lTime;uint8_t *buf = (uint8_t*)ps_malloc(test_size);memset(buf, 0, test_size);randomSeed(analogRead(0)); // randomize using noise from analog pin 0for(uint32_t i = 0; i < test_size; i++){buf[i] = random(0, 255); // get a random number from 0 to 255}Serial.printf("norflash speed test start \n");lTime = micros();write_arbitrary_data(0x00, buf, test_size);lTime = micros() - lTime;Serial.printf("write %d byte data end, spi frequency: %ld, time: %f s\n", test_size, SPI_FREQUENCY, (lTime / 1000000.0));memset(buf, 0, test_size);lTime = micros();read_data(0x00, buf, test_size);lTime = micros() - lTime;Serial.printf("read %d byte data end, spi frequency: %ld, time: %f s\n", test_size, SPI_FREQUENCY, (lTime / 1000000.0));if(buf){free(buf);}
速度测试结果如下:
我这里只测试了GD25Q64,没有测试W25Q128,读写速度应该是差不多的。
SPI 40MHz读写1MB数据测试:
SPI 20MHz读写1MB数据测试:
结束语
好了,关于spi flash的编程就介绍到这里。本文只列举了ESP8266和ESP32-S2的情况,ESP32或者其他MCU基本也是一样的,大家举一反三即可。如果这篇文章对你有帮助,可以点赞收藏,如果还有什么问题,欢迎在评论区留言或者私信给我。
想了解更多Arduino的内容,可以关注一下博主,后续我还会继续分享更多的经验给大家。
esp8266基于Arduino的开发教程汇总:
https://blog.csdn.net/ShenZhen_zixian/article/details/121659482
Arduino应用开发——spi flash(以esp32和w25qxx为例)相关推荐
- STM32F4 HAL库开发 -- SPI Flash
一.驱动 bsp_spi_flash.c #include "THC_Board_include_h.h"/* Private define ------------------- ...
- 如何给ESP32选择外接SPI Flash
给ESP32选择外接SPI Flash [前言]: 许多用户在使用 ESP32 做开发的时候,都会碰到如何选择外接 Flash 的难题,因为不同厂商的 Flash 存在很多差异,Flash 支持的工 ...
- WT32-SC01是ESP32驱动3.5彩屏开发板方案适合用arduino方式开发吗?因为需要彩屏和电容触摸的驱动的
ESP32驱动3.5寸彩屏开发板方案因为带有彩屏和电容触摸的驱动,能否用arduino方式开发,这是很多熟悉arduino开发的技术人员关心的问题. 目前启明云端推出的http://esp32.8ms ...
- esp8266,esp32中的SPI FLASH 访问模式(QIO QOUT DIO DOUT)
本文 ESP8266 和 ESP32 支持四种不同的 SPI flash 访问模式:DIO.DOUT.QIO 和 QOUT. 这些可以通过 esptool.py write_flash 的 --fla ...
- (实测可用)STM32CubeMX教程-STM32L431RCT6开发板研究串口通信(SPI flash)
一.开发板平台简介: 1.开发板资源简介 (1)开发板主芯片型号:STM32L431RCT6 (2)开发板主芯片封装:LQFP-64_10x10x05P (3)开发板主芯片内核:ARM® Cortex ...
- STM32开发 -- W25Q32JV SPI FlASH详解
如需转载请注明出处:https://juyou.blog.csdn.net/article/details/103168687 flash这部分也是很重要的一部分了. 我们将利用 STM32F1 自带 ...
- STM32开发实战:W25Q32JV SPI Flash详解
STM32开发实战:W25Q32JV SPI Flash详解 在STM32单片机的应用中,使用SPI Flash能够有效地扩展程序和数据存储空间.W25Q32JV SPI Flash是一种常用的Fla ...
- STM32F103标准库开发---SPI实验---W25Qxx系列外部Flash芯片
STM32F103标准库开发----目录 STM32F103标准库开发----SPI实验----基本原理 STM32F103标准库开发----SPI实验----底层驱动程序 W25Qxx全系列---- ...
- ESP32系列:三线SPI硬件设计及程序驱动设计(SPI FLASH为例)
(1)三线SPI硬件设计 首先,ESP32是支持三线制和四线制的,三线制为半双工通信,四线制为全双工通信.如果你们用的芯片不支持三线制,就不要再折腾了,支不支持三线制,看芯片的datasheet.如图 ...
最新文章
- 二维码Data Matrix的解码实现(zxing-cpp)
- MVC3 基本业务开发框架(强转)
- 边缘计算 — 业务的需求特征
- 【C 语言】二级指针作为输入 ( 二维数组 | 二维数组遍历 | 二维数组排序 )
- 中国证券期货业南方信息技术中心二期约1.5万个机柜建设项目EPC总包定了!
- html5相关笔记(一)
- Equipment upload - ERP ACK
- base64编码以及url safe base64是怎么工作的?
- oracle已经有了注释符再注释,关于oracle的注释位置
- 【SPI】java基础之SPI框架实现
- vue 列表 萌层 鼠标移入移出_vue鼠标移入添加class样式,鼠标移出去除样式(active)实现方法...
- LUOGU P3919 【模板】可持久化数组(主席树)
- Yii Framework2.0开发教程(4)在yii中定义全局变量
- 快速搭建pgadmin4环境
- 基于JavaWeb的会议室预约管理系统
- b站React禹哥版视频笔记-React应用(基于react脚手架)
- IS_ERR()宏是什么意思
- 迅为iTOP-RK3568开发板使用手册目录
- 《Python编程无师自通》挑战练习答案
- 除了四大“门派”菌,一文了解肠道菌群的其它17个小众“门派”细菌
热门文章
- K线形态识别_旭日东升
- 机器人出魔切还是三相_卡莎出岚切还是魔切?职业选手给出答案,原来我们一直都出错了!...
- 20160301.CCPP体系详解(0040天)
- android invalidate 不刷新,浅谈Android invalidate 分析
- 单链表创建的LinkList L与LinkList *L区分的问题
- 39.在Java中定义一个不做事且没有参数的构造方法的作用?
- chrome浏览器如何跟踪新开标签的网络请求?
- 话题 | 研究生期间专心搞科研 or 兼职?
- excel插入行 uipath_【RPA之家UiPath官方教程】Excel和数据表
- 谷歌地图不可信 发言人称无法证明失联飞机位置