# flash_module **Repository Path**: posty_banana/flash_module ## Basic Information - **Project Name**: flash_module - **Description**: flash_module子模块 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2026-08-06 - **Last Updated**: 2026-09-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Post Flash HAL 模块说明 对底层 Flash 做统一封装:外部 Serial NOR(SPI / QSPI)与 MCU 片内 Flash 的读 / 写 / 擦除 / 同步。上层通过 `Post_Hal_Flash*` API 操作适配器;平台侧实现对应后端的 Bus / OnChip `Ops`,板级提供 NOR 命令集与 JEDEC 表,再完成绑定与初始化。 **库内只有算法与状态机,不含任何芯片 opcode / JEDEC 数据。** ## 1. 目录结构 | 路径 | 说明 | | ---------------------------------------------------- | -------------------------------------------------------- | | `inc/post_flash.h` | 对外 Flash API(adapter / 读写擦 / 同步 / 错误码) | | `inc/post_flash_factory.h` | 后端绑定:`OpenNorOverSpi` / `OpenNorOverQspi` / `OpenOnChip` | | `inc/post_flash_bus_spi.h` | SPI 总线 port(`SpiBusOps` / 总线上下文) | | `inc/post_flash_bus_qspi.h` | QSPI 总线 port(命令帧模型 `QspiBusCmd`) | | `inc/post_flash_nor_cmd.h` | NOR 命令集描述(板级填写 opcode / 状态位掩码) | | `inc/post_flash_nor_chip.h` | NOR JEDEC 芯片表描述(板级填写容量 / 页扇区块大小) | | `src/post_flash.c` | 通用适配器逻辑(参数检查、busy 状态、API 转发) | | `src/post_flash_nor.c` | 通用 Serial NOR 驱动(读写擦 / JEDEC 探测 / 4-byte addr) | | `src/post_flash_internal.c` | MCU 片内 Flash 后端(unlock / 页编程拆分 / 扇区擦) | | `src/post_flash_factory.c` | Factory 绑定入口 | | `src/post_flash_bus_spi.c` / `post_flash_bus_qspi.c` | 总线上下文初始化 | | `src/post_flash_nor_cmd.c` / `post_flash_nor_chip.c` | 命令集校验 / JEDEC 查表 | | `lib/post_common_type.h` | 公共类型、`POST_ERR`、内存宏等(可用 CMake 关掉,改用父工程) | | `lib/post_common_log.h` / `lib/post_common_log.c` | 最小日志(可用 CMake 关掉,改用父工程) | | `CMakeLists.txt` | CMake 工程入口 | | `Makefile` | 薄封装:`make` / `make rebuild` / `make test` / `make run` | | `test/config/` | 单测用 NOR 命令集与 JEDEC 表(产品侧参考 `board/` 写法) | | `test/mock/` | 板级 Ops 模拟(SPI / QSPI / 片内 Fake Flash) | | `test/tool/` | 夹具、建适配器工具、gtest 入口 | | `test/core/` | 单元测试用例(`TEST_F`) | | `test/example/` | 用法示例(可被 gtest 调用,也可当集成文档阅读) | 独立使用时带走:`inc/` + `src/` + `lib/`。作为子工程且父工程已有 type/log 时,可只带走 `inc/` + `src/`。 `inc/` 仅对外接口;`src/post_flash_nor.h`、`src/post_flash_internal.h` 为内部头文件,产品代码不要 include。 ## 2. 分层原则 | 层级 | 位置 | 换芯片是否要改 | | ---------- | --------------------------------------------------------- | --------------- | | 公共 API | `inc/post_flash.h` + `src/post_flash.c` | 否 | | 总线 port | `inc/post_flash_bus_*.h` + `src/post_flash_bus_*.c` | 否(换平台改 port 实现) | | 命令集描述 + 校验 | `inc/post_flash_nor_cmd.h` + `src/post_flash_nor_cmd.c` | 否 | | JEDEC 查表 | `inc/post_flash_nor_chip.h` + `src/post_flash_nor_chip.c` | 否 | | NOR 驱动 | `src/post_flash_nor.h` + `src/post_flash_nor.c` | 否 | | Factory | `inc/post_flash_factory.h` + `src/post_flash_factory.c` | 否 | | 片内驱动 | `src/post_flash_internal.h` + `src/post_flash_internal.c` | 否 | | **产品配置** | `board/`* 或 `test/config/`* | **是(只改这里)** | ## 3. 使用约定 ### 3.1 返回值与错误码 - 返回类型为 `POST_ERR`:`POST_SUCCESS` / `POST_FAILURE` - 详细错误见 `Post_Hal_FlashGetLastError()`(`Post_Hal_FlashErrorCode`) - 常见细码:`INVALID_PARAM`、`NOT_INIT`、`BUSY`、`RANGE`、`ALIGN`、`WEL`、`PROTECT`、`TIMEOUT`、`IO`、`UNSUPPORTED` ### 3.2 NOR 编程语义(必读) - NOR 编程只能将位从 **1 → 0**;写入目标区须事先 **Erase**(本模块不自动擦) - `Post_Hal_FlashErase(adapter, addr, len)` 按扇区覆盖 `[addr, addr+len)`;起始地址可不对齐,后端向下对齐 - `Post_Hal_FlashEraseSector` / `EraseBlock` 要求地址分别按扇区 / 块对齐 - 跨页写入由 NOR 后端自动拆分页编程 ### 3.3 适配器与状态 - 业务只操作 `Post_Hal_FlashAdapter`,不要直接访问 `m_state[]` 私有缓冲 - IO 进行中 `m_isBusy == TRUE`,并发调用同一 adapter 会返回 `BUSY` - `Post_Hal_FlashInit` 对已初始化实例幂等;`Uninit` 在 busy 时拒绝 ### 3.4 线程安全 - **模块非线程安全**:`m_isBusy`、`m_lastError` 无锁保护 - NOR 写 / 擦路径会在 port 提供 `m_lock` / `m_unlock` 时加总线锁;**读路径不加锁** - 多任务场景推荐:专用 Flash 服务任务串行化所有 API,或由 port 的 lock 覆盖全路径 ### 3.5 内存模型 - 绑定块(`Post_Hal_FlashNorSpi` / `NorQspi` / `OnChipDev`)与 `Post_Hal_FlashAdapter` 均由产品侧 **静态分配**,库内无 `malloc` - `m_state[]` 大小由 `_Static_assert` 校验,勿手动访问 ## 4. 移植步骤 ### 4.1 外部 SPI NOR 1. 板级定义 `Post_Hal_NorCmdSet`(按芯片手册填写 opcode / 状态位掩码)与 `Post_Hal_NorChipDesc[]`(JEDEC / 容量 / 页扇区块) 2. 实现 `Post_Hal_SpiBusOps`(至少 `m_transfer` / `m_csLow` / `m_csHigh` / `m_getTickMs`) 3. `Post_Hal_SpiBusInitContext(...)` 绑定 instance、ops、config(频率 / 超时) 4. 静态分配 `Post_Hal_FlashNorSpi`,填入 `m_cmdSet` / `m_chipTable` / `m_chipCount` 5. `Post_Hal_FlashOpenNorOverSpi` → `Post_Hal_FlashInit` → 业务 API → `Post_Hal_FlashUninit` ### 4.2 外部 QSPI NOR 1. 同 SPI:板级命令集 + JEDEC 表 2. 实现 `Post_Hal_QspiBusOps`(至少 `m_command` / `m_getTickMs`) 3. `Post_Hal_QspiBusInitContext(...)` → 填入 `Post_Hal_FlashNorQspi` 4. `Post_Hal_FlashOpenNorOverQspi` → `Post_Hal_FlashInit` → 业务 API ### 4.3 MCU 片内 Flash 1. 实现 `Post_Hal_FlashOnChipOps`(至少 `m_read` / `m_program` / `m_eraseSector` / `m_unlock` / `m_lock`) 2. 填写 `Post_Hal_FlashOnChipConfig`(容量 / 页 / 扇区 / 块大小) 3. 静态分配 `Post_Hal_FlashOnChipDev`,填入 `m_ctx` 4. `Post_Hal_FlashOpenOnChip` → `Post_Hal_FlashInit` → 业务 API 单测配置参考:`test/config/post_flash_test_nor_cmd.*`、`test/config/post_flash_test_nor_chip_table.*`。 ## 5. 使用示例 ### 5.1 外部 SPI NOR:擦除 / 写入 / 读取 ```c #include "post_flash.h" #include "post_flash_factory.h" #include "post_flash_bus_spi.h" #include "post_flash_nor_cmd.h" #include "post_flash_nor_chip.h" /* 板级:命令集与 JEDEC 表(示例,按实际芯片手册修改) */ #define BOARD_NOR_CMD_WRITE_ENABLE (0x06u) #define BOARD_NOR_CMD_READ_STATUS1 (0x05u) #define BOARD_NOR_CMD_READ_JEDEC_ID (0x9Fu) #define BOARD_NOR_CMD_READ_DATA (0x03u) #define BOARD_NOR_CMD_PAGE_PROGRAM (0x02u) #define BOARD_NOR_CMD_SECTOR_ERASE (0x20u) #define BOARD_NOR_CMD_BLOCK_ERASE (0xD8u) #define BOARD_NOR_CMD_CHIP_ERASE (0xC7u) #define BOARD_NOR_CMD_ENTER_4BYTE (0xB7u) static const Post_Hal_NorCmdSet G_BOARD_NOR_CMDS = { .m_writeEnable = BOARD_NOR_CMD_WRITE_ENABLE, .m_readStatus1 = BOARD_NOR_CMD_READ_STATUS1, .m_readJedecId = BOARD_NOR_CMD_READ_JEDEC_ID, .m_readData = BOARD_NOR_CMD_READ_DATA, .m_pageProgram = BOARD_NOR_CMD_PAGE_PROGRAM, .m_sectorErase = BOARD_NOR_CMD_SECTOR_ERASE, .m_blockErase = BOARD_NOR_CMD_BLOCK_ERASE, .m_chipErase = BOARD_NOR_CMD_CHIP_ERASE, .m_enter4ByteAddr = BOARD_NOR_CMD_ENTER_4BYTE, .m_statusBusyMask = 0x01u, .m_statusWelMask = 0x02u, }; static const Post_Hal_NorChipDesc G_BOARD_NOR_CHIPS[] = { {0xEF4018u, "w25q128", 16u * 1024u * 1024u, 256u, 4096u, 65536u, POST_HAL_FLASH_INFO_FLAG_NONE}, }; static Post_Hal_FlashAdapter g_flashAdapter; static Post_Hal_FlashNorSpi g_norSpi; static POST_ERR Board_SpiTransfer(void *instance, const POST_U8 *tx, POST_U8 *rx, POST_U32 len) { (void)instance; (void)tx; (void)rx; (void)len; /* 调用 HAL_SPI_TransmitReceive 或等价接口 */ return POST_SUCCESS; } static POST_ERR Board_SpiCsLow(void *instance) { (void)instance; return POST_SUCCESS; } static POST_ERR Board_SpiCsHigh(void *instance) { (void)instance; return POST_SUCCESS; } static POST_ERR Board_GetTickMs(void *instance, POST_U32 *tickMs) { (void)instance; /* *tickMs = 当前毫秒时基(须单调递增) */ return POST_SUCCESS; } static const Post_Hal_SpiBusOps g_spiOps = { .m_init = NULL, .m_uninit = NULL, .m_transfer = Board_SpiTransfer, .m_csLow = Board_SpiCsLow, .m_csHigh = Board_SpiCsHigh, .m_setFreq = NULL, .m_getTickMs = Board_GetTickMs, .m_delayMs = NULL, .m_lock = NULL, .m_unlock = NULL, }; POST_ERR Device_FlashInit(void) { Post_Hal_SpiBusConfig busCfg = { .m_freqHz = 12000000u, .m_timeoutMs = 3000u, }; if (Post_Hal_SpiBusInitContext(&g_norSpi.m_bus, /* hspi */ NULL, &g_spiOps, &busCfg) != POST_SUCCESS) { return POST_FAILURE; } g_norSpi.m_cmdSet = &G_BOARD_NOR_CMDS; g_norSpi.m_chipTable = G_BOARD_NOR_CHIPS; g_norSpi.m_chipCount = 1u; if (Post_Hal_FlashOpenNorOverSpi(&g_flashAdapter, "flash0", &g_norSpi) != POST_SUCCESS) { return POST_FAILURE; } return Post_Hal_FlashInit(&g_flashAdapter); } POST_ERR Device_FlashWriteReadDemo(void) { POST_U8 buf[16] = {0x10, 0x20, 0x30, 0x40}; POST_U8 readback[16]; /* 先擦后写 */ if (Post_Hal_FlashEraseSector(&g_flashAdapter, 0u) != POST_SUCCESS) { return POST_FAILURE; } if (Post_Hal_FlashWrite(&g_flashAdapter, 0u, buf, sizeof(buf)) != POST_SUCCESS) { return POST_FAILURE; } if (Post_Hal_FlashRead(&g_flashAdapter, 0u, readback, sizeof(readback)) != POST_SUCCESS) { return POST_FAILURE; } return Post_Hal_FlashSync(&g_flashAdapter); } POST_ERR Device_FlashUninit(void) { return Post_Hal_FlashUninit(&g_flashAdapter); } ``` ### 5.2 MCU 片内 Flash ```c #include "post_flash.h" #include "post_flash_factory.h" static Post_Hal_FlashAdapter g_iflashAdapter; static Post_Hal_FlashOnChipDev g_onChipDev; static POST_ERR OnChip_Read(void *instance, POST_U32 addr, POST_U8 *buf, POST_U32 len) { (void)instance; (void)addr; (void)buf; (void)len; /* 调用 MCU HAL_FLASH_Read */ return POST_SUCCESS; } static POST_ERR OnChip_Program(void *instance, POST_U32 addr, const POST_U8 *buf, POST_U32 len) { (void)instance; (void)addr; (void)buf; (void)len; /* 调用 MCU HAL_FLASH_Program */ return POST_SUCCESS; } static POST_ERR OnChip_EraseSector(void *instance, POST_U32 addr) { (void)instance; (void)addr; return POST_SUCCESS; } static POST_ERR OnChip_Unlock(void *instance) { (void)instance; return POST_SUCCESS; } static POST_ERR OnChip_Lock(void *instance) { (void)instance; return POST_SUCCESS; } static const Post_Hal_FlashOnChipOps g_onChipOps = { .m_init = NULL, .m_uninit = NULL, .m_unlock = OnChip_Unlock, .m_lock = OnChip_Lock, .m_read = OnChip_Read, .m_program = OnChip_Program, .m_eraseSector = OnChip_EraseSector, .m_eraseBlock = NULL, .m_eraseChip = NULL, .m_getTickMs = NULL, .m_delayMs = NULL, }; POST_ERR Device_InternalFlashInit(void) { g_onChipDev.m_ctx.m_instance = NULL; g_onChipDev.m_ctx.m_portOps = &g_onChipOps; g_onChipDev.m_ctx.m_config.m_capacity = 512u * 1024u; g_onChipDev.m_ctx.m_config.m_pageSize = 8u; g_onChipDev.m_ctx.m_config.m_sectorSize = 2048u; g_onChipDev.m_ctx.m_config.m_blockSize = 0u; if (Post_Hal_FlashOpenOnChip(&g_iflashAdapter, "internal", &g_onChipDev) != POST_SUCCESS) { return POST_FAILURE; } return Post_Hal_FlashInit(&g_iflashAdapter); } ``` ## 6. 常用 API ### 6.1 生命周期 | API | 说明 | | ----------------------------------------------------------------- | ------------------ | | `Post_Hal_FlashOpenNorOverSpi` / `OpenNorOverQspi` / `OpenOnChip` | 绑定后端(Factory) | | `Post_Hal_FlashInit` / `Uninit` | 初始化 / 反初始化 | | `Post_Hal_FlashGetInfo` | 获取器件信息(Init 成功后有效) | | `Post_Hal_FlashGetLastError` | 读取最近一次细错误码 | ### 6.2 读写擦 | API | 说明 | | -------------------------------------------------------- | --------------------- | | `Post_Hal_FlashRead` | 读取 `[addr, addr+len)` | | `Post_Hal_FlashWrite` | 页编程(须先擦除;跨页自动拆分) | | `Post_Hal_FlashErase` | 按扇区擦除覆盖区间 | | `Post_Hal_FlashEraseSector` / `EraseBlock` / `EraseChip` | 单扇区 / 单块 / 整片擦 | | `Post_Hal_FlashSync` | 等待后端 BUSY 清零 | ## 7. 工程集成 ### 7.1 CMake 接入 ```cmake add_subdirectory(flash_module) target_link_libraries(your_app PRIVATE post_flash) ``` ### 7.2 CMake 常用选项 | 选项 | 默认 | 说明 | | ----------------------------- | ------------------- | ---------------------------------- | | `POST_FLASH_BUILD_TESTS` | 顶层 `ON` / 子目录 `OFF` | 是否编单测 | | `POST_FLASH_USE_BUNDLED_TYPE` | `ON` | `OFF` 时由父工程提供 `post_common_type.h` | | `POST_FLASH_USE_BUNDLED_LOG` | `ON` | `OFF` 时由父工程提供 `post_common_log.*` | | `POST_FLASH_ENABLE_LOG` | `ON` | 是否定义 `LOG_ENABLED` | | `POST_FLASH_LOG_SYSTEM_LINUX` | 顶层 `ON` | 主机彩色日志 | 开测试时必须 `POST_FLASH_USE_BUNDLED_TYPE=ON` 且 `POST_FLASH_USE_BUNDLED_LOG=ON`。 ### 7.3 本地编译与验证 ```bash cd flash_module make rebuild # 仅配置并编译 make test # rebuild + 跑全部用例 make run # 跑全部 gtest make run HWM_UNIT_FLASH_SPI_EraseWriteRead # 跑单个用例 make clean # 清理 build/ ./build/post_flash_test --gtest_filter=post_drivers_test.HWM_UNIT_FLASH_SPI_InitUninit # 跑单个用例 ``` 等价直接命令: ```bash cmake -S . -B build && cmake --build build ctest --test-dir build --output-on-failure ./build/post_flash_test --gtest_filter=post_drivers_test.HWM_UNIT_FLASH_SPI_* # 通配 ``` ### 7.4 代码跳转(F12 / clangd) 1. 执行 `make rebuild` 生成 `build/compile_commands.json` 2. 安装并启用 **clangd** 扩展 3. 建议在 **Linux / WSL** 环境下打开本仓库(与 `make` 构建环境一致) ## 8. 测试目录说明 | 路径 | 说明 | | ----------------------------------------- | ------------------------------------------ | | `test/core/post_flash_test_main.cpp` | 单元测试用例(每个 `TEST_F` 带 `@brief` / `Run:` 注释) | | `test/mock/post_flash_hal_mock.*` | SPI / QSPI / 片内 Fake Flash 与 port Ops 模拟 | | `test/tool/post_flash_test_tool.*` | 创建 adapter 的夹具工具(`CreateSpiFlash` 等) | | `test/config/` | 单测 NOR 命令集与 JEDEC 表 | | `test/example/post_flash_usage_example.*` | 集成用法示例 | ## 9. TODO(面向 MCU 裸机 / MCU RTOS / Linux 三端可用) 目标:同一套 `Post_Hal_Flash*` API,在裸机 / RTOS / Linux 各有可运行 port,并共享契约与测试。当前是主机 mock 验证的算法壳,下列项按优先级推进。 ### P0 — 正确性(不修则不能上板) 1. **读路径总线锁与多任务契约** - 改:读 / 写 / 擦全路径在 port 提供 lock 时加锁;或文档强制单任务串行并去掉「半保护」误导。 - 为啥:当前仅写擦加锁,多任务并发读 + 写会导致 SPI 帧错乱。 2. **tick 回绕安全的超时判断** - 改:`Post_Hal_NorWaitBusy` 用 `(now - start) >= timeout` 的 unsigned 回绕安全写法。 - 为啥:`uint32_t` 毫秒 tick 回绕后可能永久或过早超时。 3. **补真实 MCU port** - 改:提供 `board/` 示例(STM32 HAL_SPI / HAL_QSPI / 片内 Flash Controller)。 - 为啥:mock 只验证算法,不能证明上板可行。 ### P1 — 架构与三端契约 1. **高速 QSPI 读写的真帧模型** - 改:支持 Fast Read / Quad Read、QE 位配置;`m_dataLines` / `m_dummyCycles` 由命令集或芯片表驱动。 - 为啥:当前 QSPI 路径等价慢速 SPI,无法满足 XiP / 高速场景。 2. **擦除策略优化** - 改:大范围 `Erase(addr, len)` 优先块擦、残余扇区擦。 - 为啥:全扇区擦在大容量 Flash 上耗时过长。 3. **命令集灵活性** - 改:`m_enter4ByteAddr` 对纯 3-byte 芯片可选;支持可配置 Fast Read opcode。 - 为啥:当前校验要求所有字段非零,板级配置偏僵硬。 ### P2 — 测试与工程化 1. **补边界与故障注入测试** - 改:二次 init、擦除溢出、tick 回绕、transfer 失败中途解锁、并发(真锁下)。 - 为啥:现有用例覆盖主路径,量产故障模式覆盖不足。 2. **工程卫生** - 改:CI(gcc + sanitizer)、license/版本、STM32 可选 target 保证可编译。 - 为啥:无持续验证时,一改就漂。