一、概述
二值信號量跟互斥信號量非常相似,區別是互斥信號量擁有優先級繼承機制,而二值信號量沒有。因此二值信號量更適用於同步(任務與任務或任務與中斷的同步)。
信號量API函數允許設置一個阻塞時間,阻塞時間是當任務獲取信號量的時候由於信號量無效而導致任務進入阻塞態的最大時鍾節拍數。如果多個任務同時阻塞在同一個信號量上,那么優先級最高的任務優先獲得信號量,這樣當信號量有效的時候,高優先級的任務就會解除阻塞狀態。
二值信號量其實就是只有一個隊列項的隊列,這個特殊的隊列要么是滿的,要么是空的,正好就是二值的。任務和中斷使用這個特殊的隊列時不用在乎隊列中存了什么消息,只需要知道這個隊列是滿的還是空的即可。可以利用這個機制來完成人物與終端之間的同步。
二、應用
二值信號量的一種使用方法:
1、創建二值信號量(新版的創建二值信號量的API函數,創建后二值信號量默認是空的);
2、中斷服務函數中釋放二值信號量(如果沒有釋放,任務中獲取二值信號量超時,獲取失敗);
3、任務中獲取二值信號量(在阻塞時間等待二值信號量釋放,進入阻塞狀態,把CPU讓給其他任務);
三、相關的API函數
1.二值信號量創建函數:
函數 | 描述 |
vSemaphoreCreateBinary() | 老版本的API函數,動態創建二值信號量 |
xSemaphoreCreateBinary() | 新版本的API函數,動態創建二值信號量 |
xSemaphoreCreateBinaryStatic() | 靜態創建二值信號量 |
這里只介紹最常用的xSemaphoreCreateBinary()函數,定義如下:
SemaphoreHandle_t xSemaphoreCreateBinary(void) 參數:無 返回值:NULL——二值信號量創建失敗,其他值:創建成功的二值信號量的句柄
2.釋放信號量
函數 | 描述 |
xSemaphoreGive() | 任務級信號量釋放函數 |
xSemaphoreGiveFromISR() | 中斷級信號量釋放函數 |
1. xSemaphoreGive() BaseType_t xSemaphoreGive(xSemaphore) 參數:xSemaphore:要釋放的信號量句柄 返回值:pdPASS:釋放信號量成功;errQUEUE_FULL:釋放信號量失敗
2. xSemaphoreGiveFromISR() BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t * pxHigherPriorityTaskWoken) 參數:xSemaphore:要釋放的信號量句柄 pxHigherPriorityTaskWoken:標記退出此函數以后是否進行任務切換,當此值為pdTRUE的時候,則在退出中斷服務函數之前一定要進行一次任務切換。 返回值:pdPASS:釋放信號量成功 errQUEUE_FULL:釋放信號量失敗
3.獲取信號量
函數 | 描述 |
xSemaphoreTake() | 任務級獲取信號量函數 |
xSemaphoreTakeFromISR() | 中斷級獲取信號量函數 |
1. BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime) 參數:xSemaphore:要獲取的信號量句柄;xBlockTime:阻塞時間 返回值:pdTRUE:獲取信號量成功;pdFALSE:超時,獲取信號量失敗。 2. BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t pxHigherPriorityTaskWoken) 參數:xSemaphore:要獲取的信號量句柄; pxHigherPriorityTaskWoken: 標記退出此函數后是否進行任務切換,當此值為pdTRUE的時候,則在退出中斷服務函數前一定要進行一次任務切換;
四、實驗:
平台:GD32E507ZET6開發板
實驗目的:開發板與PC機通過串口通訊,PC機上的串口助手定時發送數據到開發板上,開發板接收到數據后原封不動地轉發回PC機。
1. 步驟:
1.移植FreeRTOS; 2.初始化串口(配置中斷的優先級,使能TX和RX引腳的時鍾,使能USART0的時鍾,配置波特率,數據位長度,停止位,校驗位等,使能輸出,使能輸入,使能串口,使能接收中斷和空閑中斷); 3.創建開始任務,開始任務的工作是創建二值信號量,創建串口任務; 4.串口中斷服務函數中判斷是接收緩沖區非空中斷還是空閑中斷,為什么要開啟這兩個中斷呢?因為沒接收到一個字節的數據就會觸發前者,而當一連串字符接收完成后則會觸發后者,開啟后者就可以接收不定長的數據。當觸發的中斷是接收緩沖區非空的中斷時,讀取數據,而當觸發的中斷是空閑中斷時,說明結束完一幀數據,此時就可以釋放信號量; 5、串口任務的工作是獲取信號量,當獲取到信號量成功后,就可以把接收到的數據原封不動地發送出去。
2. 代碼:
main.c
#include "include.h" #include "gd32e507z_eval.h" void Start_Task(void * parameter); #define START_STK_SIZE 120 #define START_TASK_PRIO 1 TaskHandle_t START_TASK_Handle; #define LED_STK_SIZE 120 #define LED_TASK_PRIO 2 TaskHandle_t LED_TASK_Handle; #define USART_STK_SIZE 500 #define USART_TASK_PRIO 3 TaskHandle_t USART_TASK_Handle; SemaphoreHandle_t BinarySemaphore; /*! \brief main function \param[in] none \param[out] none \retval none */ int main(void) { /* configure 4 bits pre-emption priority */ nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0); systick_config(); /* initialize LED */ LED_Init(); /* initialize USART0 */ /* configure NVIC and systick */ nvic_irq_enable(USART0_IRQn, 0, 0);//中斷優先級一定要配,不然中斷就沒反應 USART_Config(usart0,115200,USART_WL_8BIT,USART_STB_1BIT,USART_PM_NONE); usart_interrupt_enable(USART0,USART_INT_RBNE); usart_interrupt_enable(USART0,USART_INT_IDLE); xTaskCreate(Start_Task,"Start_Task",START_STK_SIZE,NULL,START_TASK_PRIO,&START_TASK_Handle); vTaskStartScheduler(); while(1){ } } void Start_Task(void * parameter) { BinarySemaphore = xSemaphoreCreateBinary(); xTaskCreate(USART_Task,"USART_Task",USART_STK_SIZE,NULL,USART_TASK_PRIO,&USART_TASK_Handle); vTaskDelete(NULL); }
usart.c
#include "usart.h" #include "string.h" #include "semphr.h" /*************************************** * USART0_TX -- PA9 USART0_RX -- PA10 * USART1_TX -- PA2 USART1_RX -- PA3 * USART2_TX -- PB10 USART2_RX -- PB11 * UART3_TX -- PC10 UART3_RX -- PC11 * UART4_TX -- PC12 UART4_RX -- PD2 * USART5_TX -- PC6 USART5_RX -- PC7 ****************************************/ static rcu_periph_enum USART_CLK[USARTn] = {RCU_USART0,RCU_USART1,RCU_USART2,RCU_UART3,RCU_UART4,RCU_USART5}; static rcu_periph_enum USART_GPIO_CLK[USARTn] = {USART0_GPIO_CLK,USART1_GPIO_CLK,USART2_GPIO_CLK,UART3_GPIO_CLK,UART4_GPIO_CLK,USART5_GPIO_CLK}; static uint32_t USARTx_TX_PORT[USARTn] = {GPIOA,GPIOA,GPIOB,GPIOC,GPIOC,GPIOC}; static uint32_t USARTx_TX_PIN[USARTn] = {GPIO_PIN_9,GPIO_PIN_2,GPIO_PIN_10,GPIO_PIN_10,GPIO_PIN_12,GPIO_PIN_6}; static uint32_t USARTx_RX_PORT[USARTn] = {GPIOA,GPIOA,GPIOB,GPIOC,GPIOD,GPIOC}; static uint32_t USARTx_RX_PIN[USARTn] = {GPIO_PIN_10,GPIO_PIN_3,GPIO_PIN_11,GPIO_PIN_11,GPIO_PIN_2,GPIO_PIN_7}; static uint32_t USARTx[USARTn] = {USART0,USART1,USART2,UART3,UART4,USART5}; void USART_Config(uint8_t com,uint32_t Baudrate,uint32_t WL,uint32_t STB,uint32_t Parity) { /* enable GPIO clock */ rcu_periph_clock_enable(USART_GPIO_CLK[com]); /* enable USART clock */ rcu_periph_clock_enable(USART_CLK[com]); /* connect port to USARTx_Tx */ gpio_init(USARTx_TX_PORT[com], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, USARTx_TX_PIN[com]); /* connect port to USARTx_Rx */ gpio_init(USARTx_RX_PORT[com], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, USARTx_RX_PIN[com]); /* USART configure */ usart_deinit(USARTx[com]); usart_word_length_set(USARTx[com], WL); usart_stop_bit_set(USARTx[com], STB); usart_parity_config(USARTx[com], Parity); usart_baudrate_set(USARTx[com], Baudrate); usart_receive_config(USARTx[com], USART_RECEIVE_ENABLE); usart_transmit_config(USARTx[com], USART_TRANSMIT_ENABLE); usart_enable(USARTx[com]); } void USART_Transmit_String(uint32_t usart_periph,uint8_t * str) { uint32_t len = strlen((char *)str); for(int i = 0;i < len;i++) { usart_data_transmit(usart_periph,*str); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); str++; } } void USART_Task(void * parameter) { BaseType_t err = pdPASS; uint8_t data[100] = "Welcom to Shenzhen\r\n"; while(1) { if(BinarySemaphore != NULL) { err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY); if(err == pdPASS) {
memcpy(data,REV_DATA,REV_LEN);
USART_Transmit_String(USART0,data);
memset(data,0,sizeof(data));
memset(REV_DATA,0,sizeof(REV_DATA));
REV_LEN = 0;
} } else { vTaskDelay(10); } } }
usart.h
#ifndef _USART_H_ #define _USART_H_ #include "include.h" #define USARTn 6 #define USART0_GPIO_CLK RCU_GPIOA #define USART1_GPIO_CLK RCU_GPIOA #define USART2_GPIO_CLK RCU_GPIOB #define UART3_GPIO_CLK RCU_GPIOC #define UART4_GPIO_CLK (RCU_GPIOC|RCU_GPIOD) #define USART5_GPIO_CLK RCU_GPIOC typedef enum { usart0 = 0, usart1, usart2, uart3, uart4, usart5, }COMx; void USART_Config(uint8_t com,uint32_t Baudrate,uint32_t WL,uint32_t STB,uint32_t Parity); void USART_Transmit_String(uint32_t usart_periph,uint8_t * str); void USART_Task(void * parameter); #endif
include.h
#ifndef _INCLUDE_H_ #define _INCLUDE_H_ #include "gd32e50x.h" #include "systick.h" #include "led.h" #include "usart.h" #include "FreeRTOSConfig.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "semphr.h" extern SemaphoreHandle_t BinarySemaphore; extern uint8_t REV_DATA[1000]; extern uint32_t REV_LEN; #endif
gd32e50x_it.c
void USART0_IRQHandler(void) { uint8_t data; if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) { data = usart_data_receive(USART0); REV_DATA[REV_LEN++]= data; // usart_interrupt_flag_clear(USART0,USART_INT_FLAG_RBNE); } if(usart_flag_get(USART0,USART_FLAG_IDLE)!=RESET) { xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
//以下兩行代碼作用是清除空閑標志,先讀取狀態寄存器,在讀取數據寄存器 GET_BITS(USART_STAT0(USART0), 0U, 8U); GET_BITS(USART_DATA(USART0), 0U, 8U); } }
3. 實驗現象:
PC端串口助手沒發送一串字符串都可以收到相同的一串字符串: