目录
1 同步互斥与通信
1.1 同步互斥与通信概述
参考《FreeRTOS入门与工程实践(基于DshanMCU-103)》里《第10章 同步互斥与通信》
本章是概述性的内容。可以把多任务系统当做一个团队,里面的每一个任务就相当于团队里的一个人。团队成员之间要协调工作进度(同步)、争用会议室(互斥)、沟通(通信)。多任务系统中所涉及的概念,都可以在现实生活中找到例子。
各类RTOS都会涉及这些概念:任务通知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。我们先站在更高角度来讲解这些概念。
1.2 同步与互斥的概念
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。 什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?
再举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒我。这就是使用"同步"来实现"互斥"。
有时候看代码更容易理解,伪代码如下:
void 抢厕所(void)
{
if (有人在用) 我眯一会;
用厕所;
喂,醒醒,有人要用厕所吗;
}
1.3 同步的例子:有缺陷
程序:在06_create_task_use_params的基础上,修改出12_task_sync_exclusion
创建两个任务:一个用来执行大量的计算任务,另一个永年执行打印函数显示OLED
xTaskCreate( //加返回值是 判断任务有没有创建成功
CalcTask, //计算任务
"Task1", //声音任务
128, //栈大小
NULL, //传入的参数 g_Task1Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
xTaskCreate( //加返回值是 判断任务有没有创建成功
LcdPrintTask, //LCD打印任务
"Task1", //声音任务
128, //栈大小
&g_Task2Info, //传入的参数 g_Task1Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
编写这两个函数
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
uint32_t g_sum = 0; //定义一个计数值
static volatile int g_calc_end = 0; // 计算结束标志位,使用volatile不要用编译器优化优化
uint64_t g_time = 0;
void CalcTask(void *params)
{
uint32_t i = 0; //定义一个计数值
g_time = system_get_ns(); //获取当前系统时间
for (i = 0; i < 10000000; i ++)
{
g_sum += i;
}
g_calc_end = 1; //计算完成标志位 置位
g_time = system_get_ns() - g_time; //运行这段任务消耗的时间
vTaskDelete(NULL); //计算完成杀死任务
}
void LcdPrintTask(void *params)
{
int len;
while (1)
{
LCD_PrintString(0, 0, "Waiting");
// vTaskDelay(3000);
while (g_calc_end == 0); //等待
/* 打印信息 */
if (g_LCDCanUse)
{
g_LCDCanUse = 0;
LCD_ClearLine(0, 0);
len = LCD_PrintString(0, 0, "Sum: ");
LCD_PrintHex(len, 0, g_sum, 1);
LCD_ClearLine(0, 2);
len = LCD_PrintString(0, 2, "Time(ms): ");
LCD_PrintSignedVal(len, 2, g_time/1000000); //打印消耗了多长时间
g_LCDCanUse = 1;
}
vTaskDelete(NULL); //任务自杀
}
}
这里遇到了bug!!!
-
程序卡死在while循环里了,但是这个变量已经是1了,为什么程序会卡死在上面两行汇编语句呢???
-
原因是编译器优化了我们的变量
对这个变量,执行while (g_calc_end == 0); 这条语句的时候,它会读取内存,把变量的值放到CPU某个寄存器里,以后一直就判断那个寄存器,但是这个寄存器得到的是原始的值,并没有每次都去读取内存
- 这个变量是在其他任务里被修改的,那我们使用这个变量的时候,每次都需要读内存!
- 那怎么办呢??我们加上一个volatile就可以了
烧录代码运行
计算10000000个数需要2.5S,真的是这样的吗???
我们现在有两个任务
他们是怎么调度的呢?
两个任务优先级相同,A任务运行1ms,B任务运行1ms,A任务运行1ms,B任务运行1ms
任务B执行的程序是死等,这也耗费了一半的时间,在任务B的开始就等待3S左右
vTaskDelay(3000); //开始的时候我先等待3000Tick
加上这句等待3S之后,就变成了1278ms
确实如此
这个死循环可以用其他方法来代替
- 等任务A计算完成之后,用任务A唤醒任务B
- 使用同步的时候,我们需要考虑如何提高处理器的性能!
- 让等待的任务阻塞,不参与CPU的调度!
这节课学习了同步的例子,有缺陷的例子
学习视频:【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 【精准空降到 13:38】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=25&share_source=copy_web&vd_source=8af85e60c2df9af1f0fd23935753a933&t=818
1.4 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
uint32_t g_sum = 0; //定义一个计数值
static volatile int g_calc_end = 0; // 计算结束标志位,使用volatile不要用编译器优化优化
uint64_t g_time = 0;
void CalcTask(void *params)
{
uint32_t i = 0; //定义一个计数值
g_time = system_get_ns(); //获取当前系统时间
for (i = 0; i < 10000000; i ++)
{
g_sum += i;
}
g_calc_end = 1; //计算完成标志位 置位
g_time = system_get_ns() - g_time; //运行这段任务消耗的时间
vTaskDelete(NULL); //计算完成杀死任务
}
void LcdPrintTask(void *params)
{
int len;
while (1)
{
LCD_PrintString(0, 0, "Waiting");
vTaskDelay(3000); //开始的时候我先等待3000Tick
while (g_calc_end == 0); //等待
/* 打印信息 */
if (g_LCDCanUse)
{
g_LCDCanUse = 0;
LCD_ClearLine(0, 0);
len = LCD_PrintString(0, 0, "Sum: ");
LCD_PrintHex(len, 0, g_sum, 1);
LCD_ClearLine(0, 2);
len = LCD_PrintString(0, 2, "Time(ms): ");
LCD_PrintSignedVal(len, 2, g_time/1000000); //打印消耗了多长时间
g_LCDCanUse = 1;
}
vTaskDelete(NULL); //任务自杀
}
}
/* 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( //加返回值是 判断任务有没有创建成功
CalcTask, //计算任务
"Task1", //声音任务
128, //栈大小
NULL, //传入的参数 g_Task1Info
osPriorityNormal, //优先级默认
NULL //任务句柄 无
);
xTaskCreate( //加返回值是 判断任务有没有创建成功
LcdPrintTask, //LCD打印任务
"Task1", //声音任务
128, //栈大小
&g_Task2Info, //传入的参数 g_Task1Info
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 */
3. 互斥的例子:有缺陷
讲解这个程序"06_create_task_use_params"的互斥缺陷。
4. 通信的例子:有缺陷
5. FreeRTOS的解决方案
-
正确性
-
效率:等待者要进入阻塞状态
-
多种解决方案