Как мы уже знаем, работать с ESP32 нам приходится с использованием операционной системы реального времени FreeRTOS. Это вызвано многими причинами. Во-первых, использование операционной системы обусловлено тем, что контроллер ESP32 имеет на своём борту модуль для работы с беспроводными соединениями. А это сеть и работа с сетевыми протоколами, как мы уже давно знаем, непростая и сервить обмен по сети без использования систем реального времени, очень тяжело и, следовательно, велик процент ошибок. Во-вторых, контроллер ESP32 двухъядерный. Это также предусматривает использование системы реального времени.
Насчёт теории по работе с FreeRTOS. Мы уже достаточно неплохо знаем данную систему из опыта по программированию контроллеров STM32 и ESP8266. Также мы уже написали достаточно кода и на ESP32 с использованием данной операционной системы. Поэтому вдаваться в азы работы с FreeRTOS не имеет смысла во избежание потери лишнего времени. Поэтому уроки по FreeRTOS будут носить более закрепительный характер. Мы будем знакомиться с таким материалом, с которым мы ещё не встречались. Также будем работать и с знакомыми разделами, если по работе с ними будет много нового.
Напомню лишь то, что FreeRTOS — многозадачная операционная система реального времени (ОСРВ) для встраиваемых систем.
И сегодня на повестке дня у нас мьютексы.
Слово мьютекс происходит от английского словосочетания mutual exclusion — взаимное исключение.
Мьютекс — это примитив синхронизации, обеспечивающий взаимное исключение исполнения критических участков кода.
Это приблизительно то же самое, что и двоичный семафор, только принцип работы с мьютексом немного отличается.
С двоичными семафорами мы очень плотно знакомились в уроке 104 по контроллеру STM32. Я очень много там рассказывал о смысле критических секций в коде.
Напомню лишь, что очень нередко случается ситуация, когда существует какой-нибудь ответственный процесс, который в данный момент времени должен вызываться только из одной задачи, то есть не должно быть одновременного выполнения данного процесса несколькими задачами. Например, может быть вывод в какой-то порт информации процессом, если данная задача в это же время будет вызвана процессом другим, то скорей всего данные в лучшем случае перемешаются, а в худшем половина их растеряется вообще.
В чём же всё-таки отличие мьютекса от двоичного семафора? Отличие, впрочем, здесь невелико. Во-первых, семафоры больше используются в механизме сигнализации из одной задачи в другую о каком-то событии, мьютекс — это более механизм блокировки. Во-вторых, мьютекс — это объект, а семафор — целое число. Есть ещё различия, но они незначительны.
Думаю, понять и усвоить, что же такое мьютекс и каков его смысл нам поможет практическая работа с ним.
Схема урока у нас не изменилась с прошлого занятия, так как дисплей нам опять потребуется
Проект был сделан также на основе проекта прошлого урока с именем SOFT_TIMER и назван был MUTEX_LCD.
Откроем наш проект в Espressif IDE.
Мы не будем сегодня использовать очереди в выводе строк на дисплей, а будем выводить текст в дисплей напрямую, так как очереди не дадут нам увидеть смысл мьютексов. Поэтому удалим объявление структуры и переменную её типа
typedef struct{ unsigned char y_pos; unsigned char x_pos; char *str;} qLCDData;//————————————————xQueueHandle lcd_string_queue = NULL;
Не нужна нам, соответственно, будет и функция vLCDTask, поэтому удалим её вместе с телом.
Функции по работе с таймерами periodic_timer_callback и oneshot_timer_callback также удалим вместе с их телами.
Создадим функции для четырёх аналогичных задач, в которых заведём счётчики и результат счёта будем выводить на дисплей в позиции, различные для каждой задачи. Также период работы счётчика засчёт разных задержек будет различным у каждой задачи
В функции app_main удалим создание очереди и задачи для дисплея
lcd_string_queue = xQueueCreate(10, sizeof(qLCDData));xTaskCreate(vLCDTask, «vLCDTask», 2048, NULL, 2, NULL);
После строки
LCD_ini();
удалим весь код до бесконечного цикла.
Теперь наш проект соберётся.
Создадим наши задачи с небольшой задержкой между созданием
Соберём код, прошьём контроллер. Я думаю, многие уже догадались, что у нас будет твориться на дисплее
Такой венигрет происходит из-за того, что мы бесконтрольно отправляем данные в дисплей по шине I2C и контроллер дисплея даже не может успеть понять, что ему пришло. В данном случае отправка данных из задачи в I2C и есть наша критическая секция, которая должна выполняться полностью и непрерывно только одним потоком.
Поэтому как-то надо наши данные отправлять в порядке очереди. В этом нам и поможет мьютекс, который мы сейчас создадим. Обычно в данном случае объявляется глобальная переменная. Но мы пойдём другим путём. Хотя в использовании глобальной переменной в нашем случае нет ничего страшного, так как мы не будем менять свойства мьютекса в процессе работы кода, а будем их только читать, но тем не менее считается более правильным передача в данном случае указателя на мьютекс через параметры задачи.
Поэтому объявим и создадим мьютекс в app_main
Структура для свойств мьютекса та же самая, что и для семафора. Отличается только функция для его создания.
Теперь в параметре при создании каждой задачи мы передадим указатель на наш мьютекс
xTaskCreate(task1, "task1", 2048, mutex, 5, NULL);
...
xTaskCreate(task2, "task2", 2048, mutex, 5, NULL);
...
xTaskCreate(task3, "task3", 2048, mutex, 5, NULL);
...
xTaskCreate(task4, "task4", 2048, mutex, 5, NULL);
Также в функциях для каждой задачи (task1, task2, task3 и task4) мы извлечём указатель на мьютекс из параметров
Ясное дело, что мы могли не создавать функцию для каждой задачи, а пользоваться одной. Задержку можно было передать тоже в параметрах, создав структуру из значения задержки, номера строки для дисплея и указателя на мьютекс. Причём, так и следует делать. Только, отдельные функции придают более читабельный вид коду.
Далее я не буду выкладывать код для функций всех задач, потому что он будет абсолютно одинаковым. Поэтому во всех задачах в бесконечном цикле перед выводом строки добавим следующую строку с кодом
Это будет блокировка входа для других задач, так как используем мы в других задачах один и тот же мьютекс, мы же передавали указатель на один мьютекс. Имя функции такое же как для команды взять семафор.
После того, как данные отправятся в шину I2C, мы разблокируем наш мьютекс, чтобы другие задачи могли также работать с дисплеем
и есть критическая секция, которую должен выполнять непрерывно только один поток.
Проверим теперь работу нашего кода
Вот теперь всё работает правильно. У нас создаётся впечатление, что каждая строка живёт самостоятельной жизнью. В этом и есть задача операционных систем в плане многозадачности.
Итак, на данном уроке мы познакомились с мьютексами, механизмом их использования и с тем, как они нам помогают организовать критические секции, а также их защитить от одновременного использования потоками, в нашем коде.
Всем спасибо за внимание!
Оригинал статьи находится здесь.
<<Предыдущий урок | Следующий урок>>
Недорогие отладочные платы ESP32 можно купить здесь
Логический анализатор 16 каналов можно приобрести здесь
Дисплей LCD 20x4 можно приобрести тут
Дисплей LCD 16x2 можно приобрести тут
Переходник I2C to LCD1602 2004 можно приобрести здесь
Видео в RuTube
Видео в Дзен
Видео в Youtube