# b_flash **Repository Path**: zhao-zhangbo/b_flash ## Basic Information - **Project Name**: b_flash - **Description**: 一个轻量级flash管理器---带磨损均衡和备份 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 9 - **Forks**: 11 - **Created**: 2025-01-15 - **Last Updated**: 2025-09-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 博客:https://blog.csdn.net/weixin_42992743/article/details/145160419 # b_flash — 闪存管理器 > 说明:本 README 基于 `b_flash.c` / `b_flash.h` 源码撰写,目的是作为开源项目的文档,详细说明模块目的、设计、接口、集成示例与注意事项,便于移植与维护。 (PS:随便用ai写的,将就着看吧~~~) --- ## 目录 - 概述 - 主要特性 - 设计与内部工作原理 - 数据布局与计算 - 校验机制 - 配置与宏 - 对外 API(函数说明) - 平台回调接口(必须实现) - 集成示例(参考) - 常见错误与处理策略 - 运行时注意事项与建议 - 扩展建议 - 许可证 --- ## 概述 `b_flash` 是一个轻量级、便携的闪存(Flash)管理模块,适用于嵌入式平台。它的目标是: - 在限制擦写次数与保护数据完整性的前提下,可靠地保存结构化配置信息或参数; - 提供主/备双区冗余(可选),以提高在闪存单区损坏时的数据可恢复能力; - 将与平台相关的底层 Flash 操作抽象为回调,使模块与 MCU/HAL 无关,便于移植。 适用场景:设备配置信息、校准参数、重要状态变量的持久化等。 --- ## 主要特性 - 支持主区(main)与备份区(back)冗余(可通过宏开关); - 使用位图(flag map)实现索引和追加写策略,避免频繁擦整页; - 写入/读取时使用校验(checksum)检测数据有效性; - 在数据损坏时可使用备份区恢复或写入默认值; - 平台无关:由用户提供 `pf_erase`、`pf_write`、`pf_read`、`pf_check`、`pf_default` 等回调。 --- ## 设计与内部工作原理(简要) 1. **索引/位图机制**: - 每个区(主/备)前面保留一段 flag map,若干个 32-bit 单元构成位图; - 每个 bit 对应一个数据槽(slot),写入时模块把对应 bit 标记为已写(并追加写入数据区);读取时扫描位图找到最新被置位的槽。 2. **追加写策略**: - 写数据时并不覆盖原来有效槽,而是追加一个新的槽并把新数据写入新槽;写完后将前一个槽“打坏”(把标识写成失效),以保证只有最近的槽视为有效。此策略能降低擦写次数和频繁写同一地址带来的耐久性问题。 3. **校验机制**: - 每个数据槽最后额外保存一个 32-bit 校验值(checksum),写入时根据数据计算 checksum 并写入;读取时重新计算并比较,以判断数据是否有效。 4. **备份区**: - 若启用,写入会同步写主区和备区;读取会优先尝试主区,主区失效时尝试备区,并按策略用备份修复主区或写入默认值。 5. **擦除与重建**: - 当可用槽耗尽或发现严重损坏时,模块会触发擦除扇区并使用 `pf_default` 写入默认值,重置索引。 --- ## 数据布局与计算 模块以 32-bit(4 字节)为基本单位进行分配与管理: - **每个数据槽占用的 word 数(32-bit)**: ```text data_word_number = (size + 3) / 4 + 1 # +1 用于存放 checksum ``` 其中 `size` 是用户要保存的数据字节数(可非 4 字节对齐)。 - **flag map(标识区)字数**:由实现初始化时根据 `flash_page_size` 与 `flash_page_numbre` 计算得出。模块会使用该 map 来管理可写槽的索引与数量。 - **可用槽数**: ```text flag_map_bit_number = ((flash_page_size * flash_page_numbre) / 4 - flag_map_number) / data_word_number ``` 该值表示在不擦除扇区的情况下可追加写入的数据槽最大数量。若写入次数超过这个数量,将触发擦除重写流程。 --- ## 校验机制 - 默认校验为字节求和(累加),计算流程为: ```c uint32_t checksum = get_check_sum(data, size) + checksum_address; ``` `checksum_address` 是当前 checksum 存放地址,用于减少简单碰巧匹配的概率。 - 用户可以通过提供 `pf_check` 回调来实现更强的校验(例如 CRC32),或对结构字段进行语义检查(如 magic 字段)。 --- ## 配置宏 在头文件中可以配置若干宏以控制行为: - `BACK_FUNCTION_EN`(默认:`1`) — 启用/禁用备份区功能; - `FLASH_READ_MAX_CNT` — 读取时的最大重读次数; - `B_FLASH_LOG_DEBUG_EN`, `B_FLASH_LOG_INFO_EN` — 控制日志输出; 根据项目需要调整这些宏,但请在移植时确认对底层 Flash 的写入与擦除特性。 --- ## 对外 API(主要函数) ```c int8_t b_flash_create(b_flash_manager_t *flash_manager, void *write_data, uint32_t size); int8_t b_flash_write(b_flash_manager_t *flash_manager, void *write_data, uint32_t size); int8_t b_flash_read(b_flash_manager_t *flash_manager, void *read_data, uint32_t size); ``` ### `b_flash_create` - 作用:初始化管理器结构,计算内部参数(`data_word_number`、`flag_map_number`、`flag_map_bit_number` 等),读取 flash 中现有索引并尝试恢复/读取当前有效数据;若数据失效则调用 `pf_default` 写入默认数据。 - 返回:`0` 表示成功,非 0 表示初始化失败(例如回调函数缺失或地址未对齐)。 ### `b_flash_write` - 作用:向主区(及备区,如启用)追加写入新数据,写入完成后将前一有效槽标记为失效。 - 返回:标准化状态码(实现中以 `0` 表示成功)。 ### `b_flash_read` - 作用:从主区读取最新有效数据并校验,如校验失败则尝试备份区;若两区都无效,则使用 `pf_default` 写入默认值并返回错误状态。 --- ## 平台回调接口(必须由移植层实现) 在 `flash_manager.user_init` 中,用户必须提供下列回调: ```c int8_t pf_erase(uint32_t address, uint32_t page_number); int8_t pf_write(uint32_t address, void *write_data, uint32_t size); int8_t pf_read(uint32_t address, void *read_data, uint32_t size); int8_t pf_check(void *p_src, uint32_t size); // 可选但推荐 int8_t pf_default(void *src, uint32_t size); // 必需:提供默认数据填充 ``` - `pf_erase`:擦除 `page_number` 个页,起始地址为 `address`。 - `pf_write`:将 `size` 字节写入 `address`(通常 4 字节对齐,按 32-bit 写)。 - `pf_read`:读取 `size` 字节到 `read_data`(很多 MCU 的 flash 为 memory-mapped,可直接 `memcpy`)。 - `pf_check`:可在读取后、写入前用于额外合法性检查(例如验证 magic 字段或 CRC)。 - `pf_default`:当检测到无效或严重损坏时,需要写入的默认数据由该函数填充。 注意:回调函数应返回 `0` 表示成功,非 0 表示失败;模块会根据返回值采取后续措施。 --- ## 集成示例(参考) > 下列示例为伪代码/示意,具体实现需针对目标 MCU 的 HAL/SDK 进行修改。 ```c #include "b_flash.h" typedef struct { uint32_t magic; uint8_t cfg1; uint8_t cfg2; uint16_t reserved; } app_cfg_t; b_flash_manager_t flash_manager; int8_t my_flash_erase(uint32_t addr, uint32_t page_n) { /* HAL erase */ return 0; } int8_t my_flash_write(uint32_t addr, void *data, uint32_t size) { /* HAL program */ return 0; } int8_t my_flash_read(uint32_t addr, void *dst, uint32_t size) { memcpy(dst, (void*)addr, size); return 0; } int8_t my_check(void *p, uint32_t size) { app_cfg_t *c = p; return (c->magic == 0xA5A5A5A5) ? 0 : -1; } int8_t my_default(void *dst, uint32_t size) { app_cfg_t *c = dst; c->magic=0xA5A5A5A5; c->cfg1=1; c->cfg2=2; c->reserved=0; return 0; } void app_init(void) { app_cfg_t cfg; flash_manager.user_init.pf_erase = my_flash_erase; flash_manager.user_init.pf_write = my_flash_write; flash_manager.user_init.pf_read = my_flash_read; flash_manager.user_init.pf_check = my_check; flash_manager.user_init.pf_default = my_default; flash_manager.user_init.main_flash_address = 0x0803F000; // 示例 flash_manager.user_init.back_flash_address = 0x0803E000; // 示例 flash_manager.user_init.flash_page_numbre = 1; flash_manager.user_init.flash_page_size = 0x1000; // 4KB if (b_flash_create(&flash_manager, &cfg, sizeof(cfg)) != 0) { // 处理错误 } } void save_cfg(app_cfg_t *new_cfg) { b_flash_write(&flash_manager, new_cfg, sizeof(*new_cfg)); } ``` --- ## 常见错误与处理策略 - **回调为空**:`b_flash_create` 会检验关键回调(读/写/擦除/默认等),缺失会返回错误,请确认正确赋值。 - **地址未对齐**:主/备地址需为 4 字节对齐且按页边界对齐,否则可能出现写失败或坏数据。 - **数据损坏**:若主区损坏而备区有效,模块会尝试用备份修复主区;若两区均无效,则擦除并写默认值。 - **可用槽耗尽**:如果数据写入次数超过 `flag_map_bit_number`,会触发扇区擦除重写流程。请根据写入频率与 flash 耐久度评估合适的页数/页大小。 --- ## 运行时注意事项与建议 1. **先在开发板上充分测试底层回调**(擦写与写入不可逆,应充分验证返回值和错误处理)。 2. **打开日志**(开发阶段)以观察初始化时的参数计算(`data_word_number`、`flag_map_number`、`flag_map_bit_number`);该信息对评估可写次数非常重要。 3. **数据结构设计**:尽量减少单次写入数据尺寸以增加可写槽数并延长扇区使用寿命;将频繁变更的数据与少变更的数据分开保存。 4. **备份策略**:对重要数据建议启用备份区(`BACK_FUNCTION_EN = 1`)。 --- ## 扩展建议 - 将简单求和校验替换为 CRC32 或 CRC16(可通过 `pf_check` 实现或修改内部实现); - 暴露 API 查询当前可用槽数量、已写次数等诊断信息; - 增加模拟测试框架(在 PC 上用内存数组模拟 flash),便于回归测试索引/擦除/恢复逻辑; - 改进写失败回滚策略,确保在写入中断时系统不会进入不可恢复状态。 --- ## 许可证 请在仓库中补充适当的开源许可证(如 MIT、Apache-2.0 等),并在源代码头部与 README 中注明许可证类型与版权声明。 ---