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%,某些地方应该还存在优化空间

posted @ 2024-12-24 16:20  shumei52  阅读(46)  评论(0编辑  收藏  举报