任务挂起简单点理解就是现在不需要执行这个任务,让它先暂停,就是挂起。恢复就是从刚才挂起的状态下继续运行。
API函数 任务挂起vTaskSuspend() 函数原型(tasks.c中):
1 void vTaskSuspend ( TaskHandle_t xTaskToSuspend )
参数:
任务恢复vTaskResume() 函数原型(tasks.c中):
1 void vTaskResume ( TaskHandle_t xTaskToResume )
参数:
中断函数中进行任务恢复xTaskResumeFromISR() 1 BaseType_t xTaskResumeFromISR ( TaskHandle_t xTaskToResume )
参数:
总结 :
这几个函数用起来还是很简单的,只需要传入任务的句柄即可。
注意,任务挂起是没有FromISR版本的,所以在中断中貌似就不可以使用任务挂起了。
程序验证 在上个例程的基础上,增加一个按键检测任务和外部中断函数,用来测试任务挂起与恢复。
按键任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void key_task (void *pvParameters) { u8 key; static uint8_t flag=0 ; while (1 ) { key=KEY_Scan(0 ); switch (key) { case KEY1_PRES: if (!flag) { vTaskSuspend(Task1Task_Handler); printf ("1 suspend\r\n" ); } else { vTaskResume(Task1Task_Handler); printf ("1 resume\r\n" ); } flag=~flag; break ; case K_UP_PRES: vTaskSuspend(Task2Task_Handler); printf ("2 suspend\r\n" ); break ; } vTaskDelay(10 ); } }
中断配置与中断函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void EXTIX_Init (void ) { NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4); EXTI_InitStructure.EXTI_Line = EXTI_Line4; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void EXTI4_IRQHandler (void ) { BaseType_t YieldRequired; if (KEY0==0 ) { YieldRequired=xTaskResumeFromISR(Task2Task_Handler); printf ("2 resume\r\n" ); if (YieldRequired==pdTRUE) { portYIELD_FROM_ISR(YieldRequired); } } EXTI_ClearITPendingBit(EXTI_Line4); }
整个主函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 #include "stm32f4xx.h" #include "led.h" #include "key.h" #include "usart.h" #include "FreeRTOS.h" #include "task.h" #define START_TASK_PRIO 1 #define START_STK_SIZE 128 TaskHandle_t StartTask_Handler; void start_task (void *pvParameters) ;#define TASK1_TASK_PRIO 3 #define TASK1_STK_SIZE 128 TaskHandle_t Task1Task_Handler; void task1_task (void *pvParameters) ;#define TASK2_TASK_PRIO 4 #define TASK2_STK_SIZE 128 TaskHandle_t Task2Task_Handler; void task2_task (void *pvParameters) ;#define KEY_TASK_PRIO 2 #define KEY_STK_SIZE 128 TaskHandle_t KeyTask_Handler; void key_task (void *pvParameters) ;void EXTIX_Init (void ) ;int main (void ) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); LED_Init(); KEY_Init(); EXTIX_Init(); uart_init(115200 ); xTaskCreate((TaskFunction_t )start_task, (const char * )"start_task" , (uint16_t )START_STK_SIZE, (void * )NULL , (UBaseType_t )START_TASK_PRIO, (TaskHandle_t* )&StartTask_Handler); vTaskStartScheduler(); } void start_task (void *pvParameters) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t )task1_task, (const char * )"task1_task" , (uint16_t )TASK1_STK_SIZE, (void * )NULL , (UBaseType_t )TASK1_TASK_PRIO, (TaskHandle_t* )&Task1Task_Handler); xTaskCreate((TaskFunction_t )task2_task, (const char * )"task2_task" , (uint16_t )TASK2_STK_SIZE, (void * )NULL , (UBaseType_t )TASK2_TASK_PRIO, (TaskHandle_t* )&Task2Task_Handler); xTaskCreate((TaskFunction_t )key_task, (const char * )"key_task" , (uint16_t )KEY_STK_SIZE, (void * )NULL , (UBaseType_t )KEY_TASK_PRIO, (TaskHandle_t* )&KeyTask_Handler); vTaskDelete(StartTask_Handler); taskEXIT_CRITICAL(); } void task1_task (void *pvParameters) { while (1 ) { LEDa_Toggle; vTaskDelay(500 ); } } void task2_task (void *pvParameters) { while (1 ) { LEDb_ON; vTaskDelay(200 ); LEDb_OFF; vTaskDelay(800 ); } } void key_task (void *pvParameters) { u8 key; static uint8_t flag=0 ; while (1 ) { key=KEY_Scan(0 ); switch (key) { case KEY1_PRES: if (!flag) { vTaskSuspend(Task1Task_Handler); printf ("1 suspend\r\n" ); } else { vTaskResume(Task1Task_Handler); printf ("1 resume\r\n" ); } flag=~flag; break ; case K_UP_PRES: vTaskSuspend(Task2Task_Handler); printf ("2 suspend\r\n" ); break ; } vTaskDelay(10 ); } } void EXTIX_Init (void ) { NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4); EXTI_InitStructure.EXTI_Line = EXTI_Line4; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void EXTI4_IRQHandler (void ) { BaseType_t YieldRequired; if (KEY0==0 ) { YieldRequired=xTaskResumeFromISR(Task2Task_Handler); printf ("2 resume\r\n" ); if (YieldRequired==pdTRUE) { portYIELD_FROM_ISR(YieldRequired); } } EXTI_ClearITPendingBit(EXTI_Line4); }
实验现象 程序运行起来后,两个LED任务按照自己的方式闪烁,按下KEY1,LED任务1挂起,即LED保持在常亮或常灭状态,再次按下KEY1,LED任务1恢复,即LED继续闪烁。按下KEY_UP,LED任务2挂起,再按下KEY0,LED任务2恢复。同时串口也会打印相关信息。
注意 ,中断程序中没有使用延时消抖,所以按下KEY0,从中断恢复任务时,可能会执行多次恢复,(1次挂起)多次恢复目前是没有什么影响的。
注意事项(避免程序卡死)!!! 中断函数中不可以使用vTaskDelay()! 实验中用到了按键作为中断,本想用vTaskDelay(10)进行消抖,结果是程序运行起来后,按下中断的按键,程序卡死,通过调试运行,发现程序死在了这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void vPortEnterCritical ( void ) { portDISABLE_INTERRUPTS(); uxCriticalNesting++; if ( uxCriticalNesting == 1 ) { configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); } }
英文注释的大致意思是:
这不是进入关键函数的中断安全版本,所以assert()如果是从中断上下文中调用的话。只有以“FromISR”结尾的API函数才能在中断中使用 。只有在关键嵌套计数为1时才使用assert,以防止assert函数也使用关键部分时出现递归调用。
所以FreeRTOS的API函数只有带FromISR后缀的才能在中断函数中使用,而**vTaskDelay()**好像也没有FromISR版本,所以就不能使用!推而广之,其它不带FromISR后缀的API函数也不能在中断函数中使用!
另外,中断函数本来就是为了处理紧急情况,在中断函数中延时是不太合理的。
中断函数中必须使用带FromISR后缀的API函数! 这一条和上一条其实是一个意思,实验中在中断函数中对信号量进行释放,使用的是xTaskResumeFromISR()函数,如果改成vTaskResume(),实测发现程序同样会卡死在这里。
中断的优先级不能设置的过高(对应数字过小)! 按键中断的优先级设置:
1 2 3 4 5 NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06 ; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00 ; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
第2行的抢占优先级为6是没有问题的,如果改成3,程序在进入按键中断会卡死在这里(port.c文件的末尾):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #if ( configASSERT_DEFINED == 1 ) void vPortValidateInterruptPriority ( void ) { uint32_t ulCurrentInterrupt; uint8_t ucCurrentPriority; ulCurrentInterrupt = vPortGetIPSR(); if ( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER ) { ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ]; configASSERT( ucCurrentPriority >= ucMaxSysCallPriority ); } configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue ); } #endif
注意里面的几段:
如果一个被分配了高于configMAX_SYSCALL_INTERRUPT_PRIORITY
优先级的中断的服务例程(ISR)调用了一个ISR安全的FreeRTOS API函数,那么下面的断言将失败。ISR安全FreeRTOS API函数必须仅 被分配优先级在configMAX_SYSCALL_INTERRUPT_PRIORITY
或以下的中断调用。
这句的意思是,如果在中断函数中使用了FreeRTOS的API函数,当然前提也是使用带FromISR后缀的,中断的优先级不能高于宏定义configMAX_SYSCALL_INTERRUPT_PRIORITY,这个宏定义在FreeRTOSConfig.h中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifdef __NVIC_PRIO_BITS #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 #endif #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
即中断优先级设置范围为5~15 (0xf)。
当然,如果中断函数中没有使用FreeRTOS的API,那么中断的优先级就不受限制。
优先级分组:中断控制器(NVIC)允许定义每个中断优先级的比特被分割成定义中断的优先级比特和定义中断的次优先级比特。为简单起见,必须将所有位定义为抢占优先位 。如果不是这样(如果某些位表示次优先级),下面的断言将失败。
如果应用程序只使用CMSIS库进行中断配置,那么在启动调度程序之前,通过调用NVIC_SetPriorityGrouping(0);
可以在所有Cortex-M设备上实现正确的设置。但是请注意,一些特定于供应商的外设库假设了非零优先级组设置,在这种情况下,使用值为0将导致不可预测的行为。
这两段意思是在说优先级分组的事,即所有位都是抢占优先级,没有次优先级,即中断分组模式4,也就是在主函数设置的:
1 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
如果换成其它的,比如NVIC_PriorityGroup_3,程序进入中断后也会卡死在。
完整工程代码已保存至GitHub:https://github.com/xxpcb/FreeRTOS-STM32F407-examples