目錄
- 1 同步互斥與通信
- 1.1 同步互斥與通信概述
- 1.2 同步與互斥的概念
- 1.3 同步的例子:有缺陷
- 1.4 freertos.c源碼
- 3. 互斥的例子:有缺陷
- 4. 通信的例子:有缺陷
- 5. FreeRTOS的解決方案
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_Task1InfoosPriorityNormal, //優先級默認NULL //任務句柄 無);
xTaskCreate( //加返回值是 判斷任務有沒有創建成功LcdPrintTask, //LCD打印任務"Task1", //聲音任務128, //棧大小&g_Task2Info, //傳入的參數 g_Task1InfoosPriorityNormal, //優先級默認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),Task3static 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; //定義坐標xuint8_t y; //定義坐標ychar 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),Task3static 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); //開始的時候我先等待3000Tickwhile (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_Task1InfoosPriorityNormal, //優先級默認NULL //任務句柄 無);xTaskCreate( //加返回值是 判斷任務有沒有創建成功LcdPrintTask, //LCD打印任務"Task1", //聲音任務128, //棧大小&g_Task2Info, //傳入的參數 g_Task1InfoosPriorityNormal, //優先級默認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的解決方案
-
正確性
-
效率:等待者要進入阻塞狀態
-
多種解決方案