1、準備材料 正點原子stm32f407探索者開發板V2.4 STM32CubeMX軟體(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP模擬器 XCOM V2.6串口助手 一個滑動變阻器 2、學習目標 本文主要學習 FreeRTOS 信號量的相關知識 ...
1、準備材料
STM32CubeMX軟體(Version 6.10.0)
Keil µVision5 IDE(MDK-Arm)
一個滑動變阻器
2、學習目標
本文主要學習 FreeRTOS 信號量的相關知識,包括創建/刪除信號量、釋放信號量、獲取信號量等知識
3、前提知識
3.1、信號量概述
信號量是進程間用於通信的一種手段,其是基於隊列實現的,信號量更適用於進程間同步,信號量包括二值信號量(Binary Semaphores)和計數信號量(Counting Semaphores)
二值信號量就是只有一個項的隊列,該隊列不為空則為滿(所謂二值),二值信號量就像一個標誌,適和用於進程間同步的通信
舉個例子:ADC 的周期採集中斷負責採集完成後將採集到的 ADC 的值寫入數據緩存區中並且釋放信號量,總是嘗試獲取信號量的數據處理任務在 ADC 採集中斷釋放信號量之後成功獲取,然後退出阻塞狀態對寫入數據緩存區中採集到的 ADC 值進行處理,上述過程如下圖所示:(註釋1)
如下圖所示為使用二值信號量來同步任務和中斷的工作流程 (註釋2)
計數信號量就是有固定長度的隊列,隊列中每個單元都是一個標誌,其通常用於對多個共用資源的訪問進行控制
舉個例子:一家餐館有 4 張可供用餐的桌子,我們創建一個長度為 4 ,初值為 4 的計數信號量來表示當前可供用餐的桌子數量,當有客人進來用餐時會 “獲取” 一張餐桌,這時用來表示可供用餐的桌子數量的計數信號量就會減少一個,當有客人離開時會 “釋放” 一張餐桌,這時用來表示可供用餐的桌子數量的計數信號量就會增加一個,上述過程如下圖所示:
如下圖所示為計數信號量的工作流程
3.2、創建信號量
信號量在使用之前也必須先創建,信號量被創建完之後是無效的,也即為 0 ,而由於信號量分為二值信號量和計數信號量兩種,因此FreeRTOS也提供了不同的API函數,具體如下所述
/**
* @brief 動態分配記憶體創建二值信號量函數
* @param xSemaphore:創建的二值信號量句柄
* @retval None
*/
void vSemaphoreCreateBinary(SemaphoreHandle_t xSemaphore);
/**
* @brief 靜態分配記憶體創建二值信號量函數
* @param pxSemaphoreBuffer:指向一個StaticSemaphore_t類型的變數,該變數將用於保存信號量的狀態
* @retval 返回創建成功的信號量句柄,如果返回NULL則表示因為pxSemaphoreBuffer為空無法創建
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
StaticSemaphore_t *pxSemaphoreBuffer);
/**
* @brief 動態分配記憶體創建計數信號量函數
* @param uxMaxCount:可以達到的最大計數值
* @param uxInitialCount:創建信號量時分配給信號量的計數值
* @retval 返回創建成功的信號量句柄,如果返回NULL則表示記憶體不足無法創建
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount);
/**
* @brief 靜態分配記憶體創建計數信號量函數
* @param uxMaxCount:可以達到的最大計數值
* @param uxInitialCount:創建信號量時分配給信號量的計數值
* @param pxSempahoreBuffer:指向StaticSemaphore_t類型的變數,該變數然後用於保存信號量的數據結構體
* @retval 返回創建成功的信號量句柄,如果返回NULL則表示因為pxSemaphoreBuffer為空無法創建
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t pxSempahoreBuffer);
3.3、釋放信號量
以下兩個函數不僅僅可以用於釋放二值信號量,還可以用於釋放計數信號量和互斥量,具體如下所示
/**
* @brief 釋放信號量函數
* @param xSemaphore:要釋放的信號量的句柄
* @retval 如果信號量釋放成功,則返回pdTRUE;如果發生錯誤,則返回pdFALSE
*/
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
/**
* @brief 釋放信號量的中斷安全版本函數
* @param pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
* @retval 如果成功給出信號量,則返回pdTRUE,否則errQUEUE_FULL
*/
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken);
3.4、獲取信號量
以下兩個函數不僅僅可以用於獲取二值信號量,還可以用於獲取計數信號量和互斥量,具體如下所示
/**
* @brief 獲取信號量函數
* @param xSemaphore:正在獲取的信號量的句柄
* @param xTicksToWait:等待信號量變為可用的時間
* @retval 成功獲得信號量則返回pdTRUE;如果xTicksToWait過期,信號量不可用,則返回pdFALSE
*/
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
/**
* @brief 獲取信號量的中斷安全版本函數
* @param xSemaphore:正在獲取的信號量的句柄
* @param pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
* @retval 成功獲取則返回pdTRUE,未成功獲取則返回pdFALSE
*/
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken);
3.5、刪除信號量
/**
* @brief 刪除信號量,包括互斥鎖型信號量和遞歸信號量
* @param xSemaphore:被刪除的信號量的句柄
* @retval None
*/
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
3.6、工具函數
/**
* @brief 獲取信號量計數
* @param xSemaphore:正在查詢的信號量的句柄
* @retval 如果信號量是計數信號量,則返回信號量的當前計數值。如果信號量是二進位信號量,則當信號量可用時,返回1,當信號量不可用時,返回 0
*/
UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore);
4、實驗一:二值信號量的應用
4.1、實驗目標
- 創建一個二值信號量 BinarySem_ADC
- 配置 ADC1 IN5 在 500ms 的定時器驅動下周期採集 ADC 值,採集完成後將採集值寫入緩存數組,然後釋放二值信號量 BinarySem_ADC
- 創建一個任務 TASK_ADC,該任務總是嘗試獲取二值信號量 BinarySem_ADC,獲取成功之後,將寫入緩存數組的 ADC 採集值進行轉換,然後通過 USART1 輸出給用戶
4.2、CubeMX相關配置
首先讀者應按照 "FreeRTOS教程1 基礎知識" 章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求
本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀“STM32CubeMX教程9 USART/UART 非同步通信”,如下圖所示
本實驗需要設置 TIM3 作為 ADC1 IN5 觸發源的單通道 ADC 採集,採集周期為 500ms ,因此需要配置 ADC1 和 TIM3,感興趣讀者可以閱讀”STM32CubeMX教程13 ADC - 單通道轉換“實驗,如下圖所示
由於我們將要在 ADC 採集完成中斷中使用 FreeRTOS 的釋放二值信號量的函數,因此需要將其優先順序設置在 15~5 之間,在這裡設置為 7,具體如下圖所示
單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Tasks and Queues 選項卡雙擊預設任務修改其參數,具體如下圖所示
然後在 Configuration 中單擊 Timers and Semaphores ,在 Binary Semaphores 中單擊 Add 按鈕新增加一個名為 BinarySem_ADC 的二值信號量,具體如下圖所示
配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可
4.3、添加其他必要代碼
按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述
首先應該在 freertos.c 中添加信號量的頭文件,如下所述
/*freertos.c中添加頭文件*/
#include "semphr.h"
然後在該文件中重新實現 ADC 採集完成中斷回調函數,在該函數中獲取採集完成的 ADC 值,將其保存在全局變數 adc_value 中,然後釋放二值信號量 BinarySem_ADC ,如下所述
/*轉換完成中斷回調*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
/*定時器中斷啟動單通道轉換*/
if(hadc->Instance == ADC1)
{
adc_value = HAL_ADC_GetValue(hadc);
BaseType_t highTaskWoken = pdFALSE;
if(BinarySem_ADCHandle != NULL)
{
xSemaphoreGiveFromISR(BinarySem_ADCHandle, &highTaskWoken);
portYIELD_FROM_ISR(highTaskWoken);
}
}
}
接下來仍然在該文件中實現任務 TASK_ADC 的函數體內容,該任務函數總是嘗試獲取二值信號量,一旦獲取成功表示 ADC 轉換完成,就將 ADC 轉換完成的值變為電壓值,然後通過 USART1 輸出給用戶顯示,如下所述
/*ADC任務函數*/
void TASK_ADC(void *argument)
{
/* USER CODE BEGIN TASK_ADC */
/* Infinite loop */
for(;;)
{
if(xSemaphoreTake(BinarySem_ADCHandle, portMAX_DELAY) == pdTRUE)
{
uint32_t Volt = (3300 * adc_value)>>12;
printf("val:%d, Volt:%d\r\n", adc_value, Volt);
}
}
/* USER CODE END TASK_ADC */
}
最後在 main.c 文件主函數 main() 中以中斷方式啟動 ADC 轉換即可,如下所述
//以中斷方式啟動ADC1
HAL_ADC_Start_IT(&hadc1);
//啟動ADC1觸發源定時器TIM3
HAL_TIM_Base_Start(&htim3);
4.4、燒錄驗證
燒錄程式,打開串口助手,可以發現每隔一段時間就會輸出當前 ADC1 IN5 通道採集到的 ADC 的值,將其接入一個滑動變阻器,當滑動變阻器從一端滑動到另一端時,串口輸出的採集值也在從 0 逐漸變為最大值 4095 ,整個過程串口輸出信息如下圖所示
5、實驗二:計數信號量的應用
5.1、實驗目標
- 創建一個計數信號量 CountingSem_Tables ,設置最大值為 5 ,初始值設為 5 ,表示飯店內初始有 5 張桌子
- 啟動 RTC 周期喚醒中斷,喚醒周期為 3s ,在 RTC 喚醒中斷中釋放信號量,模擬有客人離開飯店
- 創建任務 TASK_KEY2 ,當按鍵 KEY2 按下時嘗試獲取信號量,模擬客人進店,同時調用獲取信號量計數查詢函數,查詢當前可用餐桌個數
5.2、CubeMX相關配置
首先讀者應按照 "FreeRTOS教程1 基礎知識" 章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求
本實驗需要初始化開發板上 KEY2 用戶按鍵做普通輸入,具體配置步驟請閱讀 “STM32CubeMX教程3 GPIO輸入 - 按鍵響應” ,註意雖開發板不同但配置原理一致,如下圖所示
本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀 “STM32CubeMX教程9 USART/UART 非同步通信” ,如下圖所示
本實驗需要配置 RTC 周期喚醒中斷,具體配置步驟和參數介紹讀者可閱讀”STM32CubeMX教程10 RTC 實時時鐘 - 周期喚醒、鬧鐘A/B事件和備份寄存器“實驗,此處不再贅述,這裡參數、時鐘配置如下圖所示
由於需要在 RTC 周期喚醒中斷中使用 FreeRTOS 的 API 函數,因此 RTC 周期喚醒中斷的優先順序應該設置在 15~5 之間,此處設置為 7 ,具體如下圖所示
單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Tasks and Queues 選項卡,雙擊預設任務修改其參數,具體如下圖所示
然後在 Configuration 中單擊 Timers and Semaphores 選項卡,在最下方的 Counting Semaphores 中單擊右下角的 Add 按鈕增加一個計數信號量,具體如下圖所示
配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可
5.3、添加其他必要代碼
按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述
首先應該在 freertos.c 中添加信號量的頭文件,如下所述
/*freertos.c中添加頭文件*/
#include "stdio.h"
#include "semphr.h"
然後在該文件中重新實現周期喚醒回調函數,該函數用於周期釋放計數信號量,具體如下所示
/*周期喚醒回調函數*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
if(CountingSem_TablesHandle != NULL)
{
BaseType_t highTaskWoken = pdFALSE;
//釋放計數信號量
xSemaphoreGiveFromISR(CountingSem_TablesHandle, &highTaskWoken);
portYIELD_FROM_ISR(highTaskWoken);
}
}
最後仍然在該文件中實現任務 TASK_KEY2 ,該任務負責當按鍵 KEY2 按下時嘗試獲取計數信號量,當無任何按鍵按下時不斷輸出當前計數信號量可用數量,具體如下所示
void TASK_KEY2(void *argument)
{
/* USER CODE BEGIN TASK_KEY2 */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
//獲取計數信號量
BaseType_t result = xSemaphoreTake(CountingSem_TablesHandle, pdMS_TO_TICKS(100));
if(result == pdTRUE) printf("Check In OK\r\n");
else printf("Check In Fail\r\n");
//按鍵消抖
osDelay(pdMS_TO_TICKS(300));
}
else
{
UBaseType_t AvailableTables = uxSemaphoreGetCount(CountingSem_TablesHandle);
printf("Now AvailableTables is : %d\r\n", (uint16_t)AvailableTables);
osDelay(pdMS_TO_TICKS(10));
}
}
/* USER CODE END TASK_KEY2 */
}
5.4、燒錄驗證
燒錄程式,打開串口助手,發現不斷輸出當前可用計數信號量數量,當按住按鍵 KEY2 不鬆開,連續模擬客人進店,可以發現在模擬 3 個客人進店之後,剩餘可用計數信號量的數量變為了 2 個,然後每隔一段時間計算信號量的數量慢慢增加直到最大值 5 ,接著按住按鍵 KEY2 不鬆開,連續模擬 5 個客人進店,當 5 個客人進店之後,再次按下 KEY2 按鍵可以發現串口輸出 ”Check In Fail“,表示當前已無剩餘可用計數信號量,上述整個過程如下圖所示
6、註釋詳解
註釋1:圖片來源於 STM32Cube高效開發教程(高級篇) 第五章
註釋2:圖片來源於 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
參考資料
Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf