-
@[toc]
中断
大家看到中断后,有没有想到一个名词------异常呢?若大家想到了,但是记不起相关概念;或者是,大家没想到这个名词,没关系,下面小编就给大家伙讲讲中断 、异常 相关知识。 异常 异常,是指任何使CPU执行程序时脱离正常运行状态转而跑飞的任何事件,若不及时处理,系统可能会面临崩溃危机。 异常,可分为同步异常 与异步异常 。由内部事件,如零除引起的算术异常等一系列处理器指令故障引起的事件,称之为同步异常;而异步异常,主要只外部硬件装置产生的异常,若按键按下后产生的事件。 同步异常与异步异常在程序执行上的区别是:当一个同步异常产生后,系统必须立刻处理该异常,而不能继续执行原程序;但是当异步异常发生后,系统可以缓处理或者是忽略不处理。其本质上的区别,个人认为还得是因为异常的来源不同。中断 中断,属于异步异常。用一个形象的现象来讲解中断:某天,小明(CPU)正在酣畅淋漓地打游戏(主程序),突然,其女友过来找他有急事(中断事件),这时小明是不是得优先处理女友的事情(中断服务),之后才能继续玩游戏(恢复主程序)。 通过中断机制,CUP可以实现:当外设不需要CPU处理时,CPU可以干一些其他的事情,如轮询任务、动态内存管理等等;而不需要持续地查询外设状态,一旦外设状态符合条件,就使用中断机制启动特定任务即可。
中断相关的硬件可划分为三类:
- 外设:当外设请求CPU时,会产生一个中断信号,请求中断;
- 中断控制器 :中断控制器是CPU外设之一,其既能接收其他外设的中断信号,自己也能够发出中断信号给CPU 。通过该外设,可以实现对中断源的优先级、触发方式、打开、关闭等操作进行设置。
- CPU :CPU能够响应中断请求,中断当前程序,进而执行中断服务程序。NVIC(中断控制器)最多支持240个中断,每个中断最多支持256个优先级。
中断处理过程 某中断发送中断请求后,中断处理需要经过以下过程:
- 步骤一:中断响应(由硬件自动化完成 );在此过程中主要任务为:保护现场 与找到中断服务程序地址。
- 步骤二:执行中断服务程序(由用户编写 );中断服务程序没有参数与返回值 且要求尽量简短 ,其调用不是由用户完成,而是由硬件自动调用。
- 步骤三:中断返回(由硬件自动实现 );此过程主要为恢复现场。
就拿上述小明女友让小明处理某事作为中断处理过程的示例,则其示意图可为下图:
中断机制的弊端 尽管使用中断能够提高系统的实时性,但是使用中断也存在一定的弊端:
- 中断会增加程序执行 的不确定性 与时间长度;
- 中断会增加栈空间;
- 中断会抢占正在使用的资源;
FreeRTOS里中断管理支持:
- 开/关中断;
- 恢复中断;
- 中断使能;
- 中断屏蔽;
- 设置中断优先级
函数解析
FreeRTOS中,实际上是没有中断的,中断还是由硬件产生 。以STM32为例,中断请求的响应过程还是由STM32的中断服务函数完成,而FreeRTOS在其中的作用可以说成是唤醒某个阻塞或就绪任务。 也就是说,在硬件STM32的中断函数中,需要使用信号量 或事件等完成中断与FreeRTOS系统任务的通信,实现通过中断函数来唤醒一次或多次FreeRTOS系统任务。
与中断管理相关的函数有:
- portDISABLE_INTERRUPTS:中断暂停函数;
函数
portDISABLE_INTERRUPTS()
实际上还是使用声明重新定的一个函数:#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
函数vPortRaiseBASEPRI()
的实现则是通过汇编完成的。
- portENABLE_INTERRUPTS :使能中断函数;
与中断暂停函数一样,函数
portENABLE_INTERRUPTS()
实际上还是使用声明重新定的一个函数:#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
函数vPortRaiseBASEPRI()
也是通过汇编完成的。
ARM汇编指令
- msr MSR指令,存储通用寄存器的值到特殊功能寄存器 。 指令格式为:
msr spec_reg, Rm
,spec_reg
表示特殊寄存器,Rm
表示通用寄存器。 - isb 指令同步隔离(与流水线、 MPU有关)。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。
- dsb 数据同步隔离(与流水线、 MPU 和 cache等有关),较为严格: 仅当所有在它前面的存储器访问都执行完毕后,才执行它在后面的指令(亦即任何指令都要等待)。
FreeRTOS中断使用示例
示例的主要目的:是使用中断以及事件组完成基于FreeRTOS实时操作系统的中断处理功能。 示例中详细任务为:
1.创建一个事件组以及两个个FreeRTOS任务 ,并且将是否创建成功显示到LCD屏上; 2.初始化 两个按键 的外部中断 ,并且在回调函数中完成事件组的触发功能; 3.在 FreeRTOS任务1中 ,接收两个事件 ,并完成预设功能 ------其中一个事件触发一次翻转一次LED状态;另一个事件触发后记录其触发次数,并且显示在LCD屏中。 4.FreeRTOS任务2 需要完成的功能有:实现两个按键中任意一个按键按下后反转一个LED灯状态 ,并且要求按键消抖。
示例代码
c// 声明事件 #define EVENT7 (0x01 << 6) #define EVENT8 (0x01 << 7) //任务控制权柄 TaskHandle_t xHandleTsak[4]; // 事件控制权柄 EventGroupHandle_t myxEventGroupHandle_t = NULL; int main(void) { //存储创建任务的返回值 BaseType_t xReturn[5] ; //LCD显示 char temp[30] = "task fail:"; //动态创建任务 xReturn[0] = xTaskCreate( (TaskFunction_t )eventTask2,(const char *)"task1", (uint16_t)128,(void*) NULL,1,&xHandleTsak[0]); if (pdPASS != xReturn[0]) strcat(temp,"1"); xReturn[2] = xTaskCreate( (TaskFunction_t )eventTask4,(const char *)"task3", (uint16_t)128,(void*) NULL,1,&xHandleTsak[2]); if (pdPASS != xReturn[2]) strcat(temp,"3"); if(strlen(temp) == 10) sprintf(temp,"task suc"); // 创建事件 myxEventGroupHandle_t = xEventGroupCreate(); if(myxEventGroupHandle_t == 0) strcat(temp,"event fail"); else strcat(temp,"event suc"); // 显示任务 事件是否创建成功 LCD_DisplayStringLine(Line3,(uint8_t*)temp); changeAllLedByStateNumber(OFF); // 启动调度 vTaskStartScheduler(); return 0; } /******************************************** * 函数功能:事件测试函数2 * 函数参数:无 * 函数返回值:无 ********************************************/ void eventTask2(void) { // 设置变量接收事件 EventBits_t r_event; // 保存LED的状态 int ledState[] = {0,0,0,0,0,0,0}; // 记录LED的位置 uint16_t ledLocation[] = {LED1,LED2,LED3,LED4,LED5,LED6,LED7}; // 记录EVENT8触发次数 uint16_t eventTriggerCount = 0; // 保存显示到LCD中的数据 char temp[30]; // 用于循环 int i = 0; while(1) { //阻塞等待8个事件中任意一个事件 r_event = xEventGroupWaitBits(myxEventGroupHandle_t,EVENT7|EVENT8, pdTRUE,pdFALSE,portMAX_DELAY); //判断事件类型 if((r_event&EVENT7) !=0) ++ledState[6]; else if((r_event&EVENT8) !=0) { sprintf(temp," Event8 count:%d",++eventTriggerCount); LCD_DisplayStringLine(Line4,(uint8_t*)temp); changeAllLedByStateNumber(OFF); } // 设置每个LED灯状态 for(i=0;i<7;++i) changeLedStateByLocation(ledLocation[i],ledState[i]%2); portDISABLE_INTERRUPTS(); } } /******************************************** * 函数功能:事件测试函数4 * 函数参数:无 * 函数返回值:无 ********************************************/ void eventTask4(void) { // 记录按键按下的位置 按键触发次数 unsigned char keyNumber = 0; // 记录按键状态 0-按键空闲状态 1-按键按下状态 2-按键松开状态 int state = 0; // 保存系统时间 static portTickType myPreviousWakeTime; // 保存阻塞时间 const volatile TickType_t xDelay10ms = pdMS_TO_TICKS( 50UL ); // 获取当前时间 myPreviousWakeTime = xTaskGetTickCount(); while(1) { // 按键第一次按下 消抖 if( state == 0 ) { keyNumber = scanKeyNoTime(); // 只有按键按下后才跳转 if(keyNumber != 0) { HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_SET); state = 1; } } // 按键真正按下状态 if( state == 1 ) { /* 读取按键是否按下 若没按下则跳转到状态1 否则就执行按键服务函数 并且跳转到状态2*/ keyNumber = scanKeyNoTime(); if(keyNumber == 0) state = 0; else { if(keyNumber == 3 ) rollbackLedByLocation(LED3); if(keyNumber == 4 ) rollbackLedByLocation(LED4); state = 2; } } // 按键松开状态 if( state == 2 ) { keyNumber = scanKeyNoTime(); // 判断按键是否松开 只有按键松开 才跳转到状态0 if(keyNumber == 0) state = 0; } xTaskDelayUntil(&myPreviousWakeTime,xDelay10ms); } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { // 记录当前某些信息 BaseType_t pxHigherPriorityTaskWoken; uint32_t ulReturn; uint16_t event; /* 进入临界段,临界段可以嵌套 */ ulReturn = taskENTER_CRITICAL_FROM_ISR(); // 读取对应按键的状态 GPIO_PinState pinState = HAL_GPIO_ReadPin( GPIOB,GPIO_Pin ); if(pinState == GPIO_PIN_RESET ) { // 判断中断位置 if(GPIO_Pin == GPIO_PIN_0 ) { rollbackLedByLocation(LED1); event = EVENT7; } else if(GPIO_Pin == GPIO_PIN_1 ) { rollbackLedByLocation(LED2); event = EVENT8; } // 发送事件请求 xEventGroupSetBitsFromISR(myxEventGroupHandle_t,event, &pxHigherPriorityTaskWoken); // 如果需要就切换任务 portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); } /* 退出临界段 */ taskEXIT_CRITICAL_FROM_ISR( ulReturn ); }
上述测试中,STM32负责中断响应以及"虚假"中断服务函数执行,而FReeRTOS的任务
eventTask2()
才是真正的中断服务函数。"虚假"的中断服务函数与"真正的"中断服务函数间是通过事件 来完成中断 与任务 通信过程的。 因此,在上述函数中,很明显的可以知道:eventTask2()
才是真正的中断服务函数,因为每次外部中断触发后,任务eventTask2()
也都会触发。 其次,中断函数需要精简,遵循快进快出的原则 。而在上述测试中,"虚假"中断服务函数HAL_GPIO_EXTI_Callback()
还是没有做到这一点,应该要将函数内部翻转LED状态的函数删除,只留下唤醒事件的必要代码以及按键相关代码即可。 至于小编为什么要这样子写呢?🤔一开始是为了测试外部中断是否可用,后面就懒得删了,毕竟还是可以观察到外部中断是否触发以及此时是否应该触发事件。😅 至于函数eventTask4()
中按键部分,小编是采用FreeRTOS的绝对延时进制以及状态机完成消抖。(状态机写的比较烂,大家将就将就吧!🤣🤣🤣)
小编也有其他的一些相关文章,欢迎各位看官点击观看😉😉😉
- 【FreeRTOS】详细讲解FreeRTOS中任务管理并通过示例讲述其用法
- 【FreeRTOS】详细讲解FreeRTOS的软件定时器及通过示例讲述其用法
- 【FreeRTOS】详细讲解FreeRTOS中消息队列并通过示例讲述其用法
- 【FreeRTOS】详细讲解FreeRTOS中事件(event)并通过具体示例讲述其用法
最后 ,欢迎大家留言或私信交流,咱们共同进步!😁😁😁
【FreeRTOS】详细讲解FreeRTOS里中断管理并通过示例讲述其用法
黑心萝卜三条杠2023-08-30 17:52
相关推荐
a小胡哦6 小时前
Windows、Mac、Linux,到底该怎么选?别说我什么都不会1 天前
鸿蒙轻内核M核源码分析系列十五 CPU使用率CPUP袁庭新1 天前
CentOS7通过yum无法安装软件问题解决方案别说我什么都不会2 天前
鸿蒙轻内核M核源码分析系列十二 事件Eventqq_437896433 天前
动态内存分配算法对比:最先适应、最优适应、最坏适应与邻近适应别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列十一 (2)信号量Semaphore别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列十 软件定时器Swtmr别说我什么都不会4 天前
鸿蒙轻内核M核源码分析系列九 互斥锁Mutex别说我什么都不会4 天前
鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory别说我什么都不会5 天前
鸿蒙轻内核M核源码分析系列六 任务及任务调度(3)任务调度模块