引言
在嵌入式開發領域,任務同步與通信是系統穩定運行的核心。STM32配合FreeRTOS操作系統,為開發者提供了強大的工具支持。其中,二值信號量和計數信號量作為FreeRTOS的關鍵同步機制,分別用于任務間的簡單同步和資源計數控制。二值信號量如同一個開關,用于控制任務的進入與退出;而計數信號量則用于管理有限資源的訪問,確保資源的合理分配。本文將深入剖析這兩種信號量的工作原理、使用方法及在STM32平臺上的應用實例,助力開發者精準掌握其精髓,提升系統開發效率與穩定性。
什么是二值信號量?
二值信號量是一種同步機制,用于在多任務環境中協調任務的執行。它本質上是一個只能取兩個值(通常是0和1)的變量,類似于一個開關。當信號量的值為1時,表示資源可用;當值為0時,表示資源已被占用。在FreeRTOS中,二值信號量主要用于任務間的同步,而不是用于互斥。它的工作原理如下:當一個任務需要資源時,它會嘗試獲取信號量。如果信號量的值為1,任務會將其值減1并繼續執行;如果信號量的值為0,任務會進入阻塞狀態,等待信號量變為1。當另一個任務釋放資源時,它會將信號量的值加1,從而喚醒等待的任務。
什么是計數信號量?
計數信號量是一種用于同步和資源管理的機制,它維護一個非負整數值,表示可用資源的數量。在FreeRTOS中,計數信號量通常用于管理有限數量的資源,例如緩沖區、硬件設備等。計數信號量的工作原理如下:當一個任務需要使用資源時,它會嘗試獲取信號量。如果信號量的值大于0,任務會將其值減1并繼續執行,表示占用了一個資源;如果信號量的值為0,任務會進入阻塞狀態,等待資源變為可用。當任務釋放資源時,它會將信號量的值加1,從而表示資源數量增加,可能會喚醒等待的任務。
二值信號量
創建二值信號量句柄
步驟都跟前面的創建任務差不多,只不過在創建任務的基礎上添加了二值信號量的相關函數,從本文章開始就不再細說怎么創建任務,直接開始講二值信號量的創建及使用。
首先定義一下二值信號量的句柄并為其賦初始值為NULL:
SemaphoreHandle_t BinarySem_Handle =NULL; //二值信號量句柄
然后是xSemaphoreCreateBinary,它是FreeRTOS操作系統提供的一個函數,用于創建一個二值信號量,前面也說了,二值信號量是一個特殊的信號量,它的值只能是0或1,通常用于任務間的同步。其函數原型為:
SemaphoreHandle_t xSemaphoreCreateBinary(void);
這個函數沒有參數,但有一個返回值,通常返回的是一個二值信號量的句柄,如果創建成功就返回相應的值,失敗就返回NULL。
發送二值信號量
也叫釋放二值信號量,其函數為xSemaphoreGive(),該函數原型為:
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
參數xSemaphore為需要發送或釋放的信號量句柄,這里我們填入上面定義好的二值信號量句柄。同時,該函數還有一個返回值,返回的類型是BaseType_t,通常是一個整數類型。如果返回值為pdTRUE(在freertos中通常定義為1),則表示信號量發送或釋放成功。如果返回值為pdFALSH(在freertos中通常定義為0),則表示發送或釋放失敗。
獲取二值信號量
有發送就有獲取,其函數為xSemaphoreTake(),該函數原型為:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
參數xSemaphore跟上面的一樣,填入定義好的句柄即可。參數xTicksToWait為任務等待信號量的最大時間,可根據需要選擇,通常為portMAX_DELAY。該函數還有一個返回值,跟上面一樣,這里就跳過了。
二值信號量示例代碼
#include "myfreertos.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include "Usart.h"
#include "oled.h"
#include "Task.h"
#include "led.h"
#include "key.h"TaskHandle_t MyTaskHandler;//任務句柄TaskHandle_t MyTask1Handler;//任務1句柄TaskHandle_t SendTask_Handler; //發送消息句柄TaskHandle_t ReceiveTask_Handler;//接收消息句柄void MyTask(void *pvParameters); //聲明啟動函數void MyTask1(void *pvParameters); //聲明任務1函數void Send_task(void *pvParameters); //聲明發送消息函數void Receive_task(void *pvParameters); //聲明接收消息函數SemaphoreHandle_t BinarySem_Handle =NULL; //二值信號量句柄void Start_Task(void)
{xTaskCreate(MyTask,"MyTask",128,NULL,1,&MyTaskHandler);//動態方法創建任務vTaskStartScheduler();//啟動任務調動
}void MyTask(void *arg) //開始創建任務函數
{taskENTER_CRITICAL(); //進入臨界區 /* 創建二值信號量 BinarySem */BinarySem_Handle = xSemaphoreCreateBinary(); xTaskCreate(MyTask1,"MyTask1",50,NULL,2,&MyTask1Handler);//動態方法創建任務1xTaskCreate(Receive_task,"Receive_task",50,NULL,3,&ReceiveTask_Handler);//創建接收消息任務xTaskCreate(Send_task,"Send_task",50,NULL,4,&SendTask_Handler); //創建發送消息任務vTaskDelete(MyTaskHandler); //刪除開始任務taskEXIT_CRITICAL(); //退出臨界區
}void MyTask1(void *arg) //任務1函數體
{while(1){OLED_ShowString(1,1,"Runing Task Led");GPIO_ResetBits(GPIOC,GPIO_Pin_13);vTaskDelay(300);GPIO_SetBits(GPIOC,GPIO_Pin_13);vTaskDelay(900);}
}//接收任務函數
void Receive_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */while(1){ //獲取二值信號量 xSemaphore,沒獲取到則一直等待xReturn = xSemaphoreTake(BinarySem_Handle,100); /* 等待時間 */if(pdTRUE == xReturn)OLED_ShowString(3,1,"Receive_OK");elseOLED_ShowString(3,1,"Receive_NO");vTaskDelay(200);}
}//發送任務函數
void Send_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */while(1){if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==1){xReturn = xSemaphoreGive( BinarySem_Handle );//給出二值信號量if( xReturn == pdTRUE )OLED_ShowString(2,1,"Send_OK");}else{OLED_ShowString(2,1,"Send_NO");}vTaskDelay(200);}
}
計數信號量
創建計數信號量
跟二值信號量一樣,先定義一個計數信號量句柄并給其賦初值為NULL:
SemaphoreHandle_t CountSem_Handle =NULL; //計數信號量句柄
xSemaphoreCreateCounting()是FreeRTOS中用于創建計數信號量的函數,其函數原型為:
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount
)
參數uxMaxCount是計數信號量的最大計數值,當信號量值達此值時,不能再通過xSemaphoreGive增加其值。參數uxInitialCount為信號量的初始計數值。該函數還有一個返回值,如果成功創建信號量,就返回信號量的句柄,失敗則返回NULL。
發送計數信號量與獲取計數信號量
步驟都跟二值信號量大致相同,這里就不過多論述。
計數信號量示例代碼
#include "myfreertos.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include "Usart.h"
#include "oled.h"
#include "Task.h"
#include "led.h"
#include "key.h"TaskHandle_t MyTaskHandler;//任務句柄TaskHandle_t MyTask1Handler;//任務1句柄TaskHandle_t SendTask_Handler; //發送消息句柄TaskHandle_t ReceiveTask_Handler;//接收消息句柄void MyTask(void *pvParameters); //聲明啟動函數void MyTask1(void *pvParameters); //聲明任務1函數void Send_task(void *pvParameters); //聲明發送消息函數void Receive_task(void *pvParameters); //聲明接收消息函數SemaphoreHandle_t CountSem_Handle =NULL; //計數信號量句柄void Start_Task(void)
{xTaskCreate(MyTask,"MyTask",128,NULL,1,&MyTaskHandler);//動態方法創建任務vTaskStartScheduler();//啟動任務調動
}void MyTask(void *arg) //開始創建任務函數
{taskENTER_CRITICAL(); //進入臨界區 /* 創建 CountSem */CountSem_Handle = xSemaphoreCreateCounting(5,5); xTaskCreate(MyTask1,"MyTask1",50,NULL,2,&MyTask1Handler);//動態方法創建任務1xTaskCreate(Receive_task,"Receive_task",50,NULL,3,&ReceiveTask_Handler);//創建接收消息任務xTaskCreate(Send_task,"Send_task",50,NULL,4,&SendTask_Handler); //創建發送消息任務vTaskDelete(MyTaskHandler); //刪除開始任務taskEXIT_CRITICAL(); //退出臨界區
}void MyTask1(void *arg) //任務1函數體
{while(1){OLED_ShowString(1,1,"Runing Task Led");GPIO_ResetBits(GPIOC,GPIO_Pin_13);vTaskDelay(300);GPIO_SetBits(GPIOC,GPIO_Pin_13);vTaskDelay(900);}
}//接收任務函數
void Receive_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */while(1){ //獲取計數信號量 xSemaphore,沒獲取到則一直等待xReturn = xSemaphoreTake(CountSem_Handle,100); /* 等待時間 */if(pdTRUE == xReturn)OLED_ShowString(3,1,"Receive_OK");//elseOLED_ShowString(3,1,"Receive_NO");vTaskDelay(200);}
}//發送任務函數
void Send_task(void *pvParameters)
{BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認為pdPASS */while(1){if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==1){xReturn = xSemaphoreGive( CountSem_Handle );//給出計數信號量if( xReturn == pdTRUE )OLED_ShowString(2,1,"Send_OK");}else{OLED_ShowString(2,1,"Send_NO");}vTaskDelay(200);}
}
二值信號量與計數信號量的區別
二值信號量只有兩種狀態(0和1),通常用于控制互斥訪問,一次只允許一個進程進入臨界區。而計數信號量可以有多個值,用于表示資源的數量,允許多個進程同時訪問有限數量的資源。
講解一下上面兩個代碼的思路
二值信號量
這個我主要是通過按下按鍵然后去釋放二值信號量,按下按鍵釋放二值信號量,然后被接收二值信號量任務接收,該任務就會執行里面的程序,執行完以后退出,二值信號量的信息返回值有由1變0,等待下一次信號的到來。值得注意的是,二值信號量是單次事件,如果需要執行多個事件,執行事件的順序就從高優先級向低優先級執行,不能同時執行。
計數信號量
跟二值信號量的思路邏輯是一樣的,但對于計數信號量而言,它能同時執行多個任務,提高了資源的利用率。
總結
以上是我的個人看法,如有不足,歡迎指出!