# 引言 在嵌入式系统中,SPI Flash 常用于存储配置参数、日志或固件升级数据。当引入 RTOS 后,多个任务可能同时发起读写请求,若缺乏互斥保护,轻则数据覆盖,重则破坏 Flash 内部状态机。本文将从底层机制出发,探讨几种互斥方案的适用场景与实现细节。 # 为什么需要互斥? SPI Flash 操作通常包含多个步骤:发送命令、地址、数据,等待忙状态。这些步骤必须作为一个整体执行,不可被其他任务打断。例如,一个任务正在执行页编程(Page Program),另一个任务突然发起擦除操作,会导致总线冲突或数据损坏。因此,互斥的核心是保证 Flash 操作的原子性。 # 方案一:关中断(Critical Section) ## 原理 关中断是 RTOS 中最简单的互斥手段。通过屏蔽系统中断,阻止任务调度发生,从而保证当前任务独占 CPU 和 Flash。 ## 实现示例 ```c // 使用 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)可以运行,但其他任务无法抢占当前任务。 ## 实现示例 ```c 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,访问后释放。 ## 配置步骤 1. 创建 Mutex 句柄(全局变量)。 2. 在任务中调用 `osMutexAcquire` 获取锁。 3. 执行 Flash 操作。 4. 调用 `osMutexRelease` 释放锁。 ## 代码示例(CMSIS-RTOS v2) ```c 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 是大多数场景的首选,尤其当操作耗时较长时。理解每种方案的原理和限制,才能设计出健壮的嵌入式系统。