当前位置:首页 > 通信技术 > 通信技术
[导读]在无人机飞控或工业采集系统中,MCU往往需要同时管理SPI(高速ADC)、I2C(温湿度)、UART(GPS)三类总线。当多个传感器“同时”请求响应时,软件仲裁(Software Arbitration) 是保障系统不“死锁”的关键。本文将提供一套基于RTOS互斥锁与中断优先级的实战仲裁方案。



在无人机飞控或工业采集系统中,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环境下,合理的任务优先级与超时机制,是避免系统死锁的最后一道防线。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭