参考《FreeRTOS入门与工程实践(基于DshanMCU-103).pdf》
文章目录
- 前言
- 编写任务函数
- 创建任务
- 任务
- 保护措施
- 写了个bug
- 疑问
- 遗留问题
- 效果
- freertos.c
- 学习链接
前言
配套源码:06_create_task_use_params
我们创建3个任务,使用同一个函数,但是在LCD上打印不一样的信息。
3个任务使用同一个函数,但是他们运行的栈不一样,局部变量是不同的版本,不同的实体~
本次要完成的任务效果,在OLED上显示……如下图
编写任务函数
我们要在屏幕的(x,y)坐标处打印任务的名称,把这些写在一个函数的形参里,使用结构体来传递
根据描述定义这个结构体
struct TaskPrintInfo{
uint8_t x; //定义坐标x
uint8_t y; //定义坐标y
char name[16]; //定义要打印输出的内容,最多显示16个字符
};
假设先打印Task1,这是5个字符,返回值是5,拿到这个返回值,并且在这个返回值的地方开始打印,打印一个冒号
然后再冒号的下一个地方打印计数值count
编写任务函数
void LCDPrintfTask(void *params)
{
//将传入的参数,转换成 struct TaskPrintInfo 这个结构体
struct TaskPrintInfo *pInfo = params;
uint32_t count = 0; //定义一个计数值
uint8_t length; //长度
while(1)
{
/* 打印信息 */
length = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name); //在(x,y)处打印name里的内容,返回打印了多少个字符
length += LCD_PrintString(length, pInfo->y, ":"); //从返回的那个长度开始打印一个冒号
LCD_PrintSignedVal(length, pInfo->y, count++); //在冒号的下一个位置开始打印计数值count
}
}
- 将传入的参数,转换成 struct TaskPrintInfo 这个结构体
- 在(x,y)处打印name里的内容,返回打印了多少个字符
- 从返回的那个长度开始打印一个冒号
- 在冒号的下一个位置开始打印计数值count,并且count++
创建任务
先编写三个全局变量,用来传递参数
static struct TaskPrintInfo g_Task1Info = {0, 0, "Task1"}; // (0,0),Task1
static struct TaskPrintInfo g_Task2Info = {0, 3, "Task2"}; // (0,3),Task2
static struct TaskPrintInfo g_Task3Info = {0, 6, "Task3"}; // (0,6),Task3
将 g_Task1Info结构体 传给 LCDPrintfTask这个任务,那怎么传递呢?
- 在创建任务的时候就进行参数传递,指定这个任务需要什么参数!!!
任务
xTaskCreate( //加返回值是 判断任务有没有创建成功
LCDPrintfTask, //孤勇者的函数
"Task1", //声音任务
128, //栈大小
&g_Task1Info, //传入的参数 g_Task1Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
xTaskCreate( //加返回值是 判断任务有没有创建成功
LCDPrintfTask, //孤勇者的函数
"Task2", //声音任务
128, //栈大小
&g_Task2Info, //传入的参数 g_Task2Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
xTaskCreate( //加返回值是 判断任务有没有创建成功
LCDPrintfTask, //孤勇者的函数
"Task2", //声音任务
128, //栈大小
&g_Task3Info, //传入的参数 g_Task3Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
任务是轮流运行的,有可能LCD_PrintString运行时间比较长,LCD_PrintString没执行完成,又切换任务2,又切换任务3
OLED的底层函数都是IIC,如果IIC被打断,那显示就不正常,所以我们需要加一些保护措施!
保护措施
加入全局变量保护一下,先定义这个变量g_LCDCanUse,默认是1,表示能使用LCD,0就不能使用了
static int g_LCDCanUse = 1; //默认=1 能使用LCD
在任务的函数里添加这个变量
void LCDPrintfTask(void *params)
{
//将传入的参数,转换成 struct TaskPrintInfo 这个结构体
struct TaskPrintInfo *pInfo = params;
uint32_t count = 0; //定义一个计数值
uint8_t length; //长度
while(1)
{
/* 打印信息 */
if (g_LCDCanUse) //g_LCDCanUse == 1 能使用LCD
{
g_LCDCanUse = 0; // 在这里禁止其他任务使用LCD
length = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name); //在(x,y)处打印name里的内容,返回打印了多少个字符
length += LCD_PrintString(length, pInfo->y, ":"); //从返回的那个长度开始打印一个冒号
LCD_PrintSignedVal(length, pInfo->y, count++); //在冒号的下一个位置开始打印计数值count,并且count++
g_LCDCanUse = 1; //用完再恢复成1,表示可以继续使用LCD
}
}
}
这里没有用到同步和互斥,只用到一个全局变量,也许是可行的!
写了个bug
烧录程序,这里OLED没有显示
原因是没有初始化OLED
在初始化MX_FREERTOS_Init里添加如下代码
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear();
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
加入初始化代码,烧录运行,又又又有bug,只有一个任务在运行,韦老师是只有任务3在运行,我的现象是只有任务1在运行
???
修改的办法:在LCDPrintfTask任务函数里加入Delay即可,代码如下
void LCDPrintfTask(void *params)
{
//将传入的参数,转换成 struct TaskPrintInfo 这个结构体
struct TaskPrintInfo *pInfo = params;
uint32_t count = 0; //定义一个计数值
uint8_t length; //长度
LCD_Init();
LCD_Clear(); // 清屏
while(1)
{
/* 打印信息 */
if (g_LCDCanUse) //g_LCDCanUse == 1 能使用LCD
{
g_LCDCanUse = 0; // 在这里禁止其他任务使用LCD
length = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name); //在(x,y)处打印name里的内容,返回打印了多少个字符
length += LCD_PrintString(length, pInfo->y, ":"); //从返回的那个长度开始打印一个冒号
LCD_PrintSignedVal(length, pInfo->y, count++); //在冒号的下一个位置开始打印计数值count,并且count++
g_LCDCanUse = 1; //用完再恢复成1,表示可以继续使用LCD
}
mdelay(500);
}
}
现象:
这样我们就使用了同一个函数,创建了三个任务!三个任务里的count的值是局部变量,他们的值并不一定一样!
疑问
为什么不加延时mdelay函数,其他任务不能运行了呢?
如果不加延时函数
三个任务的结构都是打印函数,在Task3没打印完的时候,就进行任务切换,切换到Task1里,读取这个g_LCDCanUse变量值是0,不能进行打印!所以不能打印信息了,继续切换到Task2,也不能打印,再次切换回Task3的时候,能从上次切换出去的地方继续执行打印函数,执行完后g_LCDCanUse变量赋1,如果在这里没有瞬间切换出去,那么就继续执行这个任务了,继续循环,马上把g_LCDCanUse变量清零,执行打印函数,切换到其他任务,还是不能执行~~如此反复执行非常耗时的打印函数,所以就有bug
我们加上delay之后,在很长一段时间内这个g_LCDCanUse变量都是1,其他任务都是可以执行打印函数的,这样就比较简单的解决了这个问题。
遗留问题
- 提问:==如何互斥地访问LCD?==使用全局变量,大概率可以,但是不是万无一失。(全局变量不可靠)
- 提问:为什么先创建任务1,再创建任务2,再创建任务3,但是执行的顺序却不是123 ???为何是后面创建的task3先运行?
效果
freertos.c
/* USER CODE BEGIN Header */
#include "driver_led.h"
#include "driver_lcd.h"
#include "driver_mpu6050.h"
#include "driver_timer.h"
#include "driver_ds18b20.h"
#include "driver_dht11.h"
#include "driver_active_buzzer.h"
#include "driver_passive_buzzer.h"
#include "driver_color_led.h"
#include "driver_ir_receiver.h"
#include "driver_ir_sender.h"
#include "driver_light_sensor.h"
#include "driver_ir_obstacle.h"
#include "driver_ultrasonic_sr04.h"
#include "driver_spiflash_w25q64.h"
#include "driver_rotary_encoder.h"
#include "driver_motor.h"
#include "driver_key.h"
#include "driver_uart.h"
#include "music.h"
/**
******************************************************************************
* File Name : freertos.c
* Description : Code for freertos applications
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
BaseType_t ret; // long
static TaskHandle_t xSoundTaskHandle; // void * 在全局变量里记录句柄
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 * 在全局变量里记录句柄
/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
struct TaskPrintInfo{
uint8_t x; //定义坐标x
uint8_t y; //定义坐标y
char name[16]; //定义要打印输出的内容,最多显示16个字符
};
static struct TaskPrintInfo g_Task1Info = {0, 0, "Task1"}; // (0,0),Task1
static struct TaskPrintInfo g_Task2Info = {0, 3, "Task2"}; // (0,3),Task2
static struct TaskPrintInfo g_Task3Info = {0, 6, "Task3"}; // (0,6),Task3
static int g_LCDCanUse = 1; //默认=1 能使用LCD
void LCDPrintfTask(void *params)
{
//将传入的参数,转换成 struct TaskPrintInfo 这个结构体
struct TaskPrintInfo *pInfo = params;
uint32_t count = 0; //定义一个计数值
uint8_t length; //长度
LCD_Init();
LCD_Clear(); // 清屏
while(1)
{
/* 打印信息 */
if (g_LCDCanUse) //g_LCDCanUse == 1 能使用LCD
{
g_LCDCanUse = 0; // 在这里禁止其他任务使用LCD
length = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name); //在(x,y)处打印name里的内容,返回打印了多少个字符
length += LCD_PrintString(length, pInfo->y, ":"); //从返回的那个长度开始打印一个冒号
LCD_PrintSignedVal(length, pInfo->y, count++); //在冒号的下一个位置开始打印计数值count,并且count++
g_LCDCanUse = 1; //用完再恢复成1,表示可以继续使用LCD
}
mdelay(500);
}
}
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear();
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
// defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
// /* 创建任务:声 */
// // 先创建一个动态分配内存的任务
// ret = xTaskCreate( //加返回值是 判断任务有没有创建成功
// PlayMusic, //孤勇者的函数
// "SoundTask", //声音任务
// 128, //栈大小
// NULL, //无传入的参数
// osPriorityNormal, //优先级默认
// & xSoundTaskHandle //任务句柄
// );
//
// /* 创建任务:光 */
// // 创建一个静态分配内存的任务
// 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
// );
xTaskCreate( //加返回值是 判断任务有没有创建成功
LCDPrintfTask, //孤勇者的函数
"Task1", //声音任务
128, //栈大小
&g_Task1Info, //传入的参数 g_Task1Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
xTaskCreate( //加返回值是 判断任务有没有创建成功
LCDPrintfTask, //孤勇者的函数
"Task2", //声音任务
128, //栈大小
&g_Task2Info, //传入的参数 g_Task2Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
xTaskCreate( //加返回值是 判断任务有没有创建成功
LCDPrintfTask, //孤勇者的函数
"Task2", //声音任务
128, //栈大小
&g_Task3Info, //传入的参数 g_Task3Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @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 */
LCD_Init();
LCD_Clear();
for(;;)
{
//Led_Test();
//LCD_Test();
//MPU6050_Test();
//DS18B20_Test();
//DHT11_Test();
//ActiveBuzzer_Test();
//PassiveBuzzer_Test();
//ColorLED_Test();
IRReceiver_Test(); //影
//IRSender_Test();
//LightSensor_Test();
//IRObstacle_Test();
//SR04_Test();
//W25Q64_Test();
//RotaryEncoder_Test();
//Motor_Test();
//Key_Test();
//UART_Test();
}
/* USER CODE END StartDefaultTask */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */
学习链接
学习视频:【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 21:00】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=18&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=1260
传送门