Использование очередей в Arduino FreeRTOS – подробное руководство для начинающих

В нашей предыдущей статье, посвященной использованию операционной системы FreeRTOS в плате Arduino Uno, мы рассмотрели создание задачи для мигания светодиодом. В этой же статье мы будем рассматривать принципы функционирования FreeRTOS уже более углубленно и изучим взаимодействие между различными задачами в ней. Также мы рассмотрим использование очередей для передачи данных от одной задачи к другой и продемонстрируем работу этих процессов с помощью ЖК дисплея 16x2 и фоторезистора, подключенных к плате Arduino Uno.

Также мы рассмотрим инструмент FreeRTOS API, который помогает удалять задачи, которые закончили выполнение поставленной им задачи. Удаление задач в ряде случаев целесообразно поскольку позволяет освободить выделенную им память. В продолжение нашего предыдущего руководства по FreeRTOS в Arduino мы будем использовать функцию vTaskDelete() API для удаления одной из задач. Задача может использовать функцию vTaskDelete() API чтобы удалить саму себя, или с ее помощью удалить другую задачу.

Использование очередей в Arduino FreeRTOS

Чтобы использовать этот API (application programming interface - программный интерфейс приложения) вам необходимо сконфигурировать файл FreeRTOSConfig.h. Этот файл используется для настройки FreeRTOS под конкретное приложение. С его помощью можно изменить алгоритмы планирования задач и многие другие параметры FreeRTOS. Этот файл можно найти в каталоге Arduino на вашем компьютере. У нас он располагается по адресу \Documents\Arduino\libraries\FreeRTOS\src как показано на следующем рисунке.

Местоположение файла FreeRTOSConfig.h на нашем компьютере

Откройте этот файл с помощью любого текстового редактора (например, блокнота) и найдите в нем фразу #define INCLUDE_vTaskDelete. Убедитесь в том, что напротив нее стоит параметр ‘1’ (1 означает разрешение на работу, 0 – запрет использования этой функции).

Проверка настроек в файле FreeRTOSConfig.h

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

Удаление задачи в FreeRTOS в Arduino

Для удаления задачи нам необходимо использовать API функцию vTaskDelete(), в ней всего лишь один аргумент.

pxTaskToDelete – это имя обработчика той задачи, которую мы хотим удалить. Это 6-й аргумент в функции xTaskCreate(). В нашем предыдущем руководстве по FreeRTOS в Arduino мы этот аргумент указывали как NULL, но мы можем непосредственно указать адрес обработчика задачи. К примеру, мы можем это сделать следующим образом:

Теперь в функции vTaskCreate() мы определим 6-й ее аргумент следующим образом:

Теперь доступ к содержимому (content) данной задачи мы можем получить с помощью введенного обработчика задачи. Также можно будет сделать так, чтобы задача удалила саму себя – для этого нужно будет ввести NULL вместо обработчика задачи.

То есть, к примеру, если мы захотим удалить задачу Task 3 из самой же задачи task 3, нам необходимо будет написать vTaskDelete( NULL ); внутри самой задачи Task3. А если мы хотим удалить, к примеру, задачу task 3 из задачи task 2, то внутри функции task2 нам необходимо будет ввести команду vTaskDelete(xTask3Handle);.

Например, внесем изменения в код одной функции нашей предыдущей статьи по этой тематике. Добавим код для удаления задачи Task2 из самой же задачи task2, для этого добавим команду vTaskDelete(NULL); в функцию void TaskBlink2(void *pvParameters).

Теперь можете загрузить код программы в плату Arduino и посмотреть работу программы. Вы увидите, что второй светодиод не мигает поскольку задача task2 была удалена в коде программы. При этом в окне монитора последовательной связи вы должны увидеть следующую картину:

Информация в окне монитора последовательной связи в случае удаленной задачи Task2

Мы рассмотрели каким образом можно удалить любую задачу в операционной системе FreeRTOS в Arduino, теперь перейдем к рассмотрению принципа работы очередей в FreeRTOS.

Что такое очереди в FreeRTOS?

Очередь – это структура данных, которая содержит ограниченное число элементов фиксированного размера, которая работает по схеме FIFO (First-in First-out – первым ушел, первым вышел). Очереди обеспечивают механизмы взаимодействия задача-задача, задача-прерывание и прерывание-задача.

Максимальное число элементов, которое может содержать очередь, называется ее длиной. При создании очереди устанавливаются и ее длина, и размер каждого элемента в ней.

Полную документацию по использованию очередей в FreeRTOS можно найти по следующей ссылке. В схематичном виде эти механизмы представлены на следующем рисунке.

Принципы работы очередей в FreeRTOS

Первый случай на представленном рисунке характеризует взаимодействие между задачами TaskA и TaskB с помощью очереди. Очередь в данном случае может содержать максимум 5 чисел целого типа. При создании очереди она не содержит никаких значений, то есть является пустой.

На втором рисунке показано как задача TaskA передает значение своей локальной переменной в конец очереди. Поскольку очередь до этого была пустой, то это значение записывается в конец очереди.

На третьем рисунке показано что задача TaskA изменяет значение своей локальной переменной перед тем как ее снова передать в очередь. Очередь в этом случае будет содержать копии обоих этих значений. Очередь при этом будет содержать три пустых места.

Задача TaskB считывает значение из очереди в свою локальную переменную. Поскольку TaskB считывает значение с начала (головы) очереди, то первоначально она считывает значение 10, которое было передано задачей TaskA первым.

Затем задача TaskB удаляет из очереди один элемент – соответственно в конец очереди при этом добавляется новый пустой элемент (то есть получаем принцип буфера). Соответственно, если задача TaskB будет снова считывать значение из очереди, то она считает второе значение, которое передавала TaskA.

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

Создание очереди в FreeRTOS

В этом проекте мы рассмотрим вывод значения, считываемого с фоторезистора, на экран ЖК дисплея 16х2. Таким образом, в нашем проекте будет две задачи:

  1. Task1 – считывание аналогового значения с фоторезистора.
  2. Task2 – вывод аналогового значения на экран ЖК дисплея 16х2.

В данном случае роль очереди будет состоять в том, чтобы передавать данные, генерируемые задачей Task1, в задачу Task2. Таким образом, в Task1 мы будем передавать аналоговое значение в очередь, а в Task2 мы будем считывать это значение из очереди.

В результате мы будем использовать три функции для работы с очередью:

  1. Создание очереди.
  2. Передача данных в очередь.
  3. Считывание (прием) данных из очереди.

Рассмотрим эти задачи более подробно.

1. Создание очереди.

Для создания очереди мы будем использовать API-функцию xQueueCreate(). В ней два аргумента.

uxQueueLength: максимальное число элементов, которое может содержать очередь в любой момент времени.
uxItemSize: размер в байтах элементов очереди.

Если эта функция возвращает NULL, то очередь не будет создана вследствие недостатка памяти, а если она возвращает ненулевое значение (не NULL), то очередь будет успешно создана. Сохраним это значение в переменной чтобы использовать его как обработчик (дескриптор) для дальнейшего доступа к очереди.

С помощью этой команды будет создано 4 элемента в очереди, каждый элемент размером 2 байта (размер элемента int). Возвращенное функцией значение мы сохраним в переменной queue1 чтобы использовать ее в дальнейшем как обработчик (дескриптор).

2. Передача данных в очередь в FreeRTOS

Для передачи данных в очередь в FreeRTOS имеется два варианта API для этой цели:

  1. xQueueSendToBack() – используется для передачи данных в конец (хвост) очереди.
  2. xQueueSendToFront() – используется для передачи данных в начало (голову) очереди.

xQueueSend() – это, фактически, эквивалент функции xQueueSendToBack().

Все эти функции имеют три аргумента.

xQueue: обработчик очереди, через который данные передаются (записываются в очередь). Эта переменная – та же самая, которую мы использовали в качестве возвращаемого функцией xQueueCreate значения.
pvItemToQueue: указатель на данные, которые необходимо копировать в очередь.
xTicksToWait: максимальное количество времени, которое задача должна оставаться в состоянии блокировки (Blocked state) чтобы дождаться пустого промежутка в очереди, во время которого она может получить доступ к очереди.

Если установить xTicksToWait в portMAX_DELAY, то задача будет ждать своей очереди бесконечно долго (без тайм-аута) при условии, что INCLUDE_vTaskSuspend имеет значение 1 в FreeRTOSConfig.h, в противном случае вы можете использовать макрос pdMS_TO_TICKS() для преобразования времени, указанного в миллисекундах, в время, указанное в тиках.

3. Получение данных из очереди в FreeRTOS

Для приема (считывания) данных из очереди используется функция xQueueReceive(). Элемент, который считывается (принимается), удаляется из очереди. Эта функция содержит три аргумента.

Первый и третий аргументы в этой функции такие же, как в ранее рассмотренных функциях для передачи данных в очередь. Но второй аргумент отличается.

const pvBuffer: указатель на область памяти, в которую будут копироваться считываемые данные.

Схема проекта

Схема проекта для демонстрации возможности использования очередей в FreeRTOS в Arduino представлена на следующем рисунке.

Схема проекта для демонстрации возможности использования очередей в FreeRTOS в Arduino

После ее сборки на макетной плате у нас получилась конструкция следующего вида.

Вид собранной конструкции нашего проекта на макетной плате

Пример использования очередей FreeRTOS в Arduino IDE

1. Первым делом нам необходимо запустить Arduino IDE и подключить в программе заголовочный файл Arduino_FreeRTOS.h. Также необходимо подключить библиотеку для работы с ЖК дисплеем 16х2 и библиотеку для работы с очередями (queue.h).

2. Далее инициализируем обработчик (дескриптор) очереди чтобы иметь возможность работать с содержимым очереди. Также сообщим плате Arduino к каким ее контактам подключен ЖК дисплей.

3. В функции void setup() мы инициализируем ЖК дисплей и последовательную связь со скоростью 9600 бод. Создадим очередь и две задачи с одинаковыми приоритетами (в дальнейшем вы можете поэкспериментировать с изменением этих приоритетов). Очередь у нас будет состоять из 4-х элементов целого типа. И, наконец, запустим планировщик (scheduler).

4. Далее запрограммируем две функции - TaskDisplay и TaskLDR. В функции TaskLDR мы будем считывать значение с аналогового контакта A0 (к нему подключен фоторезистор) и сохранять его в переменной current_intensity. Затем мы будем передавать значение этой переменной в очередь с помощью функции xQueueSend и переводить задачу в состояние блокировки на 1 секунду с помощью функции vTaskDelay().

5. В функции TaskDisplay мы будем принимать из очереди значение переменной с помощью функции xQueueReceive. Функция xQueueReceive() возвращает значение pdPASS если данные были приняты успешно из очереди, и значение errQUEUE_EMPTY если очередь была пуста (соответственно, из нее ничего не было принято). Также в функции TaskDisplay мы будем отображать принятое значение на экране ЖК дисплея с помощью функции lcd.print().

На этом все – мы закончили написание кода программы для демонстрации возможностей использования очередей в FreeRTOS в Arduino. Полный код программы и видео, демонстрирующее работу проекта, приведены в конце статьи.

Теперь подключите ЖК дисплей и фоторезистор к плате Arduino UNO в соответствии со схемой, приведенной выше в статье, и загрузите программу в плату Arduino. Откройте окно монитора последовательной связи (serial monitor) и наблюдайте работу проекта. Вы увидите как будут переключаться задачи и как значение, считанное с фоторезистора, будет изменяться в зависимости от изменения условий освещенности.

Пример работы нашей программы в окне монитора последовательной связи

Тестирование работы проекта использования очередей в Arduino FreeRTOS

Примечание: большинство библиотек, сделанных для работы с различными датчиками, не поддерживаются ядром FreeRTOS в связи с использованием функций задержки (delay function) внутри этих библиотек. Задержки полностью останавливают работу процессора (об нежелательности их использования можно прочитать в этой статье), следовательно, ядро FreeRTOS также останавливает свою работу и код FreeRTOS не выполняется. Поэтому для работы с FreeRTOS необходимо выбирать только те библиотеки, которые не используют функции задержки (delay) в своей работе, или же самостоятельно изменять код этих библиотек таким образом, чтобы в нем не использовались задержки.

Если вас заинтересовала данная тематика и вы хотите углубить свои знания в этой теме, то вы можете прочитать на нашем сайте статью об использовании семафоров и мьютексов в FreeRTOS на Arduino.

Исходный код программы (скетча)

Видео, демонстрирующее работу проекта

(Проголосуй первым!)
Загрузка...
752 просмотров

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

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