学习目标

  1. 理解信号量的概念
  2. 掌握信号量发流程
  3. 掌握二进制信号量
  4. 熟悉计数型信号量
  5. 掌握互斥信号量
  6. 熟悉递归互斥信号量

    学习内容

    概念

    在 FreeRTOS 中,信号量(Semaphore)是一种用于实现任务之间同步和资源共享的机制。它是一种计数型的同步原语,用于控制对共享资源的访问和保护。
    在FreeRTOS中,包含4种类型的信号量:

  7. 二进制信号量(Binary Semaphore):

二进制信号量是最基本的信号量类型。它的计数值要么为0(表示信号量已被获取),要么为1(表示信号量可用)。二进制信号量常用于实现互斥访问共享资源的场景,只允许一个任务访问资源。
在 FreeRTOS 中,你可以使用 xSemaphoreCreateBinary() 函数创建一个二进制信号量。任务可以通过 xSemaphoreTake() 函数获取信号量,通过 xSemaphoreGive() 函数释放信号量。

  1. 计数型信号量(Counting Semaphore):

计数型信号量允许指定初始计数值,并支持多个任务同时获取信号量。计数型信号量的计数值表示可用的资源数量。
在 FreeRTOS 中,你可以使用 xSemaphoreCreateCounting() 函数创建一个计数型信号量。任务可以使用 xSemaphoreTake() 函数获取信号量,使用 xSemaphoreGive() 函数释放信号量。

  1. 互斥信号量(Mutex Semaphore):

互斥信号量也用于实现资源的互斥访问,类似于二进制信号量。但与二进制信号量不同的是,互斥信号量允许同一个任务多次获取信号量,而不会导致死锁。在任务持有互斥信号量时,其他任务无法获取该信号量,必须等待该任务释放信号量。
在 FreeRTOS 中,你可以使用 xSemaphoreCreateMutex() 函数创建一个互斥信号量。任务可以使用 xSemaphoreTake()函数获取信号量,使用 xSemaphoreGive() 函数释放信号量。

  1. 递归互斥信号量(Recursive Mutex Semaphore):

递归互斥信号量是一种特殊的信号量类型,用于解决任务在嵌套调用中对资源的重复获取。它允许同一个任务多次获取信号量而不会导致死锁。
在 FreeRTOS 中,你可以使用 xSemaphoreCreateRecursiveMutex() 函数创建一个递归互斥信号量。任务可以使用 xSemaphoreTakeRecursive() 函数多次获取信号量,并使用 xSemaphoreGiveRecursive() 函数相应地释放信号量。

开发流程

  1. 创建信号量
  2. 开启一个任务,用来等待信号量
  3. 开启一个任务,用来发送信号量

059.png

二进制信号量

功能 描述
xSemaphoreCreateBinary 创建二进制信号量
xSemaphoreTake 等待信号
xSemaphoreGive 发送信号

信号量的创建

  1. SemaphoreHandle_t xSemaphoreCreateBinary();

返回值为信号量的句柄。

等待信号操作

  1. BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime );
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. TickType_t xBlockTime表示要等待的时间。通常我们一直等到有信号到来,这里我们可以填写portMAX_DELAY
  3. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

发送信号操作

  1. BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

案例一

  1. 开启两个任务,分别去等待信号。
  2. 开启按键扫描任务,当点击按键时,发送信号 ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include “main.h”

    include “FreeRTOS.h”

    include “task.h”

    include “semphr.h”

    include “Usart0.h”

TaskHandle_t task_handler; TaskHandle_t task_key_handler; TaskHandle_t task1_handler; TaskHandle_t task2_handler; SemaphoreHandle_t sema_handler;

void task1(void *pvParameters) { BaseType_t result; while(1) { result = xSemaphoreTake(sema_handler, portMAX_DELAY); if(result == pdTRUE) { printf(“task1\n”); } else { printf(“task1 Error\n”); } } }

void task2(void *pvParameters) { BaseType_t result; while(1) { result = xSemaphoreTake(sema_handler, portMAX_DELAY); if(result == pdTRUE) { printf(“task2\n”); } else { printf(“task2 Error\n”); } } }

void task_key(void *pvParameters) { FlagStatus pre_state = RESET; BaseType_t result; while(1) { FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0); if(SET == state && pre_state == RESET) { // 当前高电平, 上一次为低电平,按下 pre_state = state;

  1. result = xSemaphoreGive(sema_handler);
  2. } else if(RESET == state && pre_state == SET) {
  3. // 当前高电平, 上一次为低电平,抬起
  4. pre_state = state;
  5. }
  6. vTaskDelay(20);
  7. }

}

void start_task(void *pvParameters) { taskENTER_CRITICAL();

  1. xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
  2. xTaskCreate(task1, "task1", 64, NULL, 3, &task1_handler);
  3. xTaskCreate(task2, "task2", 64, NULL, 2, &task2_handler);
  4. vTaskDelete(task_handler);
  5. taskEXIT_CRITICAL();

}

void Usart0_recv(uint8_t *data, uint32_t len) { printf(“recv: %s\n”, data); }

static void GPIO_config() { // 时钟初始化 rcu_periph_clock_enable(RCU_GPIOA); // 配置GPIO模式 gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0); }

int main(void) { NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0); systick_config(); GPIO_config(); Usart0_init();

  1. sema_handler = xSemaphoreCreateBinary();
  2. xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
  3. vTaskStartScheduler();
  4. while(1) {}

}

  1. 观察,两个任务是否获得信号。<br />改变两个任务的优先级,观察两个任务信号的获取情况。
  2. <a name="EMQHj"></a>
  3. #### 案例二
  4. 在案例1的基础上,通过串口接收,来发送信号。
  5. ```c
  6. void Usart0_recv(uint8_t *data, uint32_t len)
  7. {
  8. printf("recv: %s\n", data);
  9. xSemaphoreGiveFromISR(sema_handler, NULL);
  10. }

xSemaphoreGiveFromISR中断中发送信号

计数型信号量

功能 描述
xSemaphoreCreateCounting 创建计数型信号量
xSemaphoreTake 等待信号
xSemaphoreGive 发送信号

信号量的创建

  1. SemaphoreHandle_t xSemaphoreCreateCounting( const UBaseType_t uxMaxCount,
  2. const UBaseType_t uxInitialCount);

参数说明:

  1. const UBaseType_t uxMaxCount最大计数值。
  2. const UBaseType_t uxInitialCount初始化当前计数值。

返回值为信号量的句柄。

等待信号操作

  1. BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime );
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. TickType_t xBlockTime表示要等待的时间。通常我们一直等到有信号到来,这里我们可以填写portMAX_DELAY
  3. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

发送信号操作

  1. BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

案例一

  1. 开启两个任务,等待信号,接收到信号后,处理耗时操作
  2. 开启按键扫描,点击按键时发送信号 ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include “main.h”

    include “FreeRTOS.h”

    include “task.h”

    include “semphr.h”

    include “Usart0.h”

TaskHandle_t task_handler; TaskHandle_t task_key_handler; TaskHandle_t task1_handler; TaskHandle_t task2_handler; SemaphoreHandle_t sema_handler;

void task1(void *pvParameters) { BaseType_t result; while(1) { result = xSemaphoreTake(sema_handler, portMAX_DELAY); if(result == pdTRUE) { printf(“task1 %ld\n”, uxSemaphoreGetCount(sema_handler)); } else { printf(“task1 Error\n”); } vTaskDelay(2500); } }

void task2(void *pvParameters) { BaseType_t result; while(1) { result = xSemaphoreTake(sema_handler, portMAX_DELAY); if(result == pdTRUE) { printf(“task2 %ld\n”, uxSemaphoreGetCount(sema_handler)); } else { printf(“task2 Error\n”); } vTaskDelay(2500); } }

void task_key(void *pvParameters) { FlagStatus pre_state = RESET; BaseType_t result; while(1) { FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0); if(SET == state && pre_state == RESET) { // 当前高电平, 上一次为低电平,按下 pre_state = state;

  1. result = xSemaphoreGive(sema_handler);

// if(result == pdTRUE) { // printf(“semaphore give success\n”); // } else { // printf(“semaphore give error\n”); // } } else if(RESET == state && pre_state == SET) { // 当前高电平, 上一次为低电平,抬起 pre_state = state; } vTaskDelay(20); } }

void start_task(void *pvParameters) { taskENTER_CRITICAL();

  1. xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
  2. xTaskCreate(task1, "task1", 64, NULL, 3, &task1_handler);
  3. xTaskCreate(task2, "task2", 64, NULL, 2, &task2_handler);
  4. vTaskDelete(task_handler);
  5. taskEXIT_CRITICAL();

}

void Usart0_recv(uint8_t *data, uint32_t len) { printf(“recv: %s\n”, data); }

static void GPIO_config() { // 时钟初始化 rcu_periph_clock_enable(RCU_GPIOA); // 配置GPIO模式 gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0); }

int main(void) { NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0); systick_config(); GPIO_config(); Usart0_init();

  1. sema_handler = xSemaphoreCreateCounting(100, 0);
  2. xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
  3. vTaskStartScheduler();
  4. while(1) {}

}

  1. 观察,两个任务是否获得信号。<br />频繁点击按键,观察效果。
  2. <a name="SjfQh"></a>
  3. #### 案例二
  4. 在案例1的基础上,通过串口接收,来发送信号。
  5. ```c
  6. void Usart0_recv(uint8_t *data, uint32_t len)
  7. {
  8. printf("recv: %s\n", data);
  9. xSemaphoreGiveFromISR(sema_handler, NULL);
  10. }

xSemaphoreGiveFromISR中断中发送信号

互斥信号量

功能 描述
xSemaphoreCreateMutex 创建互斥信号量
xSemaphoreTake 等待信号
xSemaphoreGive 发送信号

信号量的创建

  1. SemaphoreHandle_t xSemaphoreCreateMutex();

返回值为信号量的句柄。

等待信号操作

  1. BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime );
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. TickType_t xBlockTime表示要等待的时间。通常我们一直等到有信号到来,这里我们可以填写portMAX_DELAY
  3. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

发送信号操作

  1. BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

    案例一

    开启两个任务,同时等待和发送信号,观察任务调用 ```c

    include “gd32f4xx.h”

    include “systick.h”

    include

    include “main.h”

    include “FreeRTOS.h”

    include “task.h”

    include “semphr.h”

    include “Usart0.h”

TaskHandle_t task_handler; TaskHandle_t task1_handler; TaskHandle_t task2_handler; SemaphoreHandle_t sema_handler;

void task1(void *pvParameters) { BaseType_t result; while(1) { result = xSemaphoreTake(sema_handler, portMAX_DELAY); if(result == pdTRUE) { printf(“task1\n”); } else { printf(“task1 Error\n”); } xSemaphoreGive(sema_handler); vTaskDelay(2000); } }

void task2(void *pvParameters) { BaseType_t result; while(1) { result = xSemaphoreTake(sema_handler, portMAX_DELAY); if(result == pdTRUE) { printf(“task2\n”); } else { printf(“task2 Error\n”); } xSemaphoreGive(sema_handler); vTaskDelay(2000); } }

void start_task(void *pvParameters) { taskENTER_CRITICAL();

  1. xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
  2. xTaskCreate(task2, "task2", 64, NULL, 2, &task2_handler);
  3. vTaskDelete(task_handler);
  4. taskEXIT_CRITICAL();

}

void Usart0_recv(uint8_t *data, uint32_t len) { printf(“recv: %s\n”, data); }

int main(void) { NVIC_SetPriorityGrouping(NVIC_PRIGROUP_PRE4_SUB0); systick_config(); Usart0_init();

  1. sema_handler = xSemaphoreCreateMutex();
  2. xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
  3. vTaskStartScheduler();
  4. while(1) {}

}

  1. <a name="D5QKT"></a>
  2. ### 递归互斥信号量
  3. | 功能 | 描述 |
  4. | --- | --- |
  5. | xSemaphoreCreateRecursiveMutex | 创建递归互斥信号量 |
  6. | xSemaphoreTakeRecursive | 等待信号 |
  7. | xSemaphoreGiveRecursive | 发送信号 |
  8. 信号量的创建
  9. ```c
  10. SemaphoreHandle_t xSemaphoreCreateRecursiveMutex();

返回值为信号量的句柄。

等待信号操作

  1. BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime );
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. TickType_t xBlockTime表示要等待的时间。通常我们一直等到有信号到来,这里我们可以填写portMAX_DELAY
  3. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

发送信号操作

  1. BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xSemaphore);
  1. SemaphoreHandle_t xSemaphore表示要等待的哪个信号量句柄。
  2. 返回值类型为BaseType_t,表示成功或者失败,取值为pdPASSpdFAIL

示例代码:

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. #include "semphr.h"
  8. #include "Usart0.h"
  9. TaskHandle_t task_Handler;
  10. TaskHandle_t task1_Handler;
  11. TaskHandle_t task2_Handler;
  12. SemaphoreHandle_t sema_handler;
  13. void task1(void *pvParameters) {
  14. BaseType_t result;
  15. while(1) {
  16. printf("task1 take 0\n");
  17. xSemaphoreTakeRecursive(sema_handler, portMAX_DELAY);
  18. printf("task1 take 1\n");
  19. xSemaphoreTakeRecursive(sema_handler, portMAX_DELAY);
  20. printf("task1 give\n");
  21. xSemaphoreGiveRecursive(sema_handler);
  22. vTaskDelay(1000);
  23. }
  24. }
  25. void task2(void *pvParameters) {
  26. BaseType_t result;
  27. while(1) {
  28. printf("task2 take 0\n");
  29. xSemaphoreTakeRecursive(sema_handler, portMAX_DELAY);
  30. printf("task2 take 1\n");
  31. xSemaphoreTakeRecursive(sema_handler, portMAX_DELAY);
  32. printf("task2 give\n");
  33. xSemaphoreGiveRecursive(sema_handler);
  34. vTaskDelay(1000);
  35. }
  36. }
  37. void start_task(void *pvParameters) {
  38. taskENTER_CRITICAL();
  39. xTaskCreate(task1, "task1", 128, NULL, 2, &task1_Handler);
  40. xTaskCreate(task2, "task2", 128, NULL, 2, &task2_Handler);
  41. vTaskDelete(task_Handler);
  42. taskEXIT_CRITICAL();
  43. }
  44. void Usart0_recv(uint8_t *data, uint32_t len)
  45. {
  46. printf("recv: %s\n", data);
  47. }
  48. static void GPIO_config() {
  49. // 时钟初始化
  50. rcu_periph_clock_enable(RCU_GPIOA);
  51. // 配置GPIO模式
  52. gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
  53. }
  54. int main(void)
  55. {
  56. systick_config();
  57. GPIO_config();
  58. Usart0_init();
  59. sema_handler = xSemaphoreCreateRecursiveMutex();
  60. if(sema_handler == NULL) {
  61. printf("create error\r\n");
  62. }
  63. xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_Handler);
  64. vTaskStartScheduler();
  65. while(1) {}
  66. }

比较

四种信号量类型的常见应用场景的比较:

  1. 二进制信号量:

应用场景:二进制信号量适用于任务间的互斥、同步和事件通知。例如,当多个任务需要共享一个资源时,可以使用二进制信号量来保证同一时间只有一个任务访问该资源。
示例应用:多个任务竞争访问共享打印机资源。

  1. 计数信号量:

应用场景:计数信号量适用于任务间的资源共享和限制、任务同步和事件通知。它可以表示一组可用资源的数量,任务可以通过获取计数信号量来申请和释放这些资源。
示例应用:限制同时执行的任务数量、任务间的生产者-消费者模式。

  1. 互斥信号量:

应用场景:互斥信号量用于互斥访问共享资源的场景。它确保在任意给定时间只有一个任务可以访问共享资源,避免了数据竞争和不一致性。
示例应用:多个任务竞争访问共享数据结构、临界区保护。

  1. 递归互斥信号量:

应用场景:递归互斥信号量适用于同一任务需要多次获取互斥资源的场景。它允许同一任务在获取资源后再次获取,而不会引起死锁。
示例应用:任务递归调用、嵌套临界区保护。
需要根据具体的应用需求选择合适的信号量类型。如果需要简单的互斥访问,互斥信号量可能是最合适的选择。如果需要限制资源数量或任务同步,计数信号量可以派上用场。而对于同一任务需要多次获取资源的情况,递归互斥信号量提供了便利。

练习题

  1. 实现二进制信号量开发流程
  2. 实现计数型信号量开发流程
  3. 实现互斥型信号量开发流程
  4. 实现递归互斥信号量开发流程