FreeRTOS中断服务程序(ISR)详细讲解
个人博客:blogs.wurp.top
简介
1、什么是中断服务程序(ISR):
- 中断服务程序(Interrupt Service Routine, ISR)是用于处理硬件中断的函数。当某个外设(如定时器、串口、GPIO等)触发中断时,CPU会暂停当前执行的任务,转而执行对应的ISR。
2、主要特点:
- 快速响应:ISR必须尽可能快地执行完毕,以减少对系统实时性的影响。
- 不能阻塞:ISR中不能调用任何可能引起阻塞的函数(如
vTaskDelay()
)。 - 使用特定API:FreeRTOS提供了专门的API来在ISR中与任务通信,例如
xQueueSendFromISR()
和xSemaphoreGiveFromISR()
。
3、FreeRTOS中的中断处理机制:
- FreeRTOS 提供了完整的中断处理支持,包括中断服务程序(ISR)的编写、中断优先级管理以及与任务之间的交互。
中断的基本概念
1、中断的触发与响应
- 中断 是一种由硬件或软件触发的事件,用于引起处理器的响应。
- 中断源 可以是定时器、外设(如串口、ADC)、异常(如除零错误)等。
2、中断优先级与嵌套
- 每个中断都有一个优先级,用于决定多个中断同时发生时的处理顺序。
- FreeRTOS 支持多级中断优先级管理。
FreeRTOS中中断服务程序的结构
1、中断服务函数的定义
void vMyISR(void)
{// 1. 保存寄存器(可选,取决于具体硬件)portSAVE_CONTEXT();// 2. 处理中断逻辑// 例如:读取中断源、清除中断标志等// 3. 如果需要触发任务切换,使用 FreeRTOS 提供的函数if (xHigherPriorityTaskWoken == pdTRUE) {portYIELD_FROM_ISR();}// 4. 恢复寄存器(可选,取决于具体硬件)portRESTORE_CONTEXT();
}
portSAVE_CONTEXT()
和 portRESTORE_CONTEXT()
是用于保存和恢复处理器寄存器的宏,确保中断处理过程中不会破坏任务的上下文。
portYIELD_FROM_ISR()
是 FreeRTOS中用于在中断中请求任务调度的函数。只有在中断处理过程中有更高优先级的任务被唤醒时才调用。
在 FreeRTOS 中,不能在中断服务程序中直接调用任何 FreeRTOS API 函数,除非它们是专门设计用于中断上下文的(如 xQueueSendFromISR()
或 xSemaphoreGiveFromISR()
)。
2、使用portENTER_CRITICAL()和portEXIT_CRITICAL()保护临界区
portENTER_CRITICAL()
:进入临界区,通常会禁用所有中断(具体实现依赖于硬件平台)。在此之后执行的代码将不会被中断。
portEXIT_CRITICAL()
:退出临界区,恢复中断状态。此时允许中断和任务切换继续发生。
portENTER_CRITICAL();
// 临界区代码,例如修改共享变量
x = x + 1;
portEXIT_CRITICAL();
3、中断服务程序中的任务通知与队列操作
void vTimerISR(void) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;// 向任务发送通知xTaskNotifyFromISR(xTaskHandle, 0x1234, eSetBits, &xHigherPriorityTaskWoken);// 如果需要切换上下文portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
在FreeRTOS中使用中断
1、配置中断源
- 使用
NVIC_EnableIRQ()
启用特定的中断源。 - 在 FreeRTOS 中,中断优先级的配置通常依赖于所使用的处理器架构(如 ARM Cortex-M 系列)。
- 使用
NVIC_SetPriority()
函数设置中断优先级。 - 需要将中断优先级设置为低于内核中断(如 PendSV 和 SysTick),以避免抢占内核任务。
2、注册中断服务函数
- 每个中断源都需要一个对应的中断服务函数。
- 在 ISR 中,应尽量减少处理时间,并避免调用 FreeRTOS API 函数(除非使用专门的中断安全函数)。
- 如果需要在 ISR 中触发任务调度,可以使用
xTaskNotifyFromISR()
或xSemaphoreGiveFromISR()
等函数。
3、使用FreeRTOS提供的中断API
// 设置某个中断的优先级
NVIC_SetPriority(USART1_IRQn, configLIBRARY_KERNEL_INTERRUPT_PRIORITY);// 启用中断
NVIC_EnableIRQ(USART1_IRQn);// 中断服务函数
void USART1_IRQHandler(void) {// 处理中断逻辑if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {// 读取数据uint8_t data = USART_ReceiveData(USART1);// 触发任务通知xTaskNotifyFromISR(xTaskHandle, data, eSetValue, NULL);}
}
中断服务程序的最佳实践
1、简单的中断服务程序示例
以下是一个简单的 FreeRTOS 中断服务程序示例,假设使用的是 STM32 微控制器:
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"// 定义一个二值信号量
SemaphoreHandle_t xSemaphore = NULL;// 中断服务程序
void EXTI0_IRQHandler(void)
{// 检查是否发生中断if (EXTI_GetITStatus(EXTI_Line0) != RESET){// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);// 使用 xSemaphoreGiveFromISR 发送信号量xSemaphoreGiveFromISR(xSemaphore, NULL);}
}// 任务函数
void vTaskFunction(void *pvParameters)
{while (1){// 等待信号量if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE){// 处理中断事件printf("Interrupt occurred!\n");}}
}int main(void)
{// 初始化系统时钟、GPIO 等// ...// 创建信号量xSemaphore = xSemaphoreCreateBinary();// 创建任务xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);// 启动调度器vTaskStartScheduler();// 如果调度器启动失败,进入死循环for (;;);
}
2、使用队列传递数据的ISR示例
// 队列定义
QueueHandle_t xQueue;// 初始化队列
xQueue = xQueueCreate(10, sizeof(int));
if (xQueue == NULL) {// 队列创建失败处理
}// 在 ISR 中发送数据
void vTimerIsr(void) {int data = 42;// 将数据发送到队列if (xQueueSendFromISR(xQueue, &data, NULL) != pdTRUE) {// 发送失败处理}
}// 在任务中接收数据
void vTaskFunction(void *pvParameters) {int receivedData;while (1) {if (xQueueReceive(xQueue, &receivedData, portMAX_DELAY) == pdTRUE) {// 处理接收到的数据printf("Received: %d\n", receivedData);}}
}
3、使用任务通知的ISR示例
#include "FreeRTOS.h"
#include "task.h"// 定义任务句柄
TaskHandle_t xTaskHandle = NULL;// 任务函数
void vTaskFunction(void *pvParameters)
{// 任务进入等待状态,等待任务通知while (1) {ulTaskNotifyTake(pdTRUE, portMAX_DELAY);// 接收到通知后的处理逻辑printf("任务接收到通知,执行操作...\n");}
}// 外设中断服务程序
void vMyInterruptHandler(void)
{// 发送任务通知给任务xTaskNotifyGiveFromISR(xTaskHandle, NULL);
}// 主函数
int main(void)
{// 创建任务xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandle);// 启动调度器vTaskStartScheduler();// 不会到达这里for (;;);
}
总结
1、中断服务程序的重要性
1.1 实时响应:
- 中断服务程序能够迅速响应外部事件(如按键、定时器溢出、传感器信号等),确保系统对突发事件的及时处理。
1.2 任务调度:
- 在某些情况下,ISR可能会触发任务切换或通知其他任务,从而实现高效的多任务调度和资源管理。
1.3 系统稳定性:
- 正确编写和使用ISR可以避免因中断处理不当导致的系统崩溃或死锁问题,提高系统的稳定性和可靠性。
1.4 资源管理:
- ISR通常用于处理硬件资源(如外设寄存器、DMA传输等),确保这些资源被正确访问和释放,防止资源冲突。
1.5 性能优化:
- 通过合理设计ISR,可以减少中断处理时间,提升系统整体性能,尤其是在高频率中断场景下。
2、FreeRTOS中ISR的设计原则
2.1 简短且快速执行
- ISR应尽可能简短,避免长时间的执行。
- 长时间运行的ISR可能导致系统响应延迟,甚至影响其他任务的调度。
2.2 避免使用阻塞操作
- 在ISR中不应调用任何会导致任务挂起或等待的函数,例如vTaskDelay()、xQueueReceive()等。
- 这些操作可能引起死锁或系统不稳定。
2.3 使用FreeRTOS提供的中断安全函数
- FreeRTOS提供了一些专门用于ISR的函数,如:xQueueSendFromISR()、xSemaphoreGiveFromISR()、xTaskNotifyFromISR()
- 这些函数可以在ISR中安全地与任务通信。
2.4 避免直接修改共享资源
- 在ISR中应避免直接访问共享变量或数据结构,以免引发竞态条件。
- 可以通过队列、信号量等方式间接传递数据。
2.5 保持中断优先级的合理分配
- 中断优先级应根据任务的实时性要求进行合理配置。
- 高优先级中断应尽量减少对低优先级中断的影响。
2.6 及时清除中断源
- 在ISR中处理完中断后,应立即清除中断源,防止重复触发。
2.7 使用中断嵌套时需谨慎
- 如果使用中断嵌套(即高优先级中断可以打断低优先级中断),需确保不会导致任务调度异常。
2.8 调试和测试
- 在开发过程中,应充分测试ISR的行为,确保其符合预期。
- 使用调试工具跟踪ISR的执行情况,避免潜在的错误。