任务状态_改进播放控制
FreeRTOS源码概述(内存管理,入口函数,数据类型和编程规范)创建任务(声光色影,使用任务参数)删除任务(使用遥控器控制音乐)-CSDN博客https://blog.csdn.net/2302_79504723/article/details/143121434?spm=1001.2014.3001.5502
任务的暂停与继续(简单介绍)
在这个文章中我们简单的实现了任务的删除与创建 但是怎么才能实现任务的暂停与继续
在我们执行任务时我们有任务的准备(ready)以及任务的运行(Running)
同时也有任务阻塞(Blocked),以及任务的暂停(Suspended)
如图
暂停音乐的代码
FreeRTOS的部分代码
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
uint8_t dev, data;
int len;
int bRunning;
TaskHandle_t xSoundTaskHandle = NULL;
BaseType_t ret;
LCD_Init();
LCD_Clear();
IRReceiver_Init();
LCD_PrintString(0, 0, "Waiting control");
while (1)
{
/* ¶ÁÈ¡ºìÍâÒ£¿ØÆ÷ */
if (0 == IRReceiver_Read(&dev, &data))
{
if (data == 0xa8) /* play */
{
/* ´´½¨²¥·ÅÒôÀÖµÄÈÎÎñ */
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);
bRunning=1;
}else{
if(bRunning){
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Suspend Task");
vTaskSuspend(xSoundTaskHandle);
PassiveBuzzer_Control(0);
bRunning=0;
}else{
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Resume Task");
vTaskResume(xSoundTaskHandle);
bRunning=1;
}
}
}
else if (data == 0xa2) /* power */
{
/* ɾ³ý²¥·ÅÒôÀÖµÄÈÎÎñ */
if (xSoundTaskHandle != NULL)
{
LCD_ClearLine(0, 0);
LCD_PrintString(0, 0, "Delete Task");
vTaskDelete(xSoundTaskHandle);
PassiveBuzzer_Control(0); /* Í£Ö¹·äÃùÆ÷ */
xSoundTaskHandle = NULL;
}
}
}
}
/* USER CODE END StartDefaultTask */
}
music.c的部分代码
void MUSIC_Analysis(void){
uint16_t MusicBeatNum = ((((sizeof(Music_Lone_Brave))/2)/3)-1);
uint16_t MusicSpeed = Music_Lone_Brave[0][2];
for(uint16_t i = 1;i<=MusicBeatNum;i++){
//BSP_Buzzer_SetFrequency(Tone_Index[Music_Lone_Brave[i][0]][Music_Lone_Brave[i][1]]);
PassiveBuzzer_Set_Freq_Duty(Tone_Index[Music_Lone_Brave[i][0]][Music_Lone_Brave[i][1]], 50);
//HAL_Delay(MusicSpeed/Music_Lone_Brave[i][2]);
//mdelay(MusicSpeed/Music_Lone_Brave[i][2]);
vTaskDelay(MusicSpeed/Music_Lone_Brave[i][2]);
}
}
为什么不用mdelay要用vTaskDelay?
在任务状态中vTaskDelay作用是对当前的任务进行阻塞,可以使它运行其他的任务。
在本程序中music的任务优先级高于其他的所有任务,如果单纯的delay那么程序就会进入死循环。
任务管理与调度
调度
1.相同优先级的任务轮流运行
2.最高优先级的任务先运行
3.高优先级的任务未执行完,低优先级的任务无法执行
4.一旦高优先级任务就绪马上运行
5.最高优先级的任务有多个他们轮流运行
同一优先级的所有任务他们都是通过链式进行连接的
优先级
在FreeRTOS中有56的优先级 如图
在同一优先级创建三个任务(判断执行顺序)
在创建好任务,开启任务调度器,系统会自动创建一个级别为一的prvIdleTask任务
创建任务时就会创建三个TCB结构体
pxCurrentTCB(全局TCB)的使用
指向当前(最新)创建的TCB结构体
所以pxCurrentTCB指向的是第三个任务的结构体,那么在24这个优先级中先执行任务2,再执行任务1,然后执行任务2,不断轮询(原理:在创建任务时会创建一个1ms进入一次中断的中断函数Tick)
在StartDefaultTask中创建一个高于当前任务优先级的任务
在StartDefaultTask中创建一个高于当前任务优先级的任务就会立刻ready该任务以及Running该任务,在这个任务中我们会触发vTaskDelay使任务进入DelayTaskList链表中进行等待以保证其他任务的运行,在vTaskDelay期间会触发调度器通过index检测当前任务执行到哪
在此期间每过1ms就会触发一次中断(cnt++,任务的调度操作(先看DelayList中的任务是否可以恢复,然后再找第一个非空的任务启动它))
注意
如果触发中断suspend状态,就会使任务进入xSuspendTaskList链表,这个链表不会通过调度
只能手动调出
空闲任务及其钩子函数
介绍
使用钩子函数的前提
任务如何退出
1.自杀 vTaskDelete(NULL)
就是在任务本身进行的删除操作,但是不能是自己杀自己,这种自杀的操作只能是交给空闲任务来删除,就是在启动调度器时系统会创造一个优先级为1的任务,来进行杀人
存在问题
由于空闲任务的优先级太低,那么可能无法执行杀人的操作,这样的话任务的创造,删除不断进行就会有很多空间碎片化,那么空间就会浪费,所以我们要将这些任务中delay转化为阻塞的函数,以保证空闲任务得以执行,以保证空闲任务负责处理动态内存分配(如通过
pvPortMalloc()
)后释放的内存块。以下是这句话的翻译
1. 空闲任务的优先级
- 优先级低:空闲任务的优先级通常设置为最低,因此在有其他高优先级任务就绪时,空闲任务不会获得 CPU 的执行机会。这可能导致空闲任务无法及时运行,从而影响内存管理和资源的释放。
2. 任务的创建和删除
- 任务创建和删除:频繁地创建和删除任务会导致系统中产生内存碎片。尤其是动态内存分配(通过
pvPortMalloc()
)和释放(通过vTaskDelete()
)的频繁操作,会导致内存块的分散,使得内存的使用效率降低。3. 将 Delay 转化为阻塞的函数
阻塞与延迟:如果任务使用
vTaskDelay()
等延迟函数,任务会进入阻塞状态,这样在其阻塞期间,CPU 可以运行其他任务,包括空闲任务。这确实有助于确保空闲任务能够在有其他任务等待时得以执行。使用阻塞函数:将延迟操作转化为阻塞函数(如
xSemaphoreTake()
等)可以更好地控制任务的执行流。在某些情况下,使用信号量或其他同步机制,可以确保任务在等待某些条件满足时主动放弃 CPU,让空闲任务得以运行。4. 确保空闲任务得以执行
- 保证内存管理:空闲任务的职责包括处理动态内存的释放。确保空闲任务能够定期执行是非常重要的,因为它会释放已经删除任务占用的内存块。若空闲任务得不到执行,系统内存可能会逐渐枯竭,导致后续的内存分配失败。
5. 总结
提到的将延迟转化为阻塞函数以确保空闲任务能够执行的观点是正确的。这样做不仅可以减少内存碎片化,还可以提高系统的资源使用效率。在设计 FreeRTOS 应用程序时,合理使用任务的延迟和阻塞机制是确保系统稳定性和性能的重要策略。
2.他杀vTaskDelete(句柄)
在别的任务中删除别的任务
两个Delay函数
vTaskDelay
vTaskDelay()
是 FreeRTOS 中用于延迟任务执行的一个重要函数。它允许任务在一定时间内进入阻塞状态,释放 CPU 控制权,使得其他任务得以运行。这对于多任务环境中的资源管理和调度非常重要。下面是关于 vTaskDelay()
的详细说明:
函数原型
void vTaskDelay( TickType_t xTicksToDelay );
参数
xTicksToDelay
: 延迟的时间,以“滴答(ticks)”为单位。这是 FreeRTOS 内部时间管理的基本单位,通常与系统节拍频率相关。
功能
- 当调用
vTaskDelay()
时,调用该函数的任务会被放入阻塞状态,直到指定的时间结束。在此期间,调度器将允许其他就绪任务获得 CPU 时间。
使用场景
- 周期性任务: 当任务需要在特定时间间隔内执行时,可以使用
vTaskDelay()
来创建延迟。 - 资源控制: 在执行某些操作(如发送数据)后,任务可能需要等待一段时间以避免过度使用资源。
示例代码
以下是一个简单的示例,展示如何使用 vTaskDelay()
创建一个周期性任务:
#include "FreeRTOS.h"
#include "task.h"
void vTaskFunction(void *pvParameters) {
for(;;) {
// 执行某些操作
// ...
// 延迟 100 个滴答
vTaskDelay(pdMS_TO_TICKS(100)); // 将毫秒转换为滴答
}
}
int main(void) {
xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
vTaskStartScheduler(); // 启动调度器
for(;;); // 此处不应该执行
}
注意事项
-
阻塞状态: 使用
vTaskDelay()
会使任务进入阻塞状态,期间不会占用 CPU 时间。这对于降低 CPU 使用率是有帮助的。 -
最小延迟:
vTaskDelay()
的最小延迟为一个滴答,因此如果给定的时间小于一个滴答,任务将立即恢复执行。 -
在中断上下文中不可用:
vTaskDelay()
不能在中断服务例程(ISR)中调用。需要使用其他机制,如消息队列或信号量。
总结
vTaskDelay()
是 FreeRTOS 中一个非常实用的函数,帮助开发者有效地管理任务调度和资源使用。通过合理使用延迟,开发者可以避免 CPU 过载和资源浪费,确保系统的高效运行。
xTaskDelayUntil
xTaskDelayUntil()
是 FreeRTOS 中用于实现周期性任务的函数。与 vTaskDelay()
不同,xTaskDelayUntil()
允许任务在指定的时间间隔内精确地执行。这对于需要定时运行的任务尤其有用,可以避免由于任务执行时间不确定而导致的时间偏移。
函数原型
BaseType_t xTaskDelayUntil( TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement );
参数
pxPreviousWakeTime
: 指向存储上次唤醒时间的变量的指针。这个变量在任务首次调用xTaskDelayUntil()
前应被初始化为当前时间。xTimeIncrement
: 每次调用xTaskDelayUntil()
时要延迟的时间,单位为滴答(ticks)。
功能
xTaskDelayUntil()
会使任务阻塞,直到到达下一个预定的唤醒时间。这种方法可以实现准确的周期性执行,特别是在需要维持精确时间间隔的场景中。
使用场景
- 周期性任务: 适合需要在固定时间间隔内重复执行的任务。
- 高精度定时: 当需要保证任务之间的执行时间不变时,
xTaskDelayUntil()
是更好的选择。
示例代码
以下是使用 xTaskDelayUntil()
的一个简单示例,创建一个周期性执行的任务:
#include "FreeRTOS.h"
#include "task.h"
// 定义滴答时间
#define pdMS_TO_TICKS(ms) ((ms) / portTICK_PERIOD_MS)
void vTaskFunction(void *pvParameters) {
TickType_t xLastWakeTime;
const TickType_t xFrequency = pdMS_TO_TICKS(100); // 每 100 毫秒执行一次
// 初始化 xLastWakeTime
xLastWakeTime = xTaskGetTickCount();
for(;;) {
// 执行任务
// ...
// 使用 xTaskDelayUntil
xTaskDelayUntil(&xLastWakeTime, xFrequency); // 延迟直到下一个周期
}
}
int main(void) {
xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
vTaskStartScheduler(); // 启动调度器
for(;;); // 此处不应该执行
}
注意事项
-
初始化: 在第一次调用
xTaskDelayUntil()
前,必须正确初始化pxPreviousWakeTime
,通常使用xTaskGetTickCount()
获取当前滴答计数。 -
精确性:
xTaskDelayUntil()
比vTaskDelay()
更加精确,因为它根据上次唤醒时间计算下一个唤醒时间,而不是简单地将当前时间加上延迟。 -
不可在 ISR 中调用: 与
vTaskDelay()
一样,xTaskDelayUntil()
也不能在中断服务例程(ISR)中调用。
总结
xTaskDelayUntil()
是一个强大的工具,可以帮助开发者创建高精度的周期性任务。通过确保任务以固定的时间间隔执行,开发者可以提高系统的响应性和稳定性。这在实时系统中尤其重要,有助于维持任务执行的周期性和时间一致性。
两个Delay的区别
在 FreeRTOS 中,vTaskDelay()
和 xTaskDelayUntil()
是用于实现任务延迟的两个不同函数。它们在用法和功能上有一些显著的区别。
1. vTaskDelay()
-
功能:
vTaskDelay()
用于将任务阻塞一段指定的时间(以滴答为单位)。 -
参数:
TickType_t xTicksToDelay
: 要延迟的时间,以滴答数(ticks)为单位。
-
行为:
- 当调用
vTaskDelay()
时,任务将被挂起,直到指定的延迟时间过去。延迟时间是从调用vTaskDelay()
时开始计算的。 - 因此,如果任务在某个时刻调用
vTaskDelay()
,它会延迟一段时间,之后才能重新获得 CPU 控制权。
- 当调用
-
不适合的场景:
vTaskDelay()
的主要缺点是无法保证任务之间的周期性执行,因为如果任务执行时间过长,可能会导致后续任务的延迟。
2. xTaskDelayUntil()
-
功能:
xTaskDelayUntil()
用于实现周期性任务。它可以确保任务在固定的时间间隔内重复执行。 -
参数:
TickType_t *pxPreviousWakeTime
: 指向存储上次唤醒时间的变量的指针。TickType_t xTimeIncrement
: 每次调用xTaskDelayUntil()
时要延迟的时间(以滴答数为单位)。
-
行为:
xTaskDelayUntil()
以上次唤醒时间为基础,计算下一个唤醒时间。这意味着,无论任务的执行时间是多少,它都会确保每次执行之间的时间间隔一致。- 这使得
xTaskDelayUntil()
特别适合于需要以固定时间间隔执行的任务。
主要区别总结
何时使用
- 使用
vTaskDelay()
: 当你需要在任务中实现一次性的延迟,而不关心下一次执行的时机时。 - 使用
xTaskDelayUntil()
: 当你需要确保任务以固定的时间间隔运行时,尤其在周期性任务和实时应用中。
代码的验证
vTaskDelay
void LcdPrintTask(void *params)
{
struct TaskPrintInfo *pInfo = params;
uint32_t cnt = 0;
int len;
BaseType_t preTime;
preTime=xTaskGetTickCount();
uint64_t t1,t2;
while (1)
{
/* ´òÓ¡ÐÅÏ¢ */
if (g_LCDCanUse)
{
g_LCDCanUse = 0;
len = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name);
len += LCD_PrintString(len, pInfo->y, ":");
LCD_PrintSignedVal(len, pInfo->y, cnt++);
g_LCDCanUse = 1;
mdelay(cnt&0x3);
}
t1=system_get_ns();
//vTaskDelayUntil(&preTime,500);
vTaskDelay(500);
t2=system_get_ns();
LCD_ClearLine(pInfo->x, pInfo->y+2);
LCD_PrintSignedVal(pInfo->x, pInfo->y+2, t2-t1);
}
}
vTaskDelayUntil
void LcdPrintTask(void *params)
{
struct TaskPrintInfo *pInfo = params;
uint32_t cnt = 0;
int len;
BaseType_t preTime;
preTime=xTaskGetTickCount();
uint64_t t1,t2;
while (1)
{
/* ´òÓ¡ÐÅÏ¢ */
if (g_LCDCanUse)
{
g_LCDCanUse = 0;
len = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name);
len += LCD_PrintString(len, pInfo->y, ":");
LCD_PrintSignedVal(len, pInfo->y, cnt++);
g_LCDCanUse = 1;
mdelay(cnt&0x3);
}
t1=system_get_ns();
vTaskDelayUntil(&preTime,500);
//vTaskDelay(500);
t2=system_get_ns();
LCD_ClearLine(pInfo->x, pInfo->y+2);
LCD_PrintSignedVal(pInfo->x, pInfo->y+2, t2-t1);
}
}