尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FreeRTOS(创建任务)

FreeRTOS(创建任务) 任务三要素下面我们来试试两种创建任务方式动态分配/静态分配用到的函数//动态分配xTaskCreate();//静态分配xTaskCreateStatic();两个函数的参数下面是代码示例/* USER CODE BEGIN Variables *///全局变量staticStackType_t g_pucStackofColorTask[128];//定义堆大小staticStaticTask_t g_TCBofColorTask;//定义TCB结果staticTaskHandle_t xColorTaskHandle;//定义句柄/* USER CODE END Variables *//* USER CODE BEGIN Init *///局部变量TaskHandle_t xLightTaskHandle;BaseType_t ret;//接收返回值/* USER CODE END Init *//* USER CODE BEGIN RTOS_THREADS *//* add threads, ... *//*创建任务光*/retxTaskCreate(Led_Test,LightTask,128,NULL,osPriorityNormal,xLightTaskHandle);/*创建任务色*/xColorTaskHandlexTaskCreateStatic(ColorLED_Test,ColorLEDTask,128,NULL,osPriorityNormal,g_pucStackofColorTask,g_TCBofColorTask);/* USER CODE END RTOS_THREADS */估算栈的大小栈的大小由这三个决定现场就是切换任务时会在栈内分配一块新的内存PSR来保存这个任务的全部寄存器16个所以占16*464字节局部变量由定义的变量大小决定返回地址由这个调用关系决定可以看到假设这个是五级调用每级要保存的寄存器一般为9个所以返回地址的大小为594180字节大小例子进到结构体看看有多少个成员7个所以这个任务的栈大概250字节左右而我们设置的是128uint_32t即128*4512足够了同一函数创建多个任务任务代码如下/* Private function prototypes -----------------------------------------------*//* USER CODE BEGIN FunctionPrototypes *///定义参数结构体structTaskPtintInfo{uint8_tx;uint8_ty;charName[16];};//定义参数staticstructTaskPtintInfoTask1{0,0,Task1:};staticstructTaskPtintInfoTask2{0,3,Task2:};staticstructTaskPtintInfoTask3{0,6,Task3:};staticintg_LCDCanUse1;voidLcdPrintTask(void*parms){LCD_Init();//初始任务被我注释掉了里面包括了lcd初始化和清屏函数所以这里再调用一下LCD_Clear();structTaskPtintInfo*pInfoparms;int32_tcnt0;intlen0;while(1){if(g_LCDCanUse1){g_LCDCanUse0;lenLCD_PrintString(pInfo-x,pInfo-y,pInfo-Name);//赋值字符串长度给lenLCD_PrintSignedVal(len,pInfo-y,cnt);g_LCDCanUse1;}mdelay(500);}}/* USER CODE END FunctionPrototypes */g_LCDCanUse变量是用来保护每个任务的从之前的学习中我们知道每个任务能运行的时间是固定的假设在我们这个显示OLED任务运行到一半时被切换到另外的一个任务那么很有可能在I2C传输这里被切换一个完整的I2C传输被打断会出现问题的所以我们使用g_LCDCanUse变量来保证这个任务能接上。这里的实现代码是当Task3运行时将g_LCDCanUse设置为0所以切换到Task2和Task1时因为g_LCDCanUse0无法进入if内部任务所以只有当切换到Task3运行完任务将g_LCDCanUse设置为1时才能到下一个任务正常运行不过这个方法不是万无一失的那怎么解决这个互斥访问LCD呢后面我们在解决这里设置一个mdelay(500)是为了能保证其它任务正常执行假设没有这个延迟函数那当Task3结束if语句任务时因为把g_LCDCanUse设置为1所以它会再次进入if语句里面那Task3就会一直卡在if里面其它任务就一直得不到执行所以设置一个延迟函数是为了在运行到延迟函数时能切换到其它任务来执行创建任务代码如下/*创建文件同一函数多个任务*/xTaskCreate(LcdPrintTask,LcdTask1,128,Task1,osPriorityNormal,NULL);xTaskCreate(LcdPrintTask,LcdTask2,128,Task2,osPriorityNormal,NULL);xTaskCreate(LcdPrintTask,LcdTask3,128,Task3,osPriorityNormal,NULL);看过我第一节理论课应该都知道这里能同一个函数创建不同任务是因为每个任务都有属于自己的栈删除任务代码如下voidStartDefaultTask(void*argument){/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */LCD_Init();LCD_Clear();staticTaskHandle_t xPlayMusicHandleNULL;uint8_tdev,data;intlen;IRReceiver_Init();LCD_PrintString(0,0,Waitting Conctol);while(1){if(IRReceiver_Read(dev,data)0){/*创建播放音乐任务*/if(data0xa8){LCD_ClearLine(0,0);externvoidPlayMusic(void*params);//防止创建多个任务if(xPlayMusicHandleNULL){LCD_PrintString(0,0,Play Music);xTaskCreate(PlayMusic,playmusci,128,NULL,osPriorityNormal,xPlayMusicHandle);}}/*删除播放音乐任务*/if(data0xa2){//判断有没有创建任务if(xPlayMusicHandle!NULL){LCD_ClearLine(0,0);LCD_PrintString(0,0,Stop Music);vTaskDelete(xPlayMusicHandle);xPlayMusicHandleNULL;}}}}/* USER CODE END StartDefaultTask */}我在初始任务这里来实现创建任务和删除任务注意static TaskHandle_t xPlayMusicHandle NULL;这里定义句柄要初始化因为局部变量是随机的如果你要判定的话很大可能是创建不了任务的同时会拿垃圾句柄去删除任务这就会导致按play键没反应按power键有一次反应提高蜂鸣器优先级我们在上小节源代码基础上添加一些小改动可以看到我们提高了播放音乐任务的优先级但如果我们只做这个改动会导致这个任务优先级最高一直占用了CPU资源这就会导致其它任务的停止并且不能删除这任务怎么处理呢如下只要将该任务进入阻塞状态那么其它任务就能正常运行使任务进入暂停状态来控制音乐的暂停和播放状态转换图控制音乐播放代码如下voidStartDefaultTask(void*argument){/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */LCD_Init();LCD_Clear();staticTaskHandle_t xPlayMusicHandleNULL;uint8_tdev,data;intlen;IRReceiver_Init();LCD_PrintString(0,0,Waitting Conctol);uint8_tTaskState_Flag1;//0表示任务暂停1表示任务进行while(1){if(IRReceiver_Read(dev,data)0){/*创建播放音乐任务*/if(data0xa8){LCD_ClearLine(0,0);externvoidPlayMusic(void*params);//防止创建多个任务if(xPlayMusicHandleNULL){LCD_PrintString(0,0,Play Music);xTaskCreate(PlayMusic,playmusci,128,NULL,osPriorityNormal1,xPlayMusicHandle);}else//再次按下play键如果已经创建任务{if(TaskState_Flag1){/*使任务进入暂停状态*/vTaskSuspend(xPlayMusicHandle);PassiveBuzzer_Control(0);//暂停蜂鸣器LCD_PrintString(0,0,Stop Music);TaskState_Flag0;}elseif(TaskState_Flag0){/*使任务恢复就绪状态*/vTaskResume(xPlayMusicHandle);LCD_PrintString(0,0,Ready Music);TaskState_Flag1;}}}/*删除播放音乐任务*/if(data0xa2){if(xPlayMusicHandle!NULL){LCD_ClearLine(0,0);LCD_PrintString(0,0,Delect Music);vTaskDelete(xPlayMusicHandle);PassiveBuzzer_Control(0);//暂停蜂鸣器xPlayMusicHandleNULL;}}}}任务的管理和调度还记得我们的说过的默认任务调度策略吗如下这些任务调度核心是链表我们来到CubeMX里面看到FreeTROS的配置参数中可以看到任务优先级有56个我们来到定义这个优先级宏内部看看可以看到这三种链表而pxReadyTasksLists链表就是管理不同优先级处于就绪态的任务链表以我们的代码为例当我们创建这些任务的使用会按这些任务的优先级放到就绪链表对应优先级链表中而我们设置的默认优先级就是24可以看到在24优先级链表中链接着三个任务的结构体其它链表都是空的那它是怎么调度的呢来到main.c文件中可以看到该函数osKernelStart();//启动内核vTaskStartScheduler();//启动调度器在启动内核函数内部在启动调度器函数里面可以看到还会创建一个空闲任务这个空闲任务的优先级是0在添加进就绪链表函数中里面有一个全局指针pxCurrent如上当我们创建第一个任务StartDefaultTasks时没有其它任务它是全局最高的优先级任务所以全局指针会指向它然后创建第二个任务时它又会指向第二个任务以此类推最终它会指向最后一个任务所以这也是为什么创建任务一、二、三的时候调度器会先调用任务三从图一时间轴我们可以知道在我们设置好的时间中我们这里设置的是每1m会触发一次中断触发Tick中断后cnt然后会触发调度怎么调度呢从图二可以知道它会从链表中从上到下遍历这些优先级链表看看哪个不是空的这里假设25优先级链表还没有任务当它到24优先级链表时就会找到StartDefaultTasks任务然后就让pxCurrent指向这个任务于是它找到了下一个要运行的任务然后启动它这也是为什么任务三启动后会到任务一启动那假设我设置一个更高的优先级任务呢如图二当创建这个优先级更高的任务时系统会立马停止并保存当前任务去执行这个优先级更高的任务而且我们知道如果这个任务不调用vTaskDelay函数让它进入阻塞状态那它就会一直占用CPU资源那调用vTaskDelay函数会发生什么还记得我们进入启动调度器函数内部有三种链表吗如下当调用vTaskDelay函数时系统会将该任务从RredyLists移到DelayedTaskList链表中这里面有一个Value记录延迟2个Tick接着回到时间轴我们可以看到当最高优先级任务进入阻塞状态时会触发调度那它又会回到就绪链表从上往下遍历这些链表回到24优先级链表里面时会有一个index记录项这个index会指向上一次运行的任务即第一个任务它会根据这个index找出下一个任务执行即任务二回到时间轴我们可以看到当经过两个Tick时间触发Tick中断时它不仅会正常计数cnt和调度还会去判断一下Delay链表里面的任务时间有没有到这是发生在调度里面的当Delay链表里面的任务时间到了以后会将它重新放回就绪链表中然后发起调度最后又会先执行它最后还是回到时间轴假设我们调用vTaskSusoend函数呢那系统会将它放到第三钟链表中因为在触发Tick中断中并没有到这个链表判断的操作所以要从这个链表将任务拿出来只能调用vTaskResume函数将它放回到就绪链表中最后总结一下有一个系统时钟Tick中断当发生Tick中断时它会做三件事情累加计数、判断一下Delay链表里面的任务时间有没有到、发起调度空闲函数从这个图我们可以知道当一个任务没有进入死循环而且设置一个时间当这个时间到了以后不经过其它处理直接返回的话会返回到prvTaskExitError函数这是个错误函数这个函数会关闭所有中断我们知道所有任务都是依靠中断来执行的所有这会导致所有任务停止所以任务退出只有两种方式“自杀或者他杀”我们了解过在删除任务的时候调用删除任务的函数来释放被删除任务的TCB和栈空间拿自杀呢之前的FreeRTOS纯理论那节时的疑惑可以在这里解除了自杀会由空闲函数来帮你释放TCB和栈空间的任务但空闲任务的优先级是最低了0我们可以知道其它任务优先级是远远大于空闲任务的所以这些任务的执行会导致空闲任务永远无法给这些自杀任务“收尸”那怎么办呢第一事件驱动第二尽量使用延时函数vTaskDelay、vTaskDelayuint不要用死循环我们可以进入mdelayudelay函数内部可以看到它们就是死循环所以我们只要把它们改成阻塞函数vTaskDelay、vTaskDelayuint那么这些任务做完自己的事情时就会进入阻塞状态不参与调度就让出了CPU资源这样空闲函数就有机会执行了那么有同学疑惑了假如这些任务轮流退出进入阻塞状态那空闲函数不也一直得不到执行吗我们分两种情况来看情况一两个任务交替阻塞/就绪但总有任务处于就绪态假设任务 A 和任务 B 都是普通优先级A 刚进入阻塞态B 刚好退出阻塞态。此时 B 处于就绪态调度器会直接切换到 B 运行空闲任务不会被选中。等 B 也进入阻塞态后如果此时 A 还没到延时结束的时间就绪链表为空空闲任务才会被调度。所以在这种“你方唱罢我登场”的交替中空闲任务确实可能长时间得不到运行——但这没关系因为 CPU 并没有闲着它一直在执行有用的任务A 或 B。空闲任务本来就是在“无事可做”时才运行的它不运行恰恰说明系统很忙、很健康。情况二所有任务都进入阻塞态只有当所有任务都阻塞比如都在vTaskDelay等待中时就绪链表才为空调度器才会选择空闲任务运行。此时空闲任务会执行直到某个任务的延时到期、重新进入就绪态调度器又会立刻切走空闲任务。所以你的担心其实反了空闲任务得不到运行恰恰说明系统里一直有任务在干活这是好事。真正要担心的是某个高优先级任务一直处于就绪态比如死循环不阻塞那才会把 CPU 占死让其它任务和空闲任务都得不到运行。总结场景空闲任务是否运行有任务处于就绪态不运行CPU 被该任务占用所有任务都阻塞运行就绪链表为空高优先级任务死循环不阻塞不运行且其它任务也被饿死所以只要任务都会主动进入阻塞态空闲任务总有机会运行——哪怕它被“挤”了很久但只要所有任务都阻塞的那一刻到来它就会立刻被调度。所以我们不用担心空闲函数得不到运行的问题。最后提醒一下空闲函数里面还有一个钩子函数可以提供我们写代码逻辑两个Delay函数xTaskDelay函数时间轴xTaskDelayunitl函数时间轴可以看到这两个阻塞函数的时间轴是不一样的第一个任务与任务之间的时间间隔是不固定的第二个则是有周期性的下面我们通过代码来看一下xTaskDelay代码uint64_tt1,t2;voidLcdPrintTask(void*parms){//Time;LCD_Init();LCD_Clear();structTaskPtintInfo*pInfoparms;int32_tcnt0;intlen0;while(1){if(g_LCDCanUse1){g_LCDCanUse0;lenLCD_PrintString(pInfo-x,pInfo-y,pInfo-Name);//赋值字符串长度给lenLCD_PrintSignedVal(len,pInfo-y,cnt);g_LCDCanUse1;mdelay(cnt0x3);//随机取几个数}t1system_get_ns();vTaskDelay(500);//500000000t2system_get_ns();LCD_ClearLine(pInfo-x,pInfo-y);LCD_PrintSignedVal(pInfo-x,pInfo-y2,t2-t1);}}现象xTaskDelayunitl代码如下staticintg_LCDCanUse1;TickType_t Time_Test;uint64_tt1,t2;voidLcdPrintTask(void*parms){Time_TestxTaskGetTickCount();LCD_Init();LCD_Clear();structTaskPtintInfo*pInfoparms;int32_tcnt0;intlen0;while(1){if(g_LCDCanUse1){g_LCDCanUse0;lenLCD_PrintString(pInfo-x,pInfo-y,pInfo-Name);//赋值字符串长度给lenLCD_PrintSignedVal(len,pInfo-y,cnt);g_LCDCanUse1;mdelay(cnt0x3);//随机取几个数}t1system_get_ns();vTaskDelayUntil(Time_Test,500);//500000000t2system_get_ns();LCD_ClearLine(pInfo-x,pInfo-y);LCD_PrintSignedVal(pInfo-x,pInfo-y2,t2-t1);}}现象区别如上
返回列表