基于STM32瑞士军刀--【FreeRTOS开发】学习笔记(三)|| 任务创建 / 删除 / 优先级 / 管理调度

韦东山老师《FreeRTOS入门与工程实践(基于DshanMCU-103)》书籍获取:https://gitee.com/jingcheng11/embedded-data.git

任务

任务的三大要素:函数、栈&TCB结构体、优先级

创建任务方式

创建任务时可以使用 2 个函数:动态分配内存静态分配内存

动态分配内存

动态分配内存函数会分配栈和TCB结构体在这里插入图片描述
句柄理解

静态分配内存

静态分配内存函数需要事先指定栈和TCB结构体
在这里插入图片描述

使用一个函数创建不同的任务

多个任务可以使用同一个函数,怎么体现它们的差别?

  • 栈不同
  • 创建任务时可以传入不同的参数

我们创建 3 个任务,使用同一个函数,但是在 LCD 上打印不一样的信息。
在这里插入图片描述
在具体的任务中,要注意的点:
在OLED显示时,如果删掉下图中包含的“延时函数”,OLED就会只显示一行(只会显示Task3部分)。其实其他任务已经在执行了,只是可能没有进入到打印就被切换Task3了(因为打印的时间比较久,所以很大概率会被这个时间切换,而一旦被切换,全局变量g_LCDCanUse被置为0,所以TASK2、3永远打印不出来)。但是加上延时之后,会有很大概率在执行延时函数的时候被切换(这个时候全局变量g_LCDCanUse是1,所以能执行打印;另外的Task)。

提问:为什么只显示Task3部分?
答:Task3是被最后创建的,会先被执行。具体解释看下图。
任务调度这里韦东山老师讲的特别清楚!
在这里插入图片描述另外,为什么要添加全局变量?因为在LCD打印的时候,虽然使用了三行函数,但是实际上打印出来的是一行话。所以不能被打断。所以,使用全局变量g_LCDCanUse能实现互斥访问LCD,但不是万无一失的。(注意:虽然能实现,但这里的方法不太好,只是解释代码,后面会逐步改进)在这里插入图片描述

创建&删除音乐播放任务

怎么删除任务?

  • 自杀:vTaskDelete(NULL)
  • 被杀:别的任务执行 vTaskDelete(pvTaskCode),pvTaskCode 是自己的句柄
  • 杀人:执行 vTaskDelete(pvTaskCode),pvTaskCode 是别的任务的句柄

删除任务时使用的函数如下:void vTaskDelete( TaskHandle_t xTaskToDelete );

在这里插入图片描述
上述代码音乐播放有卡顿,播放和停止播放效果。

改进音乐播放效果(优先级)

  • FreeRTOS 会确保最高优先级的、可运行的任务,马上就能执行
  • 对于相同优先级的、可运行的任务,轮流执行

想让音乐播放效果更好(比较快,不卡顿)可以通过改变优先级和vTaskDelay函数来实现。

音乐不卡顿

osPriorityNormal+1  //只需要将优先级 +1 即可

仅仅做这一步,在实际实验过程中会发现,音乐确实不卡顿了,但是在按下“停止播放”按钮后丝毫无反应!这是因为我们创建音乐播放任务优先级高,且内部死循环,所以它一直抢占CPU资源,导致停止播放任务一直执行不了。

那如何解决呢?

修改延时函数

下面的mdelay函数一直内部死循环,将这行的延时函数改成vTaskDelay就可以解决上述 “暂停不了音乐” 的问题。

vTaskDelay(MusicSpeed/Music_Lone_Brave[i][2]);

通过这一操作,既保持了好的音乐播放效果,也不影响其他任务
在这里插入图片描述
通过上述操作,可以实现音乐播放和删除任务,但是存在一个问题:音乐每次都是从头开始播放的,那么如何使得音乐播放是从上次暂停的位置开始播放呢? ,这就涉及到任务状态转换

任务状态

可以分为运行状态和非运行状态;非运行状态又可分为三种。总体可以分为:

  • 运行状态(Running)
  • 阻塞状态(Blocked)
  • 暂停状态(Suspended)
  • 就绪状态(Ready)

在这里插入图片描述

音乐暂停&接着播放

我们需要实现按下"Play"键播放,再次按"Play"键暂停,再次按"Play"键播放…这样的功能。具体函数,我们来分析一下。当第一次按play,音乐播放任务处于运行(Running)状态,该函数内部有函数vTaskDelay,因此可能会进入阻塞状态;在这两种状态下,如图所示需要通过vTaskSuspend()函数进入暂停状态;当再次按play键,我们需要音乐接着播放,此时,需要跳出暂停状态回到ready状态,需要通过vTaskResume()函数实现;当在ready状态时,由于“音乐播放任务”的优先级比较高,因此很快进入Running状态实现再次播放音乐。
那如何进行状态之间的切换呢?我们需要一个标志位。具体代码如下图所示:

//部分函数定义与初始化不做展示了
int FlagMusic = 0;
    while (1)
    {
      /*读取红外遥控器*/
      if(0 == IRReceiver_Read(&dev, &data)){
          if(data == 0xa8){
            /*创建音乐播放任务*/
              extern void PlayMusic(void *params);
              
              if(xSoundTaskHandle == NULL){
                  LCD_ClearLine(0, 0);
                  LCD_PrintString(0, 0, "Create task");
                  ret  = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal+1, &xSoundTaskHandle);
                  FlagMusic = 1;
              }
              else
              {
                  if(FlagMusic)
                  {
                      PassiveBuzzer_Control(0);
                      LCD_PrintString(0, 0, "Suspend task");
                      vTaskSuspend(xSoundTaskHandle);
                      FlagMusic = 0;
                  }
                  else
                  {
                      //PassiveBuzzer_Control(1);
                      LCD_PrintString(0, 0, "Resume task");
                      vTaskResume(xSoundTaskHandle);
                      FlagMusic = 1;
                  }
              }
          }else if(data == 0xa2){
            /*删除音乐播放任务*/
            if(xSoundTaskHandle != NULL){
                PassiveBuzzer_Control(0);
                LCD_ClearLine(0, 0);
                LCD_PrintString(0, 0, "Delete task");
                vTaskDelete(xSoundTaskHandle);
                xSoundTaskHandle = NULL;
            }
        }
      }
    }

任务管理与调度

韦东山老师任务调度,在视频20多分钟的时候,讲了详细的任务执行过程。
任务管理与任务调动的核心是链表操作,能够发生任务调动依赖于Tick中断

Tick

系统时钟使用定时器产生固定间隔的中断:产生Tick中断。
中断函数会做三件事:

  • cnt++,累加计数
  • 判断DelayedTaskList里面的任务是否可恢复,如果可恢复会将该任务移到ReadyList中去
  • 发起调度:
    去高优先级到低优先级去遍历ReadyList链表,直到找到非空项,找到非空链表之后,取出下一个要运行的任务去运行(这里会有相应索引找到下一个要运行的任务,比如已经运行完Task1和Task2了,此时就会运行Task3)。
    在这里插入图片描述
    两次中断之间的时间被称为时间片。程序中设置阻塞和暂停的目的是充分利用时间片资源

空闲函数

介绍

空闲任务(Idle 任务)的作用之一:释放被删除的任务的内存。
除了上述目的之外,为什么必须要有空闲任务?一个良好的程序,它的任务都是事件驱
动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。

在使用vTaskStartScheduler()函数来创建、启动调度器时,这个函数内部会创建空闲任务:

  • 空闲任务优先级为 0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞
    在这里插入图片描述

空闲任务的优先级为 0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上
被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。要注意的是:如果使用vTaskDelete()来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循
环每执行一次,就会调用一次钩子函数。钩子函数可以:测量系统的空闲时间、让系统进入省电模式、执行一些低优先级的、后台的、需要连续执行的函数等等。

任务退出

  • 自杀:vTaskDelete(NULL)
  • 他杀:vTaskDelete(handle)

不设置死循环,退出任务

系统瘫痪的写法:
在这里插入图片描述
正确写法:

加上 vTaskDelete(NULL),也就是”自杀“,自杀后由空闲任务 “收尸”(做一些任务清除工作,如释放TCB结构体、释放栈)。

在这里插入图片描述

两个delay 函数

  • vTaskDelay() 非周期性运行,等待指定个数的Tick Interrupt才能变为就绪状态。
    **加粗样式**
  • vTaskDelayUntil() 使任务周期性运行,等待到指定的绝对时刻,才能变为就绪态。
    使用该函数,需要传入参数:pxPreviousWakeTime(上一次被唤醒的时间 )和xTimeIncrement(要阻塞到(pxPreviousWakeTime + xTimeIncrement)),也就是起始时间和增量时间。
    在这里插入图片描述
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值