加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 1 硬件介绍
    • 2 软件开发
    • 3 运行测试
    • 4 读写速度测试
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

Arduino应用开发——spi flash(以esp32和w25qxx为例)

10/25 14:22
5341
阅读需 34 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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 W25Q128GD25Q64 说明
VCC VCC 电源正,3.3V
GND GND 电源负
GPIO12SPI_CLK CLK SPI时钟线
GPIO11SPI_MOSI DI SPI数据线,esp32s2输出,flash输入
GPIO13SPI_MISO DO SPI数据线,esp32s2输入,flash输出
GPIO10SPI_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 init
    pinMode(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_SPI
    SPI.begin(NORFLASH_CLK_PIN, NORFLASH_MISO_PIN, NORFLASH_MOSI_PIN, NORFLASH_CS_PIN);
    SPI.setDataMode(SPI_MODE0);
    SPI.setBitOrder(MSBFIRST);
    SPI.setFrequency(SPI_FREQUENCY);
#else
    pinMode(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 status
    uint8_t data = 0;
    write_enable();                
    data = read_status();
#ifdef NORFLASH_DEBUG_ENABLE
    Serial.printf("norflash write enable status:");
    Serial.println(data, BIN);
#endif

    // read device id
    uint16_t device_id = 0;
    device_id = read_norflash_id();
#ifdef NORFLASH_DEBUG_ENABLE
    Serial.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_SPI
    for(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_SPI
    uint8_t data = 0;
    data = SPI.transfer(tx_data);
    return data;
#else if SOFTWARE_SPI
    uint8_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_ENABLE
        Serial.printf("status = 0, flash is busy of %sn", 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_ENABLE
        Serial.print("head:");
        Serial.println(head);
    #endif
        write_one_block_data(addr, pbuf, head);
        pbuf = pbuf + head;
        addr = addr + head;
    }
    if(page != 0) 
    {
    #ifdef NORFLASH_DEBUG_ENABLE
        Serial.print("page:");
        Serial.println(page);
    #endif
        for(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_ENABLE
        Serial.print("remain:");
        Serial.println(remain);
    #endif
        write_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 offset
	secremain = 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 data
		for(i = 0; i < secremain; i++)
		{// check data, if all data is 0xFF no need erase sector
			if(save_buffer[secoff + i] != 0XFF)
            {// need erase sector
                break;	  
            }
		}
		if(i < secremain)
		{// erase sector and write data
			sector_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 sector
            write_one_sector_data(addr, write_buf, secremain);	
        }			   
		if(len == secremain)
        {// write done
            break; 
        }
		else
		{// continue write
			secpos ++;  // sector number + 1
			secoff = 0; // sector offset = 0 	 

		   	write_buf += secremain;  // write buff offset
			addr += secremain;       // addr offset	   
		   	len -= secremain;		 // remaining data len
			if(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 byte
    data = read_byte(0);
    device_id |= (data << 8);  // high byte
    digitalWrite(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 0
    for(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 sn", 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 sn", 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的内容,可以关注一下博主,后续我还会继续分享更多的经验给大家。

相关推荐

电子产业图谱