ESP32 Урок 16. FreeRTOS. Двоичные семафоры



В уроке 14 мы познакомились с мьютексами. Также мы немного определились с вопросом, чем вообще мьютексы отличаются от семафоров вообще и от двоичных семафоров в частности. Хотя это отличие в основном символическое.

С семафорами мы встречались и ранее, когда писали код для stm32, поэтому с теорией их использования и назначения мы, в принципе, знакомы. Также мы знаем, что семафоры бывают двоичные и счётные. Двоичные семафоры от счётных отличаются тем, что они те же самые счётные, но считать они умеют только до единицы. Работали мы с двоичными семафорами в уроке 104 по stm32. Поэтому теперь мы лишь закрепим данную тему, но в части использования двоичных семафоров при работе с контроллерами esp32. Хотя не просто закрепим. Вообще, пишут, что двоичные семафоры предназначены для синхронизации задач и более активно они применяются в основном для синхронизации именно следующих задач: синхронизации обработчика прерываний от какой-то периферии с задачей-обработчиком, которая продолжает реакцию события.  Они позволяют переводить задачу из состояния блокировки в состояние готовности к выполнению каждый раз, когда происходит прерывание. Это дает возможность перенести большую часть кода, отвечающего за обработку внешнего события, из обработчика прерывания в тело задачи, выполнение которой синхронизировано с соответствующим прерыванием. Вот этим мы займёмся. Хотя в прошлом уроке мы именно этим и занимались, только для этого мы использовали не семафор, а очередь, которая является более продвинутым семафором, позволяющим хранить в себе ещё и элементы, представляющие собой либо какое-то значение определённого типа, либо указатель на значение или некий набор значений.

Схема у нас также осталась прежняя

 

 

И проект мы сделаем также из проекта прошлого урока с именем TIMER_GROUP и назовём его BIN_SEM_LCD.

Откроем наш проект в Espressif IDE и в файле main.c удалим переменную для очереди, с помощью которой происходила синхронизация задачи с прерыванием

 

static xQueueHandle s_timer_queue;

 

Также в функции app_main мы удалим и создание данной очереди

 

s_timer_queue = xQueueCreate(10, sizeof(timer_event_t));

 

И, так как таймера нам достаточно будет для работы с двоичным семафором одного, то удалим создание второго таймера

 

tg_timer_init(TIMER_GROUP_1, TIMER_0, false, 1);

 

 

Объявления данных структур со свойствами таймерам также можно удалить

 

typedef struct {
  int timer_group;
  int timer_idx;
  int alarm_interval;
  bool auto_reload;
} timer_info_t;
//————————————————
typedef struct {
  timer_info_t info;
  uint64_t timer_counter_value;
} timer_event_t;

 

Объявим глобальную переменную для нашего семафора

 

 

Функцию print_timer_counter удалим вместе с телом.

Создадим семафор в функции app_main

 

 

Создание задачи пока удалим

 

xTaskCreate(task1, «task1», 2048, NULL, 5, NULL);

 

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

 

 

В функции tg_timer_init удалим работу с переменной структуры

 

timer_info_t *timer_info = calloc(1, sizeof(timer_info_t));
timer_info->timer_group = group;
timer_info->timer_idx = timer;
timer_info->auto_reload = auto_reload;
timer_info->alarm_interval = timer_interval_sec;

 

 

А в следующей строке указатель на данную переменную заменим на NULL

 

timer_isr_callback_add(group, timer, timer_group_isr_callback, NULL, 0);

 

В функции timer_group_isr_callback вот это нам, соответственно тоже не нужно

 

timer_info_t *info = (timer_info_t *) args;

 

Вот этот код также весь удалим

 

uint64_t timer_counter_value = timer_group_get_counter_value_in_isr(info->timer_group, info->timer_idx);
timer_event_t evt = {
  .info.timer_group = info->timer_group,
  .info.timer_idx = info->timer_idx,
  .info.auto_reload = info->auto_reload,
  .info.alarm_interval = info->alarm_interval,
  .timer_counter_value = timer_counter_value
};
if (!info->auto_reload) {
  timer_counter_value += info->alarm_interval * TIMER_SCALE;
  timer_group_set_alarm_value_in_isr(info->timer_group, info->timer_idx, timer_counter_value);
}
xQueueSendFromISR(s_timer_queue, &evt, &high_task_awoken);

 

Здесь мы должны отдать семафор для синхронизации с нашей задачей, используя функцию, специально предназначенную для прерываний

 

 

Перейдём в функцию задачи-обработчика task1 и удалим объявление второго счётчика

 

uint16_t cnt0 = 0, cnt1 = 0;

 

Также удалим объявление второго массива

 

char str1[15] = {0};

 

Эта переменная нам также не нужна

 

timer_event_t evt;

 

Удалим также и получение из очереди

 

xQueueReceive(s_timer_queue, &evt, portMAX_DELAY);

 

А вместо этого как раз здесь будет код, дожидающийся того момента, когда функция обработки прерывания отдаст семафор, и мы этот семафор забираем

 

 

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

Вертикальную позицию также обнулим

 

xLCDData.y_pos = 0;

 

Условие также удалим

if(evt.info.timer_group==0)
{

 

После вот этой строки

 

cnt0++;

 

удалим весь код до самого окончания бесконечного цикла.

Соберём наш код и прошьём контроллер и, если всё нормально написали, то на дисплее мы увидим наращивание счётчика, происходящее раз в две секунды

 

 

Давайте попробуем при создании таймера сделать возможным устанавливать период в милисекундах

Для этого вначале исправим входной параметр для большей информативности

 

static void tg_timer_init(int group, int timer, bool auto_reload, int timer_interval_msec)

 

В теле данной функции в данной строке код будет следующий

 

  timer_set_alarm_value(group, timer, timer_interval_msec * TIMER_SCALE / 1000);

 

Вот и всё. Таймер теперь будет инициализироваться с периодом в милисекундах.

Если мы соберём код и прошьём микроконтроллер, не меняя в вызове функции значение, то данные на дисплее будут обновляться раз в две милисекунды или 500 раз в секунду

 

 

 

 

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

Всем спасибо за внимание!

 

Данная статья в Дзен.

 

 

Предыдущий урок Программирование МК ESP32 Следующий урок

 

Исходный код

 

 

Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32

Логический анализатор 16 каналов можно приобрести здесь

Дисплей LCD 20×4 можно приобрести здесь Дисплей LCD 20×4

Дисплей LCD 16×2

Переходник I2C to LCD можно приобрести здесь I2C to LCD1602 2004

 

 

Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)

ESP32 FreeRTOS. Двоичные семафоры

 

Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)

ESP32 FreeRTOS. Двоичные семафоры

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*