ESP32 Урок 14. FreeRTOS. Мьютексы



Как мы уже знаем, работать с 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, мы разблокируем наш мьютекс, чтобы другие задачи могли также работать с дисплеем

 

 

То есть, отправка данных в шину или вот этот код

 

LCD_String(str1);
xSemaphoreGive(mutex);

 

и есть критическая секция, которую должен выполнять непрерывно только один поток.

Проверим теперь работу нашего кода

 

 

Вот теперь всё работает правильно. У нас создаётся впечатление, что каждая строка живёт самостоятельной жизнью. В этом и есть задача операционных систем в плане многозадачности.

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

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

 

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

 

 

Предыдущий урок Программирование МК 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 не будет опубликован. Обязательные поля помечены *

*