В уроке 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;
Объявим глобальную переменную для нашего семафора
Создание задачи пока удалим
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 можно купить здесь
Логический анализатор 16 каналов можно приобрести здесь
Дисплей LCD 20x4 можно приобрести тут
Дисплей LCD 16x2 можно приобрести тут
Переходник I2C to LCD1602 2004 можно приобрести здесь
Видео в RuTube
Видео в Дзен
Видео в Youtube