简介
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的执行情况,避免潜在的错误。