单片机:STM32F103C8T6
  • M3内核
  • 20KB的RAM
  • 64KB的ROM
存储设备:W25Q64(SPI通信)
项目大小/数量说明
总容量64 Mbit = 8 MByte = 8,388,608 B地址范围 0x000000 ~ 0x7FFFFF
地址位宽24 位最大 16 MB 地址空间,实际用 8 MB
页 Page256 B共 32768 页,页是最小编程单位
扇区 Sector4 KB = 4096 B共 2048 个扇区,最小擦除单位
块 Block64 KB = 65536 B共 128 个 64KB 块
32KB 块32 KB支持 32KB 块擦除,共 256 个
每扇区16 页4KB / 256B = 16
每 64KB 块16 个扇区 = 256 页64KB / 4KB = 16
每 32KB 块8 个扇区 = 128 页32KB / 4KB = 8

STM32 SPI初始化

static SPI_HandleTypeDef hspi1;

HAL_StatusTypeDef spi_bus_init(void)
{
    GPIO_InitTypeDef gpio = {0};

    __HAL_RCC_GPIOA_CLK_ENABLE(); // 开启时钟
    __HAL_RCC_SPI1_CLK_ENABLE(); // 开启时钟 

    gpio.Pin = GPIO_PIN_5 | GPIO_PIN_7; //SPI1的引脚
    gpio.Mode = GPIO_MODE_AF_PP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &gpio);

    gpio.Pin = GPIO_PIN_6; //SPI1的引脚
    gpio.Mode = GPIO_MODE_INPUT;
    gpio.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &gpio);

    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 7U;

    return HAL_SPI_Init(&hspi1);
}

SPI_HandleTypeDef *spi_bus_handle(void)
{
    return &hspi1;
}

读写W25Q64

选中/取消选中设备(片选)
static void select_device(const w25q64_t *dev)
{
    HAL_GPIO_WritePin(dev->cs_port, dev->cs_pin, GPIO_PIN_RESET);
}

static void deselect_device(const w25q64_t *dev)
{
    HAL_GPIO_WritePin(dev->cs_port, dev->cs_pin, GPIO_PIN_SET);
}
向w25q64发送和接收数据(封装SPI收发)
static w25q64_status_t transmit(w25q64_t *dev, const uint8_t *data, size_t size)
{
    HAL_StatusTypeDef status;

    status = HAL_SPI_Transmit(dev->spi, (uint8_t *)(uintptr_t)data,
                              (uint16_t)size, 100U); //超时时间
    return (status == HAL_OK) ? 0 : -1;//0成功,-1超时
}

static w25q64_status_t receive(w25q64_t *dev, uint8_t *data, size_t size)
{
    HAL_StatusTypeDef status;

    status = HAL_SPI_Receive(dev->spi, data, (uint16_t)size,100U);//超时时间
    return (status == HAL_OK) ? 0 : -1;//0成功,-1超时
}

读取状态寄存器

static w25q64_status_t read_status_1(w25q64_t *dev, uint8_t *status_reg)
{
    const uint8_t command = 0x05U;
    w25q64_status_t status;

    select_device(dev);//片选选中
    status = transmit(dev, &command, 1U);
    if (status == 0) {
        status = receive(dev, status_reg, 1U);
    }
    deselect_device(dev);//片选取消
    return status;
}

W25Q64写使能

static w25q64_status_t write_enable(w25q64_t *dev)
{
    const uint8_t command = 0x06U;
    w25q64_status_t status;

    select_device(dev); //片选选中
    status = transmit(dev, &command, 1U);
    deselect_device(dev); //片选取消
    return status;
}

初始化

w25q64_status_t w25q64_init(w25q64_t *dev,
                            SPI_HandleTypeDef *spi,
                            GPIO_TypeDef *cs_port,
                            uint16_t cs_pin)
{
    GPIO_InitTypeDef gpio = {0};
    const uint8_t wake_command = 0xABU; //唤醒w25q64
    w25q64_status_t status;
	//保存硬件信息
    dev->spi = spi;
    dev->cs_port = cs_port;
    dev->cs_pin = cs_pin;
    dev->jedec_id = 0U;
	//打开 CS GPIO 时钟
    if (cs_port == GPIOA) {
        __HAL_RCC_GPIOA_CLK_ENABLE();
    } else if (cs_port == GPIOB) {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    } else if (cs_port == GPIOC) {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    } else {
        return -1;
    }
	//配置 CS 引脚
    gpio.Pin = cs_pin;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_SET);
    HAL_GPIO_Init(cs_port, &gpio);

    select_device(dev);//片选选中
    status = transmit(dev, &wake_command, 1U);//唤醒
    deselect_device(dev);//片选取消
    if (status != 0) {
        return status;
    }
    HAL_Delay(1U);

    status = w25q64_wait_ready(dev, 100U);
    if (status != W25Q64_OK) {
        return status;
    }
    status = w25q64_read_jedec_id(dev, &dev->jedec_id);
    if (status != W25Q64_OK) {
        return status;
    }
    return (dev->jedec_id == W25Q64_EXPECTED_ID) ? W25Q64_OK : W25Q64_BAD_ID;
}

等待 w25q64空闲

w25q64_status_t w25q64_wait_ready(w25q64_t *dev, uint32_t timeout_ms)
{
    const uint32_t start = HAL_GetTick();
    uint8_t status_reg = 0U;
    w25q64_status_t status;

    do {
        status = read_status_1(dev, &status_reg);
        if (status != 0) {
            return status;
        }
        if ((status_reg & 0x01U) == 0U) {
            return 0;
        }
    } while ((HAL_GetTick() - start) < timeout_ms);

    return -1;
}

读取w25q64的JEDEC ID

w25q64_status_t w25q64_read_jedec_id(w25q64_t *dev, uint32_t *jedec_id)
{
    const uint8_t command = 0x9FU;
    uint8_t id[3] = {0};
    w25q64_status_t status;
    
    select_device(dev);//片选选中
    status = transmit(dev, &command, 1U);
    if (status == 0) {
        status = receive(dev, id, sizeof(id));
    }
    deselect_device(dev);//片选取消
    if (status == 0) {
        *jedec_id = ((uint32_t)id[0] << 16U) |
                    ((uint32_t)id[1] << 8U) |
                    (uint32_t)id[2];
    }
    return status;
}

读数据

w25q64_status_t w25q64_read(w25q64_t *dev, uint32_t address,
                            void *data, size_t size)
{
    uint8_t command[4];
    w25q64_status_t status;

    if ((dev == NULL) || ((data == NULL) && (size != 0U)) ||
        (address > 8UL * 1024UL * 1024UL) ||  //总大小
        (size > (size_t)(8UL * 1024UL * 1024UL - address))) {
        return W25Q64_BAD_PARAM; //判断读写的地址是否超出
    }
    if (size == 0U) {
        return 0;
    }

    command[0] = 0x03U; //读命令
    command[1] = (uint8_t)(address >> 16U);
    command[2] = (uint8_t)(address >> 8U);
    command[3] = (uint8_t)address;

    select_device(dev); //片选选中
    status = transmit(dev, command, sizeof(command));
    if (status == W25Q64_OK) {
        status = receive(dev, (uint8_t *)data, size);
    }
    deselect_device(dev); //片选取消
    return status;
}

页编程(写数据) w25q64_page_program

w25q64_status_t w25q64_page_program(w25q64_t *dev, uint32_t address,
                                    const void *data, size_t size)
{
    uint8_t command[4];
    w25q64_status_t status;

    if ((dev == NULL) || (data == NULL) || (size == 0U) ||
        (size > 256UL) ||
        ((address & (256UL - 1U)) + size > 256UL) || //页大小
        (address >= 8UL * 1024UL * 1024UL) ||
        (size > (size_t)(W25Q64_TOTAL_SIZE - address))) {
        return -1;
    }

    status = w25q64_wait_ready(dev, 10U);
    if (status != 0) {
        return status;
    }
    status = write_enable(dev);
    if (status != 0) {
        return status;
    }

    command[0] = 0x02U;
    command[1] = (uint8_t)(address >> 16U);
    command[2] = (uint8_t)(address >> 8U);
    command[3] = (uint8_t)address;

    select_device(dev);
    status = transmit(dev, command, sizeof(command));
    if (status == 0) {
        status = transmit(dev, (const uint8_t *)data, size);
    }
    deselect_device(dev);
    if (status != 0) {
        return status;
    }
    return w25q64_wait_ready(dev, 10U);
}

扇区擦除

w25q64_status_t w25q64_sector_erase(w25q64_t *dev, uint32_t address)
{
    uint8_t command[4];
    w25q64_status_t status;

    if ((dev == NULL) || (address >= (8UL * 1024UL * 1024UL)) ||
        ((address & (4096UL - 1U)) != 0U)) { //扇区数量
        return -1;
    }

    status = w25q64_wait_ready(dev, 10u);
    if (status != 0) {
        return status;
    }
    status = write_enable(dev);
    if (status != 0) {
        return status;
    }

    command[0] = 0x20U; //4K擦除
    command[1] = (uint8_t)(address >> 16U);
    command[2] = (uint8_t)(address >> 8U);
    command[3] = (uint8_t)address;

    select_device(dev);
    status = transmit(dev, command, sizeof(command));
    deselect_device(dev);
    if (status != 0) {
        return status;
    }
    return w25q64_wait_ready(dev, 10U);
}

和 littlefs 的关系

littlefs 操作W25Q64 驱动
readw25q64_read
progw25q64_page_program
erasew25q64_sector_erase
syncw25q64_wait_ready

Littlefs配置

static uint8_t read_buffer[256UL]; //三个缓存
static uint8_t program_buffer[256UL];
static uint8_t lookahead_buffer[32U];
const struct lfs_config *lfs_port_config(w25q64_t *flash)
{
    static struct lfs_config config;

    config.context = flash; //
    config.read = port_read; //读回调
    config.prog = port_program; //写回调(编程)
    config.erase = port_erase; //擦除回调
    config.sync = port_sync; //异步回调
    config.read_size = 1U;
    config.prog_size = 256UL; //页大小
    config.block_size = 4096UL;//扇区大小
    config.block_count = (8UL * 1024UL * 1024UL) / 4096UL;//扇区数量
    config.block_cycles = 500;
    config.cache_size = 256UL; //缓存大小
    config.lookahead_size = 32U;
    config.compact_thresh = 0;
    config.read_buffer = read_buffer; //littlefs 读取缓存
    config.prog_buffer = program_buffer; //littlefs 写入缓存
    config.lookahead_buffer = lookahead_buffer; //空闲块查找位图
    config.name_max = 0U;
    config.file_max = 0U;
    config.attr_max = 0U;
    config.metadata_max = 0U;
    config.inline_max = 0U;

    return &config;
}

地址范围检测

littlefs 访问 Flash 时,不直接提供绝对地址,而是提供:

block  :逻辑块编号
offset :块内偏移
size   :访问长度这个函数检查:
  1. block 不能超过块总数。
  2. offset 不能超过一个块的大小。
  3. offset + size 不能越过当前块。
static bool range_is_valid(const struct lfs_config *cfg,
                           lfs_block_t block,
                           lfs_off_t offset,
                           lfs_size_t size)
{
    return (block < cfg->block_count) &&
           (offset <= cfg->block_size) &&
           (size <= (cfg->block_size - offset));
}

读回调

static int port_read(const struct lfs_config *cfg, lfs_block_t block,
                     lfs_off_t offset, void *buffer, lfs_size_t size)
{
    w25q64_t *flash = (w25q64_t *)cfg->context;
    uint32_t address;

    if (!range_is_valid(cfg, block, offset, size)) {
        return LFS_ERR_INVAL;
    }
    address = ((uint32_t)block * cfg->block_size) + offset;
    return (w25q64_read(flash, address, buffer, size) == 0)
               ? LFS_ERR_OK
               : LFS_ERR_IO;
}

写回调

static int port_program(const struct lfs_config *cfg, lfs_block_t block,
                        lfs_off_t offset, const void *buffer, lfs_size_t size)
{
    w25q64_t *flash = (w25q64_t *)cfg->context;
    uint32_t address;
    const uint8_t *source = (const uint8_t *)buffer;

    if (!range_is_valid(cfg, block, offset, size) ||
        ((offset % cfg->prog_size) != 0U) ||
        ((size % cfg->prog_size) != 0U)) {
        return LFS_ERR_INVAL;
    }

    address = ((uint32_t)block * cfg->block_size) + offset;
    while (size != 0U) {
        if (w25q64_page_program(flash, address, source,
                                256UL) != 0) {
            return LFS_ERR_IO;
        }
        address += 256UL;
        source += 256UL;
        size -= 256UL;
    }
    return LFS_ERR_OK;
}

擦除回调

static int port_erase(const struct lfs_config *cfg, lfs_block_t block)
{
    w25q64_t *flash = (w25q64_t *)cfg->context;
    uint32_t address;

    if (block >= cfg->block_count) {
        return LFS_ERR_INVAL;
    }
    address = (uint32_t)block * cfg->block_size;
    return (w25q64_sector_erase(flash, address) == 0)
               ? LFS_ERR_OK
               : LFS_ERR_IO;
}

异步回调

static int port_sync(const struct lfs_config *cfg)
{
    w25q64_t *flash = (w25q64_t *)cfg->context;
    return (w25q64_wait_ready(flash, 500U) == 0)
               ? LFS_ERR_OK
               : LFS_ERR_IO;
}