【FreeRTOS】任务状态&改进播放控制

发布于:2024-06-23 ⋅ 阅读:(21) ⋅ 点赞:(0)


在这里插入图片描述

参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》

本节课实现音乐任务的创建,音乐播放的暂停与继续播放,删除任务。

代码为:08_task_priority

重点:使用链表讲解内部原理。

  • 任务状态:09_task_suspend,添加音乐暂停/恢复功能
  • 优先级、链表管理
  • 任务切换、tick

1 任务状态

以前我们很简单地把任务的状态分为两种:运行(Runing)、非运行(Not Running)。 对于非运行的状态,还可以继续细分,比如前面的FreeRTOS_04_task_priority中:

  • Task3执行vTaskDelay后:处于非运行状态,要过3秒种才能再次运行
  • Task3运行期间,Task1、Task2也处于非运行状态,但是它们随时可以运行
  • 这两种"非运行"状态就不一样,可以细分为:
  • 阻塞状态(Blocked)
  • 暂停状态(Suspended)
  • 就绪状态(Ready)

1.1 阻塞状态(Blocked)

在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于阻塞状态(Blocked)。重点在于:母亲在等待。

在FreeRTOS_04_task_priority实验中,如果把任务3中的vTaskDelay调用注释掉,那么任务1、任务2根本没有执行的机会,任务1、任务2被"饿死"了(starve)。

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:

  • 任务要等待某个事件,事件发生后它才能运行
  • 在等待事件过程中,它不消耗CPU资源
  • 在等待事件的过程中,这个任务就处于阻塞状态(Blocked)

在阻塞状态的任务,它可以等待两种类型的事件:

  • 时间相关的事件
    • 可以等待一段时间:我等2分钟
    • 也可以一直等待,直到某个绝对时间:我等到下午3点
  • 同步事件:这事件由别的任务,或者是中断程序产生
    • 例子1:任务A等待任务B给它发送数据
    • 例子2:任务A等待用户按下按键
    • 同步事件的来源有很多(这些概念在后面会细讲):
      • 队列(queue)
      • 二进制信号量(binary semaphores)
      • 计数信号量(counting semaphores)
      • 互斥量(mutexes)
      • 递归互斥量、递归锁(recursive mutexes)
      • 事件组(event groups)
      • 任务通知(task notifications)

在等待一个同步事件时,可以加上超时时间。比如等待队里数据,超时时间设为10ms:

  • 10ms之内有数据到来:成功返回
  • 10ms到了,还是没有数据:超时返回

1.2 暂停状态(Suspended)

在日常生活的例子中,母亲正在电脑前跟同事沟通,母亲可以暂停:

  • 好烦啊,我暂停一会
  • 领导说:你暂停一下

FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。

要退出暂停状态,只能由别人来操作:

  • 别的任务调用:vTaskResume
  • 中断程序调用:xTaskResumeFromISR

实际开发中,暂停状态用得不多。

1.3 就绪状态(Ready)

这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

1.4 完整的状态转换图

完整的状态转换图如下:

在这里插入图片描述


2 举个例子

任务的状态:Ready状态(就绪)和 Running状态(运行)

在这里插入图片描述

现在给小孩喂饭,这个就处于Running状态,回复信息就处于Ready状态,随时可以切换过去
当回复信息时候,这个就处于Running状态,给小孩喂饭就处于Ready状态

Ready状态随时有机会切换为Running状态,只要有机会运行,就称为Running状态,当Running状态被调度出去后,就成了Ready状态

还有其他状态,Blocked(被阻塞,在等待某些event事件),suspend(暂停状态,单纯的不想干活,就只想休息)

在这里插入图片描述
创建播放音乐的任务,这个任务创建好后,就处于Ready状态,这个任务的优先级最高,能运行的时候,它就处于Running状态,当调用vTaskDelay的时候,就处于Blocked状态(等待某些事件)

在这里插入图片描述
当时间到了,就会被唤醒,就变成Ready就绪状态,当能运行的时候,就变成Running状态

suspend暂停状态只能调用vTaskSuspend函数进入

  • 可以自己调用vTaskSuspend进入,也可以别人调用vTaskSuspend函数
    在这里插入图片描述

3 编写代码

编写代码实现功能:第一次按下播放按键,创建音乐播放任务,再次按下播放按键,暂停音乐播放,再次按下播放按键,恢复音乐播放,按power按键,删除音乐播放任务

工程:09_task_suspend

在默认任务里修改代码,如下:

/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
    
    uint8_t dev, data;
    int len;
    uint8_t status;     //状态变量
    uint8_t bRunning;   //是不是正在运行
    
    BaseType_t ret; // long
    TaskHandle_t xSoundTaskHandle = NULL;           // 句柄 初始值是NULL	  
    
    LCD_Init();
    LCD_Clear();

    IRReceiver_Init();
    LCD_PrintString(0, 0, "Waiting Control");

    while (1)   // 在屏幕上输出接收到的键值
    {
        
        /* 读取红外遥控器 */
        if (0 == IRReceiver_Read(&dev, &data))
        {
            if (data == 0x22)   /*play*/
            {
                /* 创建播放音乐的任务 */
                //这里需要判断是否多次按下播放按键,判断句柄是否等于NULL
                if (xSoundTaskHandle == NULL)   /* 句柄等于NULL,才来创建这个任务 */
                {
                    LCD_ClearLine(0, 0);    //清屏
                    LCD_PrintString(0, 0, "Create Task");
                    ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal+1, &xSoundTaskHandle);
                    bRunning = 1;   //正在运行。等于1
                }
                else
                {
                    /* 要么suspend,要么resume恢复它 */
                    if (bRunning)   //如果bRunning == 1
                    {
                        LCD_ClearLine(0, 0);    //清屏
                        LCD_PrintString(0, 0, "Suspend Task");
                        vTaskSuspend(xSoundTaskHandle);     // 如果任务正在运行,就将任务切换成suspend状态
                        PassiveBuzzer_Control(0);           // Stop Buzzer 停止蜂鸣器,清除噪音
                        bRunning = 0;
                    }
                    else
                    {
                        LCD_ClearLine(0, 0);    //清屏
                        LCD_PrintString(0, 0, "Resume Task");
                        vTaskResume(xSoundTaskHandle);     // 如果任务Suspend,就将任务vTaskResume,恢复
                        bRunning = 1;
                    }
                }
            }
            else if (data == 0xA2)
            {
                /* 删除播放音乐的任务 */
                if (xSoundTaskHandle != NULL)   //如果这个句柄xSoundTaskHandle != NULL,才会删除
                {
                    LCD_ClearLine(0, 0);    //清屏
                    LCD_PrintString(0, 0, "Delete Task");
                    vTaskDelete(xSoundTaskHandle);
                    PassiveBuzzer_Control(0);   // Stop Buzzer 停止蜂鸣器
                    xSoundTaskHandle = NULL;    //删除完成,赋值NULL
                }
            }
            len = LCD_PrintString(0, 6, "Key name: ");
            LCD_PrintString(len, 6, IRReceiver_CodeToString(data));
        }
    }

  /* USER CODE END StartDefaultTask */
}

在这里插入图片描述