ESP32-QSPI-外部flash驱动
在某些情况下,有些资源过于大,所以需要使用外部flash保存数据
这篇文章使用ESP32S3作为主控,驱动读写外部flash
1.硬件连接
flash通常为8pin,最主要的线为这六根:
- MOSI GPIO11
- MISO GPIO13
- CLK 12
- HD 9
- WP 14
- CS 10
需要注意的是,flash的HD和WP引脚在不同的读写模式下,具有不同的功能
- HD引脚的默认功能用于暂停SPI通信
- WP用于保护Flash存储器的某些部分不被写入或擦除
通信接口:目前市场是常见的flash支持五种通信方式:
- SPI:最常用的通信方式,使用两根数据线进行双向传输
- DOUT:数据读取使用两根数据线
- DIO:数据和地址传输都是用两根数据线
- IO0(通常标记为 MOSI 或 D0): 数据线0
- IO1(通常标记为 MISO 或 D1): 数据线1
- QOUT:数据读取使用四根数据线
- QIO:数据和地址传输都是用四根数据线
- IO0(通常标记为 MOSI 或 D0): 数据线0
- IO1(通常标记为 MISO 或 D1): 数据线1
- IO2(通常标记为 WP 或 D2): 数据线2
- IO3(通常标记为 HOLD 或 D3): 数据线3
2.软件驱动
驱动使用IDF中的驱动库:esp_flash
,简单的配置相关接口,就可以进行读写操作了,ESP32中,模组内部的flash也使用的该组件
代码中,可以很方便的配置相关信息:连接引脚,时钟速率,读取模式,使用的SPI
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <unity.h>
#include "esp_flash.h"
#include "esp_private/spi_common_internal.h"
#include "esp_flash_spi_init.h"
#include "memspi_host_driver.h"
#include "spi_flash_mmap.h"
#include <esp_attr.h>
#include "esp_log.h"
#include "unity.h"
#include "driver/gpio.h"
#include "soc/io_mux_reg.h"
#include "sdkconfig.h"
#include "esp_partition.h"
#include "esp_rom_gpio.h"
#include "esp_rom_sys.h"
#include "esp_timer.h"
#include "spi_flash_mmap.h"
#include "esp_private/spi_flash_os.h"
#include "esp_timer.h"
#define TAG "SPI_FLASH"
typedef esp_flash_spi_device_config_t flashtest_config_t;
#define FSPI_PIN_NUM_MOSI 11
#define FSPI_PIN_NUM_MISO 13
#define FSPI_PIN_NUM_CLK 12
#define FSPI_PIN_NUM_HD 9
#define FSPI_PIN_NUM_WP 14
#define FSPI_PIN_NUM_CS 10
// Just use the same pins for HSPI
#define HSPI_PIN_NUM_MOSI FSPI_PIN_NUM_MOSI
#define HSPI_PIN_NUM_MISO FSPI_PIN_NUM_MISO
#define HSPI_PIN_NUM_CLK FSPI_PIN_NUM_CLK
#define HSPI_PIN_NUM_HD FSPI_PIN_NUM_HD
#define HSPI_PIN_NUM_WP FSPI_PIN_NUM_WP
#define HSPI_PIN_NUM_CS FSPI_PIN_NUM_CS
#define MAX_ADDR_24BIT 0x1000000
#define TEST_SPI_SPEED 80
#define TEST_SPI_READ_MODE SPI_FLASH_QIO
flashtest_config_t ext_flash_config = {
.io_mode = TEST_SPI_READ_MODE,
.freq_mhz = TEST_SPI_SPEED,
.host_id = SPI2_HOST,
.cs_id = 0,
.cs_io_num = FSPI_PIN_NUM_CS,
.input_delay_ns = 0,
};
static void setup_bus(spi_host_device_t host_id)
{
if (host_id == SPI2_HOST)
{
ESP_LOGI(TAG, "setup flash on SPI%u (FSPI) CS0...\n", host_id + 1);
spi_bus_config_t fspi_bus_cfg = {
.mosi_io_num = FSPI_PIN_NUM_MOSI,
.miso_io_num = FSPI_PIN_NUM_MISO,
.sclk_io_num = FSPI_PIN_NUM_CLK,
.quadhd_io_num = FSPI_PIN_NUM_HD,
.quadwp_io_num = FSPI_PIN_NUM_WP,
.max_transfer_sz = 16 * 1024,
};
esp_err_t ret = spi_bus_initialize(host_id, &fspi_bus_cfg, 0);
TEST_ESP_OK(ret);
}
#if SOC_SPI_PERIPH_NUM > 2
else if (host_id == SPI3_HOST)
{
ESP_LOGI(TAG, "setup flash on SPI%u (HSPI) CS0...\n", host_id + 1);
spi_bus_config_t hspi_bus_cfg = {
.mosi_io_num = HSPI_PIN_NUM_MOSI,
.miso_io_num = HSPI_PIN_NUM_MISO,
.sclk_io_num = HSPI_PIN_NUM_CLK,
.quadhd_io_num = HSPI_PIN_NUM_HD,
.quadwp_io_num = HSPI_PIN_NUM_WP,
.max_transfer_sz = 16 * 1024,
};
esp_err_t ret = spi_bus_initialize(host_id, &hspi_bus_cfg, 0);
TEST_ESP_OK(ret);
// HSPI have no multiline mode, use GPIO to pull those pins up
gpio_set_direction(HSPI_PIN_NUM_HD, GPIO_MODE_OUTPUT);
gpio_set_level(HSPI_PIN_NUM_HD, 1);
gpio_set_direction(HSPI_PIN_NUM_WP, GPIO_MODE_OUTPUT);
gpio_set_level(HSPI_PIN_NUM_WP, 1);
}
#endif
else
{
ESP_LOGE(TAG, "invalid bus");
}
}
static void setup_new_chip(const flashtest_config_t *test_cfg, esp_flash_t **out_chip)
{
setup_bus(test_cfg->host_id);
esp_flash_spi_device_config_t dev_cfg = {
.host_id = test_cfg->host_id,
.io_mode = test_cfg->io_mode,
.freq_mhz = test_cfg->freq_mhz,
.cs_id = test_cfg->cs_id,
.cs_io_num = test_cfg->cs_io_num,
.input_delay_ns = test_cfg->input_delay_ns,
};
esp_flash_t *init_chip;
esp_err_t err = spi_bus_add_flash_device(&init_chip, &dev_cfg);
if (err != ESP_OK)
{
printf("error in spi bus init:%d\n", err);
return;
}
err = esp_flash_init(init_chip);
if (err != ESP_OK)
{
printf("error in esp flash init:%d\n", err);
return;
}
*out_chip = init_chip;
}
void write_erase_read_time_test(esp_flash_t *chip, uint32_t size)
{
printf("\ntest start...\n");
printf("test size:%ld\n", size);
long long start_time, end_time;
uint8_t *write_buffer = malloc(size);
uint8_t *read_buffer = malloc(size);
// 写入部分测试值
for (int i = 0; i < size; i++)
{
write_buffer[i] = i;
}
start_time = esp_timer_get_time();
esp_flash_erase_region(chip, 0, size);
end_time = esp_timer_get_time();
printf("erase time:%lluus\n", end_time - start_time);
start_time = esp_timer_get_time();
esp_flash_write(chip, write_buffer, 0, size);
end_time = esp_timer_get_time();
printf("write time:%lluus\n", end_time - start_time);
start_time = esp_timer_get_time();
esp_flash_read(chip, read_buffer, 0, size);
end_time = esp_timer_get_time();
printf("read time:%lluus\n", end_time - start_time);
printf("test end...\n\n");
free(write_buffer);
free(read_buffer);
}
#define TEST_DATA_SIZE 1024
void app_main(void)
{
esp_flash_t *chip;
setup_new_chip(&ext_flash_config, &chip);
uint32_t size;
esp_err_t err = esp_flash_get_size(chip, &size);
if (err != ESP_OK)
{
printf("error in esp flash get size :%d\n", err);
return;
}
printf("get size:%ld\n", size);
write_erase_read_time_test(chip, 1 * TEST_DATA_SIZE);
write_erase_read_time_test(chip, 2 * TEST_DATA_SIZE);
write_erase_read_time_test(chip, 4 * TEST_DATA_SIZE);
write_erase_read_time_test(chip, 8 * TEST_DATA_SIZE);
write_erase_read_time_test(chip, 16 * TEST_DATA_SIZE);
}
3.性能实际测试
时钟速率为20、40、80Mhz,使用QIO模式,分别对1、2、4、8、16KB文件大小进行读写测试,结果如下
写/读测试(us) | 20Mhz | 40Mhz | 80Mhz |
---|---|---|---|
1KB | 3711/213 | 3453/154 | 3069/124 |
2KB | 3634/408 | 3120/289 | 2431/228 |
4KB | 16787/809 | 15806/560 | 16778/440 |
8KB | 196421/1584 | 17545/1105 | 16426/864 |
16KB | 42919/3155 | 39205/2192 | 36918/1713 |
出于好奇,我也测试了一下SPI与QSPI的速度差距,但是只测试了80Mhz时钟速率下的:
SPI 写/读测试(us) | 80Mhz |
---|---|
1KB | 3056/206 |
2KB | 2852/392 |
4KB | 13440/768 |
8KB | 16411/1521 |
16KB | 35974/3024 |
可以看到,虽然QSPI的数据理论上是SPI的四倍,但是实际测试下来,速度提升还不到两倍,没有想象中那么高
实际的QSPI的速率也太低了,按照16KB的速度折合下来也才10MB每秒,只有理论速度的25%,某些地方应该还存在优化空间