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为例)相关推荐

  1. STM32F4 HAL库开发 -- SPI Flash

    一.驱动 bsp_spi_flash.c #include "THC_Board_include_h.h"/* Private define ------------------- ...

  2. 如何给ESP32选择外接SPI Flash

    给ESP32选择外接SPI Flash [前言]:  许多用户在使用 ESP32 做开发的时候,都会碰到如何选择外接 Flash 的难题,因为不同厂商的 Flash 存在很多差异,Flash 支持的工 ...

  3. WT32-SC01是ESP32驱动3.5彩屏开发板方案适合用arduino方式开发吗?因为需要彩屏和电容触摸的驱动的

    ESP32驱动3.5寸彩屏开发板方案因为带有彩屏和电容触摸的驱动,能否用arduino方式开发,这是很多熟悉arduino开发的技术人员关心的问题. 目前启明云端推出的http://esp32.8ms ...

  4. esp8266,esp32中的SPI FLASH 访问模式(QIO QOUT DIO DOUT)

    本文 ESP8266 和 ESP32 支持四种不同的 SPI flash 访问模式:DIO.DOUT.QIO 和 QOUT. 这些可以通过 esptool.py write_flash 的 --fla ...

  5. (实测可用)STM32CubeMX教程-STM32L431RCT6开发板研究串口通信(SPI flash)

    一.开发板平台简介: 1.开发板资源简介 (1)开发板主芯片型号:STM32L431RCT6 (2)开发板主芯片封装:LQFP-64_10x10x05P (3)开发板主芯片内核:ARM® Cortex ...

  6. STM32开发 -- W25Q32JV SPI FlASH详解

    如需转载请注明出处:https://juyou.blog.csdn.net/article/details/103168687 flash这部分也是很重要的一部分了. 我们将利用 STM32F1 自带 ...

  7. STM32开发实战:W25Q32JV SPI Flash详解

    STM32开发实战:W25Q32JV SPI Flash详解 在STM32单片机的应用中,使用SPI Flash能够有效地扩展程序和数据存储空间.W25Q32JV SPI Flash是一种常用的Fla ...

  8. STM32F103标准库开发---SPI实验---W25Qxx系列外部Flash芯片

    STM32F103标准库开发----目录 STM32F103标准库开发----SPI实验----基本原理 STM32F103标准库开发----SPI实验----底层驱动程序 W25Qxx全系列---- ...

  9. ESP32系列:三线SPI硬件设计及程序驱动设计(SPI FLASH为例)

    (1)三线SPI硬件设计 首先,ESP32是支持三线制和四线制的,三线制为半双工通信,四线制为全双工通信.如果你们用的芯片不支持三线制,就不要再折腾了,支不支持三线制,看芯片的datasheet.如图 ...

最新文章

  1. 二维码Data Matrix的解码实现(zxing-cpp)
  2. MVC3 基本业务开发框架(强转)
  3. 边缘计算 — 业务的需求特征
  4. 【C 语言】二级指针作为输入 ( 二维数组 | 二维数组遍历 | 二维数组排序 )
  5. 中国证券期货业南方信息技术中心二期约1.5万个机柜建设项目EPC总包定了!
  6. html5相关笔记(一)
  7. Equipment upload - ERP ACK
  8. base64编码以及url safe base64是怎么工作的?
  9. oracle已经有了注释符再注释,关于oracle的注释位置
  10. 【SPI】java基础之SPI框架实现
  11. vue 列表 萌层 鼠标移入移出_vue鼠标移入添加class样式,鼠标移出去除样式(active)实现方法...
  12. LUOGU P3919 【模板】可持久化数组(主席树)
  13. Yii Framework2.0开发教程(4)在yii中定义全局变量
  14. 快速搭建pgadmin4环境
  15. 基于JavaWeb的会议室预约管理系统
  16. b站React禹哥版视频笔记-React应用(基于react脚手架)
  17. IS_ERR()宏是什么意思
  18. 迅为iTOP-RK3568开发板使用手册目录
  19. 《Python编程无师自通》挑战练习答案
  20. 除了四大“门派”菌,一文了解肠道菌群的其它17个小众“门派”细菌

热门文章

  1. K线形态识别_旭日东升
  2. 机器人出魔切还是三相_卡莎出岚切还是魔切?职业选手给出答案,原来我们一直都出错了!...
  3. 20160301.CCPP体系详解(0040天)
  4. android invalidate 不刷新,浅谈Android invalidate 分析
  5. 单链表创建的LinkList L与LinkList *L区分的问题
  6. 39.在Java中定义一个不做事且没有参数的构造方法的作用?
  7. chrome浏览器如何跟踪新开标签的网络请求?
  8. 话题 | 研究生期间专心搞科研 or 兼职?
  9. excel插入行 uipath_【RPA之家UiPath官方教程】Excel和数据表
  10. 谷歌地图不可信 发言人称无法证明失联飞机位置