官方文档:
https://freertos.org/Documentation/02-Kernel/02-Kernel-features/02-Queues-mutexes-and-semaphores/02-Binary-semaphores
https://freertos.org/Documentation/02-Kernel/02-Kernel-features/02-Queues-mutexes-and-semaphores/03-Counting-semaphores

信号量semaphore词源

semaphore除了表示信号量,还有旗语的意思。组成包括:

  • sema- 语义、符号/sign 的含义。如语义化版本Sematic Version,语义学Semasiology
  • phor- 带来/bring, 如欣快的euphoric, 隐喻metaphor, 发光体luminophor

组合起来表示带来信号。

信号量的使用

B站上有个信号量机制讲解视频https://www.bilibili.com/video/BV18P41187NV
信号灯(交通灯)就是一种信号量,在不同方向的汽车要使用同一个交叉路口的时候, 在同一时间,信号灯只允许某一方向的汽车通行。
在FreeRTOS中, 获取一个信号量即take,释放一个信号量即give。获取信号量相当于这个任务来到了十字路口,如果没有获取到信号量他就在十字路口这里等着,直到等到了信号量再继续执行。释放信号量可以在获取信号量的任务本身中进行,也可以在中断和回调中进行。
binary信号量是指只能取0或者1,counting信号量可以取大于1的值。
综合来说,信号量起到了和JavaScript的promise、await类似的作用。take相当于await等待。give相当于在promise的fullfilled。等待信号量释放的过程,相当于是等待另外一个异步程序执行完毕的过程。和await类似,避免了JavaScript的回调地狱,将异步函数执行完毕后需要做的操作仍然放在本任务中而不是放在回调后面。
对于有限的资源,JavaScript需要自行去实现信号量的计数,而freeRTOS直接提供了信号量。
下面是esp-idf的ledc fade渐变调光的示例代码,截取了其中和信号量有关的代码,以便说明。部分代码顺序略有调整,但不影响实际的代码工作逻辑。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/ledc.h"
#include "esp_err.h"
/*
* ....... 省略代码
*/
// 这是渐变调光结束后注册的用户回调函数。其中释放give了用于监控回调是否完成的信号量。user_arg在这个回调函数注册的时候传入了信号量。
static IRAM_ATTR bool cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg)
{
    BaseType_t taskAwoken = pdFALSE;

    if (param->event == LEDC_FADE_END_EVT) {
        SemaphoreHandle_t counting_sem = (SemaphoreHandle_t) user_arg;
        xSemaphoreGiveFromISR(counting_sem, &taskAwoken);
    }

    return (taskAwoken == pdTRUE);
}

void app_main(void)
{
/*
* ....... 省略代码
*/
    // 在主程序中注册了计数信号量,数字为要调光的led的通道数量。最后一个参数零表示计数信号量的起始值为0,也就是没有任何资源。
    SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(LEDC_TEST_CH_NUM, 0);
    // 在这儿注册led调光结束的回调函数, 并且将计数信号量作为用户参数也传入比较函数中。 
    ledc_cbs_t callbacks = {
        .fade_cb = cb_ledc_fade_end_event
    };
    for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
        ledc_cb_register(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, &callbacks, (void *) counting_sem);
    }
    while (1) {
        // 在主程序中会循环调亮调暗的过程。先调亮到LEDC_TEST_DUTY,再调暗到0
        printf("1. LEDC fade up to duty = %d\n", LEDC_TEST_DUTY);
        for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
            ledc_set_fade_with_time(ledc_channel[ch].speed_mode,
                                    ledc_channel[ch].channel, LEDC_TEST_DUTY, LEDC_TEST_FADE_TIME);
            ledc_fade_start(ledc_channel[ch].speed_mode,
                            ledc_channel[ch].channel, LEDC_FADE_NO_WAIT);
        }
        // 因为上面的渐变调光函数事实上会调用异步任务或者中断函数ISR执行,完全执行需要时间,所以这里先取走了所有的信号量。因为起始的信号量为零,需要等待所有调光完成回调函数释放信号量后才能继续执行。
        for (int i = 0; i < LEDC_TEST_CH_NUM; i++) {
            xSemaphoreTake(counting_sem, portMAX_DELAY);
        }
        // 下面又从最高的亮度调为零,与上面类似,通过take信号量的方式等待调光执行完成。
        printf("2. LEDC fade down to duty = 0\n");
        for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
            ledc_set_fade_with_time(ledc_channel[ch].speed_mode,
                                    ledc_channel[ch].channel, 0, LEDC_TEST_FADE_TIME);
            ledc_fade_start(ledc_channel[ch].speed_mode,
                            ledc_channel[ch].channel, LEDC_FADE_NO_WAIT);
        }

        for (int i = 0; i < LEDC_TEST_CH_NUM; i++) {
            xSemaphoreTake(counting_sem, portMAX_DELAY);
        }
/*
* ....... 省略代码
*/
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

标签: none 阅读量: 50

添加新评论