2. Flash驱动抽象层设计
任务目标
本案例设计并实现一个Flash驱动抽象层,为上层Bootloader提供统一的擦除、写入和读取接口,屏蔽不同Flash芯片的底层差异。完成本案例后,上层应用无需感知具体芯片型号,仅通过抽象接口即可操作Flash。
接口定义
抽象层定义三个核心操作,覆盖Flash操作的最小必要集合:
- 擦除:按扇区擦除,传入扇区编号
- 写入:按地址写入数据缓冲区
- 读取:按地址读取数据到缓冲区
关键完整代码
/* flash_abstraction.h */
#ifndef FLASH_ABSTRACTION_H
#define FLASH_ABSTRACTION_H
#include <stdint.h>
#include <stddef.h>
/* 抽象层统一错误码 */
typedef enum {
FLASH_OK = 0,
FLASH_ERR_PARAM,
FLASH_ERR_ERASE,
FLASH_ERR_WRITE,
FLASH_ERR_READ
} FlashStatus;
/* 抽象接口:由具体芯片驱动实现 */
typedef struct {
FlashStatus (*eraseSector)(uint32_t sectorAddr);
FlashStatus (*write)(uint32_t addr, const uint8_t *data, uint32_t len);
FlashStatus (*read)(uint32_t addr, uint8_t *buf, uint32_t len);
} FlashOps;
/* 全局操作句柄,Bootloader通过它访问Flash */
extern const FlashOps g_flashOps;
#endif /* FLASH_ABSTRACTION_H */
/* flash_abstraction.c */
#include "flash_abstraction.h"
#include "flash_chip_a.h" /* 芯片A驱动头文件 */
#include "flash_chip_b.h" /* 芯片B驱动头文件 */
/* 当前目标芯片选择:通过编译宏切换 */
#ifdef FLASH_CHIP_A
static FlashStatus eraseA(uint32_t addr) {
return chipA_eraseSector(addr);
}
static FlashStatus writeA(uint32_t addr, const uint8_t *data, uint32_t len) {
return chipA_program(addr, data, len);
}
static FlashStatus readA(uint32_t addr, uint8_t *buf, uint32_t len) {
return chipA_read(addr, buf, len);
}
const FlashOps g_flashOps = {
eraseA,
writeA,
readA
};
#elif defined(FLASH_CHIP_B)
static FlashStatus eraseB(uint32_t addr) {
return chipB_erase(addr);
}
static FlashStatus writeB(uint32_t addr, const uint8_t *data, uint32_t len) {
return chipB_write(addr, data, len);
}
static FlashStatus readB(uint32_t addr, uint8_t *buf, uint32_t len) {
return chipB_read(addr, buf, len);
}
const FlashOps g_flashOps = {
eraseB,
writeB,
readB
};
#else
#error "No flash chip selected. Define FLASH_CHIP_A or FLASH_CHIP_B."
#endif
/* 芯片A驱动接口声明(flash_chip_a.h)——实际工程中由芯片厂商驱动提供 */
#ifndef FLASH_CHIP_A_H
#define FLASH_CHIP_A_H
#include <stdint.h>
FlashStatus chipA_eraseSector(uint32_t addr);
FlashStatus chipA_program(uint32_t addr, const uint8_t *data, uint32_t len);
FlashStatus chipA_read(uint32_t addr, uint8_t *buf, uint32_t len);
#endif
/* 芯片B驱动接口声明(flash_chip_b.h) */
#ifndef FLASH_CHIP_B_H
#define FLASH_CHIP_B_H
#include <stdint.h>
FlashStatus chipB_erase(uint32_t addr);
FlashStatus chipB_write(uint32_t addr, const uint8_t *data, uint32_t len);
FlashStatus chipB_read(uint32_t addr, uint8_t *buf, uint32_t len);
#endif
编译与集成方式
将上述文件加入Bootloader工程源码目录。编译时通过宏选择目标芯片:
# 选择芯片A
gcc -DFLASH_CHIP_A -c flash_abstraction.c
# 选择芯片B
gcc -DFLASH_CHIP_B -c flash_abstraction.c
芯片A和芯片B的具体驱动实现(如chipA_eraseSector等)由芯片厂商提供或自行编写,本抽象层只负责接口适配,不包含具体芯片时序逻辑。
验证方法
编写如下测试代码验证抽象层接口行为:
/* test_flash_abs.c */
#include <assert.h>
#include <string.h>
#include "flash_abstraction.h"
/* 模拟芯片A驱动实现(仅用于验证抽象层) */
static uint8_t fakeFlash[4096];
FlashStatus chipA_eraseSector(uint32_t addr) {
if (addr >= sizeof(fakeFlash)) return FLASH_ERR_PARAM;
memset(&fakeFlash[addr], 0xFF, 512); /* 假设扇区512字节 */
return FLASH_OK;
}
FlashStatus chipA_program(uint32_t addr, const uint8_t *data, uint32_t len) {
if (addr + len > sizeof(fakeFlash)) return FLASH_ERR_PARAM;
memcpy(&fakeFlash[addr], data, len);
return FLASH_OK;
}
FlashStatus chipA_read(uint32_t addr, uint8_t *buf, uint32_t len) {
if (addr + len > sizeof(fakeFlash)) return FLASH_ERR_PARAM;
memcpy(buf, &fakeFlash[addr], len);
return FLASH_OK;
}
int main(void) {
uint8_t wbuf[] = {0x11, 0x22, 0x33, 0x44};
uint8_t rbuf[4] = {0};
/* 通过抽象接口写入并读回 */
assert(g_flashOps.eraseSector(0x100) == FLASH_OK);
assert(g_flashOps.write(0x100, wbuf, sizeof(wbuf)) == FLASH_OK);
assert(g_flashOps.read(0x100, rbuf, sizeof(rbuf)) == FLASH_OK);
assert(memcmp(wbuf, rbuf, sizeof(wbuf)) == 0);
/* 验证擦除后数据为0xFF */
memset(rbuf, 0, sizeof(rbuf));
assert(g_flashOps.eraseSector(0x100) == FLASH_OK);
assert(g_flashOps.read(0x100, rbuf, sizeof(rbuf)) == FLASH_OK);
for (int i = 0; i < 4; i++) {
assert(rbuf[i] == 0xFF);
}
return 0;
}
编译运行:
gcc -DFLASH_CHIP_A -o test_flash test_flash_abs.c flash_abstraction.c
./test_flash
通过条件:程序正常退出且无assert失败,说明抽象层正确完成了擦除、写入、读取的接口转发,且数据一致性与擦除语义符合预期。若更换为芯片B,只需将编译宏改为-DFLASH_CHIP_B并提供对应驱动实现,测试代码无需修改。