SPI/I2C/UART三总线在多传感器系统中的仲裁设计
扫描二维码
随时随地手机看文章
在无人机飞控或工业采集系统中,MCU往往需要同时管理SPI(高速ADC)、I2C(温湿度)、UART(GPS)三类总线。当多个传感器“同时”请求响应时,软件仲裁(Software Arbitration) 是保障系统不“死锁”的关键。本文将提供一套基于RTOS互斥锁与中断优先级的实战仲裁方案。
一、总线特性与冲突风险分析
总线 冲突场景 仲裁难点
SPI 多从机片选(CS)冲突 硬件NSS冲突或软件CS时序重叠
I2C 多主机(Multi-Master)仲裁 总线忙(BUSY)检测与时钟拉伸
UART 接收中断与发送阻塞 无硬件仲裁,纯靠软件缓冲
核心思想:在RTOS环境下,将“总线使用权”视为一种共享资源,通过互斥量(Mutex)或信号量(Binary Semaphore)进行保护。
二、SPI仲裁:互斥锁(Mutex)保护
SPI总线通常只有一个主机,冲突源于软件同时操作不同CS引脚。解决方案是为整个SPI总线创建一个互斥锁。
1. RTOS任务与资源定义
// 资源定义
SPI_HandleTypeDef hspi1;
osMutexId_t spi1_mutex_id;
// 任务句柄
osThreadId_t adcTaskHandle;
osThreadId_t imuTaskHandle;
2. SPI驱动封装(加锁)
// 封装的SPI发送接收函数(带仲裁)
HAL_StatusTypeDef SPI1_TransmitReceive(uint8_t *tx, uint8_t *rx, uint16_t len) {
HAL_StatusTypeDef status;
// 请求SPI总线使用权(阻塞等待,超时1ms)
if (osMutexAcquire(spi1_mutex_id, 1) == osOK) {
// 临界区:操作CS和SPI外设
HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_RESET);
status = HAL_SPI_TransmitReceive(&hspi1, tx, rx, len, 100);
HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_SET);
osMutexRelease(spi1_mutex_id); // 释放总线
} else {
return HAL_TIMEOUT; // 仲裁失败
}
return status;
}
3. 任务调用示例
void ADCTask(void *arg) {
while(1) {
if (SPI1_TransmitReceive(adc_tx, adc_rx, 3) == HAL_OK) {
// 处理ADC数据
}
osDelay(10);
}
}
仲裁效果:即使ADCTask和IMUTask同时调用SPI1_TransmitReceive,互斥锁也能保证同一时刻只有一个任务在驱动SCK和MOSI。
三、I2C仲裁:硬件BUSY检测与时钟拉伸
I2C总线支持多主机,但软件层面仍需处理总线忙状态。
1. 软件仲裁逻辑
在发起I2C传输前,必须检查总线是否空闲。
// I2C总线仲裁函数
HAL_StatusTypeDef I2C1_ArbitrateAndTransmit(uint8_t addr, uint8_t *data, uint16_t len) {
uint32_t timeout = HAL_GetTick();
// 等待总线空闲(BUSY位清除)
while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY) {
if (HAL_GetTick() - timeout > 5) { // 5ms超时
// 总线死锁恢复:强制复位I2C外设
HAL_I2C_MspDeInit(&hi2c1);
HAL_I2C_MspInit(&hi2c1);
return HAL_ERROR;
}
osDelay(1);
}
// 总线空闲,执行传输
return HAL_I2C_Master_Transmit(&hi2c1, addr, data, len, 100);
}
2. 中断与DMA协同
若I2C使用DMA,需确保DMA流不与SPI或UART的DMA发生冲突(在CubeMX中合理分配DMA通道)。
四、UART仲裁:环形缓冲区(Ring Buffer)与DMA
UART没有硬件仲裁机制,其“仲裁”体现在接收端不丢包。
1. 接收中断+环形缓冲区
#define UART_RX_BUF_LEN 256
uint8_t gps_rx_buf[UART_RX_BUF_LEN];
uint16_t rx_head = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) { // GPS
// 将数据存入环形缓冲区(先进先出)
ring_buffer_push(gps_rx_buf, &rx_head, received_byte);
// 重新开启接收中断
HAL_UART_Receive_IT(&huart2, &received_byte, 1);
}
}
2. 发送仲裁(避免覆盖)
// 发送函数,检查上一次发送是否完成
void UART2_Send(uint8_t *data, uint16_t len) {
if (huart2.gState == HAL_UART_STATE_READY) {
HAL_UART_Transmit_DMA(&huart2, data, len);
} else {
// 仲裁失败:丢弃数据或缓存
}
}
五、中断优先级与死锁预防
1. 优先级金字塔
为防止高优先级任务饿死低优先级任务,需精心设计中断和任务优先级:
• UART RX中断:最高(保证不丢字节)。
• SPI DMA中断:中高。
• I2C事件中断:中。
• 应用任务(ADC/IMU/GPS):低(受RTOS调度)。
2. 死锁(Deadlock)规避
• 优先级继承:RTOS的互斥锁应开启优先级继承(Priority Inheritance),防止优先级反转。
• 超时机制:所有osMutexAcquire和HAL_I2C_Master_Transmit必须有超时,避免永久阻塞。
六、结语
多总线系统的仲裁设计,本质是资源争用管理。SPI靠互斥锁,I2C靠BUSY检测与复位,UART靠环形缓冲。在RTOS环境下,合理的任务优先级与超时机制,是避免系统死锁的最后一道防线。





