| 项目 | 大小/数量 | 说明 |
| 总容量 | 64 Mbit = 8 MByte = 8,388,608 B | 地址范围 0x000000 ~ 0x7FFFFF |
| 地址位宽 | 24 位 | 最大 16 MB 地址空间,实际用 8 MB |
| 页 Page | 256 B | 共 32768 页,页是最小编程单位 |
| 扇区 Sector | 4 KB = 4096 B | 共 2048 个扇区,最小擦除单位 |
| 块 Block | 64 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 |
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;
}
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);
}
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;
}
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_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_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_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 操作 | W25Q64 驱动 |
read | w25q64_read |
prog | w25q64_page_program |
erase | w25q64_sector_erase |
sync | w25q64_wait_ready |
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 :访问长度这个函数检查:
block 不能超过块总数。
offset 不能超过一个块的大小。
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;
}