是的,多个任务共享 SPI、I2C、UART 等外设时,通常必须加锁,否则会出现资源竞争、数据错乱、通信失败等问题。这是 RTOS 编程中常见的同步问题。
外设通常是共享资源,比如:
osMutex
/ xSemaphoreCreateMutex
)每次访问前加锁,访问完释放:
osMutexAcquire(spiMutex, osWaitForever);
HAL_SPI_Transmit(...);
osMutexRelease(spiMutex);
让某一个任务专门处理 SPI/I2C 请求,其他任务通过消息队列通知它,属于串行化访问方案。
HAL_I2C_Master_Transmit()
);资源类型 | 推荐加锁方式 | 说明 |
---|---|---|
SPI | osMutex |
保护 HAL 传输函数 |
I2C | osMutex |
确保读写是原子操作 |
UART | osMutex + RingBuf |
可读写分离,但仍需写锁 |
ADC/DMA | 一般加锁或用信号量 | DMA 同步较特殊 |
外设访问任务 | osMessageQueue + 专属线程 |
更复杂但线程安全(推荐在复杂项目中) |
是的,必须加锁或序列化访问,这是使用 RTOS 时避免“竞态条件”的基本要求。
你可以选:
osMutex
;osMessageQueue
分发。序列化访问指的是:多个任务不直接操作某个共享资源(如 SPI/I2C/UART),而是通过“一个专门的任务”来统一访问该资源。
这样做的核心目的是 避免并发冲突,实现 线程安全。
模式 | 优点 | 缺点 |
---|---|---|
加锁访问 | 代码简单、快速实现 | 易滥用;多个任务仍需等待锁,可能嵌套死锁或优先级反转 |
序列化访问 | 明确职责,资源操作集中管理 | 实现复杂,需要设计消息格式/队列 |
Task_SPI_Manager
void Task_SPI_Manager(void *argument) {
for (;;) {
SPI_Request_t req;
if (osMessageQueueGet(spiQueue, &req, NULL, osWaitForever) == osOK) {
switch (req.cmd) {
case SPI_CMD_WRITE:
HAL_SPI_Transmit(req.hspi, req.data, req.len, 100);
break;
case SPI_CMD_READ:
HAL_SPI_Receive(req.hspi, req.data, req.len, 100);
break;
// 其他操作...
}
// 可选:发信号通知请求完成
if (req.doneSem != NULL)
osSemaphoreRelease(req.doneSem);
}
}
}
SPI_Request_t req = {
.cmd = SPI_CMD_WRITE,
.hspi = &hspi1,
.data = buf,
.len = len,
.doneSem = doneSemHandle // 可选,用于同步
};
osMessageQueuePut(spiQueue, &req, 0, 0);
osSemaphoreAcquire(doneSemHandle, osWaitForever); // 等待完成
typedef enum {
SPI_CMD_WRITE,
SPI_CMD_READ,
// ...
} SPI_Command_t;
typedef struct {
SPI_Command_t cmd;
SPI_HandleTypeDef *hspi;
uint8_t *data;
uint16_t len;
osSemaphoreId_t doneSem; // 可选
} SPI_Request_t;
使用情况 | 推荐方案 |
---|---|
简单项目,偶尔访问 | osMutex 加锁 |
复杂系统,多个任务频繁操作 SPI/I2C | 序列化访问任务 |
UART 打印 | RingBuffer + 信号 |
LCD/Flash/EEPROM 控制 | 序列化访问任务 |
以下是一个 CMSIS-RTOS2 + FreeRTOS + HAL 环境下的 SPI 访问序列化任务模板,用于多个任务安全共享一个 SPI 接口。
Core/
├── Inc/
│ └── spi_serial.h
└── Src/
└── spi_serial.c
spi_serial.h
#ifndef SPI_SERIAL_H
#define SPI_SERIAL_H
#include "cmsis_os2.h"
#include "main.h" // 包含 hspi 句柄
typedef enum {
SPI_CMD_WRITE,
SPI_CMD_READ
} SPI_Command_t;
typedef struct {
SPI_Command_t cmd;
SPI_HandleTypeDef *hspi;
uint8_t *data;
uint16_t len;
osSemaphoreId_t doneSem; // 可用于同步请求完成
} SPI_Request_t;
void SPI_SerialInit(void);
osStatus_t SPI_AsyncRequest(SPI_Request_t *req); // 非阻塞
osStatus_t SPI_BlockingRequest(SPI_Request_t *req); // 阻塞等待完成
#endif // SPI_SERIAL_H
spi_serial.c
#include "spi_serial.h"
#define SPI_QUEUE_LENGTH 8
static osMessageQueueId_t spiQueue;
static osThreadId_t spiTaskHandle;
static void SPI_Task(void *argument) {
SPI_Request_t req;
for (;;) {
if (osMessageQueueGet(spiQueue, &req, NULL, osWaitForever) == osOK) {
switch (req.cmd) {
case SPI_CMD_WRITE:
HAL_SPI_Transmit(req.hspi, req.data, req.len, HAL_MAX_DELAY);
break;
case SPI_CMD_READ:
HAL_SPI_Receive(req.hspi, req.data, req.len, HAL_MAX_DELAY);
break;
default:
break;
}
if (req.doneSem) {
osSemaphoreRelease(req.doneSem);
}
}
}
}
void SPI_SerialInit(void) {
spiQueue = osMessageQueueNew(SPI_QUEUE_LENGTH, sizeof(SPI_Request_t), NULL);
const osThreadAttr_t spiTaskAttr = {
.name = "SPISerialTask",
.priority = osPriorityNormal,
.stack_size = 512
};
spiTaskHandle = osThreadNew(SPI_Task, NULL, &spiTaskAttr);
}
osStatus_t SPI_AsyncRequest(SPI_Request_t *req) {
return osMessageQueuePut(spiQueue, req, 0, 0);
}
osStatus_t SPI_BlockingRequest(SPI_Request_t *req) {
osSemaphoreId_t sem = osSemaphoreNew(1, 0, NULL);
req->doneSem = sem;
osStatus_t status = osMessageQueuePut(spiQueue, req, 0, 0);
if (status == osOK) {
osSemaphoreAcquire(sem, osWaitForever);
}
osSemaphoreDelete(sem);
return status;
}
void SomeTask(void *argument) {
uint8_t tx[2] = {0x9F, 0x00};
SPI_Request_t req = {
.cmd = SPI_CMD_WRITE,
.hspi = &hspi1,
.data = tx,
.len = sizeof(tx),
.doneSem = NULL
};
SPI_BlockingRequest(&req); // 等待传输完成
osDelay(10);
}
.hspi
为不同句柄,如 &hspi1
, &hspi2
;osMutex
,但本方式适合高可靠性的设备资源抽象。为适配 多个 SPI 外设(如 SPI1
, SPI2
, SPI3
等),你可以按照以下思路扩展现有框架,支持每个 SPI 接口有独立的队列与后台任务,从而安全地并发访问多个 SPI。
为每个 SPI_HandleTypeDef
实例分配一个独立的任务与消息队列,实现访问串行化,支持多个 SPI。
spi_serial.h
修改:#ifndef SPI_SERIAL_H
#define SPI_SERIAL_H
#include "cmsis_os2.h"
#include "main.h" // HAL SPI 句柄
typedef enum {
SPI_CMD_WRITE,
SPI_CMD_READ
} SPI_Command_t;
typedef struct {
SPI_Command_t cmd;
uint8_t *data;
uint16_t len;
osSemaphoreId_t doneSem;
} SPI_Request_t;
// 初始化一个 SPI 序列化通道
void SPI_SerialInit(SPI_HandleTypeDef *hspi);
// 发送请求(阻塞/非阻塞)
osStatus_t SPI_BlockingRequest(SPI_HandleTypeDef *hspi, SPI_Request_t *req);
osStatus_t SPI_AsyncRequest(SPI_HandleTypeDef *hspi, SPI_Request_t *req);
#endif
spi_serial.c
关键实现:#include "spi_serial.h"
#include
#define MAX_SPI_INSTANCES 3
#define SPI_QUEUE_LENGTH 8
typedef struct {
SPI_HandleTypeDef *hspi;
osMessageQueueId_t queue;
osThreadId_t thread;
} SPI_Context_t;
static SPI_Context_t spiContexts[MAX_SPI_INSTANCES];
static uint32_t spiContextCount = 0;
static SPI_Context_t* findContext(SPI_HandleTypeDef *hspi) {
for (uint32_t i = 0; i < spiContextCount; ++i) {
if (spiContexts[i].hspi == hspi)
return &spiContexts[i];
}
return NULL;
}
static void SPI_SerialTask(void *argument) {
SPI_Context_t *ctx = (SPI_Context_t *)argument;
SPI_Request_t req;
while (1) {
if (osMessageQueueGet(ctx->queue, &req, NULL, osWaitForever) == osOK) {
switch (req.cmd) {
case SPI_CMD_WRITE:
HAL_SPI_Transmit(ctx->hspi, req.data, req.len, HAL_MAX_DELAY);
break;
case SPI_CMD_READ:
HAL_SPI_Receive(ctx->hspi, req.data, req.len, HAL_MAX_DELAY);
break;
default:
break;
}
if (req.doneSem) {
osSemaphoreRelease(req.doneSem);
}
}
}
}
void SPI_SerialInit(SPI_HandleTypeDef *hspi) {
if (spiContextCount >= MAX_SPI_INSTANCES || findContext(hspi)) return;
SPI_Context_t *ctx = &spiContexts[spiContextCount++];
ctx->hspi = hspi;
ctx->queue = osMessageQueueNew(SPI_QUEUE_LENGTH, sizeof(SPI_Request_t), NULL);
char name[16];
snprintf(name, sizeof(name), "SPI_Task_%ld", spiContextCount);
const osThreadAttr_t attr = {
.name = name,
.stack_size = 512,
.priority = osPriorityNormal,
};
ctx->thread = osThreadNew(SPI_SerialTask, ctx, &attr);
}
osStatus_t SPI_AsyncRequest(SPI_HandleTypeDef *hspi, SPI_Request_t *req) {
SPI_Context_t *ctx = findContext(hspi);
if (!ctx) return osErrorParameter;
return osMessageQueuePut(ctx->queue, req, 0, 0);
}
osStatus_t SPI_BlockingRequest(SPI_HandleTypeDef *hspi, SPI_Request_t *req) {
osSemaphoreId_t sem = osSemaphoreNew(1, 0, NULL);
if (!sem) return osErrorNoMemory;
req->doneSem = sem;
SPI_Context_t *ctx = findContext(hspi);
if (!ctx) {
osSemaphoreDelete(sem);
return osErrorParameter;
}
osStatus_t status = osMessageQueuePut(ctx->queue, req, 0, 0);
if (status == osOK) {
osSemaphoreAcquire(sem, osWaitForever);
}
osSemaphoreDelete(sem);
return status;
}
void app_main(void) {
SPI_SerialInit(&hspi1);
SPI_SerialInit(&hspi2);
}
void Task1(void *arg) {
uint8_t tx[2] = {0x9F, 0x00};
SPI_Request_t req = {
.cmd = SPI_CMD_WRITE,
.data = tx,
.len = sizeof(tx),
};
SPI_BlockingRequest(&hspi1, &req);
}
void Task2(void *arg) {
uint8_t tx[1] = {0x90};
SPI_Request_t req = {
.cmd = SPI_CMD_WRITE,
.data = tx,
.len = 1,
};
SPI_BlockingRequest(&hspi2, &req);
}