天天看點

FreeRTOS學習筆記(4)——信号量一、頭檔案二、二值信号量三、計數信号量

一、頭檔案

#include "FreeRTOS.h"
#include "semphr.h"
           

二、二值信号量

2.1 運作機制

FreeRTOS學習筆記(4)——信号量一、頭檔案二、二值信号量三、計數信号量

建立信号量時,系統會為建立的信号量對象配置設定記憶體,并把可用信号量初始化為使用者自定義的個數, 二值信号量的最大可用信号量個數為 1。

二值信号量擷取,任何任務都可以從建立的二值信号量資源中擷取一個二值信号量,擷取成功則傳回正确,否則任務會根據使用者指定的阻塞逾時時間來等待其它任務/中斷釋放信号量。在等待這段時間,系統将任務變成阻塞态,任務将被挂到該信号量的阻塞等待清單中。

假如某個時間中斷/任務釋放了信号量,那麼,由于擷取無效信号量而進入阻塞态的任務将獲得信号量并且恢複為就緒态狀态。

2.2 相關API說明

2.2.1 xSemaphoreCreateBinary

用于建立一個二值信号量,并傳回一個句柄。

函數 #define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
參數
傳回值 二值信号量句柄

要想使用該函數必須在

FreeRTOSConfig.h

中把

configSUPPORT_DYNAMIC_ALLOCATION

定義為 1 來使能。

FreeRTOS學習筆記(4)——信号量一、頭檔案二、二值信号量三、計數信号量

2.2.2 vSemaphoreDelete

用于删除一個信号量,包括二值信号量,計數信号量,互斥量和遞

歸互斥量。如果有任務阻塞在該信号量上,那麼不要删除該信号量。

函數 void vSemaphoreDelete( SemaphoreHandle_t xSemaphore )
參數 xSemaphore: 信号量句柄
傳回值

2.2.3 xSemaphoreGive(任務)

用于釋放信号量的宏。釋放的信号量對象必須是已經被建立的,可以用于二值信号量、計數信号量、互斥量的釋放,但不能釋放由函數 xSemaphoreCreateRecursiveMutex() 建立的遞歸互斥量。此外該函數不能在中斷中使用。

函數 xSemaphoreGive( SemaphoreHandle_t xSemaphore )
參數 xSemaphore: 信号量句柄
傳回值 成功傳回 pdTRUE,否則傳回 pdFALSE

2.2.4 xSemaphoreGiveFromISR(中斷)

用于釋放一個信号量,帶中斷保護。被釋放的信号量可以是二進制信号量和計數信号量。和普通版本的釋放信号量 API 函數有些許不同,它不能釋放互斥量,這是因為互斥量不可以在中斷中使用,互斥量的優先級繼承機制隻能在任務中起作用,而在中斷中毫無意義。

函數 xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken )
參數

xSemaphore: 信号量句柄

pxHigherPriorityTaskWoken: 從 FreeRTOS V7.3.0 版本開始, pxHigherPriorityTaskWoken 一個或者多個任務有可能阻塞在同一個信号量上,調用函數 xSemaphoreTakeFromISR()會喚醒阻塞在該信号量上優先級最高的信号量入隊任務,如果被喚醒的任務的優先級大于或者等于被中斷的任務的優先級,那麼形參 pxHigherPriorityTaskWoken 就會被設定為 pdTRUE,然後在中斷退出前執行一次上下文切換,中斷退出後則直接傳回剛剛被喚醒的高優先級的任務。從 FreeRTOS V7.3.0 版本開始, pxHigherPriorityTaskWoken 是一個可選的參數,可以設定為 NULL

傳回值 成功傳回 pdTRUE,否則傳回 errQUEUE_FULL

2.2.5 xSemaphoreTake(任務)

用于擷取信号量,不帶中斷保護。擷取的信号量對象可以是二值信号量、計數信号量和互斥量,但是遞歸互斥量并不能使用這個 API 函數擷取。

函數 xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime )
參數

xSemaphore: 信号量句柄

xBlockTime: 等待信号量可用的最大逾時時間,機關為 tick(即系統節拍周期)。如果宏 INCLUDE_vTaskSuspend 定義為 1 且形參 xTicksToWait 設定為 portMAX_DELAY ,則任務将一直阻塞在該信号量上(即沒有逾時時間)

傳回值 成功傳回 pdTRUE,否則傳回 errQUEUE_EMPTY

2.2.6 xSemaphoreTakeFromISR(中斷)

用于擷取信号量,是一個不帶阻塞機制擷取信号量的函數,擷取對象必須由是已經建立的信号量,信号量類型可以是二值信号量和計數信号量,它與 xSemaphoreTake()函數不同,它不能用于擷取互斥量,因為互斥量不可以在中斷中使用,并且互斥量特有的優先級繼承機制隻能在任務中起作用,而在中斷中毫無意義。

函數 xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken)
參數

xSemaphore: 信号量句柄

pxHigherPriorityTaskWoken: 一個或者多個任務有可能阻塞在同一個信号量上,調用函數 xSemaphoreTakeFromISR()會喚醒阻塞在該信号量上優先級最高的信号量入隊任務,如果被喚醒的任務的優先級大于或者等于被中斷的任務的優先級,那麼形參 pxHigherPriorityTaskWoken 就會被設定為 pdTRUE,然後在中斷退出前執行一次上下文切換,中斷退出後則直接傳回剛剛被喚醒的高優先級的任務。從 FreeRTOS V7.3.0 版本開始, pxHigherPriorityTaskWoken 是一個可選的參數,可以設定為 NULL

傳回值 成功傳回 pdTRUE,否則傳回 errQUEUE_EMPTY

2.3 示例

2.3.1 阻塞式

/* FreeRTOS 頭檔案 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 開發闆硬體 bsp 頭檔案 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任務句柄 ********************************/
/*
* 任務句柄是一個指針,用于指向一個任務,當任務建立好之後,它就具有了一個任務句柄
* 以後我們要想操作這個任務都需要通過這個任務句柄,如果是自身的任務操作自己,那麼
* 這個句柄可以為 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 建立任務句柄 */
static TaskHandle_t Receive_Task_Handle = NULL;/* LED 任務句柄 */
static TaskHandle_t Send_Task_Handle = NULL;/* KEY 任務句柄 */
 
/***************************** 核心對象句柄 *****************************/
/*
* 信号量,消息隊列,事件标志組,軟體定時器這些都屬于核心的對象,要想使用這些核心
* 對象,必須先建立,建立成功之後會傳回一個相應的句柄。實際上就是一個指針,後續我
* 們就可以通過這個句柄操作這些核心對象。
*
* 核心對象說白了就是一種全局的資料結構,通過這些資料結構我們可以實作任務間的通信,
* 任務間的事件同步等各種功能。至于這些功能的實作我們是通過調用這些核心對象的函數
* 來完成的
*
*/
SemaphoreHandle_t BinarySem_Handle = NULL;

static void AppTaskCreate(void);/* 用于建立任務 */ 
static void Receive_Task(void* pvParameters);/* Receive_Task 任務實作 */ 
static void Send_Task(void* pvParameters);/* Send_Task 任務實作 */ 
static void BSP_Init(void);/* 用于初始化闆載相關資源 */

int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */

    /* 開發闆硬體初始化 */
    BSP_Init();
    printf("按下 KEY1 或者 KEY2 進行任務與任務間的同步\n");
    /* 建立 AppTaskCreate 任務 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,/* 任務入口函數 */
                          (const char* )"AppTaskCreate",/* 任務名字 */
                          (uint16_t )512, /* 任務棧大小 */
                          (void* )NULL,/* 任務入口函數參數 */
                          (UBaseType_t )1, /* 任務的優先級 */
                          (TaskHandle_t*)&AppTaskCreate_Handle);/* 任務控制塊指針 */
    /* 啟動任務排程 */
    if (pdPASS == xReturn)
    {
        vTaskStartScheduler(); /* 啟動任務,開啟排程 */
    }
    else
    {
        return -1;
    } 

    while (1); /* 正常不會執行到這裡 */
}

/***********************************************************************
* @ 函數名 : AppTaskCreate
* @ 功能說明: 為了友善管理,所有的任務建立函數都放在這個函數裡面
* @ 參數 : 無
* @ 傳回值 : 無
***************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */
  
    taskENTER_CRITICAL(); //進入臨界區
   
    /* 建立 BinarySem */ 
    BinarySem_Handle = xSemaphoreCreateBinary();
    if (NULL != BinarySem_Handle) 
    {
        printf("BinarySem_Handle 二值信号量建立成功!\r\n"); 
    }
  
    /* 建立 Receive_Task 任務 */
    xReturn = xTaskCreate((TaskFunction_t )Receive_Task,/* 任務入口函數 */
                          (const char* )"Receive_Task",/* 任務名字 */
                          (uint16_t )512, /* 任務棧大小 */
                          (void* )NULL, /* 任務入口函數參數 */
                          (UBaseType_t )2, /* 任務的優先級 */
                          (TaskHandle_t* )&Receive_Task_Handle);/* 任務控制塊指針 */
    if (pdPASS == xReturn)
    {
        printf("建立 Receive_Task 任務成功!\r\n");
    }
  
    /* 建立 Send_Task 任務 */
    xReturn = xTaskCreate((TaskFunction_t )Send_Task, /* 任務入口函數 */
                          (const char* )"Send_Task",/* 任務名字 */
                          (uint16_t )512, /* 任務棧大小 */
                          (void* )NULL,/* 任務入口函數參數 */
                          (UBaseType_t )3, /* 任務的優先級 */
                          (TaskHandle_t* )&Send_Task_Handle);/* 任務控制塊指針 */
    if (pdPASS == xReturn)
    {
        printf("建立 Send_Task 任務成功!\n\n");
    }
    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任務
    
    taskEXIT_CRITICAL(); //退出臨界區
}

/**********************************************************************
* @ 函數名 : Receive_Task
* @ 功能說明: Receive_Task 任務主體
* @ 參數 :
* @ 傳回值 : 無
********************************************************************/
static void Receive_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */ 
    while (1) 
    { 
        //擷取二值信号量 xSemaphore,沒擷取到則一直等待 
        xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */ 
                                portMAX_DELAY); /* 等待時間 */ 
        if (pdTRUE == xReturn) 
        {
            printf("BinarySem_Handle 二值信号量擷取成功!\n\n"); 
            LED1_TOGGLE; 
        } 
    }
} 

/**********************************************************************
* @ 函數名 : Send_Task
* @ 功能說明: Send_Task 任務主體
* @ 參數 :
* @ 傳回值 : 無
********************************************************************/
static void Send_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */ 
    while (1) 
    { 
        /* KEY1 被按下 */
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xSemaphoreGive( BinarySem_Handle );//給出二值信号量 
            if ( xReturn == pdTRUE ) 
            {
                printf("BinarySem_Handle 二值信号量釋放成功!\r\n"); 
           }
            else 
            {
                printf("BinarySem_Handle 二值信号量釋放失敗!\r\n"); 
            }
        }

        /* KEY2 被按下 */ 
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            xReturn = xSemaphoreGive( BinarySem_Handle );//給出二值信号量 
            if ( xReturn == pdTRUE ) 
            {
                printf("BinarySem_Handle 二值信号量釋放成功!\r\n"); 
            }
            else 
            {
                printf("BinarySem_Handle 二值信号量釋放失敗!\r\n"); 
            }
        } 
        vTaskDelay(20); 
    } 
} 

/***********************************************************************
* @ 函數名 : BSP_Init
* @ 功能說明: 闆級外設初始化,所有闆子上的初始化均可放在這個函數裡面
* @ 參數 :
* @ 傳回值 : 無
*********************************************************************/
static void BSP_Init(void)
{
    /*
    * STM32 中斷優先級分組為 4,即 4bit 都用來表示搶占優先級,範圍為:0~15
    * 優先級分組隻需要分組一次即可,以後如果有其他的任務需要用到中斷,
    * 都統一用這個優先級分組,千萬不要再分組,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
   
    /* LED 初始化 */
    LED_GPIO_Config();
  
    /* 序列槽初始化 */
    USART_Config();
   
    /* 按鍵初始化 */
    Key_GPIO_Config();     
}
           

2.3.2 非阻塞式

void vTestISR( void )
{
    BaseType_t pxHigherPriorityTaskWoken;
    uint32_t ulReturn;
    /* 進入臨界段,臨界段可以嵌套 */
    ulReturn = taskENTER_CRITICAL_FROM_ISR();
   
    /* 判斷是否産生中斷 */
    {
        /* 如果産生中斷,清除中斷标志位 */
  
        //釋放二值信号量,發送接收到新資料标志,供前台程式查詢 
        xSemaphoreGiveFromISR(BinarySem_Handle,&pxHigherPriorityTaskWoken); 
  
        //如果需要的話進行一次任務切換,系統會判斷是否需要進行切換 
        portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); 
    }
   
    /* 退出臨界段 */
    taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}
           

三、計數信号量

3.1 運作機制

FreeRTOS學習筆記(4)——信号量一、頭檔案二、二值信号量三、計數信号量

計數信号量可以用于資源管理,允許多個任務擷取信号量通路共享資源,但會限制任 務的最大數目。通路的任務數達到可支援的最大數目時,會阻塞其他試圖擷取該信号量的任務,直到有任務釋放了信号量。這就是計數型信号量的運作機制,雖然計數信号量允許多個任務通路同一個資源,但是也有限定,比如某個資源限定隻能有 3 個任務通路,那麼第 4 個任務通路的時候,會因為擷取不到信号量而進入阻塞,等到有任務(比如任務 1)釋放掉該資源的時候,第 4 個任務才能擷取到信号量進而進行資源的通路。

3.2 相關API說明

3.2.1 xSemaphoreCreateCounting

用于建立一個計數信号量,并傳回一個句柄。

函數 SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount)
參數

uxMaxCount: 計數信号量的最大值,當達到這個值的時候,信号量不能再被釋放

uxInitialCount: 建立計數信号量的初始值

傳回值 如果建立成功則傳回一個計數信号量句柄,用于通路建立的計數信号量。如果建立不成功則傳回 NULL

要想使用該函數必須在

FreeRTOSConfig.h

中把

configSUPPORT_DYNAMIC_ALLOCATION

定義為 1 來使能。

FreeRTOS學習筆記(4)——信号量一、頭檔案二、二值信号量三、計數信号量

3.3 示例

/* FreeRTOS 頭檔案 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 開發闆硬體 bsp 頭檔案 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
/**************************** 任務句柄 ********************************/
/*
* 任務句柄是一個指針,用于指向一個任務,當任務建立好之後,它就具有了一個任務句柄
* 以後我們要想操作這個任務都需要通過這個任務句柄,如果是自身的任務操作自己,那麼
* 這個句柄可以為 NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 建立任務句柄 */
static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task 任務句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task 任務句柄 */
 
/***************************** 核心對象句柄 *****************************/
/*
* 信号量,消息隊列,事件标志組,軟體定時器這些都屬于核心的對象,要想使用這些核心
* 對象,必須先建立,建立成功之後會傳回一個相應的句柄。實際上就是一個指針,後續我
* 們就可以通過這個句柄操作這些核心對象。
*
* 核心對象說白了就是一種全局的資料結構,通過這些資料結構我們可以實作任務間的通信,
* 任務間的事件同步等各種功能。至于這些功能的實作我們是通過調用這些核心對象的函數
* 來完成的
*
*/
SemaphoreHandle_t CountSem_Handle = NULL;

static void AppTaskCreate(void);/* 用于建立任務 */ 
static void Take_Task(void* pvParameters);/* Take_Task 任務實作 */
static void Give_Task(void* pvParameters);/* Give_Task 任務實作 */
static void BSP_Init(void);/* 用于初始化闆載相關資源 */

int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */

    /* 開發闆硬體初始化 */
    BSP_Init();
    printf("車位預設值為 5 個,按下 KEY1 申請車位,按下 KEY2 釋放車位!\n\n");
    /* 建立 AppTaskCreate 任務 */
    xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,/* 任務入口函數 */
                          (const char* )"AppTaskCreate",/* 任務名字 */
                          (uint16_t )512, /* 任務棧大小 */
                          (void* )NULL,/* 任務入口函數參數 */
                          (UBaseType_t )1, /* 任務的優先級 */
                          (TaskHandle_t*)&AppTaskCreate_Handle);/* 任務控制塊指針 */
    /* 啟動任務排程 */
    if (pdPASS == xReturn)
    {
        vTaskStartScheduler(); /* 啟動任務,開啟排程 */
    }
    else
    {
        return -1;
    } 

    while (1); /* 正常不會執行到這裡 */
}

/***********************************************************************
* @ 函數名 : AppTaskCreate
* @ 功能說明: 為了友善管理,所有的任務建立函數都放在這個函數裡面
* @ 參數 : 無
* @ 傳回值 : 無
***************************************************************/
static void AppTaskCreate(void)
{
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */
  
    taskENTER_CRITICAL(); //進入臨界區
   
    /* 建立 CountSem */
    CountSem_Handle = xSemaphoreCreateCounting(5,5);
    if (NULL != CountSem_Handle) 
    {
        printf("CountSem_Handle 計數信号量建立成功!\r\n");
    }
  
    /* 建立 Take_Task 任務 */
    xReturn = xTaskCreate((TaskFunction_t )Take_Task, /* 任務入口函數 */
                          (const char* )"Take_Task",/* 任務名字 */
                          (uint16_t )512, /* 任務棧大小 */
                          (void* )NULL, /* 任務入口函數參數 */
                          (UBaseType_t )2, /* 任務的優先級 */
                          (TaskHandle_t* )&Take_Task_Handle);/* 任務控制塊指針 */
    if (pdPASS == xReturn)
    {
        printf("建立 Take_Task 任務成功!\r\n");
    }
  
    /* 建立 Give_Task 任務 */
    xReturn = xTaskCreate((TaskFunction_t )Give_Task, /* 任務入口函數 */
                          (const char* )"Give_Task",/* 任務名字 */
                          (uint16_t )512, /* 任務棧大小 */
                          (void* )NULL,/* 任務入口函數參數 */
                          (UBaseType_t )3, /* 任務的優先級 */
                          (TaskHandle_t* )&Give_Task_Handle);/* 任務控制塊指針 */
    if (pdPASS == xReturn)
    {
        printf("建立 Give_Task 任務成功!\n\n");
    }
    vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任務
    
    taskEXIT_CRITICAL(); //退出臨界區
}

/**********************************************************************
* @ 函數名 : Take_Task
* @ 功能說明: Take_Task 任務主體
* @ 參數 :
* @ 傳回值 : 無
********************************************************************/
static void Take_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdPASS;/* 定義一個建立資訊傳回值,預設為 pdPASS */ 
    while (1) 
    { 
        //如果 KEY1 被按下 
        if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) 
        { 
            /* 擷取一個計數信号量 */ 
            xReturn = xSemaphoreTake(CountSem_Handle, /* 計數信号量句柄 */ 
                                                  0); /* 等待時間:0 */ 
            if ( pdTRUE == xReturn ) 
            {
                printf( "KEY1 被按下,成功申請到停車位。\n" ); 
            }
            else 
            {
                printf( "KEY1 被按下,不好意思,現在停車場已滿!\n" ); 
            }
        }
        vTaskDelay(20); //每 20ms 掃描一次
    }
} 

/**********************************************************************
* @ 函數名 : Give_Task
* @ 功能說明: Give_Task 任務主體
* @ 參數 :
* @ 傳回值 : 無
********************************************************************/
static void Give_Task(void* parameter) 
{ 
    BaseType_t xReturn = pdTRUE;/* 定義一個建立資訊傳回值,預設為 pdPASS */ 
    /* 任務都是一個無限循環,不能傳回 */ 
    while (1) 
    { 
        //如果 KEY2 被按下 
        if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) 
        { 
            /* 擷取一個計數信号量 */ 
            xReturn = xSemaphoreGive(CountSem_Handle);//給出計數信号量 
            if ( pdTRUE == xReturn ) 
            {
                printf( "KEY2 被按下,釋放 1 個停車位。\n" ); 
            }
            else 
            {
                printf( "KEY2 被按下,但已無車位可以釋放!\n" ); 
            } 
            vTaskDelay(20); //每 20ms 掃描一次 
    } 
}

/***********************************************************************
* @ 函數名 : BSP_Init
* @ 功能說明: 闆級外設初始化,所有闆子上的初始化均可放在這個函數裡面
* @ 參數 :
* @ 傳回值 : 無
*********************************************************************/
static void BSP_Init(void)
{
    /*
    * STM32 中斷優先級分組為 4,即 4bit 都用來表示搶占優先級,範圍為:0~15
    * 優先級分組隻需要分組一次即可,以後如果有其他的任務需要用到中斷,
    * 都統一用這個優先級分組,千萬不要再分組,切忌。
    */
    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
   
    /* LED 初始化 */
    LED_GPIO_Config();
  
    /* 序列槽初始化 */
    USART_Config();
   
    /* 按鍵初始化 */
    Key_GPIO_Config();     
}
           

• 由 Leung 寫于 2020 年 11 月 17 日

• 參考:野火FreeRTOS視訊與PDF教程

繼續閱讀