В уроке 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;
Объявим глобальную переменную для нашего семафора
1 2 |
xQueueHandle lcd_string_queue = NULL; xSemaphoreHandle xBinSem01; |
Функцию print_timer_counter удалим вместе с телом.
Создадим семафор в функции app_main
1 2 |
lcd_string_queue = xQueueCreate(10, sizeof(qLCDData)); vSemaphoreCreateBinary(xBinSem01); |
Создание задачи пока удалим
xTaskCreate(task1, «task1», 2048, NULL, 5, NULL);
А создадим мы её позже, когда уже будет инициализирован дисплей, причём создание задачи будет только при условии успешного создания семафора
1 2 |
LCD_ini(); if (xBinSem01 != NULL) 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);
Здесь мы должны отдать семафор для синхронизации с нашей задачей, используя функцию, специально предназначенную для прерываний
1 2 |
BaseType_t high_task_awoken = pdFALSE; xSemaphoreGiveFromISR(xBinSem01, &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);
А вместо этого как раз здесь будет код, дожидающийся того момента, когда функция обработки прерывания отдаст семафор, и мы этот семафор забираем
1 2 |
for(;;) { xSemaphoreTake(xBinSem01, 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
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Переходник I2C to LCD можно приобрести здесь I2C to LCD1602 2004
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий