RTOS串口驱动:中断服务函数优先级与多任务互斥 - 第4节
4.1 中断向量表:CPU 响应中断的入口索引
中断向量表(Interrupt Vector Table, IVT)是处理器在响应中断时查找服务函数入口地址的固定数据结构。对于 ARM Cortex-M 系列内核,向量表默认位于地址 0x00000000,其中前 16 个为系统异常向量,从第 17 个开始为外部中断向量。
SCB->VTOR 寄存器设置),以便在运行时动态更新中断服务函数指针,支持中断服务函数的动态注册。
4.2 NVIC 配置:中断优先级与使能控制
嵌套向量中断控制器(NVIC)是 Cortex-M 内核中管理中断的核心外设。NVIC 负责中断的使能/除能、挂起/清除、优先级分组以及优先级设置。在串口驱动中,正确配置 NVIC 是确保中断响应实时性的前提。
4.2.1 优先级分组与抢占优先级
Cortex-M3/M4 支持 8 位优先级寄存器,但具体实现位数由芯片厂商决定(通常为 4 位)。通过 NVIC_PriorityGroupConfig() 设置优先级分组,决定抢占优先级和子优先级的位数分配。
| 分组编号 | 抢占优先级位数 | 子优先级位数 | 抢占优先级等级数 | 适用场景 |
|---|---|---|---|---|
| NVIC_PriorityGroup_0 | 0 | 4 | 1 | 所有中断同级,无嵌套 |
| NVIC_PriorityGroup_1 | 1 | 3 | 2 | 简单系统,两级嵌套 |
| NVIC_PriorityGroup_2 | 2 | 2 | 4 | 常用配置,平衡嵌套深度 |
| NVIC_PriorityGroup_3 | 3 | 1 | 8 | RTOS 推荐,多级嵌套 |
| NVIC_PriorityGroup_4 | 4 | 0 | 16 | 完全抢占,无子优先级 |
4.2.2 NVIC 配置代码模板
以下代码展示了串口中断的 NVIC 配置流程,包括使能中断、设置优先级和清除挂起标志。
/* 串口中断 NVIC 配置函数 */
void USART_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 1. 设置优先级分组:3 位抢占 + 1 位子优先级 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
/* 2. 配置 USART1 中断 */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
/* 3. 设置抢占优先级为 2(0 最高,7 最低) */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
/* 4. 设置子优先级为 0 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
/* 5. 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 6. 清除可能的挂起标志(上电后可能遗留) */
NVIC_ClearPendingIRQ(USART1_IRQn);
}
4.3 中断服务函数(ISR)编写基础
中断服务函数是中断驱动串口接收/发送的核心执行体。ISR 必须遵循“短小精悍”原则——只做最必要的操作,将耗时处理推迟到任务级。串口 ISR 的典型职责包括:读取数据寄存器、判断中断类型、将数据存入缓冲区、通知任务处理。
4.3.1 ISR 命名与向量表关联
在 STM32 标准库中,中断服务函数名已在启动文件中预定义。开发者只需实现同名函数即可自动关联到向量表。常见串口 ISR 名称如下:
| 中断向量名称 | 对应外设 | 触发条件 |
|---|---|---|
| USART1_IRQHandler | USART1 | 接收/发送/错误中断 |
| USART2_IRQHandler | USART2 | 接收/发送/错误中断 |
| USART3_IRQHandler | USART3 | 接收/发送/错误中断 |
| UART4_IRQHandler | UART4 | 接收/发送/错误中断 |
4.3.2 串口 ISR 标准框架
以下代码展示了一个完整的串口接收中断服务函数框架,包含中断类型判断、数据读取和缓冲区写入。
/* 串口1中断服务函数 */
void USART1_IRQHandler(void)
{
uint8_t data;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 1. 判断中断类型:接收中断 */
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
/* 2. 读取数据寄存器(硬件自动清除 RXNE 标志) */
data = (uint8_t)(USART1->DR & 0xFF);
/* 3. 将数据写入接收环形缓冲区 */
if (xQueueSendFromISR(xRxQueue, &data, &xHigherPriorityTaskWoken) != pdPASS)
{
/* 缓冲区满:溢出处理(可置位溢出标志) */
g_uart_overflow = 1;
}
/* 4. 退出 ISR 时请求上下文切换 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/* 5. 判断中断类型:发送完成中断 */
if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
{
/* 清除发送完成标志 */
USART_ClearITPendingBit(USART1, USART_IT_TC);
/* 通知发送任务可以继续发送下一帧 */
xSemaphoreGiveFromISR(xTxSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/* 6. 判断中断类型:错误中断(溢出/帧错误/噪声) */
if (USART_GetITStatus(USART1, USART_IT_ORE) != RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_ORE);
/* 记录错误计数 */
g_uart_error_count++;
}
}
• 使用
FromISR 后缀的 API 函数(如 xQueueSendFromISR)操作 RTOS 对象• 通过
portYIELD_FROM_ISR 触发上下文切换,确保高优先级任务及时执行• 避免在 ISR 中调用阻塞函数(如
vTaskDelay)或打印调试信息• 中断标志位清除操作必须在 ISR 返回前完成,防止重复触发
4.4 中断使能与全局控制
除了 NVIC 通道使能,串口外设本身也提供中断使能寄存器。开发者需要在串口初始化阶段,通过 USART_ITConfig() 函数开启特定中断类型。
/* 串口中断使能配置(在 USART_Init 之后调用) */
void USART_InterruptEnable(void)
{
/* 使能接收中断:当接收到数据时触发 */
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
/* 使能发送完成中断:当数据发送完成时触发 */
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
/* 使能错误中断:检测溢出、帧错误等 */
USART_ITConfig(USART1, USART_IT_ORE, ENABLE);
/* 注意:不要使能发送缓冲区空中断(TXE),
除非使用中断方式发送,否则会持续触发 */
}
4.5 中断优先级与 RTOS 调度互斥
在 RTOS 环境下,中断优先级设置直接影响系统实时性和任务调度的确定性。错误的优先级配置可能导致优先级反转、死锁或任务饿死。
- SysTick 最高优先级: 系统节拍中断必须设置为最高抢占优先级(通常为 0),确保时间片轮转和任务切换不受阻塞。
- 串口中断中等优先级: 建议设置为 2~3(抢占优先级),避免长时间屏蔽其他外设中断,同时保证串口数据不丢失。
- 临界区保护: 在 ISR 中访问共享资源(如环形缓冲区)时,使用
taskENTER_CRITICAL_FROM_ISR()保护,防止与任务级代码产生竞态。 - 中断延迟控制: 串口 ISR 执行时间应控制在微秒级,避免影响其他中断的响应延迟。
/* 中断优先级配置示例(适用于 FreeRTOS) */
void System_Interrupt_Config(void)
{
/* 设置优先级分组:3 位抢占 + 1 位子优先级 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
/* SysTick 中断:最高优先级 */
NVIC_SetPriority(SysTick_IRQn, 0);
/* 串口中断:中等优先级 */
NVIC_SetPriority(USART1_IRQn, 2);
/* 其他外设中断:较低优先级 */
NVIC_SetPriority(TIM2_IRQn, 4);
NVIC_SetPriority(EXTI0_IRQn, 5);
}
• 串口 ISR 执行期间,SysTick 无法抢占,系统节拍丢失,任务调度延迟
• 高频率串口数据流可能导致系统节拍长时间无法响应,最终触发看门狗复位
• 在 FreeRTOS 中,
portYIELD_FROM_ISR 依赖于 SysTick 中断进行上下文切换,若串口优先级高于 SysTick,切换请求可能被延迟