stm32 移植 flashDB 驱动外部flash(spi和qspi)

发布时间:2026/9/1 9:01:56
stm32 移植 flashDB 驱动外部flash(spi和qspi) 什么是flashDBflashDB源自于easyFlash是一套可靠读写flash的库让你操作更容易。为什么可靠怎么就更容易我们都知道flash是有写入寿命的包括现在的固态硬盘以w25q128为例擦写寿命10万次而且每次修改写入新的数值都要擦除整个扇区然后写入整个页。聪明的工程师就想到一个办法就是让每个扇区每个页都雨露均沾。这样就延长了整个flash的使用寿命。如果是使用过“挣点元子”的flash操作会发现每次修改写入新的值其做法就是将数据从flash中读取到RAM中然后擦除flash再写入。如果在擦除的过程中意外断电你的数据就无了。这就是所谓的“可靠”怎么就更容易呢你在读写flash的时候有没有因为分配存储空间而动过脑子呢假如说你中间的数据长度要增加了你是否写过自动分配存储空间的程序呢而flashDB是通过你自己定义的字符串作为地址以后的曾删改查都使用这个字符串。SFUD链接SFUD: 一款使用 JEDEC SFDP 标准的串行 (SPI) Flash 通用驱动库FlashDB链接FlashDB: 一款支持 KV 数据和时序数据的超轻量级数据库移植SFUD为什么要移植SFUD因为我们使用的是外部flash各种型号还有可能后期扩容更换型号。而SFUD兼容很多flash不需要你二次修改代码。就像电脑插不同硬盘都能兼容一样。而SFUD你只需要把你的spi读写接口给他或者QSPI都可以下面我们两种都会提到。必要步骤1. 把sfud这个目录复制到自己工程中即可2. 开始移植打开sfud_cfg.h修改一下内容3. 打开sfud_port.cSPI接口移植3.1. spi接口移植代码下面是修改好的#include sfud.h #include stdarg.h #include stdio.h #include main.h #include string.h void sfud_log_info(const char *format, ...); extern SPI_HandleTypeDef hspi4; extern DMA_HandleTypeDef hdma_spi4_tx; extern DMA_HandleTypeDef hdma_spi4_rx; typedef struct { SPI_TypeDef *spix; SPI_HandleTypeDef *spi_handle; GPIO_TypeDef *cs_gpiox; uint16_t cs_gpio_pin; } spi_user_data, *spi_user_data_t; static char log_buf[256]; void sfud_log_debug(const char *file, const long line, const char *format, ...); static void spi_lock(const sfud_spi *spi) { // __disable_irq(); } static void spi_unlock(const sfud_spi *spi) { // __enable_irq(); } /** * SPI write data then read data */ static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf, size_t read_size) { sfud_err result SFUD_SUCCESS; spi_user_data_t spi_dev (spi_user_data_t) spi-user_data; HAL_StatusTypeDef state HAL_OK; // spi_user_data_t spi_dev (spi_user_data_t) spi-user_data; if (write_size) { SFUD_ASSERT(write_buf); } if (read_size) { SFUD_ASSERT(read_buf); } HAL_GPIO_WritePin(spi_dev-cs_gpiox, spi_dev-cs_gpio_pin, GPIO_PIN_RESET); if (write_size) { state HAL_SPI_Transmit(spi_dev-spi_handle, (uint8_t *)write_buf, write_size, 1000); while (HAL_SPI_GetState(spi_dev-spi_handle) ! HAL_SPI_STATE_READY); } if (state ! HAL_OK) { goto __exit; } if (read_size) { memset((uint8_t *)read_buf, 0xFF, read_size); state HAL_SPI_Receive(spi_dev-spi_handle, read_buf, read_size, 1000); while (HAL_SPI_GetState(spi_dev-spi_handle) ! HAL_SPI_STATE_READY); } __exit: HAL_GPIO_WritePin(spi_dev-cs_gpiox, spi_dev-cs_gpio_pin, GPIO_PIN_SET); return result; } /* about 100 microsecond delay */ static void retry_delay_100us(void) { uint32_t delay 2400; while (delay--); } static spi_user_data spi4 { .spix SPI4, .cs_gpiox SPI4_CS_GPIO_Port, .cs_gpio_pin SPI4_CS_Pin, .spi_handle hspi4}; sfud_err sfud_spi_port_init(sfud_flash *flash) { sfud_err result SFUD_SUCCESS; switch (flash-index) { case SFUD_W25_DEVICE_INDEX: { /* set the interfaces and data */ flash-spi.wr spi_write_read; flash-spi.lock spi_lock; flash-spi.unlock spi_unlock; flash-spi.user_data spi4; /* about 100 microsecond delay */ flash-retry.delay retry_delay_100us; /* adout 60 seconds timeout */ flash-retry.times 60 * 10000; break; } } return result; } /** * This function is print debug info. * * param file the file which has call this function * param line the line number which has call this function * param format output format * param ... args */ void sfud_log_debug(const char *file, const long line, const char *format, ...) { va_list args; /* args point to the first variable parameter */ va_start(args, format); printf([SFUD](%s:%ld) , file, line); /* must use vprintf to print */ vsnprintf(log_buf, sizeof(log_buf), format, args); printf(%s\r\n, log_buf); va_end(args); } /** * This function is print routine info. * * param format output format * param ... args */ void sfud_log_info(const char *format, ...) { va_list args; /* args point to the first variable parameter */ va_start(args, format); printf([SFUD]); /* must use vprintf to print */ vsnprintf(log_buf, sizeof(log_buf), format, args); printf(%s\r\n, log_buf); va_end(args); }1spi_lock函数其实用不到这是怕正在写入的时候被中断打断但是我们不需要2spi_write_read函数中写入spi相关的代码这部分代码参考了demo3sfud_spi_port_init下面的内容就是我们的接口函数映射到SFUD了4static spi_user_data spi4 { .spix SPI4, .cs_gpiox SPI4_CS_GPIO_Port, .cs_gpio_pin SPI4_CS_Pin, .spi_handle hspi4};中给定你的cs引脚代码参考的路径QSPI接口移植3.2 qspi接口移植代码由于qspi内容会多一点点恰好官方的demo也有那我们就直接使用他们的demo将这里的sfud_port.c替换掉我们工程中的然后我们需要修改一部分的代码#include sfud.h #include stdarg.h #include stdio.h #include main.h #include string.h void sfud_log_info(const char *format, ...); sfud_err qspi_send_then_recv(const void *send_buf, size_t send_length, void *recv_buf, size_t recv_length); extern QSPI_HandleTypeDef hqspi; typedef struct { QSPI_HandleTypeDef *spix; GPIO_TypeDef *cs_gpiox; uint16_t cs_gpio_pin; } spi_user_data, *spi_user_data_t; static char log_buf[256]; void sfud_log_debug(const char *file, const long line, const char *format, ...); static void spi_lock(const sfud_spi *spi) { // __disable_irq(); } static void spi_unlock(const sfud_spi *spi) { // __enable_irq(); } /** * SPI write data then read data */ static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf, size_t read_size) { sfud_err result SFUD_SUCCESS; spi_user_data_t spi_dev (spi_user_data_t) spi-user_data; if (write_size) { SFUD_ASSERT(write_buf); } if (read_size) { SFUD_ASSERT(read_buf); } /* reset cs pin */ if (spi_dev-cs_gpiox ! NULL) HAL_GPIO_WritePin(spi_dev-cs_gpiox, spi_dev-cs_gpio_pin, GPIO_PIN_RESET); if (write_size read_size) { /* read data */ qspi_send_then_recv(write_buf, write_size, read_buf, read_size); } else if (write_size) { /* send data */ qspi_send_then_recv(write_buf, write_size, NULL, (uint16_t)(uintptr_t)NULL); } /* set cs pin */ if (spi_dev-cs_gpiox ! NULL) HAL_GPIO_WritePin(spi_dev-cs_gpiox, spi_dev-cs_gpio_pin, GPIO_PIN_SET); return result; } /** * QSPI fast read data */ static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format, uint8_t *read_buf, size_t read_size) { sfud_err result SFUD_SUCCESS; QSPI_CommandTypeDef Cmdhandler; extern QSPI_HandleTypeDef hqspi; /* 指令阶段配置 */ Cmdhandler.Instruction qspi_read_cmd_format-instruction;// 设置读取指令码 // 根据配置选择指令传输模式 switch(qspi_read_cmd_format-instruction_lines) { case 0: Cmdhandler.InstructionMode QSPI_INSTRUCTION_NONE; break; // 无指令阶段 case 1: Cmdhandler.InstructionMode QSPI_INSTRUCTION_1_LINE; break; // 单线传输 case 2: Cmdhandler.InstructionMode QSPI_INSTRUCTION_2_LINES; break; // 双线传输 case 4: Cmdhandler.InstructionMode QSPI_INSTRUCTION_4_LINES; break; // 四线传输Quad模式 } /* 地址阶段配置 */ Cmdhandler.Address addr; // 设置读取起始地址 Cmdhandler.AddressSize QSPI_ADDRESS_24_BITS; // 固定使用24位地址支持16MB空间 // 根据配置选择地址传输模式 switch(qspi_read_cmd_format-address_lines) { case 0: Cmdhandler.AddressMode QSPI_ADDRESS_NONE; break; // 无地址阶段 case 1: Cmdhandler.AddressMode QSPI_ADDRESS_1_LINE; break; // 单线传输 case 2: Cmdhandler.AddressMode QSPI_ADDRESS_2_LINES; break; // 双线传输 case 4: Cmdhandler.AddressMode QSPI_ADDRESS_4_LINES; break; // 四线传输Quad模式 } /* 高级模式配置 */ Cmdhandler.SIOOMode QSPI_SIOO_INST_EVERY_CMD; // 每次发送指令非仅首次 Cmdhandler.AlternateByteMode QSPI_ALTERNATE_BYTES_NONE; // 禁用交替字节 Cmdhandler.DdrMode QSPI_DDR_MODE_DISABLE; // 禁用双倍数据率模式 Cmdhandler.DdrHoldHalfCycle QSPI_DDR_HHC_ANALOG_DELAY; // DDR时钟延迟配置 Cmdhandler.AlternateBytes 0; // 交替字节内容清零 Cmdhandler.AlternateBytesSize 0; // 交替字节长度为0 Cmdhandler.DummyCycles qspi_read_cmd_format-dummy_cycles;// 设置空周期数Flash准备时间 /* 数据阶段配置 */ Cmdhandler.NbData read_size; // 设置要读取的数据长度 // 根据配置选择数据传输模式 switch(qspi_read_cmd_format-data_lines) { case 0: Cmdhandler.DataMode QSPI_DATA_NONE; break; // 无数据传输 case 1: Cmdhandler.DataMode QSPI_DATA_1_LINE; break; // 单线传输SPI case 2: Cmdhandler.DataMode QSPI_DATA_2_LINES;break; // 双线传输Dual SPI case 4: Cmdhandler.DataMode QSPI_DATA_4_LINES;break; // 四线传输Quad SPI } /* 命令执行阶段 */ // 发送配置好的QSPI命令5秒超时 HAL_QSPI_Command(hqspi, Cmdhandler, 5000); /* 数据接收阶段 */ // 接收数据到缓冲区5秒超时 if (HAL_QSPI_Receive(hqspi, read_buf, 5000) ! HAL_OK) { // 接收失败处理 sfud_log_info(QSPI receive failed! Error code: 0x%X, hqspi.ErrorCode); hqspi.State HAL_QSPI_STATE_READY; // 重置QSPI状态机 result SFUD_ERR_READ; // 返回读取错误 } return result; } /* about 100 microsecond delay */ static void retry_delay_100us(void) { uint32_t delay 2400; while (delay--); } static spi_user_data spi1 { .spix hqspi, .cs_gpiox QSPI_CS_GPIO_Port, .cs_gpio_pin QSPI_CS_Pin}; sfud_err sfud_spi_port_init(sfud_flash *flash) { sfud_err result SFUD_SUCCESS; switch (flash-index) { case SFUD_W25_DEVICE_INDEX: { /* set the interfaces and data */ flash-spi.wr spi_write_read; flash-spi.qspi_read qspi_read; flash-spi.lock spi_lock; flash-spi.unlock spi_unlock; flash-spi.user_data spi1; /* about 100 microsecond delay */ flash-retry.delay retry_delay_100us; /* adout 60 seconds timeout */ flash-retry.times 60 * 10000; break; } } return result; } /** * This function is print debug info. * * param file the file which has call this function * param line the line number which has call this function * param format output format * param ... args */ void sfud_log_debug(const char *file, const long line, const char *format, ...) { va_list args; /* args point to the first variable parameter */ va_start(args, format); printf([SFUD](%s:%ld) , file, line); /* must use vprintf to print */ vsnprintf(log_buf, sizeof(log_buf), format, args); printf(%s\r\n, log_buf); va_end(args); } /** * This function is print routine info. * * param format output format * param ... args */ void sfud_log_info(const char *format, ...) { va_list args; /* args point to the first variable parameter */ va_start(args, format); printf([SFUD]); /* must use vprintf to print */ vsnprintf(log_buf, sizeof(log_buf), format, args); printf(%s\r\n, log_buf); va_end(args); } /** * This function can send or send then receive QSPI data. */ sfud_err qspi_send_then_recv(const void *send_buf, size_t send_length, void *recv_buf, size_t recv_length) { // assert_param(send_buf); // assert_param(recv_buf); // assert_param(send_length ! 0); QSPI_CommandTypeDef Cmdhandler; unsigned char *ptr (unsigned char *)send_buf; size_t count 0; sfud_err result SFUD_SUCCESS; /* get instruction */ Cmdhandler.Instruction ptr[0]; Cmdhandler.InstructionMode QSPI_INSTRUCTION_1_LINE; count; /* get address */ if (send_length 1) { if (send_length 4) { /* address size is 3 Byte */ Cmdhandler.Address (ptr[1] 16) | (ptr[2] 8) | (ptr[3]); Cmdhandler.AddressSize QSPI_ADDRESS_24_BITS; count 3; } else { return SFUD_ERR_READ; } Cmdhandler.AddressMode QSPI_ADDRESS_1_LINE; } else { Cmdhandler.Address 0 ; Cmdhandler.AddressMode QSPI_ADDRESS_NONE; Cmdhandler.AddressSize 0; } Cmdhandler.AlternateBytes 0; Cmdhandler.AlternateByteMode QSPI_ALTERNATE_BYTES_NONE; Cmdhandler.AlternateBytesSize 0; Cmdhandler.SIOOMode QSPI_SIOO_INST_EVERY_CMD; Cmdhandler.AlternateByteMode QSPI_ALTERNATE_BYTES_NONE; Cmdhandler.DdrMode QSPI_DDR_MODE_DISABLE; Cmdhandler.DdrHoldHalfCycle QSPI_DDR_HHC_ANALOG_DELAY; if (send_buf recv_buf) { /* recv data */ /* set dummy cycles */ if (count ! send_length) { Cmdhandler.DummyCycles (send_length - count) * 8; // } else { Cmdhandler.DummyCycles 0; } /* set recv size */ Cmdhandler.DataMode QSPI_DATA_1_LINE; Cmdhandler.NbData recv_length; HAL_QSPI_Command(hqspi, Cmdhandler, 5000); if (recv_length ! 0) { if (HAL_QSPI_Receive(hqspi, recv_buf, 5000) ! HAL_OK) { sfud_log_info(qspi recv data failed(%d)!, hqspi.ErrorCode); hqspi.State HAL_QSPI_STATE_READY; result SFUD_ERR_READ; } } return result; } else { /* send data */ /* set dummy cycles */ Cmdhandler.DummyCycles 0; /* determine if there is data to send */ if (send_length - count 0) { Cmdhandler.DataMode QSPI_DATA_1_LINE; } else { Cmdhandler.DataMode QSPI_DATA_NONE; } /* set send buf and send size */ Cmdhandler.NbData send_length - count; HAL_QSPI_Command(hqspi, Cmdhandler, 5000); if (send_length - count 0) { if (HAL_QSPI_Transmit(hqspi, (uint8_t *)(ptr count), 5000) ! HAL_OK) { sfud_log_info(qspi send data failed(%d)!, hqspi.ErrorCode); hqspi.State HAL_QSPI_STATE_READY; result SFUD_ERR_WRITE; } } return result; } }(1)首先屏蔽spi_lock和spi_unlock内部的程序2static spi_user_data spi1 { .spix hqspi, .cs_gpiox NULL, .cs_gpio_pin NULL };中给定你的cs引脚3qspi_read函数中的中文注释是我用AI生成的结构更好看一点编译测试4.之后你就可以调用demo中的程序查看是否移植成功打印下面内容就是成功了移植FlashDBFlashDB 底层的 Flash 管理及操作依赖于 RT-Thread 的 FAL (Flash Abstraction Layer) Flash 抽象层开源软件包 该开源库也支持运行在裸机平台。所以只需要将所用到的 Flash 对接到FAL即可完成整个移植工作。1.将其添加到工程中2.进入到port-fal-samples-porting目录中将蓝色部分删除3.进入到inc目录将fdb_cfg_template.h更名为fdb_cfg.h4.在FDB_WRITE_GRAN后面写一个“1”。注释说明了1为外部闪存5.进入到fal_cfg.h将下面这两个头文件删除我们没有使用RT-Thread操作系统目录5.做一些定义#ifndef _FAL_CFG_H_ #define _FAL_CFG_H_ #define FAL_PART_HAS_TABLE_CFG #define NOR_FLASH_DEV_NAME norflash0 /* Flash device Configuration */ extern struct fal_flash_dev nor_flash0; /* flash device table */ #define FAL_FLASH_DEV_TABLE \ { \ nor_flash0, \ } /* Partition Configuration */ #ifdef FAL_PART_HAS_TABLE_CFG /* partition table */ #define FAL_PART_TABLE \ { \ {FAL_PART_MAGIC_WORD, KVDB, NOR_FLASH_DEV_NAME, 0, 1*1024*1024, 0}, \ {FAL_PART_MAGIC_WORD, TSDB, NOR_FLASH_DEV_NAME, 1*1024*1024, 2*1024*1024, 0}, \ }// 固定标识 分区名 所属设备 起始地址 分区大小 权限默认0 #endif /* FAL_PART_HAS_TABLE_CFG */ #endif /* _FAL_CFG_H_ */6.打开fal_flash_sfud_port.c文件这个函数是初始化的部分我们将黄色框中写入我们需要flash初始化的部分也就是SFUD初始化程序static int init(void) { #ifdef RT_USING_SFUD /* RT-Thread RTOS platform */ sfud_dev rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME); #else /* bare metal platform */ if (sfud_init() SFUD_SUCCESS) { sfud_flash *sfud_norflash0 sfud_get_device(SFUD_W25_DEVICE_INDEX); sfud_dev sfud_norflash0; } #endif if (NULL sfud_dev) { return -1; } /* update the flash chip information */ nor_flash0.blk_size sfud_dev-chip.erase_gran; nor_flash0.len sfud_dev-chip.capacity; return 0; }这里面主要部分就是#else后面的首先if (sfud_init() SFUD_SUCCESS)判断sfud是否初始化成功如果成功就sfud_get_device(SFUD_W25_DEVICE_INDEX);获取sfud设备信息这个信息就是我们在sfud_cfg.h中的索引如果你用的QSPI就用下面的程序if (sfud_init() SFUD_SUCCESS) { /* enable qspi fast read mode, set four data lines width */ sfud_dev sfud_get_device(SFUD_W25_DEVICE_INDEX); sfud_qspi_fast_read_enable(sfud_dev, 4); }9.编译通过10.将demo中测试程序添加进来注意flashDB下的samples目录是demo程序你需要添加他的头文件路径打印下面的内容就是移植成功了本文简化了添加头文件路径引用头文件demo程序等基础操作如果你有移植不成功的话咱们可以私信交流