ESP32 с FreeRTOS: Начало работы с семафорами (Arduino IDE)

В этом руководстве мы познакомим вас с семафорами FreeRTOS для ESP32 и объясним, как их использовать в Arduino IDE. Семафоры – это сигналы (или флаги), которые позволяют синхронизировать задачи и управлять событиями. Они могут использоваться для указания того, что событие произошло или что ресурс доступен. В отличие от очередей, семафоры не передают данные.

Руководство по семафорам FreeRTOS для ESP32 в Arduino IDE

Существует два типа семафоров: бинарные семафоры и счётные семафоры. В этом руководстве мы создадим и рассмотрим два различных примера, чтобы показать, как работают эти два типа семафоров.

Впервые работаете с FreeRTOS? Начните с этого руководства: ESP32 with FreeRTOS (Arduino IDE) — Getting Started Guide: Creating Tasks.

Введение в семафоры

Семафоры – это инструменты сигнализации в FreeRTOS, используемые для координации задач. Они обычно применяются для указания того, что событие произошло или что ресурс доступен. В отличие от очередей, семафоры не передают данные, только «счёт», или «флаг», или «сигнал» (или как угодно это назовите), используемый для запуска действий, когда что-то происходит.

Простой пример работы бинарного семафора

Простой пример работы бинарного семафора

Они позволяют задачам ожидать события, такие как нажатие кнопки или обнаружение движения, или сигнализировать о том, что событие произошло. Это делает их особенно полезными в сценариях, связанных с прерываниями и синхронизацией задач.

Поскольку семафоры не хранят данные, они потребляют меньше памяти, чем очереди, и идеально подходят для лёгкой сигнализации событий между задачами.

Существует два типа семафоров:

  • Бинарный семафор: сигнализирует о единичном событии. Это инструмент синхронизации, который может быть либо пустым (0), либо полным (1). Это как сигнал, которого задача ожидает перед тем, как продолжить выполнение.

  • Счётный семафор: отслеживает несколько событий (это может быть одно и то же событие несколько раз). Это как очередь событий до максимального счёта, который вы определяете. В отличие от очередей FreeRTOS, они не переносят данные. Только сигнал синхронизации. Вы лучше поймёте, как это работает, позже на примере.

Основные функции семафоров

Вот некоторые основные функции бинарных и счётных семафоров при использовании ESP32 с Arduino IDE. Далее мы рассмотрим эти функции в практических примерах.

Создание бинарного семафора

Для создания бинарного семафора используйте функцию xSemaphoreCreateBinary(). Она возвращает дескриптор SemaphoreHandle_t в случае успеха или NULL, если создание не удалось.

Создание счётного семафора

Для создания счётного семафора используйте функцию xSemaphoreCreateCounting(). Она возвращает дескриптор SemaphoreHandle_t в случае успеха или NULL, если создание не удалось. В качестве аргумента передайте максимальный счёт.

Взятие семафора (получение из семафора)

Используйте функцию xSemaphoreTake(semaphore, timeout) в задаче для ожидания или взятия семафора. Для бинарного семафора она блокируется до тех пор, пока семафор не станет доступным (состояние 1), устанавливая его в 0 при взятии.

Для счётного семафора она уменьшает счёт, если он больше 0, или блокируется, если счёт равен 0. Параметр timeout указывает, как долго ожидать (в тиках); portMAX_DELAY означает бесконечное ожидание. Это означает, что задача будет заблокирована, пока не появится значение семафора для взятия.

Передача семафора

Для передачи семафора используйте функцию xSemaphoreGive(), если вы находитесь внутри задачи, или xSemaphoreGiveFromISR(), если используется в ISR (функциях обработчиков прерываний).

Для бинарного семафора она устанавливает состояние в 1, разблокируя ожидающую задачу (или игнорируется, если семафор уже равен 1). Для счётного семафора она увеличивает счёт до maxCount, разблокируя ожидающую задачу (игнорируется при достижении maxCount).

Пример 1: Бинарный семафор – переключение светодиода нажатием кнопки

В этом разделе мы создадим простой пример для демонстрации работы бинарного семафора и его реализации в практическом приложении. Этот пример будет переключать светодиод при нажатии кнопки. Для сигнализации о нажатии кнопки мы будем использовать семафор. Одновременно у нас будет другая задача, мигающая светодиодом, чтобы продемонстрировать, что мы можем запускать несколько задач одновременно.

ESP32 с одной кнопкой и двумя светодиодами - пример для демонстрации бинарных семафоров

Итак, вот обзор примера:

  • Мы добавим прерывание к кнопке. При нажатии кнопки соответствующий ISR передаст семафор (установит его в 1).

  • Есть другая задача, называемая LEDToggleTask(), которая будет ожидать семафор для переключения состояния светодиода. Когда семафор передаётся из ISR, эта задача выполнится и семафор будет сброшен в 0. Только когда семафор установлен в 1, при нажатии кнопки, эта задача выполнится снова.

  • Одновременно у нас есть другая задача, называемая LEDBlinkTask(), которая будет увеличивать и уменьшать яркость другого светодиода.

Необходимые компоненты

Вот список компонентов, необходимых для этого примера:

Вы можете использовать ссылки выше или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!

Схема подключения

Подключите следующую схему:

  • Красный светодиод подключён к GPIO 2

  • Синий светодиод подключён к GPIO 4

  • Кнопка подключена к GPIO 23

Следуйте следующей принципиальной схеме.

Принципиальная схема ESP32 с двумя светодиодами и одной кнопкой

Код

Загрузите следующий код в Arduino IDE.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-semaphores-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define BUTTON_PIN 23
#define LED1_PIN 2   // Toggled LED
#define LED2_PIN 4   // Blinking LED

#define DEBOUNCE_DELAY 200

SemaphoreHandle_t buttonSemaphore = NULL;

volatile uint32_t lastInterruptTime = 0;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void LEDToggleTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  bool ledState = false;
  for (;;) {
    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      ledState = !ledState;
      digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
      Serial.print("LEDToggleTask: LED1 ");
      Serial.println(ledState ? "ON" : "OFF");
    }
  }
}

void LEDBlinkTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("LEDBlinkTask: LED2 ON");
    vTaskDelay(250 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("LEDBlinkTask: LED2 OFF");
    vTaskDelay(250 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);

  // Defining the button as an interrupt
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  buttonSemaphore = xSemaphoreCreateBinary();
  if (buttonSemaphore == NULL) {
    Serial.println("Failed to create semaphore!");
    while (1);
  }

  xTaskCreatePinnedToCore(
    LEDToggleTask,          // Task function
    "LEDToggleTask",        // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    2,                      // Higher priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDBlinkTask,           // Task function
    "LEDBlinkTask",         // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    1,                      // Medium priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}

void loop() {

}

Посмотреть исходный код

Как работает код?

Начнём с определения пинов для кнопки, переключаемого светодиода и мигающего светодиода.

#define BUTTON_PIN 23
#define LED1_PIN 2    // Toggled LED
#define LED2_PIN 4    // Blinking LED

Определим задержку дребезга для кнопки в миллисекундах.

#define DEBOUNCE_DELAY 200

Создадим дескриптор для семафора, называемый buttonSemaphore.

SemaphoreHandle_t buttonSemaphore = NULL;

setup()

Давайте сначала объясним setup(), а затем проанализируем задачи.

Сначала установим кнопку как прерывание и зададим её функцию обратного вызова (ISR). В нашем случае она называется buttonISR.

pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

Мы создаём бинарный семафор с помощью функции xSemaphoreCreateBinary() на ранее созданном дескрипторе buttonSemaphore.

buttonSemaphore = xSemaphoreCreateBinary();
if (buttonSemaphore == NULL) {
  Serial.println("Failed to create semaphore!");
  while (1);
}

Затем мы создаём задачи LEDToggleTask и LEDBlinkTask с разными приоритетами.

xTaskCreatePinnedToCore(
  LEDToggleTask,          // Task function
  "LEDToggleTask",        // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  2,                      // Higher priority
  NULL,                   // Task handle
  1                       // Core ID
);

xTaskCreatePinnedToCore(
  LEDBlinkTask,           // Task function
  "LEDBlinkTask",         // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  1,                      // Medium priority
  NULL,                   // Task handle
  1                       // Core ID
);

buttonISR()

Следующие строки создают функцию buttonISR().

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

При нажатии кнопки выполняется функция buttonISR(). Если мы получаем валидное нажатие, она использует функции семафоров FreeRTOS для сигнализации другой задаче о том, что событие нажатия кнопки произошло.

Передача семафора

Следующая строка – это ключевое действие с семафором. Она «передаёт» семафор. Этот семафор подобен сигналу, который сообщает другой части программы (обычно задаче FreeRTOS – в нашем случае это LEDToggleTask), что кнопка была нажата.

xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
higherPriorityTaskWoken и portYIELD_FROM_ISR

Переменная higherPriorityTaskWoken используется для проверки, разбудит ли передача семафора задачу, имеющую более высокий приоритет, чем текущая выполняемая задача. Если это так, мы вызываем portYIELD_FROM_ISR(), чтобы позволить системе немедленно переключиться на эту задачу с более высоким приоритетом сразу после завершения прерывания. В нашем случае мы хотим немедленно переключиться на LEDToggleTask.

Другими словами:

  • По сути, higherPriorityTaskWoken используется для проверки, разблокировала ли передача семафора более важную задачу.

  • Мы передаём её в xSemaphoreGiveFromISR(), чтобы функция могла обновить значение.

  • Если оно равно pdTRUE, мы вызываем portYIELD_FROM_ISR(), чтобы позволить FreeRTOS немедленно переключиться на эту задачу.

Так FreeRTOS позволяет прерываниям безопасно запускать высокоприоритетные задачи без проблем в планировании задач.

LEDToggleTask

Задача LEDToggleTask() будет переключать состояние LED1, когда есть значение в семафоре.

void LEDToggleTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  bool ledState = false;
  for (;;) {
    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      ledState = !ledState;
      digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
      Serial.print("LEDToggleTask: LED1 ");
      Serial.println(ledState ? "ON" : "OFF");
    }
  }
}

Когда задача LEDToggleTask() запускается, она настраивает пин светодиода как выход и начинает бесконечный цикл. Внутри цикла она ожидает семафор с помощью xSemaphoreTake(buttonSemaphore, portMAX_DELAY). portMAX_DELAY означает, что задача будет ждать бесконечно, пока не появится значение в семафоре (пока не будет нажата кнопка).

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {

Когда семафор получен, задача переключит состояние светодиода и выведет его в Serial Monitor.

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
  ledState = !ledState;
  digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
  Serial.print("LEDToggleTask: LED1 ");
  Serial.println(ledState ? "ON" : "OFF");

LEDBlinkTask

Помимо другой задачи, у нас есть LEDBlinkTask, которая работает независимо и одновременно, мигая светодиодом бесконечно.

void LEDBlinkTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("LEDBlinkTask: LED2 ON");
    vTaskDelay(250 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("LEDBlinkTask: LED2 OFF");
    vTaskDelay(250 / portTICK_PERIOD_MS);
  }
}

Демонстрация

Загрузите код на вашу плату. После загрузки откройте Serial Monitor на скорости 115200 бод. Нажмите кнопку RST на ESP32, чтобы код начал выполняться.

ESP32 с двумя светодиодами и кнопкой - нажатие кнопки для переключения состояния светодиода

Светодиод, подключённый к GPIO 4, будет мигать каждые 250 миллисекунд. Нажмите кнопку, чтобы переключить состояние светодиода, подключённого к GPIO 2.

В Serial Monitor вы должны увидеть нечто подобное.

Пример бинарного семафора ESP32 - демонстрация Serial Monitor

Пример 2: Счётный семафор

В этом разделе мы создадим простой пример для демонстрации работы счётных семафоров. Мы создадим счётный семафор с максимальным счётом 5. Этот семафор будет принимать до 5 нажатий кнопки. Другая задача будет потреблять этот семафор, чтобы мигнуть светодиодом столько раз, сколько значений находится в семафоре. Когда значение потребляется из семафора, можно добавить новое значение.

Вкратце, вот обзор того, как работает проект:

  • Мы присоединим прерывание к кнопке. При нажатии кнопки обработчик прерывания (ISR) передаст семафор – до максимального счёта 5.

  • Задача LEDBlinkTask будет ожидать семафор. Каждый раз, когда она его получает, она мигнёт светодиодом. Светодиод мигнёт один раз для каждого счёта, доступного в данный момент в семафоре.

  • Когда LEDBlinkTask потребляет значение из семафора, появляется «место» для нового счёта, добавляемого нажатием кнопки.

  • Одновременно у нас будет другая задача, называемая LEDFadeTask, которая будет плавно изменять яркость другого светодиода. Эта задача используется для демонстрации возможностей FreeRTOS по управлению многозадачностью.

Необходимые компоненты и схема подключения

Такие же, как в предыдущем примере.

Код

Скопируйте следующий код в Arduino IDE.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-semaphores-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define BUTTON_PIN 23
#define LED1_PIN 2    // Blinking LED
#define LED2_PIN 4    // Fading LED

#define DEBOUNCE_DELAY 200 // debounce for the pushbutton in milliseconds

#define SEMAPHORE_MAX_COUNT 5

SemaphoreHandle_t buttonSemaphore = NULL;
volatile uint32_t lastInterruptTime = 0;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    if (xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken) == pdTRUE) {
      Serial.println("buttonISR: Gave semaphore token");
    } else {
      Serial.println("buttonISR: Semaphore full");
    }
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void LEDBlinkTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {

    // Get and print the current semaphore count
    UBaseType_t count = uxSemaphoreGetCount(buttonSemaphore);
    Serial.print("LEDBlinkTask: Current semaphore count = ");
    Serial.println(count);

    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      Serial.println("LEDBlinkTask: Blinking LED1 for button press");
      vTaskDelay(500 / portTICK_PERIOD_MS);
      digitalWrite(LED1_PIN, HIGH);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
      digitalWrite(LED1_PIN, LOW);
      vTaskDelay(500 / portTICK_PERIOD_MS);
    }
  }
}

void LEDFadeTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    // Fade up (0 to 255)
    for (int duty = 0; duty <= 255; duty += 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0) {  // Print every 10th step
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
    // Fade down (255 to 0)
    for (int duty = 255; duty >= 0; duty -= 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0 || duty == 255) {
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

void setup() {
  Serial.begin(115200);  // Higher baud rate
  delay(1000);
  Serial.println("Starting FreeRTOS: Counting Semaphore");

  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  buttonSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
  if (buttonSemaphore == NULL) {
    Serial.println("Failed to create semaphore!");
    while (1);
  }

  xTaskCreatePinnedToCore(
    LEDBlinkTask,           // Task function
    "LEDBlinkTask",         // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    2,                      // Higher priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDFadeTask,            // Task function
    "LEDFadeTask",          // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    1,                      // Lower priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}
void loop() {}

Посмотреть исходный код

Как работает код?

Этот код довольно похож на предыдущий. Мы рассмотрим только важные секции, связанные со счётным семафором.

Создание счётного семафора

В setup() мы создаём счётный семафор с максимальным счётом 5 (SEMAPHORE_MAX_COUNT), начиная с 0. Мы делаем это с помощью функции xSemaphoreCreateCounting().

buttonSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
if (buttonSemaphore == NULL) {
  Serial.println("Failed to create semaphore!");
  while (1);
}

Создание задач

Также в setup() мы создаём наши задачи и назначаем их ядру. LEDBlinkTask имеет более высокий приоритет, чем LEDFadeTask.

xTaskCreatePinnedToCore(
  LEDBlinkTask,           // Task function
  "LEDBlinkTask",         // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  2,                      // Higher priority
  NULL,                   // Task handle
  1                       // Core ID
);

xTaskCreatePinnedToCore(
  LEDFadeTask,            // Task function
  "LEDFadeTask",          // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  1,                      // Lower priority
  NULL,                   // Task handle
  1                       // Core ID
);

ISR кнопки и счётный семафор

При нажатии кнопки выполняется функция buttonISR(). Если мы получаем валидное нажатие кнопки, мы передаём его в счётный семафор. Семафор принимает до пяти счётов. Мы используем ту же функцию, что и в предыдущем примере – xSemaphoreGiveFromISR().

if (xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken) == pdTRUE) {
  Serial.println("buttonISR: Gave semaphore token");
} else {
  Serial.println("buttonISR: Semaphore full");
}

LEDBlinkTask и взятие семафора

LEDBlinkTask ожидает бесконечно, пока в семафоре не появится счёт. Когда в семафоре есть счёт, мы берём его и мигаем светодиодом.

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
  Serial.println("LEDBlinkTask: Blinking LED1 for button press");
  vTaskDelay(500 / portTICK_PERIOD_MS);
  digitalWrite(LED1_PIN, HIGH);
  vTaskDelay(1000 / portTICK_PERIOD_MS);
  digitalWrite(LED1_PIN, LOW);
  vTaskDelay(500 / portTICK_PERIOD_MS);
}

Поскольку у нас счётный семафор, он будет мигать светодиодом столько раз, сколько счётов доступно в данный момент в семафоре.

Каждый раз, когда значение берётся из семафора, появляется новое место для добавления нового счёта (через нажатие кнопки).

Внутри этой задачи мы также выводим текущий счёт семафора, вызывая функцию uxSemaphoreGetCount() и передавая дескриптор семафора в качестве аргумента.

// Get and print the current semaphore count
UBaseType_t count = uxSemaphoreGetCount(buttonSemaphore);
Serial.print("LEDBlinkTask: Current semaphore count = ");
Serial.println(count);

LEDFadeTask

Одновременно у нас есть другая независимая задача, называемая LEDFadeTask, которая просто плавно изменяет яркость другого светодиода.

void LEDFadeTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    // Fade up (0 to 255)
    for (int duty = 0; duty <= 255; duty += 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0) {  // Print every 10th step
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
    // Fade down (255 to 0)
    for (int duty = 255; duty >= 0; duty -= 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0 || duty == 255) {
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

Демонстрация

Загрузите код на вашу плату. После загрузки откройте Serial Monitor на скорости 115200 бод. Нажмите кнопку RST на ESP32, чтобы код начал выполняться.

ESP32 с двумя светодиодами и кнопкой - нажатие кнопки для переключения состояния светодиода

Светодиод, подключённый к GPIO 4, будет постоянно плавно изменять яркость.

Нажмите кнопку несколько раз, чтобы мигнуть светодиодом, подключённым к GPIO 2, столько раз, сколько нажатий кнопки в очереди семафора.

В Serial Monitor вы должны увидеть нечто подобное. Счёт семафора будет уменьшаться по мере мигания светодиода (если вы не продолжаете нажимать кнопку).

Счётный семафор ESP32 - демонстрация Serial Monitor

Заключение

В этом руководстве вы узнали о бинарных и счётных семафорах FreeRTOS и о том, как реализовать их с ESP32, программируемым в Arduino IDE.

Семафоры позволяют нам синхронизировать задачи, сигнализировать о доступности ресурса, о произошедшем событии или о точке в задаче, где другая задача должна начать выполнение.

Мы показали вам два простых примера для демонстрации работы семафоров. Это может быть применено к гораздо более сложным приложениям с множеством задач, передающих и берущих из семафора.

Мы надеемся, что это руководство было полезным для начала реализации программирования FreeRTOS в ваших скетчах ESP32. У нас есть больше руководств из серии FreeRTOS, которые могут вам понравиться:

Чтобы узнать больше об ESP32, обязательно ознакомьтесь с нашими ресурсами:

Источник: :doc:`Random Nerd Tutorials — ESP32 with FreeRTOS: Getting Started with Semaphores (Arduino IDE) <../esp32-freertos-semaphores-arduino/index>`