FreeRTOS

来源:https://www.cnblogs.com/tenzzz/p/18238381
-Advertisement-
Play Games

初識FreeRTOS 什麼是FreeRTOS RTOS (實時操作系統)並不是指某一特定的操作系統,而是指一類操作系統,例如, µC/OS,FreeRTOS, RTX, RT-Thread 等這些都是 RTOS 類的操作系統。 因此,從 FreeRTOS 的名字中就能看出, FreeROTS 是一款 ...


初識FreeRTOS

什麼是FreeRTOS

RTOS (實時操作系統)並不是指某一特定的操作系統,而是指一類操作系統,例如, µC/OS,FreeRTOS, RTX, RT-Thread 等這些都是 RTOS 類的操作系統。 因此,從 FreeRTOS 的名字中就能看出, FreeROTS 是一款免費的實時操作系統。

操作系統是允許多個任務“同時運行” 的, 然而實際上, 一個 CPU 核心在某一時刻只能運行一個任務,而操作系統中任務調度器的責任就是決定在某一時刻 CPU 究竟要運行哪一個任務,任務調度器使得 CPU 在各個任務之間來回切換並處理任務, 由於切換處理任務的速度非常快,因此就給人造成了一種同一時刻有多個任務同時運行
的錯覺。

FreeRTOS 是眾多 RTOS 類操作系統中的一種, FreeRTOS 十分的小巧,可以在資源有限的微控制器中運行,當然了, FreeRTOS 也不僅僅局限於在微控制器中使用。 就單從文件數量上來看 FreeRTOS 要比 µC/OS 少得多。

為什麼選擇FreeRTOS

  • 免費
  • 簡單
  • 使用廣泛
  • 資料齊全
  • 可移植性強

FreeRTOS的特點

FreeRTOS移植

使用CubeMX快速移植

  1. 在 SYS 選項里,將 Debug 設為 Serial Wire ,並且將 Timebase Source 設為 TIM2 (其它定時器也行)。為何要如此配置?下文解說。

  2. 將 RCC 里的 HSE 設置為 Crystal/Ceramic Resonator

  3. 時鐘按下圖配置

    image-20240514180359669

  4. 選擇 FREERTOS 選項,並將 Interface 改為 CMSIS_V1 。V1 和 V2 有啥區別?下文解釋。

    image-20240514180514717

  5. 配置項目信息,並導出代碼。

    image-20240514180919224

    一些常見問題

    1. Timebase Source 為什麼不能設置為 SysTick ?裸機的時鐘源預設是 SysTick,但是開啟FreeRTOS 後,FreeRTOS會占用 SysTick (用來生成1ms 定時,用於任務調度),所以需要為其他匯流排提供另外的時鐘源。

    2. FreeRTOS 版本問題
      V2 的內核版本更高,功能更多,在大多數情況下 V1 版本的內核完全夠用。

    3. FreeRTOS 各配置選項卡的解釋

    4. Events:事件相關的創建

      image-20240514182104104

      Task and Queues: 任務與隊列的創建
      Timers and Semaphores: 定時器和信號量的創建
      Mutexes: 互斥量的創建
      FreeRTOS Heap Usage: 用於查看堆使用情況
      config parameters: 內核參數設置,用戶根據自己的實際應用來裁剪定製 FreeRTOS 內核 Include parameters: FreeRTOS 部分函數的使能
      User Constants: 相關巨集的定義,可以自建一些常量在工程中使用
      Advanced settings:高級設置

FreeRTOS系統配置

FreeRTOSConfig.h 文件

FreeRTOS 使用 FreeRTOSConfig.h 文件進行配置和裁剪。 FreeRTOSConfig.h 文件中有幾十個配置項,這使得用戶能夠很好地配置和裁剪 FreeRTOS。

FreeRTOSConfig.h 文件中的配置項可分為三大類:“config”配置項、“INCLUDE”配置項和其他配置項。

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/* 頭文件 */
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include <stdint.h>

extern uint32_t SystemCoreClock;

/* 基礎配置項 */
#define configUSE_PREEMPTION                            1                       /* 1: 搶占式調度器, 0: 協程式調度器, 無預設需定義 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION         1                       /* 1: 使用硬體計算下一個要運行的任務, 0: 使用軟體演算法計算下一個要運行的任務, 預設: 0 */
#define configUSE_TICKLESS_IDLE                         0                       /* 1: 使能tickless低功耗模式, 預設: 0 */
#define configCPU_CLOCK_HZ                              SystemCoreClock         /* 定義CPU主頻, 單位: Hz, 無預設需定義 */
#define configSYSTICK_CLOCK_HZ                          (configCPU_CLOCK_HZ / 8)/* 定義SysTick時鐘頻率,當SysTick時鐘頻率與內核時鐘頻率不同時才可以定義, 單位: Hz, 預設: 不定義 */
#define configTICK_RATE_HZ                              1000                    /* 定義系統時鐘節拍頻率, 單位: Hz, 無預設需定義 */
#define configMAX_PRIORITIES                            32                      /* 定義最大優先順序數, 最大優先順序=configMAX_PRIORITIES-1, 無預設需定義 */
#define configMINIMAL_STACK_SIZE                        128                     /* 定義空閑任務的棧空間大小, 單位: Word, 無預設需定義 */
#define configMAX_TASK_NAME_LEN                         16                      /* 定義任務名最大字元數, 預設: 16 */
#define configUSE_16_BIT_TICKS                          0                       /* 1: 定義系統時鐘節拍計數器的數據類型為16位無符號數, 無預設需定義 */
#define configIDLE_SHOULD_YIELD                         1                       /* 1: 使能在搶占式調度下,同優先順序的任務能搶占空閑任務, 預設: 1 */
#define configUSE_TASK_NOTIFICATIONS                    1                       /* 1: 使能任務間直接的消息傳遞,包括信號量、事件標誌組和消息郵箱, 預設: 1 */
#define configTASK_NOTIFICATION_ARRAY_ENTRIES           1                       /* 定義任務通知數組的大小, 預設: 1 */
#define configUSE_MUTEXES                               1                       /* 1: 使能互斥信號量, 預設: 0 */
#define configUSE_RECURSIVE_MUTEXES                     1                       /* 1: 使能遞歸互斥信號量, 預設: 0 */
#define configUSE_COUNTING_SEMAPHORES                   1                       /* 1: 使能計數信號量, 預設: 0 */
#define configUSE_ALTERNATIVE_API                       0                       /* 已棄用!!! */
#define configQUEUE_REGISTRY_SIZE                       8                       /* 定義可以註冊的信號量和消息隊列的個數, 預設: 0 */
#define configUSE_QUEUE_SETS                            1                       /* 1: 使能隊列集, 預設: 0 */
#define configUSE_TIME_SLICING                          1                       /* 1: 使能時間片調度, 預設: 1 */
#define configUSE_NEWLIB_REENTRANT                      0                       /* 1: 任務創建時分配Newlib的重入結構體, 預設: 0 */
#define configENABLE_BACKWARD_COMPATIBILITY             0                       /* 1: 使能相容老版本, 預設: 1 */
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS         0                       /* 定義線程本地存儲指針的個數, 預設: 0 */
#define configSTACK_DEPTH_TYPE                          uint16_t                /* 定義任務堆棧深度的數據類型, 預設: uint16_t */
#define configMESSAGE_BUFFER_LENGTH_TYPE                size_t                  /* 定義消息緩衝區中消息長度的數據類型, 預設: size_t */

/* 記憶體分配相關定義 */
#define configSUPPORT_STATIC_ALLOCATION                 0                       /* 1: 支持靜態申請記憶體, 預設: 0 */
#define configSUPPORT_DYNAMIC_ALLOCATION                1                       /* 1: 支持動態申請記憶體, 預設: 1 */
#define configTOTAL_HEAP_SIZE                           ((size_t)(10 * 1024))   /* FreeRTOS堆中可用的RAM總量, 單位: Byte, 無預設需定義 */
#define configAPPLICATION_ALLOCATED_HEAP                0                       /* 1: 用戶手動分配FreeRTOS記憶體堆(ucHeap), 預設: 0 */
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP       0                       /* 1: 用戶自行實現任務創建時使用的記憶體申請與釋放函數, 預設: 0 */

/* 鉤子函數相關定義 */
#define configUSE_IDLE_HOOK                             0                       /* 1: 使能空閑任務鉤子函數, 無預設需定義  */
#define configUSE_TICK_HOOK                             0                       /* 1: 使能系統時鐘節拍中斷鉤子函數, 無預設需定義 */
#define configCHECK_FOR_STACK_OVERFLOW                  0                       /* 1: 使能棧溢出檢測方法1, 2: 使能棧溢出檢測方法2, 預設: 0 */
#define configUSE_MALLOC_FAILED_HOOK                    0                       /* 1: 使能動態記憶體申請失敗鉤子函數, 預設: 0 */
#define configUSE_DAEMON_TASK_STARTUP_HOOK              0                       /* 1: 使能定時器服務任務首次執行前的鉤子函數, 預設: 0 */

/* 運行時間和任務狀態統計相關定義 */
#define configGENERATE_RUN_TIME_STATS                   0                       /* 1: 使能任務運行時間統計功能, 預設: 0 */
#if configGENERATE_RUN_TIME_STATS
#include "./BSP/TIMER/btim.h"
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()        ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE()                FreeRTOSRunTimeTicks
#endif
#define configUSE_TRACE_FACILITY                        1                       /* 1: 使能可視化跟蹤調試, 預設: 0 */
#define configUSE_STATS_FORMATTING_FUNCTIONS            1                       /* 1: configUSE_TRACE_FACILITY為1時,會編譯vTaskList()和vTaskGetRunTimeStats()函數, 預設: 0 */

/* 協程相關定義 */
#define configUSE_CO_ROUTINES                           0                       /* 1: 啟用協程, 預設: 0 */
#define configMAX_CO_ROUTINE_PRIORITIES                 2                       /* 定義協程的最大優先順序, 最大優先順序=configMAX_CO_ROUTINE_PRIORITIES-1, 無預設configUSE_CO_ROUTINES為1時需定義 */

/* 軟體定時器相關定義 */
#define configUSE_TIMERS                                1                               /* 1: 使能軟體定時器, 預設: 0 */
#define configTIMER_TASK_PRIORITY                       ( configMAX_PRIORITIES - 1 )    /* 定義軟體定時器任務的優先順序, 無預設configUSE_TIMERS為1時需定義 */
#define configTIMER_QUEUE_LENGTH                        5                               /* 定義軟體定時器命令隊列的長度, 無預設configUSE_TIMERS為1時需定義 */
#define configTIMER_TASK_STACK_DEPTH                    ( configMINIMAL_STACK_SIZE * 2) /* 定義軟體定時器任務的棧空間大小, 無預設configUSE_TIMERS為1時需定義 */

/* 可選函數, 1: 使能 */
#define INCLUDE_vTaskPrioritySet                        1                       /* 設置任務優先順序 */
#define INCLUDE_uxTaskPriorityGet                       1                       /* 獲取任務優先順序 */
#define INCLUDE_vTaskDelete                             1                       /* 刪除任務 */
#define INCLUDE_vTaskSuspend                            1                       /* 掛起任務 */
#define INCLUDE_xResumeFromISR                          1                       /* 恢覆在中斷中掛起的任務 */
#define INCLUDE_vTaskDelayUntil                         1                       /* 任務絕對延時 */
#define INCLUDE_vTaskDelay                              1                       /* 任務延時 */
#define INCLUDE_xTaskGetSchedulerState                  1                       /* 獲取任務調度器狀態 */
#define INCLUDE_xTaskGetCurrentTaskHandle               1                       /* 獲取當前任務的任務句柄 */
#define INCLUDE_uxTaskGetStackHighWaterMark             1                       /* 獲取任務堆棧歷史剩餘最小值 */
#define INCLUDE_xTaskGetIdleTaskHandle                  1                       /* 獲取空閑任務的任務句柄 */
#define INCLUDE_eTaskGetState                           1                       /* 獲取任務狀態 */
#define INCLUDE_xEventGroupSetBitFromISR                1                       /* 在中斷中設置事件標誌位 */
#define INCLUDE_xTimerPendFunctionCall                  1                       /* 將函數的執行掛到定時器服務任務 */
#define INCLUDE_xTaskAbortDelay                         1                       /* 中斷任務延時 */
#define INCLUDE_xTaskGetHandle                          1                       /* 通過任務名獲取任務句柄 */
#define INCLUDE_xTaskResumeFromISR                      1                       /* 恢覆在中斷中掛起的任務 */

/* 中斷嵌套行為配置 */
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS 4
#endif

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         15                  /* 中斷最低優先順序 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5                   /* FreeRTOS可管理的最高中斷優先順序 */
#define configKERNEL_INTERRUPT_PRIORITY                 ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY            ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY           configMAX_SYSCALL_INTERRUPT_PRIORITY

/* FreeRTOS中斷服務函數相關定義 */
#define xPortPendSVHandler                              PendSV_Handler
#define vPortSVCHandler                                 SVC_Handler

/* 斷言 */
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )

/* FreeRTOS MPU 特殊定義 */
//#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
//#define configTOTAL_MPU_REGIONS                                8
//#define configTEX_S_C_B_FLASH                                  0x07UL
//#define configTEX_S_C_B_SRAM                                   0x07UL
//#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY            1
//#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS             1

/* ARMv8-M 安全側埠相關定義。 */
//#define secureconfigMAX_SECURE_CONTEXTS         5

#endif /* FREERTOS_CONFIG_H */

任務的創建與刪除

什麼是任務?

任務可以理解為進程/線程,創建一個任務,就會在記憶體開闢一個空間。

任務可以被認為是一組函數,它們在運行時相互協作以完成特定的目標。

在 FreeROTS 中,任務可以分配不同的優先順序,並按照優先順序進行調度。當一個任務沒有工作可以做時,操作系統會將 CPU 時間分配給另一個優先順序更高的任務,以確保系統的正常運行。

任務通常都含有 while(1) 死迴圈。

任務創建與刪除相關函數

任務創建與刪除相關函數有如下三個:

函數名稱 函數作用
xTaskCreate() 動態方式創建任務
xTaskCreateStatic() 靜態方式創建任務
vTaskDelete() 刪除任務

任務動態創建與靜態創建的區別:

動態創建任務的堆棧由系統分配,而靜態創建任務的堆棧由用戶自己傳遞。 通常情況下使用動態方式創建任務。

xTaskCreate 函數原型

image-20240514192101116

  1. pxTaskCode:指向任務函數的指針,任務必須實現為永不返回(即連續迴圈);

  2. pcName:任務的名字,主要是用來調試,預設情況下最大長度是16;

  3. pvParameters:指定的任務棧的大小;

  4. uxPriority:任務優先順序,數值越大,優先順序越大;

  5. pxCreatedTask:用於返回已創建任務的句柄可以被引用

返回值 描述
pdPASS 任務創建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 任務創建失敗

vTaskDelete 函數原型

void vTaskDelete(TaskHandle_t xTaskToDelete);

只需將待刪除的任務句柄傳入該函數,即可將該任務刪除。

當傳入的參數為NULL,則代表刪除任務自身(當前正在運行的任務)。

創建兩個任務進行點燈實操

  1. 增加兩個任務,一個用於點亮LED1,一個用於點亮LED2

    image-20240514194134071

  2. 查看原理圖,設置兩個LED燈的引腳為輸出引腳

  3. 導出代碼後加入下麵代碼,編譯燒錄32單片機

    freertos.c文件

    /*任務1函數*/
    void StartTaskLED1(void const * argument)
    {
      for(;;)
      {
    	HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
        osDelay(500);
      }
    }
     
    /*任務2函數*/
    void StartTaskLED2(void const * argument)
    {
      for(;;)
      {
    	HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
        osDelay(1000);
      }
    }
    

任務調度

什麼是任務調度?

調度器就是使用相關的調度演算法來決定當前需要執行的哪個任務。

FreeRTOS中開啟任務調度的函數是 vTaskStartScheduler() ,但在 CubeMX 中被封裝為 osKernelStart()

FreeRTOS的任務調度規則是怎樣的?

  1. 高優先順序搶占低優先順序任務,系統永遠執行最高優先順序的任務(即搶占式調度
  2. 同等優先順序的任務輪轉調度(即時間片調度
  3. 攜程式調度:但官方已明確表示不更新,主要是用在小容量的晶元上,用得 也不多。

搶占式調度運行過程

前提:任務優先順序不同

image-20240514200647406

總結:

  1. 高優先順序任務,優先執行;
  2. 高優先順序任務不停止,低優先順序任務無法執行;
  3. 被搶占的任務將會進入就緒態

時間片調度運行過程

前提:任務優先順序相同

image-20240514200748707

總結:

  1. 同等優先順序任務,輪流執行,時間片流轉;
  2. 一個時間片大小,取決為滴答定時器中斷周期(預設1ms);
  3. 註意沒有用完的時間片不會再使用,下次任務 Task3 得到執行,還是按照一個時間片的時鐘 節拍運行

任務狀態

FreeRTOS中任務共存在4種狀態:

Running 運行態
當任務處於實際運行狀態稱之為運行態,即CPU的使用權被這個任務占用(同一時間僅一個任務 處於運行態)。

Ready 就緒態
處於就緒態的任務是指那些能夠運行(沒有被阻塞和掛起),但是當前沒有運行的任務,因為同優先順序或更高優先順序的任務正在運行。

Blocked 阻塞態
如果一個任務因延時,或等待信號量、消息隊列、事件標誌組等而處於的狀態被稱之為阻塞態。

Suspended 掛起態
類似暫停,通過調用函數 vTaskSuspend() 對指定任務進行掛起,掛起後這個任務將不被執行, 只有調用函數 xTaskResume() 才可以將這個任務從掛起態恢復。

image-20240514213639865

任務調度和任務的狀態案例分析

實驗需求

創建 4 個任務:taskLED1,taskLED2,taskKEY1,taskKEY2,任務要求如下:

taskLED1:間隔 500ms 閃爍 LED1;

taskLED2:間隔 1000ms 閃爍 LED2;

taskKEY1:如果 taskLED1 存在,則按下 KEY1 後刪除 taskLED1 ,否則創建 taskLED1 ;

taskKEY2:如果 taskLED2 正常運行,則按下 KEY2 後掛起 taskLED2 ,否則恢復 taskLED2。

uart.c

#include "stdio.h"
int fputc(int ch,FILE *f)
{
	unsigned char temp[1] = {ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);
	return ch;
}

freertos.c

void StartTaskLED1(void const * argument)
{
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
    osDelay(500);
  }
}
 
void StartTask02(void const * argument)
{
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
    osDelay(1000);
  }
}
 
void StartTaskKey1(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20); // 延時消抖
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
            {
			    printf("Key1 被按下\r\n");
			    if(TaskLED1Handle == NULL)
			    {
			    	printf("任務1不存在,準備創建任務1\r\n");
				    osThreadDef(TaskLED1, StartTaskLED1, osPriorityNormal, 0, 128);
				    TaskLED1Handle = osThreadCreate(osThread(TaskLED1), NULL);
				    if(TaskLED1Handle != NULL)
				    {
				    	printf("任務1創建成功\r\n");
				    }
			    }
			    else
			    {
				    printf("刪除任務1\r\n");
				    osThreadTerminate(TaskLED1Handle);
				    TaskLED1Handle = NULL;
		    	}
            }
			while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
        osDelay(10);
  }
}
 
void StartTaskKey2(void const * argument)
{
	static int flag = 0;	
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20); // 延時消抖
            if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
            {
    			printf("Key2 被按下\r\n");
		    	if(flag == 0)
		    	{
			    	osThreadSuspend(TaskLED2Handle);
			    	printf("任務2被掛起暫停\r\n");
			    	flag = 1;
			    }
			    else
			    {
			    	osThreadResume(TaskLED2Handle);
			    	printf("任務2重新恢復\r\n");
			    	flag = 0;
		    	}
           }
            while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
        osDelay(10);
  }
}

隊列

什麼是隊列?

隊列又稱消息隊列,是一種常用於任務間通信的數據結構,隊列可以在任務與任務間、中斷和任 務間傳遞信息。

為什麼不使用全局變數?

如果使用全局變數,任務1修改了變數 a ,等待任務3處理,但任務3處理速度很慢,在處理數據的過程中,任務2有可能又修改了變數 a ,導致任務3有可能得到的不是正確的數據。

在這種情況下,就可以使用隊列。任務1和任務2產生的數據放在流水線上,任務3可以慢慢一個個依次處理。

關於隊列的幾個名詞:創建隊列時,需要指定隊列長度及隊列項目大小。

  • 隊列項目:隊列中的每一個數據;

  • 隊列長度:隊列能夠存儲隊列項目的最大數量;

消息隊列特點

  1. 數據入隊出隊方式

    通常採用先進先出(FIFO)的數據存儲緩衝機制,即先入隊的數據會先從隊列中被讀取。也可以配置為後進先出(LIFO)方式,但用得比較少。

  2. 數據傳遞方式
    採用實際值傳遞,即將數據拷貝到隊列中進行傳遞,也可以傳遞指針,在傳遞較大的數據的時候 採用指針傳遞。

  3. 多任務訪問
    隊列不屬於某個任務,任何任務和中斷都可以向隊列發送/讀取消息

  4. 出隊、入隊阻塞
    當任務向一個隊列發送消息時,可以指定一個阻塞時間,假設此時當隊列已滿無法入隊。

阻塞時間如果設置為:

  • 0:直接返回不會等待;
  • 0~port_MAX_DELAY:等待設定的阻塞時間,若在該時間內還無法入隊,超時後直接返回不再等待;
  • port_MAX_DELAY:死等,一直等到可以入隊為止。出隊阻塞與入隊阻塞類似;

消息隊列相關 API 函數

創建隊列

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );

參數:

  • uxQueueLength:隊列可同時容納的最大項目數 。
  • uxItemSize:存儲隊列中的每個數據項所需的大小(以位元組為單位)。

返回值: 如果隊列創建成功,則返回所創建隊列的句柄 。 如果創建隊列所需的記憶體無法分配 , 則返回 NULL。

寫隊列

函數 描述
xQueueSend() 往隊列的尾部寫入消息
xQueueSendToBack() 同 xQueueSend()
xQueueSendToFront() 往隊列的頭部寫入消息
xQueueOverwrite() 覆寫隊列消息(只用於隊列長度為 1 的情況)
xQueueSendFromISR() 在中斷中往隊列的尾部寫入消息
xQueueSendToBackFromISR() 同 xQueueSendFromISR()
xQueueSendToFrontFromISR() 在中斷中往隊列的頭部寫入消息
xQueueOverwriteFromISR() 在中斷中覆寫隊列消息(只用於隊列長度為 1 的情況)
BaseType_t xQueueSend(
                        QueueHandle_t xQueue,
                        const void * pvItemToQueue,
                        TickType_t xTicksToWait
                    );

參數:

  • xQueue:隊列的句柄,數據項將發送到此隊列。
  • pvItemToQueue:待寫入數據
  • xTicksToWait:阻塞超時時間

返回值:

如果成功寫入數據,返回 pdTRUE,否則返回 errQUEUE_FULL。

讀隊列

函數 描述
xQueueReceive() 從隊列頭部讀取消息,並刪除消息
xQueuePeek() 從隊列頭部讀取消息,但是不刪除消息
xQueueReceiveFromISR() 在中斷中從隊列頭部讀取消息,並刪除消息
xQueuePeekFromISR() 在中斷中從隊列頭部讀取消息
BaseType_t xQueueReceive(
                            QueueHandle_t xQueue,
                            void *pvBuffer,
                            TickType_t xTicksToWait
                    );

參數:

  • xQueue:待讀取的隊列
  • pvItemToQueue:數據讀取緩衝區
  • xTicksToWait:阻塞超時時間

返回值:

  • 成功返回 pdTRUE
  • 否則返回 pdFALSE

消息隊列實操

要求:創建一個隊列,按下 KEY1 向隊列發送數據,按下 KEY2 向隊列讀取數據。

  1. 然後創建兩個任務和一個隊列

    image-20240515173834186

  2. 設置按鍵引腳為輸入,然後導出代碼

    freertos.c

    /*用於監聽Key1,Key1按下向隊列發送消息*/
    void StartSend(void const * argument)
    {
      /* USER CODE BEGIN StartSend */
      /* Infinite loop */
        uint16_t buf = 100;
        BaseType_t status;
        for(;;)
        {
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                osDelay(20);    //消抖
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
                {
                    status = xQueueSend(myQueue01Handle,&buf,0);
                    if(status == pdTRUE)
                    {
                        printf("寫入隊列成功\r\n");
                    }
                    else
                    {
                        printf("寫入隊列失敗\r\n");
                    }
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET);
            }
        osDelay(10);    //10ms檢測一次
        }
      /* USER CODE END StartSend */
    }
    
    /*用於監聽key2,key2按下從隊列中讀出消息*/
    void StartReceive(void const * argument)
    {
      /* USER CODE BEGIN StartReceive */
      /* Infinite loop */
        uint16_t buf = 100;
        BaseType_t status;
        for(;;)
        {
          if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
            {
                osDelay(20);    //消抖
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
                {
                    status = xQueueReceive(myQueue01Handle,&buf,0);
                    if(status == pdTRUE)
                    {
                        printf("讀出隊列成功\r\n");
                    }
                    else
                    {
                        printf("讀出隊列失敗\r\n");
                    }
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET);
            }
            osDelay(10);
        }
      /* USER CODE END StartReceive */
    }
    

    image-20240515175549520

二值信號量

什麼是信號量?

信號量(Semaphore),是在多任務環境下使用的一種機制,是可以用來保證兩個或多個關鍵代 碼段不被併發調用。

信號量這個名字,我們可以把它拆分來看,信號可以起到通知信號的作用,然後我們的量還可以 用來表示資源的數量,當我們的量只有0和1的時候,它就可以被稱作二值信號量,只有兩個狀 態,當我們的那個量沒有限制的時候,它就可以被稱作為計數型信號量。

信號量也是隊列的一種。

什麼是二值信號量?

二值信號量其實就是一個長度為1,大小為零的隊列,只有0和1兩種狀態,通常情況下,我們用它來進行互斥訪問或任務同步。

互斥訪問:比如門跟鑰匙,只有獲取到鑰匙才可以開門

任務同步:比如錄完視頻後才能看視頻

二值信號量相關 API 函數

函數 描述
xSemaphoreCreateBinary() 使用動態方式創建二值信號量
xSemaphoreCreateBinaryStatic() 使用靜態方式創建二值信號量
xSemaphoreGive() 釋放信號量
xSemaphoreGiveFromISR() 在中斷中釋放信號量
xSemaphoreTake() 獲取信號量
xSemaphoreTakeFromISR() 在中斷中獲取信號量

創建二值信號量

SemaphoreHandle_t xSemaphoreCreateBinary( void )

參數:

返回值:

  • 成功,返回對應二值信號量的句柄;
  • 失敗,返回 NULL 。

註意:創建信號量時預設會釋放

釋放二值信號量

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )

參數:

  • xSemaphore:要釋放的信號量句柄

返回值:

  • 成功,返回 pdPASS ;
  • 失敗,返回 errQUEUE_FULL 。

獲取二值信號量

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                    		TickType_t xTicksToWait );

參數:

  • xSemaphore:要獲取的信號量句柄
  • xTicksToWait:超時時間,0 表示不超時,portMAX_DELAY表示卡死等待;

返回值:

  • 成功,返回 pdPASS ;
  • 失敗,返回 errQUEUE_FULL 。

二值信號量實操

實驗需求:創建一個二值信號量,按下 KEY1 則釋放信號量,按下 KEY2 獲取信號量。

  1. 打開CubeMX,創建兩個任務用來放入和獲取信號量

    image-20240515192640377

    image-20240515192712965

  2. 進行代碼編寫

    freertos.c

    /*檢測Key1,Key1按下釋放信號量*/
    void StartKey01(void const * argument)
    {
      /* USER CODE BEGIN StartKey01 */
      /* Infinite loop */
        BaseType_t status;
        for(;;)
        {
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
                {
                    status = xSemaphoreGive(myBinarySem01Handle);
                    if(status == pdTRUE)
                    {
                        printf("釋放二值信號量成功\r\n");
                    }
                    else
                    {
                        printf("釋放二值信號量失敗\r\n");
                    }
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET);
            }
            osDelay(1);
        }
      /* USER CODE END StartKey01 */
    }
    
    /*檢測Key2,Ke21按下獲取信號量*/
    void StartKey2(void const * argument)
    {
      /* USER CODE BEGIN StartKey2 */
      /* Infinite loop */
        BaseType_t status;
        for(;;)
        {
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
                {
                    status = xSemaphoreTake(myBinarySem01Handle,0);
                    if(status == pdTRUE)
                    {
                        printf("獲取二值信號量成功\r\n");
                    }
                    else
                    {
                        printf("獲取二值信號量失敗\r\n");
                    }
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET);
            }
            osDelay(1);
        }
      /* USER CODE END StartKey2 */
    }
    
  3. 串口調試:創建信號時預設會釋放一次

    image-20240515193554629

計數型信號量

什麼是計數型信號量?

計數型信號量相當於隊列長度大於1 的隊列,因此計數型信號量能夠容納多個資源,這在計數型信號量被創建的時候確定的。

使用計數型信號量可以解決多個任務之間的同步問題,例如控制對共用資源的訪問和協調任務的執行順序。

計數型信號量相關 API 函數

計數型信號量的釋放和獲取與二值信號量完全相同 !

函數 描述
xSemaphoreCreateCounting() 使用動態方法創建計數型信號量
xSemaphoreCreateCountingStatic() 使用靜態方法創建計數型信號量
uxSemaphoreGetCount() 獲取信號量的計數值

創建計數型信號量

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                         UBaseType_t uxInitialCount);

參數:

  • uxMaxCount:可以達到的最大計數值
  • uxInitialCount:創建信號量時分配給信號量的計數值

返回值:

  • 成功,返回對應計數型信號量的句柄;

  • 失敗,返回 NULL 。

釋放計數型信號量

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )

參數:

  • xSemaphore:要釋放的信號量句柄

返回值:

  • 成功,返回 pdPASS ;

  • 失敗,返回 errQUEUE_FULL 。

獲取計數型信號量

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                    TickType_t xTicksToWait );

參數:

  • xSemaphore:要獲取的信號量句柄
  • xTicksToWait:超時時間,0 表示不超時,portMAX_DELAY表示卡死等待;

返回值:

  • 成功,返回 pdPASS ;
  • 失敗,返回 errQUEUE_FULL 。

計數型信號量實操

  1. 打開CubeMX,創建兩個任務和設置按鍵引腳為輸入,通過按鍵來放入和獲取信號量

    image-20240515205540921

  2. 使能計數信號量

    image-20240515205613351

  3. 創建一個計數信號量,設置最多存放3個信號量

    image-20240515205656296

  4. 進行代碼編寫

    /*監聽Key1,Key1按下釋放計數型信號量*/
    void StartKey01(void const * argument)
    {
      /* USER CODE BEGIN StartKey01 */
      /* Infinite loop */
        BaseType_t status;
        for(;;)
        {
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
                {
                    status = xSemaphoreGive(myCountingSem01Handle);
                    if(status == pdTRUE)
                    {
                        printf("釋放計數型信號量成功\r\n");
                    }
                    else
                    {
                        printf("釋放計數型信號量失敗\r\n");
                    }
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET);
            }
            osDelay(1);
        }
      /* USER CODE END StartKey01 */
    }
    
    /*監聽Key2,Key2按下獲取計數型信號量*/
    void StartKey2(void const * argument)
    {
      /* USER CODE BEGIN StartKey2 */
      /* Infinite loop */
        BaseType_t status;
        for(;;)
        {
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
                {
                    status = xSemaphoreTake(myCountingSem01Handle,0);
                    if(status == pdTRUE)
                    {
                        printf("獲取計數型信號量成功\r\n");
                    }
                    else
                    {
                        printf("獲取計數型信號量失敗\r\n");
                    }
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET);
            }
            osDelay(1);
        }
      /* USER CODE END StartKey2 */
    }
    
  5. 串口調試

    image-20240515211057039

互斥量

什麼是互斥量?

在多數情況下,互斥型信號量和二值型信號量非常相似,但是從功能上二值型信號量用於同步, 而互斥型信號量用於資源保護。

互斥型信號量和二值型信號量還有一個最大的區別,互斥型信號量可以有效解決優先順序反轉現 象。

什麼是優先順序反轉(翻轉)和優先順序繼承

image-20240515215626013

image-20240515220054708

以上圖為例,系統中有3個不同優先順序的任務H/M/L,最高優先順序任務H和最低優先順序任務L通過 信號量機制,共用資源。目前任務L占有資源,鎖定了信號量,Task H運行後將被阻塞,直到Task L釋放信號量後,Task H才能夠退出阻塞狀態繼續運行。但是Task H在等待Task L釋放信號量的過 程中,中等優先順序任務M搶占了任務L,從而延遲了信號量的釋放時間,導致Task H阻塞了更長時 間,這種現象稱為優先順序倒置或優先順序反轉(翻轉)。

優先順序繼承:

當一個互斥信號量正在被一個低優先順序的任務持有時, 如果此時有個高優先順序的任 務也嘗試獲取這個互斥信號量,那麼這個高優先順序的任務就會被阻塞。不過這個高優先順序的任務 會將低優先順序任務的優先順序提升到與自己相同的優先順序。

優先順序繼承並不能完全的消除優先順序翻轉的問題,它只是儘可能的降低優先順序翻轉帶來的影響。

互斥量相關 API 函數

互斥信號量不能用於中斷服務函數中!

函數 描述
xSemaphoreCreateMutex() 使用動態方法創建互斥信號量
xSemaphoreCreateMutexStatic() 使用靜態方法創建互斥信號量
SemaphoreHandle_t xSemaphoreCreateMutex( void )

參數:

返回值:

  • 成功,返回對應互斥量的句柄;

  • 失敗,返回 NULL 。

優先順序反轉(翻轉)示例

如下圖,低優先順序工作後高優先順序被阻塞,然後發生優先順序反轉,中優先順序比高優先順序先工作

image-20240515222018960

  1. 打開CubeMX,增加三個任務,優先順序分別從高到低

    image-20240515224826392

  2. 增加一個二值信號量

    image-20240515224749188

  3. 代碼編寫

    void StartTaskH(void const * argument)
    {
      /* USER CODE BEGIN StartTaskH */
      /* Infinite loop */
      for(;;)
      {
            xSemaphoreTake(myBinarySem01Handle,portMAX_DELAY);  //獲取二值信號量
            printf("高優先順序獲得二值信號量,開始工作\r\n");
            HAL_Delay(1000);
            printf("工作完畢後,釋放二值信號量\r\n");
            xSemaphoreGive(myBinarySem01Handle);    //釋放二值信號量
            osDelay(1000);
      }
      /* USER CODE END StartTaskH */
    }
    
    void StartTaskM(void const * argument)
    {
      /* USER CODE BEGIN StartTaskM */
      /* Infinite loop */
      for(;;)
      {
        printf("占用cpu資源,我就是玩\r\n");
        osDelay(1000);
      }
      /* USER CODE END StartTaskM */
    }
    
    void StartTaskL(void const * argument)
    {
      /* USER CODE BEGIN StartTaskL */
      /* Infinite loop */
      for(;;)
      {
            xSemaphoreTake(myBinarySem01Handle,portMAX_DELAY);  //獲取二值信號量
            printf("低優先順序獲得二值信號量,開始工作\r\n");
            HAL_Delay(3000);
            printf("工作完畢後,釋放二值信號量\r\n");
            xSemaphoreGive(myBinarySem01Handle);    //釋放二值信號量
            osDelay(1000);
      }
      /* USER CODE END StartTaskL */
    }
    
  4. 串口調試

    image-20240515224636011

使用互斥量優化優先順序反轉(翻轉)問題示例

  1. 使用CubeMX在優先順序反轉示例中增加互斥量,導出代碼、

    image-20240515225515383

  2. 編寫代碼

    void StartTaskH(void const * argument)
    {
      /* USER CODE BEGIN StartTaskH */
      /* Infinite loop */
      for(;;)
      {
            xSemaphoreTake(myMutex01Handle,portMAX_DELAY);  //獲取二值信號量
            printf("高優先順序獲得二值信號量,開始工作\r\n");
            HAL_Delay(1000);
            printf("工作完畢後,釋放二值信號量\r\n");
            xSemaphoreGive(myMutex01Handle);    //釋放二值信號量
            osDelay(1000);
      }
      /* USER CODE END StartTaskH */
    }
    
    void StartTaskM(void const * argument)
    {
      /* USER CODE BEGIN StartTaskM */
      /* Infinite loop */
      for(;;)
      {
        printf("占用cpu資源,我就是玩\r\n");
        osDelay(1000);
      }
      /* USER CODE END StartTaskM */
    }
    
    void StartTaskL(void const * argument)
    {
      /* USER CODE BEGIN StartTaskL */
      /* Infinite loop */
      for(;;)
      {
            xSemaphoreTake(myMutex01Handle,portMAX_DELAY);  //獲取二值信號量
            printf("低優先順序獲得二值信號量,開始工作\r\n");
            HAL_Delay(3000);
            printf("工作完畢後,釋放二值信號量\r\n");
            xSemaphoreGive(myMutex01Handle);    //釋放二值信號量
            osDelay(1000);
      }
      /* USER CODE END StartTaskL */
    }
    
  3. 串口調試

    image-20240515225403415

事件標誌組

什麼是事件標誌組?

事件標誌位
表明某個事件是否發生,聯想:全局變數 flag。通常按位表示,每一個位表示一個事件(高8位不算)

事件標誌組
是一組事件標誌位的集合, 可以簡單的理解事件標誌組,就是一個整數。

事件標誌組本質是一個 16 位或 32 位無符號的數據類型 EventBits_t ,由 configUSE_16_BIT_TICKS 決定。

雖然使用了 32 位無符號的數據類型變數來存儲事件標誌, 但其中的高8位用作存儲事件標誌組的 控制信息,低 24 位用作存儲事件標誌 ,所以說一個事件組最多可以存儲 24 個事件標誌!

image-20240516091046694

事件標誌組相關 API 函數

函數 描述
xEventGroupCreate() 使用動態方式創建事件標誌組
xEventGroupCreateStatic() 使用靜態方式創建事件標誌組
xEventGroupClearBits() 清零事件標誌位
xEventGroupClearBitsFromISR() 在中斷中清零事件標誌位
xEventGroupSetBits() 設置事件標誌位
xEventGroupSetBitsFromISR() 在中斷中設置事件標誌位
xEventGroupWaitBits() 等待事件標誌位

創建事件標誌組

EventGroupHandle_t xEventGroupCreate( void );

參數:

返回值:

  • 成功,返回對應事件標誌組的句柄;

  • 失敗,返回 NULL

設置事件標誌位

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet );

參數:

  • xEventGroup:對應事件組句柄。
  • uxBitsToSet:指定要在事件組中設置的一個或多個位的按位值。

返回值:

  • 設置之後事件組中的事件標誌位值。

清除事件標誌位

EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToClear );

參數:

  • xEventGroup:對應事件組句柄。
  • uxBitsToClear:指定要在事件組中清除的一個或多個位的按位 值。

返回值:

  • 清零之前事件組中事件標誌位的值。

等待事件標誌位

EventBits_t xEventGroupWaitBits(
                        const EventGroupHandle_t xEventGroup,
                        const EventBits_t uxBitsToWaitFor,
                        const BaseType_t xClearOnExit,
                        const BaseType_t xWaitForAllBits,
                        TickType_t xTicksToWait );

參數:

  • xEventGroup:對應的事件標誌組句柄

  • uxBitsToWaitFor:指定事件組中要等待的一個或多個事件位的按位值

  • xClearOnExit:pdTRUE——清除對應事件位,pdFALSE——不清除

  • xWaitForAllBits:pdTRUE——所有等待事件位全為1(邏輯與),pdFALSE——等待的事件位有一個為1(邏輯或)

  • xTicksToWait:超時時間,0 表示不超時,portMAX_DELAY表示卡死等待

返回值:

等待的事件標誌位值:等待事件標誌位成功,返回等待到的事件標誌位

其他值:等待事件標誌位失敗,返回事件組中的事件標誌位

事件標誌組實操

創建一個事件標誌組和兩個任務( task1 和 task2),task1 檢測按鍵,如果檢測到 KEY1 和 KEY2 都按過,則執行 task2 。

  1. 打開CubeMX,增加兩個任務,配置兩個輸入引腳給按鍵Key1和Key2

    image-20240516094556147

  2. 在代碼中創建一個事件標誌組

    /*定義事件標誌組句柄*/ 
    EventGroupHandle_t eventGroupHandle;
     /*創建事件標誌組*/
     eventGroupHandle = xEventGroupCreate();
    
  3. task1 檢測按鍵,如果檢測到 KEY1 和 KEY2 都按過,則執行 task2

    /*檢測Key1和Key2有沒有按下,如果按下則設置相應的事件標誌組位*/
    void StartTask1(void const * argument)
    {
      /* USER CODE BEGIN StartTask1 */
      /* Infinite loop */
        for(;;)
        {
            //檢測Key1有無被按下,按下後設置事件標誌組最低位
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
                {
                    printf("按鍵Key1按下\r\n");
                    xEventGroupSetBits(eventGroupHandle,0x01);
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET);
            }
            //檢測Key2有無被按下,按下後設置事件標誌組第二低位
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET)
                {
                    printf("按鍵Key2按下\r\n");
                    xEventGroupSetBits(eventGroupHandle,0x02);
                }
                while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_10) == GPIO_PIN_RESET);
            }
            osDelay(10);
        }
      /* USER CODE END StartTask1 */
    }
    
    /*該任務用於等待事件標誌組*/
    void StartTask2(void const * argument)
    {
        EventBits_t event_bit;
      /* USER CODE BEGIN StartTask2 */
      /* Infinite loop */
        for(;;)
        {
            event_bit = xEventGroupWaitBits(eventGroupHandle,0x01|0x02,pdTRUE,pdTRUE,portMAX_DELAY);
            printf("返回值:%#x,按鍵都按下,任務2執行了\r\n",event_bit); // %#x是帶格式輸出, 效果為在輸出前加0x
            osDelay(1);
        }
      /* USER CODE END StartTask2 */
    }
    
  4. 打開串口助手看運行結果

    image-20240516094841590

任務通知

什麼是任務通知?

FreeRTOS 從版本 V8.2.0 開始提供任務通知這個功能,每個任務都有一個 32 位的通知值。按照 FreeRTOS 官方的說法,使用消息通知比通過二進位信號量方式解除阻塞任務快 45%, 並且更加 省記憶體(無需創建隊列)。

在大多數情況下,任務通知可以替代二值信號量、計數信號量、事件標誌組,可以替代長度為 1 的隊列(可以保存一個 32 位整數或指針值),並且任務通知速度更快、使用的RAM更少!

image-20240516101425764

任務一給任務二發送通知,其實就是任務一操作任務二的TCB裡面的ulNotifiedValue,給它寫入相應的值

任務都有一個結構體:任務控制塊TCB,它裡邊有兩個結構體成員變數:

typedef  struct  tskTaskControlBlock 
{
	… …
    	#if ( configUSE_TASK_NOTIFICATIONS  ==  1 )
        	volatile  uint32_t    ulNotifiedValue [ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        	volatile  uint8_t      ucNotifyState [ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    	endif
	… …
} tskTCB;
#define  configTASK_NOTIFICATION_ARRAY_ENTRIES	1  	/* 定義任務通知數組的大小, 預設: 1 */

  • 一個是 uint32_t 類型,用來表示通知值
  • 一個是 uint8_t 類型,用來表示通知狀態

發送通知給任務 :

通過對以下方式的合理使用,可以在一定場合下替代原本的隊列、信號量、事件標誌組等。

  • 發送消息給任務,如果有通知未讀, 不覆蓋通知值

  • 發送消息給任務,直接覆蓋通知值

  • 發送消息給任務,設置通知值的一個或者多個位

  • 發送消息給任務,遞增通知值

任務通知的優勢和劣勢

任務通知的優勢

  1. 使用任務通知向任務發送事件或數據,比使用隊列、事件標誌組或信號量快得多。
  2. 使用其他方法時都要先創建對應的結構體,使用任務通知時無需額外創建結構體。

任務通知的劣勢

  1. 只有任務可以等待通知,中斷服務函數中不可以,因為中斷沒有 TCB 。
  2. 通知只能一對一,因為通知必須指定任務。
  3. 等待通知的任務可以被阻塞, 但是發送消息的任務,任何情況下都不會被阻塞等待。
  4. 任務通知是通過更新任務通知值來發送數據的,任務結構體中只有一個任務通知值,只能保 持一個數據。

任務通知相關 API 函數

發送通知

函數 描述
xTaskNotify() 發送通知,帶有通知值
xTaskNotifyAndQuery() 發送通知,帶有通知值並且保留接收任務的原通知值
xTaskNotifyGive() 發送通知,不帶通知值
xTaskNotifyFromISR() 在中斷中發送任務通知
xTaskNotifyAndQueryFromISR() 在中斷中發送任務通知
vTaskNotifyGiveFromISR() 在中斷中發送任務通知
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                        uint32_t ulValue,
                        eNotifyAction eAction );

參數:

  • xTaskToNotify:需要接收通知的任務句柄;

  • ulValue:用於更新接收任務通知值, 具體如何更新由形參 eAction 決定;

  • eAction:一個枚舉,代表如何使用任務通知的值;

    枚舉值 描述
    eNoAction 發送通知,但不更新值(參數ulValue未使用)
    eSetBits 被通知任務的通知值按位或ulValue。(某些場景下可代替事件組,效率更高)
    eIncrement 被通知任務的通知值增加1(參數ulValue未使用),相當於 xTaskNotifyGive
    eSetValueWithOverwrite 被通知任務的通知值設置為 ulValue。(某些場景下可代替 xQueueOverwrite ,效率更高)
    eSetValueWithoutOverwrite 如果被通知的任務當前沒有通知,則被通知的任務的通知值設為ulValue。如果被通知任務沒有取走上一個通知,又接收到了一個通 知,則這次通知值丟棄,在這種情況下視為調用失敗並返回 pdFALSE,(某些場景下可代替 xQueueSend ,效率更高)

返回值:如果被通知任務還沒取走上一個通知,又接收了一個通知,則這次通知值未能更新並返回 pdFALSE, 而其他情況均返回pdPASS。

BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
                                uint32_t ulValue,
                                eNotifyAction eAction,
                                uint32_t *pulPreviousNotifyValue );

參數:

  • xTaskToNotify:需要接收通知的任務句柄;
  • ulValue:用於更新接收任務通知值, 具體如何更新 由形參 eAction 決定;
  • eAction:一個枚舉,代表如何使用任務通知的值;
  • pulPreviousNotifyValue:對象任務的上一個任務通知值,如果為 NULL, 則不需要回傳, 這個時候就等價於函數 xTaskNotify()。

返回值:

如果被通知任務還沒取走上一個通知,又接收了一個通知,則這次通知值未能更新並返回 pdFALSE, 而其他情況均返回pdPASS。

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

參數:

  • xTaskToNotify:接收通知的任務句柄, 並讓其自身的任務通知值加 1。

返回值:

  • 總是返回 pdPASS。

等待通知

等待通知API函數只能用在任務,不可應用於中斷中!

函數 描述
ulTaskNotifyTake() 獲取任務通知,可以設置在退出此函數的時候將任務通知值清零或者減 一。當任務通知用作二值信號量或者計數信號量的時候,使用此函數來 獲取信號量。
xTaskNotifyWait() 獲取任務通知,比 ulTaskNotifyTake()更為複雜,可獲取通知值和清除通知值的指定位
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                            TickType_t xTicksToWait );

參數:

  • xClearCountOnExit:指定在成功接收通知後,將通知值清零或減 1,pdTRUE:把通知值清零(二值信號量);pdFALSE:把通知值減一(計數型信號量);
  • xTicksToWait:阻塞等待任務通知值的最大時間;超時時間,0 表示不超時,portMAX_DELAY表示卡死等待

返回值:0:接收失敗;非0:接收成功,返回任務通知的通知值

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                            uint32_t ulBitsToClearOnExit,
                            uint32_t *pulNotificationValue,
                            TickType_t xTicksToWait );

參數:

  • ulBitsToClearOnEntry:函數執行前清零任務通知值那些位 。
  • ulBitsToClearOnExit:表示在函數退出前,清零任務通知值那些位,在清 0 前,接收到的任務通知值會先被保存到形參 *pulNotificationValue 中。
  • pulNotificationValue:用於保存接收到的任務通知值。 如果不需要使用,則設置為 NULL 即可
  • xTicksToWait:等待消息通知的最大等待時間。超時時間,0 表示不超時,portMAX_DELAY表示卡死等待

任務通知實操

模擬二值信號量

  1. 創建兩個任務和設置按鍵引腳為輸入

    image-20240516153957454

  2. 設置兩個按鍵分別發送和接收二值信號量

    用到函數

    • xTaskNotifyGive()
    • ulTaskNotifyTake()
    void StartTaskSend(void const * argument)
    {
        for(;;)
        {
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                osDelay(20);
                if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)
                {
                    xTaskNotifyGive(taskReceiveHandl
    
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引言 本來博主想偷懶使用AutoUpdater.NET組件,但由於博主項目有些特殊性和它的功能過於多,於是博主自己實現一個輕量級獨立自動更新組件,可稍作修改集成到大家自己項目中,比如:WPF/Winform/Windows服務。大致思路:發現更新後,從網路上下載更新包併進行解壓,同時在 WinFor ...
  • 今天偶然知道一款叫做stylet的MVVM框架,挺小巧的,特別是它的命令觸發方式,簡單粗暴,讓人感覺很巴適,現在我做一個簡單的demo來順便來分享給大家。 本地創建一個WPF項目,此處我使用.NET 8來創建。然後引用stylet最新的nuget包。 然後刪掉App.xaml裡面自帶的啟動項 刪掉以 ...
  • 一:背景 1. 講故事 這些天有點意思,遇到的幾個程式故障都是和Windows操作系統或者第三方組件有關係,真的有點無語,今天就帶給大家一例 IIS 相關的與大家分享,這是一家國企的.NET程式,出現了崩潰急需分析。 二:WinDbg 分析 1. 為什麼會崩潰 崩潰原因相對還是好找的,雙擊dump文 ...
  • 目錄知識補給站對文件描述符集合操作的四個巨集操作伺服器IO多路復用中的select和poll的區別 知識補給站 對文件描述符集合操作的四個巨集操作 對文件描述符集合操作的四個巨集操作在select函數中起著關鍵的作用,它們用於初始化、添加、刪除和檢查文件描述符集合中的元素。這四個巨集為: FD_ZERO、F ...
  • 前言 筆者做過一段時間的車載LiDAR開發,對LidarView開源項目進行過深度定製,摸索了一套LidarView軟體的開發和調試方法 1 軟體安裝 1.1 安裝準備 以Windows10系統平臺為例,依次下載以下工具軟體,軟體(VS、Qt、cdb)的版本很重要!以下版本經過驗證是沒有問題的 序號 ...
  • 前言 最近調試NXP FRDM-MCXN947開發板,發現它的硬體i2c介面讀取的感測器數據老是不對,排查了硬體電路也發現不了啥問題;於是乎想到用邏輯分析儀試一下,果然很快定位到問題所在;還是那句話,用對的工具做對的事情,別浪費時間!這篇文章主要關於邏輯分析儀的使用教程 介紹 nanoDLA 是Mu ...
  • 第一步:下載鏡像文件 百度網盤下載https://pan.baidu.com/s/1efRQGFTbq6Kgw9axLOmWzg?pwd=emxf 第二步:打開Vmware 第三步:進行各項配置 創建新的虛擬機,選擇高級,然後下一步 直接預設下一步 選擇稍後安裝然後下一步 kali屬於Debian系 ...
  • 最近想移植個LVGL玩玩,發現文件實在是太多了,加的手疼都沒搞完,實在不想搞了就去找腳本和工具,基本沒找到一個。。。。。。 主要是自己也懶得去研究寫腳本,偶然搜到了一個博主寫的腳本,原博客地址:https://blog.csdn.net/riyue2044/article/details/13942 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...