这里写目录标题
- 1 任务状态
- 1.1 阻塞状态(Blocked)
- 1.2 暂停状态(Suspended)
- 1.3 就绪状态(Ready)
- 1.4 完整的状态转换图
- 2 举个例子
- 3 编写代码
参考《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 */
}