|
|
本帖最后由 VDVA 于 2026-6-4 22:50 编辑
一、概述
flash_task 是 W25Q512 Flash 芯片的 I/O Server,将底层异步中断驱动封装为线程安全的同步阻塞 API。
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Scan_Task │ │Motor_Task│ │Shell_Cmd │ │Flash_Test│
└─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘
│ │ │ │
│ Flash_Read() │ Flash_Write()│ Flash_Erase()│ Flash_ReadID()
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ submit() — 同步阻塞入口 │
│ req.requester = osThreadGetId() ← 自动获取线程 ID │
│ 投入队列 → 阻塞等 REPLY │
└──────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ Flash_Task (I/O Server) │
│ 取请求 → 调用驱动 → 等中断 → 回写结果 → 唤醒请求者 │
└──────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ bsp_w25q512.c (底层驱动, 异步 MDMA) │
└─────────────────────────────────────────────────────────┘
二、线程 ID 传递机制
调用者不需要手动传线程 ID。 submit() 内部自动完成:
static flash_ret_t submit(flash_req_t *req)
{
req->requester = osThreadGetId(); // ← 自动获取当前线程 ID
// ...
}
工作原理
osThreadGetId() 是 CMSIS-OS2 标准 API,返回当前正在执行的线程句柄。 因为 submit() 在调用者的线程上下文中执行(尚未阻塞),所以返回的就是调用者的 ID。
Scan_Task 调用 Flash_Read()
│
▼ submit() 在 Scan_Task 上下文中执行
│
│ osThreadGetId() → 返回 Scan_Task 的句柄
│ 存入 req->requester
│ 投入队列,阻塞...
│
▼ (Scan_Task 挂起)
Flash_Task 取出请求
│
│ 执行驱动操作...
│ 等待中断回调...
│ 回写 req->result
│
│ osThreadFlagsSet(req->requester, REPLY)
│ ▲
│ └── 就是 Scan_Task 的句柄,精准唤醒
│
▼
Scan_Task 被唤醒,返回 FLASH_OK
任何任务调用方式完全相同
// scan_task.c
void Scan_Task(void *arg) {
Flash_Read(buf, 0x1000, 256, 2000); // 自动拿到 scan_task 线程 ID
}
// motor_task.c
void Motor_Task(void *arg) {
Flash_Write(data, 0x2000, 100, 3000); // 自动拿到 motor_task 线程 ID
}
// shell_cmd.c
void cmd_flash_read(int argc, char **argv) {
Flash_Read(buf, addr, len, 2000); // 自动拿到 shell 线程 ID
}
三、初始化
自动注册,无需手动调用
// flash_task.c
TASK_REGISTER(flash_task_init, TASK_LEVEL_EARLY);
EARLY 级别:比其他 LATE 级别任务更早初始化
保证:当 flash_test_task、shell_task 等启动时,Flash 服务已就绪
初始化顺序
系统启动
│
├─ TASK_LEVEL_EARLY
│ └─ flash_task_init()
│ ├─ 创建消息队列(深度 8)
│ └─ 创建 Flash_Task 线程
│ ├─ W25Q512_RegisterDoneCb() ← 注册中断回调
│ ├─ W25Q512_Init() ← 初始化芯片
│ └─ 进入 for(;;) 等待队列
│
├─ TASK_LEVEL_NORMAL
│ └─ scan_task_init(), motor_task_init(), ...
│
└─ TASK_LEVEL_LATE
└─ shell_task_init(), flash_test_task_init(), ...
四、API 详解
4.1 返回值
typedef enum {
FLASH_OK = 0, // 操作成功
FLASH_ERR, // 硬件/驱动错误
FLASH_TIMEOUT, // 等待超时
} flash_ret_t;
4.2 Flash_Read — 读取
flash_ret_t Flash_Read(uint8_t *buf, uint32_t addr, uint32_t size, uint32_t timeout_ms);
参数 说明 约束
buf 目标缓冲区 必须 32 字节对齐
addr Flash 起始地址 0x000000 ~ 0x3FFFFFF
size 读取字节数 ≥ 1
timeout_ms 超时(ms) 建议 ≥ 2000
// ✅ 正确:32 字节对齐
uint8_t buf[512] __attribute__((aligned(32)));
Flash_Read(buf, 0x100000, 512, 2000);
// ❌ 错误:未对齐 → D-Cache 破坏相邻数据
uint8_t buf[512];
Flash_Read(buf, 0x100000, 512, 2000);
4.3 Flash_Write — 写入
flash_ret_t Flash_Write(const uint8_t *buf, uint32_t addr, uint32_t size, uint32_t timeout_ms);
参数 说明 约束
buf 数据源 无需特殊对齐
addr Flash 起始地址 目标区域须已擦除
size 写入字节数 任意大小,自动跨页拆分
timeout_ms 每页超时(ms) 建议 ≥ 3000
自动跨页拆分示例:
Flash_Write(data, 0x10F0, 600, 3000);
// 内部自动拆分为:
// 页 1: addr=0x10F0, size=16 (填满到 0x10FF)
// 页 2: addr=0x1100, size=256
// 页 3: addr=0x1200, size=256
// 页 4: addr=0x1300, size=72 (剩余数据)
4.4 Flash_EraseSector — 4KB 扇区擦除
flash_ret_t Flash_EraseSector(uint32_t addr, uint32_t timeout_ms);
参数 说明 约束
addr 扇区首地址 必须 4KB 对齐 (addr & 0xFFF == 0)
timeout_ms 超时(ms) 建议 ≥ 5000
4.5 Flash_EraseBlock64K — 64KB 块擦除
flash_ret_t Flash_EraseBlock64K(uint32_t addr, uint32_t timeout_ms);
参数 说明 约束
addr 块首地址 必须 64KB 对齐 (addr & 0xFFFF == 0)
timeout_ms 超时(ms) 建议 ≥ 10000
4.6 Flash_EraseChip — 整片擦除
flash_ret_t Flash_EraseChip(uint32_t timeout_ms);
参数 说明 约束
timeout_ms 超时(ms) 建议 ≥ 600000(10 分钟)
4.7 Flash_ReadID — 读取 JEDEC ID
uint32_t Flash_ReadID(void);
返回值 说明
0xEF4020 正常(W25Q512)
0 通信失败
五、典型使用场景
5.1 保存/加载配置参数
#define CONFIG_ADDR 0x3F0000 /* 芯片末尾 */
typedef struct {
float target_flow;
uint8_t mode;
uint32_t checksum;
} device_config_t;
void save_config(const device_config_t *cfg)
{
Flash_EraseSector(CONFIG_ADDR, 5000);
Flash_Write((const uint8_t *)cfg, CONFIG_ADDR,
sizeof(device_config_t), 3000);
}
bool load_config(device_config_t *cfg)
{
uint8_t tmp[sizeof(device_config_t)] __attribute__((aligned(32)));
if (Flash_Read(tmp, CONFIG_ADDR, sizeof(tmp), 2000) != FLASH_OK)
return false;
memcpy(cfg, tmp, sizeof(*cfg));
return true;
}
5.2 日志存储(循环写入)
static uint32_t log_addr = 0x000000;
void append_log(const uint8_t *entry, uint32_t len)
{
/* 当前扇区写满?擦除下一个 */
if ((log_addr % W25Q512_SECTOR_SIZE) == 0 && log_addr > 0)
{
Flash_EraseSector(log_addr, 5000);
}
if (Flash_Write(entry, log_addr, len, 3000) == FLASH_OK)
{
log_addr += len;
}
}
5.3 连通性自检
bool flash_self_check(void)
{
uint32_t id = Flash_ReadID();
if (id != W25Q512_JEDEC_ID) return false;
uint8_t w[256], r[256] __attribute__((aligned(32)));
for (int i = 0; i < 256; i++) w = (uint8_t)i;
Flash_EraseSector(0x3FFF00, 5000);
Flash_Write(w, 0x3FFF00, 256, 3000);
Flash_Read(r, 0x3FFF00, 256, 2000);
return (memcmp(w, r, 256) == 0);
}
5.4 OTA 固件写入
flash_ret_t ota_write_firmware(const uint8_t *fw, uint32_t fw_size)
{
#define FW_ADDR 0x100000 /* 固件存储区 */
/* 擦除固件区(按 64KB 块擦除,效率更高) */
uint32_t blocks = (fw_size + W25Q512_BLOCK64K_SIZE - 1) / W25Q512_BLOCK64K_SIZE;
for (uint32_t i = 0; i < blocks; i++)
{
flash_ret_t r = Flash_EraseBlock64K(FW_ADDR + i * W25Q512_BLOCK64K_SIZE, 10000);
if (r != FLASH_OK) return r;
}
/* 写入固件(自动跨页) */
return Flash_Write(fw, FW_ADDR, fw_size, 5000);
}
六、超时值选取参考
操作 典型耗时 最大耗时 建议超时
Flash_Read < 1 ms ~10 ms 2000 ms
Flash_Write (单页 256B) 0.4~3 ms ~10 ms 3000 ms
Flash_Write (4KB 扇区) ~50 ms ~200 ms 5000 ms
Flash_EraseSector (4KB) 45~400 ms ~800 ms 5000 ms
Flash_EraseBlock64K (64KB) 150~2000 ms ~3000 ms 10000 ms
Flash_EraseChip (64MB) 100~300 s ~600 s 600000 ms
七、注意事项
7.1 只能在任务中调用
// ✅ 任务上下文: 可以阻塞
void My_Task(void *arg) {
Flash_Write(data, addr, 256, 3000); // OK
}
// ❌ 中断上下文: 会崩溃!
void EXTI_IRQHandler(void) {
Flash_Write(data, addr, 256, 3000); // osThreadFlagsWait → 断言失败
}
// ❌ 驱动回调中: 会崩溃!
void my_done_cb(w25q_evt_t evt) {
Flash_Read(buf, addr, 256, 2000); // 死锁 + 崩溃
}
7.2 读缓冲区必须对齐
// ✅ 32 字节对齐
static uint8_t buf[4096] __attribute__((aligned(32)));
Flash_Read(buf, addr, 4096, 2000);
// ❌ 未对齐 → D-Cache Invalidate 破坏相邻内存
uint8_t buf[4096];
Flash_Read(buf, addr, 4096, 2000); // 危险!
7.3 写前必须擦除
Flash 物理特性:只能 1→0,不能 0→1。
// ❌ 未擦除就写入 → 数据不正确
Flash_Write(data, 0x5000, 256, 3000);
// ✅ 先擦除再写入
Flash_EraseSector(0x5000, 5000); // 0x5000 必须 4KB 对齐
Flash_Write(data, 0x5000, 256, 3000);
7.4 地址对齐
Flash_EraseSector(0x1000, 5000); // ✅ 4KB 对齐
Flash_EraseSector(0x1234, 5000); // ❌ 未对齐
Flash_EraseBlock64K(0x10000, 10000); // ✅ 64KB 对齐
Flash_EraseBlock64K(0x5000, 10000); // ❌ 未对齐
7.5 队列深度
消息队列深度为 8。最多同时排队 8 个请求。 若第 9 个请求到达,调用者会在 osMessageQueuePut 处阻塞(等队列有空位)。 正常情况下不会发生——Flash_Task 串行处理请求,每个请求完成后队列立即空出一个位置。
八、内部请求流程(调试参考)
8.1 请求结构体
typedef struct {
req_op_t op; // 操作类型
uint8_t *buf; // 数据缓冲区
uint32_t addr; // Flash 地址
uint32_t size; // 数据大小
uint32_t timeout_ms; // 超时(ms)
osThreadId_t requester; // ★ 自动填充的调用者线程 ID ★
volatile flash_ret_t result; // 操作结果(Flash_Task 写回)
} flash_req_t;
8.2 标志位
标志位 值 设到谁 何时设置
─────────────────────────────────────────────────────
FLASH_FLAG_DONE 0x01 Flash_Task ISR: 驱动操作成功
FLASH_FLAG_ERR 0x02 Flash_Task ISR: 驱动操作出错
FLASH_FLAG_REPLY 0x04 请求者线程 Flash_Task: 请求处理完成
8.3 写操作完整时序
Scan_Task Flash_Task 驱动/硬件
───────── ────────── ─────────
Flash_Write(buf, 0x10F0, 600, 3000)
│
├─ req = {WRITE, buf, 0x10F0, 600, 3000}
├─ req.requester = osThreadGetId()
│ (Scan_Task 的句柄)
├─ Queue.put(&req) ──────► Queue.get(&req)
│ │
├─ FlagsWait(REPLY) ├─ exec_write(req)
│ (阻塞, CPU 让出) │
│ ├─ 页1: WritePage_DMA(buf, 0x10F0, 16)
│ │ │
│ │ │ MDMA 传输...
│ │ │ Flash 内部编程...
│ │ │ ISR: drv_done_cb(DONE)
│ │ ▼
│ │ FlagsSet(Flash_Task, DONE)
│ │ │
│ │ ▼
│ │ FlagsWait → 收到 DONE
│ │
│ ├─ 页2: WritePage_DMA(buf+16, 0x1100, 256)
│ │ ... (同上)
│ │
│ ├─ 页3: WritePage_DMA(buf+272, 0x1200, 256)
│ │ ... (同上)
│ │
│ ├─ 页4: WritePage_DMA(buf+528, 0x1300, 72)
│ │ ... (同上)
│ │
│ ├─ req->result = FLASH_OK
│ │
│◄── FlagsSet(Scan_Task, REPLY)─┤
│ │
├─ 返回 FLASH_OK │ 继续取下一个请求
│ 读 req->result
▼
九、架构分层总结
┌────────────────────────────────────────────────────────────────┐
│ 应用层 (scan_task / motor_task / shell_cmd / flash_test) │
│ 调用 Flash_Read / Write / Erase 同步 API │
├────────────────────────────────────────────────────────────────┤
│ flash_task.h / flash_task.c (I/O Server) │
│ 消息队列接收请求 → 驱动操作 → 中断等待 → 回复请求者 │
│ 线程 ID 自动获取(osThreadGetId),调用者无感 │
├────────────────────────────────────────────────────────────────┤
│ bsp_w25q512.h / bsp_w25q512.c (底层驱动, 异步) │
│ MDMA + AutoPolling + 中断回调 → ThreadFlagsSet(DONE/ERR) │
├────────────────────────────────────────────────────────────────┤
│ HAL_OSPI + MDMA (STM32 HAL 库) │
├────────────────────────────────────────────────────────────────┤
│ W25Q512 芯片 (OCTOSPI / SPI Flash) │
└────────────────────────────────────────────────────────────────┘
十、API 速查表
函数 用途 关键约束
Flash_Read(buf, addr, size, ms) 读取数据 buf 须 32B 对齐
Flash_Write(buf, addr, size, ms) 写入数据(自动跨页) 须先擦除
Flash_EraseSector(addr, ms) 擦除 4KB addr 须 4KB 对齐
Flash_EraseBlock64K(addr, ms) 擦除 64KB addr 须 64KB 对齐
Flash_EraseChip(ms) 整片擦除 耗时 ~100-300s
Flash_ReadID() 读 JEDEC ID 正常返回 0xEF4020
|
|