参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》
学习视频:【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 01:22】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=19&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=82
传送门
文章目录
- 1 任务的删除
- 2 Demo: 删除任务
- 3 优先级与阻塞 改善播放效果
- 3.1 其他任务
- 3.2 改变优先级
- 4 Tick
- 5 修改优先级
- 5.1 获取优先级
- 5.2 设置优先级
1 任务的删除
删除任务时使用的函数如下:
void vTaskDelete( TaskHandle_t xTaskToDelete );
参数说明:
参数 | 描述 |
---|---|
pvTaskCode | 任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。 也可传入NULL,这表示删除自己。 |
怎么删除任务?举个不好的例子:
- 自杀:vTaskDelete(NULL)
- 被杀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄
- 杀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄
2 Demo: 删除任务
代码为: 07_delete_task
功能为:当监测到遥控器的Power按键被按下后,删除音乐播放任务。
完整版代码如下:
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, &xSoundTaskHandle);
}
}
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;
}
}
}
}
现在我们只保留一个默认的任务,修改默认的任务代码,代码如下:
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
uint8_t dev, data;
int len;
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 == 0xA8) /*play*/
{
/* 创建播放音乐的任务 */
//这里需要判断是否多次按下播放按键,判断句柄是否等于NULL
if (xSoundTaskHandle == NULL) /* 句柄等于NULL,才来创建这个任务 */
{
LCD_PrintString(0, 0, "Create Task");
ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal, &xSoundTaskHandle);
}
}
else if (data == 0xA2)
{
/* 删除播放音乐的任务 */
if (xSoundTaskHandle != NULL) //如果这个句柄xSoundTaskHandle != NULL,才会删除
{
LCD_PrintString(0, 0, "Delete Task");
vTaskDelete(xSoundTaskHandle);
xSoundTaskHandle = NULL; //删除完成,赋值NULL
}
}
}
}
/* USER CODE END StartDefaultTask */
}
这样写代码出现了一个bug,遥控器不适配,并且删除任务的时候,蜂鸣器没有关闭,蜂鸣器一直鸣叫上一个音调~
现在来改进一下程序
清除打印信息
/**
* @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;
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, &xSoundTaskHandle);
}
}
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 */
}
这样改进代码,播放效果好一点,能播放,能停止
但是这样频繁的创建任务和频繁的删除任务好吗???
每次创建都会动态分配内存,每次删除都会释放内存,这样操作容易造成内存的碎片,以后多次执行之后,可能就申请不到内存了
所以不能简单的清除一个任务,就不管后续的清理工作了
3 优先级与阻塞 改善播放效果
学习视频:【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 00:10】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=20&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=10
保留所有任务,但是会提高音乐播放任务的优先级,并且延时函数使用vTaskDelay
- 现在先体验一把 改变优先级和使用vTaskDelay的用法
在第7个程序07_delete_task的基础上 - 创建一个新的程序 08_task_priority
3.1 其他任务
打开程序,将创建光的任务取消注释,创建色的任务也取消注释
xLightTaskHandle = xTaskCreateStatic(
Led_Test, //LED测试函数,PC13以500ms间隔亮灭一次
"LightTask", //光任务
128, //栈大小,这里提供了栈的大小(长度)
NULL, //无传入的参数
osPriorityNormal, //优先级默认
g_pucStackOfLightTask, // 静态分配的栈,一个buffer,这里只提供了首地址,长度就是栈的大小,最开始栈的类型不对,栈的类型uint32_t
&g_TCBofLightTask // 取址TCB
);
/* 创建任务:色 */
xColorTaskHandle = xTaskCreateStatic(
ColorLED_Test, //LED测试函数,PC13以500ms间隔亮灭一次
"ColorTask", //光任务
128, //栈大小,这里提供了栈的大小(长度)
NULL, //无传入的参数
osPriorityNormal, //优先级默认
g_pucStackOfColorTask, // 静态分配的栈,一个buffer,这里只提供了首地址,长度就是栈的大小
&g_TCBofColorTask // 取址TCB
);
句柄也要保留!
static StackType_t g_pucStackOfLightTask[128]; // 变量前缀的意思是 全局变量g 指针p uint8_t类型uc的StackOfLightTask 光任务的栈
StaticTask_t g_TCBofLightTask; // 光任务的TCB
static TaskHandle_t xLightTaskHandle; // void * 在全局变量里记录句柄
static StackType_t g_pucStackOfColorTask[128]; // 变量前缀的意思是 全局变量g 指针p uint8_t类型uc的StackOfLightTask 色任务的栈
StaticTask_t g_TCBofColorTask; // 色任务的TCB
static TaskHandle_t xColorTaskHandle; // void * 在全局变量里记录句柄
3.2 改变优先级
ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal+1, &xSoundTaskHandle);
在创建播放音乐任务的时候修改优先级,注意是+1,不是-1
现在又出现了新的bug,按播放按键,创建一个音乐播放的任务完成后,全彩LED卡了,并且用按键删除音乐播放的操作失效了~
因为我们创建了一个高优先级的任务,里面一直在运行死循环,mdelay也是一个死循环,它不断读取时间,这个程序的优先级最高,独占了CPU,音乐播放效果非常好,没有以前的卡顿了
将mdelay函数换成vTaskDelay,主动放弃CPU,允许其他任务执行
/* USER CODE END PT */
/* Function definition -------------------------------------------------------*/
/* USER CODE BEGIN FD */
/**
* @Function name MUSIC_Begin
* @Introduce 开始播放音乐
* @Return NULL
*/
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); // 设置蜂鸣器频率,占空比50
// HAL_Delay(MusicSpeed/Music_Lone_Brave[i][2]);
// mdelay(MusicSpeed/Music_Lone_Brave[i][2]); //Delay ms
vTaskDelay(MusicSpeed/Music_Lone_Brave[i][2]); //主动放弃CPU
}![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/77a8a7dca2214120aa410c3f11b53278.gif)
}
现在可以同时运行这三个任务了,RGB灯缓慢变化,PC13间隔闪烁,遥控器也是可以创建播放任务和删除播放任务的~
非常nice!
4 Tick
对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。
"一会"怎么定义?
人有心跳,心跳间隔基本恒定。
FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。
如下图所示:
- 假设t1、t2、t3发生时钟中断
- 两次中断之间的时间被称为时间片(time slice、tick period)
- 时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms
相同优先级的任务怎么切换呢?请看下图:
- 任务2从t1执行到t2
- 在t2发生tick中断,进入tick中断处理函数:
- 选择下一个要运行的任务
- 执行完中断处理函数后,切换到新的任务:任务1
- 任务1从t2执行到t3
- 从图中可以看出,任务运行的时间并不是严格从t1,t2,t3哪里开始
有了Tick的概念后,我们就可以使用Tick来衡量时间了,比如:
vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms
// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms
注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。 如下图:
使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。
这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。
5 修改优先级
5.1 获取优先级
使用uxTaskPriorityGet来获得任务的优先级:
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。
5.2 设置优先级
使用vTaskPrioritySet 来设置任务的优先级:
void vTaskPrioritySet( TaskHandle_t xTask,
UBaseType_t uxNewPriority );
使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。
使用vTaskPrioritySet 来设置任务的优先级:
void vTaskPrioritySet( TaskHandle_t xTask,
UBaseType_t uxNewPriority );
使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;
参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。