引言
在嵌入式系统中,SPI Flash 常用于存储配置参数、日志或固件升级数据。当引入 RTOS 后,多个任务可能同时发起读写请求,若缺乏互斥保护,轻则数据覆盖,重则破坏 Flash 内部状态机。本文将从底层机制出发,探讨几种互斥方案的适用场景与实现细节。
为什么需要互斥?
SPI Flash 操作通常包含多个步骤:发送命令、地址、数据,等待忙状态。这些步骤必须作为一个整体执行,不可被其他任务打断。例如,一个任务正在执行页编程(Page Program),另一个任务突然发起擦除操作,会导致总线冲突或数据损坏。因此,互斥的核心是保证 Flash 操作的原子性。
方案一:关中断(Critical Section)
原理
关中断是 RTOS 中最简单的互斥手段。通过屏蔽系统中断,阻止任务调度发生,从而保证当前任务独占 CPU 和 Flash。
实现示例
// 使用 CMSIS-RTOS v2 接口
void flash_write_protected(uint32_t addr, uint8_t *data, uint32_t len) {
uint32_t primask = __get_PRIMASK();
__disable_irq(); // 关中断
// 执行 Flash 写操作
spi_flash_write(addr, data, len);
__set_PRIMASK(primask); // 恢复中断
}
优缺点
- 优点:实现简单,无死锁风险,适用于极短操作。
- 缺点:关中断时间过长会破坏系统实时性,若 Flash 操作耗时(如擦除需数毫秒),则不可接受。此外,在多核系统中,关中断只能屏蔽当前核,无法全局互斥。
方案二:调度锁(Scheduler Lock)
原理
调度锁通过禁止任务切换,但允许中断响应。这样,中断服务程序(ISR)可以运行,但其他任务无法抢占当前任务。
实现示例
void flash_write_sched_lock(uint32_t addr, uint8_t *data, uint32_t len) {
vTaskSuspendAll(); // 挂起调度器
// 执行 Flash 操作
spi_flash_write(addr, data, len);
xTaskResumeAll(); // 恢复调度器
}
优缺点
- 优点:比关中断更温和,允许中断响应,适合中等长度操作。
- 缺点:若操作时间过长,其他任务会被饿死;且无法防止中断服务程序(ISR)中访问 Flash(若 ISR 也操作 Flash,则仍需关中断)。
方案三:互斥量(Mutex)
原理
Mutex 是 RTOS 提供的标准同步机制,支持优先级继承,可有效避免优先级反转问题。任务在访问 Flash 前获取 Mutex,访问后释放。
配置步骤
- 创建 Mutex 句柄(全局变量)。
- 在任务中调用
osMutexAcquire获取锁。 - 执行 Flash 操作。
- 调用
osMutexRelease释放锁。
代码示例(CMSIS-RTOS v2)
osMutexId_t flash_mutex;
void flash_init_mutex(void) {
flash_mutex = osMutexNew(NULL);
}
void flash_write_mutex(uint32_t addr, uint8_t *data, uint32_t len) {
osMutexAcquire(flash_mutex, osWaitForever);
// 执行 Flash 操作
spi_flash_write(addr, data, len);
osMutexRelease(flash_mutex);
}
优缺点
- 优点:阻塞等待,不会浪费 CPU;支持优先级继承,防止低优先级任务长时间占用;适用于长操作。
- 缺点:可能引入死锁(若获取顺序不当);在中断服务程序中无法使用(需用信号量代替)。
方案对比与取舍
| 方案 | 实时性影响 | 适用场景 | 风险 | |------|------------|----------|------| | 关中断 | 高(屏蔽所有中断) | 极短操作(<10us) | 中断延迟增大 | | 调度锁 | 中(仅禁止任务切换) | 中等操作(<1ms) | 任务饿死 | | Mutex | 低(阻塞等待) | 长操作(>1ms) | 死锁、优先级反转(有继承则缓解) |
进阶:混合策略
实际项目中,常组合使用。例如,对 Flash 的写操作分为“命令发送”和“等待完成”两个阶段。命令发送阶段短,可用关中断或调度锁;等待完成阶段长,可释放锁,让其他任务运行。但需注意,等待期间 Flash 状态可能被其他任务改变,因此需设计状态机或使用硬件信号。
注意事项
- 在 ISR 中不能使用 Mutex,应使用信号量或直接关中断。
- 获取 Mutex 后,务必在操作完成后释放,建议使用
osMutexAcquire的返回值检查是否成功。 - 多任务访问 Flash 时,建议统一封装驱动接口,内部实现互斥,避免业务层重复加锁。
- 若 Flash 支持双片选或独立总线,可考虑分区域访问,减少锁竞争。
总结
选择互斥方案需权衡实时性、操作时长和系统复杂度。关中断适合极短操作,调度锁适合中等操作,而 Mutex 是大多数场景的首选,尤其当操作耗时较长时。理解每种方案的原理和限制,才能设计出健壮的嵌入式系统。