记录一下,方便以后翻阅~
本章继续学习剩余的与任务相关的API函数。
任务挂起和恢复API函数
有时候需要暂停某个任务的运行,过一段时间后再重新运行。这时要使用任务删除和重建的方法的话那么任务中的变量值就丢失了!FreeRTOS提供了任务挂起和恢复函数,可以解决该问题:
1.1 函数vTaskSuspend()
此函数用于将某个任务设置为挂起状态,进入挂起态的任务永远不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数vTaskResume()或xTaskResumeFromISR(),函数申明如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend)
入口参数:
xTaskToSuspend: 要挂起的任务的任务句柄。可通过函数xTaskGetHandle()来根据任务名字获取某个任务的任务句柄。如果参数为NULL表示挂起任务自己。
返回值:无
1.2 函数vTaskResume()
将一个任务从挂起态恢复到就绪态,只有通过函数vTaskSuspend()设置为挂起态的任务才可以用vTaskResume()恢复,函数申明如下:
void vTaskResume(TaskHandle_t xTaskToResume)
入口参数:
xTaskToResume: 要恢复的任务的任务句柄
返回值:无
1.3 函数xTaskResumeFromISR()
此函数是vTaskResume()中断版本,用于在中断服务函数中恢复一个任务,函数申明如下:
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
参数:
xTaskToResume:要恢复的任务的任务句柄
返回值:
pdTRUE:恢复运行的任务的任务优先级等于或高于正在运行的任务,意味着在退出中断服务函数以后必须进行一次上下文切换。
pdFALSE:恢复运行的任务的任务优先级低于当前正在运行的任务,意味着在退出中断服务函数后不需要进行上下文切换。
2. 任务挂起和恢复实验
2.1 实验目的
学习vTaskSuspend()、vTaskResume()和xTaskResumeFromISR()函数的用法。
2.2 实验设计
本实验设计4个任务:
start_task:用来创建其他3个任务;
key_task:按键服务任务,检测按键按下的结果,根据不同的按键结果执行不同的操作;
task1_task:应用任务1;
task2_task:应用任务2。
战舰v3有4个按键:
KEY0:中断模式,在中断服务函数中恢复任务2的运行;
KEY1:输入模式,恢复任务1的运行;
KEY2:输入模式,挂起任务2的运行;
KEY_UP:输入模式,挂起任务1的运行。
2.3 硬件
1)正点原子战舰v3开发板;
2)JLINK仿真器。
2.4 代码解读
先看下外部中断exti.c源文件的代码(本人也很少用外部中断,借此机会复习下),设置按下KEY0按键触发外部中断,在中断服务函数里恢复任务2功能,具体代码如下:
#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "FreeRTOS.h"
#include "task.h"
// 外部中断4服务程序
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
// GPIOE4 中断线以及中断初始化配置 下降沿触发
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_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; // 使能按键KEY0所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06; // 抢占优先级6,如果中断服务函数要使用FreeRTOS的API函数,那么中断优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY!这里设置为6!
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; // 子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
}
// 外部中断4服务函数
extern TaskHandle_t Task2Task_Handler; // 任务句柄
void EXTI4_IRQHandler(void)
{
BaseType_t YieldRequired;
delay_xms(20); // 消抖
if(KEY0==0)
{
YieldRequired=xTaskResumeFromISR(Task2Task_Handler); // 恢复任务2
printf("恢复任务2的运行!\r\n");
if(YieldRequired==pdTRUE)
{
/* 若函数xTaskResumeFromISR()返回值为pdTRUE,说明要恢复的这个任务的优先级等于或高于正在运行的任务(被中断打断的任务),
所以在退出中断的时候一定要进行上下文切换!*/
portYIELD_FROM_ISR(YieldRequired);
}
}
EXTI_ClearITPendingBit(EXTI_Line4); // 清除LINE4上的中断标志位
}
main.c文件里的创建的key_task()任务函数,其WKUP按键负责挂起任务1,KEY1按键负责恢复任务1,KEY2按键负责挂起任务2,恢复任务2 的函数在外部中断服务函数里,前面已经看过了。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lcd.h"
#include "key.h"
#include "exti.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 KEY_TASK_PRIO 2 // 任务优先级
#define KEY_STK_SIZE 128 // 任务堆栈大小
TaskHandle_t KeyTask_Handler; // 任务句柄
void key_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); // 任务函数
// LCD刷屏时使用的颜色
int lcd_discolor[14]={WHITE,BLACK,BLUE,BRED,GRED,GBLUE,RED,MAGENTA,GREEN,CYAN,YELLOW,BROWN,BRRED,GRAY};
// 主函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 设置系统中断优先级分组4
delay_init(); // 延时函数初始化
uart_init(115200); // 初始化串口
LED_Init(); // 初始化LED
KEY_Init(); // 初始化按键
EXTIX_Init(); // 初始化外部中断
LCD_Init(); // 初始化LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"WarShip v3");
LCD_ShowString(30,30,200,16,16,"FreeRTOS");
LCD_ShowString(30,50,200,16,16,"Task Susp and Resum");
LCD_ShowString(30,70,200,16,16,"leisure");
LCD_ShowString(30,90,200,16,16,"2021/3/14");
// 创建开始任务
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(); // 进入临界区
// 创建KEY任务
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);
// 创建TASK1任务
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);
// 创建TASK2任务
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);
vTaskDelete(StartTask_Handler); // 删除开始任务
taskEXIT_CRITICAL(); // 退出临界区
}
// key任务函数
void key_task(void *pvParameters)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
switch(key)
{
case WKUP_PRES:
vTaskSuspend(Task1Task_Handler); // 调用vTaskSuspend()函数挂起任务1
printf("挂起任务1的运行!\r\n");
break;
case KEY1_PRES:
vTaskResume(Task1Task_Handler); // 调用vTaskResume()函数恢复任务1
printf("恢复任务1的运行!\r\n");
break;
case KEY2_PRES:
vTaskSuspend(Task2Task_Handler); // 调用vTaskSuspend()函数挂起任务2
printf("挂起任务2的运行!\r\n");
break;
}
vTaskDelay(10); // 延时10ms
}
}
// task1任务函数,用于观察任务挂起和恢复的过程
void task1_task(void *pvParameters)
{
u8 task1_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); // 画一个矩形
LCD_DrawLine(5,130,115,130); // 画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
while(1)
{
task1_num++;
LED0=!LED0;
printf("任务1已经执行:%d次\r\n",task1_num);
LCD_Fill(6,131,114,313,lcd_discolor[task1_num%14]); // 填充区域
LCD_ShowxNum(86,111,task1_num,3,16,0x80); // 显示任务执行次数
vTaskDelay(1000); // 延时1s,也就是1000个时钟节拍
}
}
// task2任务函数,用于观察任务挂起和恢复的过程(中断方式)
void task2_task(void *pvParameters)
{
u8 task2_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); // 画一个矩形
LCD_DrawLine(125,130,234,130); // 画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++;
LED1=!LED1;
printf("任务2已经执行:%d次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); // 显示任务执行次数
LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); // 填充区域
vTaskDelay(1000); // 延时1s,也就是1000个时钟节拍
}
}
2.5 实验结果
如下图所示: