一、异常与中断的概念
1. 中断
- 定义:中断是由外部硬件设备(如键盘、鼠标、网络接口等)发出的信号,用于通知CPU有事件需要处理。
- 类型:
- 外部中断:来自外部设备的信号,如I/O设备的请求。
- 内部中断:由CPU内部条件引发,如定时器溢出。
- 使用场景:常用于实时系统,以响应外部事件,提高系统的响应性。
- 处理:当中断发生时,CPU会保存当前状态,跳转到中断处理程序,完成后再返回到被中断的任务。
中断机制允许CPU在处理当前任务时,当外部事件发生并请求处理时,暂时中止当前工作并快速响应这一事件。处理完事件后,CPU会回到原来被中断的地方,继续之前的工作。这种机制特别适用于需要迅速响应的紧急事件,通常在处理中断时执行简单的操作,比如标记事件或者唤醒处理任务。
在使用FreeRTOS等系统时,推荐通过信号量、消息或事件标志等方式来通知处理任务中断的发生,从而使处理任务可以进一步处理具体的事件。
通过中断机制,CPU可以避免长时间等待或查询外设状态,而是在需要时立即响应外设请求,这样有助于提高系统的实时性和效率。
2. 异常
- 定义:异常是程序执行过程中由CPU检测到的特定事件,通常是与程序本身相关的,如错误或特殊条件。
- 类型:
- 同步异常:与程序执行直接相关,如除零错误、非法操作等。
- 异步异常:由外部因素引起,但在CPU执行过程中影响的情况。
- 使用场景:主要用于错误处理和资源管理,如内存访问违例、段错误等。
- 处理:当异常发生时,CPU会转移控制到异常处理程序,处理完毕后,可以选择恢复执行或终止程序。
异常是计算机处理器在执行正常程序时突然遇到的任何事件,这些事件会迫使处理器暂时离开当前任务,转而执行特殊的处理代码。如果不及时处理,这些事件可能会导致系统出错甚至瘫痪,因此正确处理异常,预防错误发生对软件的稳定性至关重要,尤其对实时系统来说更是如此。
异常可以分为两类:同步异常和异步异常。同步异常是由内部事件引起的,比如执行特定指令时发生的错误,如除零操作;而异步异常则是由外部设备或其他因素触发的,例如外部设备产生的中断。
同步异常一旦发生,系统必须立即处理,不能继续执行原有的程序指令步骤;而异步异常则可以根据系统的设计进行延迟处理或者忽略,例如某些中断事件可以被系统忽略而继续执行当前任务。
异常处理是确保系统稳定性和可靠性的重要一环,合理设计异常处理机制能够有效应对各种意外情况,确保系统在面对异常时能够安全而高效地运行。
二、中断的运作机制
1. 中断请求的发生
外部设备或内部事件(如定时器溢出)会向处理器发送中断请求(Interrupt Request,IRQ),通知处理器有事件需要处理。
2.处理器响应
当处理器接收到中断请求后,会立即暂停当前任务的执行,保存当前任务的状态(如程序计数器、寄存器状态等),并转而执行与中断相关的处理程序。
3.中断服务程序执行
处理器会根据中断向量表找到对应中断类型的中断服务程序(ISR)的入口地址,并开始执行该程序。ISR是预先定义好的,用于处理特定类型的中断事件,例如处理设备输入、更新状态等。
4.中断处理完成
一旦ISR执行完毕,处理器会恢复之前保存的任务状态,包括恢复程序计数器和寄存器的值。
5.返回到原任务
处理器将回到被中断的任务,从中断发生的地方继续执行。如果有多个中断发生,处理器会根据中断优先级进行处理,确保高优先级的中断优先执行。
6.调度器的恢复和任务唤醒
如果ISR或其他中断处理程序导致任务状态改变(如某个任务需要唤醒或挂起),操作系统的调度器会负责根据任务的优先级重新安排任务的执行顺序。
三、FreeRTOS中断管理支持
1. 任务与中断的交互
FreeRTOS允许中断服务程序与任务之间进行通信和同步。通常通过信号量、消息队列或事件标志等机制,在中断服务程序中向任务发出信号或传递数据,从而通知任务发生了特定事件或完成了某些操作。
2. 中断优先级调度
FreeRTOS支持配置中断的优先级,并支持嵌套中断,以确保高优先级的中断能够及时地得到处理,不会被低优先级的中断或任务所阻塞。中断优先级的管理可以通过FreeRTOS提供的API来进行设置和调整,以满足系统对实时性和响应性的需求。
3. 中断嵌套保护和访问控制
FreeRTOS提供了一组API来帮助开发者正确管理中断嵌套和访问控制,例如:
taskENTER_CRITICAL_FROM_ISR();//禁用中断,保护共享资源
taskEXIT_CRITICAL_FROM_ISR();//恢复中断,允许其他中断或任务继续执行
4.调度锁和调度禁止
FreeRTOS提供了一些API来临时禁止调度,以确保在多个ISR或ISR与任务之间进行复杂的数据传递和同步操作的时候,不会因为任务的调度而导致数据不一致性问题,实现安全地访问共享资源或执行需要原子性的操作,例如:
vTaskSuspendAll();//禁止任务调度
xTaskResumeAll();//恢复任务调度
四、STM32cubeMX配置
1. 选择MCU的型号
2. 使能外部高速晶振
3. 配置时钟树,根据自己MCU的型号和需求来配置
这里因为我的MCU最大主频是80MHz,所以我配置了80MHz
4. 配置调试模式
时钟选择除了SysTick以外的,因为SysTick需要给FreeRTOS提供心跳,所以不能选
5.根据自己的引脚设置按键为外部中断模式
根据自己的情况打开内部上拉或者内部下拉电阻等等
6. 配置串口
选择异步模式
勾选中断
7. 检查中断有没有勾选
8. 软件开发接口标准
这里V1或者V2都可以选择
9.创建任务
创建两个任务,用于实现中断管理实验
10. 创建队列
Queue Size:队列的长度
Item Size:队列里面每个元素的大小
Allocation:内存空间的分配方式
在这里我已经创建了一个长度为16,元素大小为uint32_t,内存空间动态分配的队列在下面
五、用到的相关函数API
1. xQueueSendFromISR
(FreeRTOS)
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t * pxHigherPriorityTaskWoken);
QueueHandle_t xQueue
: 队列的句柄,即要发送数据到的队列的引用。const void * pvItemToQueue
: 指向要发送到队列的数据的指针。BaseType_t * pxHigherPriorityTaskWoken
: 一个指向BaseType_t
类型的指针,用于接收一个标志,表明是否有更高优先级的任务被唤醒。如果这个指针为NULL
,函数就不会更新这个值。
2. osMessageQueueGet
(CMSIS-RTOS2)
osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout);
osMessageQueueId_t mq_id
: 消息队列的ID,唯一标识一个消息队列。void *msg_ptr
: 一个指针,用于接收从队列中获取的消息数据。uint8_t *msg_prio
: 一个指针,用于接收消息的优先级。如果这个参数为NULL
,则不返回消息的优先级。uint32_t timeout
: 超时时间,定义了函数等待消息的最大时间。可以是osWaitForever
(表示无限等待)或者一个具体的超时时间(以毫秒为单位)。
3. taskENTER_CRITICAL_FROM_ISR
和 taskEXIT_CRITICAL_FROM_ISR
这两个函数通常没有参数,它们用于从中断服务例程(ISR)中进入和退出临界区。在临界区内,任务切换会被禁止,以确保代码段的原子性执行。
4. taskENTER_CRITICAL()
和 taskEXIT_CRITICAL()
同样,这两个函数也通常没有参数。它们用于从任务中进入和退出临界区,确保在临界区内的代码不会被其他任务或中断打断。
六、程序编写
生成keil5工程,并编写代码
1. 创建队列
myQueue01Handle = osMessageQueueNew (16, sizeof(uint32_t), &myQueue01_attributes);
2. 按键发生中断,向消息队列发送按键号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
uint32_t send_data1 = 0;
uint32_t ulReturn;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(Key1_Pin == GPIO_Pin)
{
send_data1 = 1;
xQueueSendFromISR(myQueue01Handle, &send_data1, &xHigherPriorityTaskWoken); //消息入队
}
else if(Key2_Pin == GPIO_Pin)
{
send_data1 = 2;
xQueueSendFromISR(myQueue01Handle, &send_data1, &xHigherPriorityTaskWoken); //消息入队
}
taskEXIT_CRITICAL_FROM_ISR(ulReturn);
}
3. 串口发生中断,向消息队列发送消息
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size)
{
uint32_t send_data1 = 0;
uint32_t ulReturn;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
ulReturn = taskENTER_CRITICAL_FROM_ISR();
if(huart == &huart1)//判断是否是串口一的中断
{
__HAL_UNLOCK(huart); //解锁串口状态
send_data1 = 3;
xQueueSendFromISR(myQueue01Handle, &send_data1, &xHigherPriorityTaskWoken); //消息入队
HAL_UARTEx_ReceiveToIdle_IT(&huart1,RxMsg,200-1); //再次开启空闲中断
}
taskEXIT_CRITICAL_FROM_ISR(ulReturn);
}
4. 任务LED1等待消息队列,并将输出对应数据
void StartTask_test(void *argument)
{
uint32_t msgValue;
for(;;)
{
osMessageQueueGet(myQueue01Handle, &msgValue, NULL, osWaitForever);
if (msgValue == 3)
{
printf("usart1 interrupt\r\n");
}
else
{
printf("Key:%d interrupt\r\n", msgValue);
}
osDelay(500);
}
}
5. 任务2计数到10时进入临界区,计数到20时退出临界区
void StartTask05(void *argument)
{
int i = 0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_2); //LED1状态每500s翻转一次
printf("led run..%d\n",i++);
if(i==10)
taskENTER_CRITICAL(), printf("进入临界区 \r\n"); //进入临界区
if(i==20)
taskEXIT_CRITICAL(),printf("退出临界区 \r\n"); //退出临界区
osDelay(1000);
}
}
七、结果验证
当程序成功编译并下载到开发板上时,以下事件发生:
- LED2进入临界区后,暂停了其他任务的执行,包括延时函数的执行。
- 当串口接收到数据时,LED1开始亮起,并将接收到的数据返回。
- 当按下按键时,LED1会亮起,并且会通过串口输出相应的按键号。