Pull to refresh

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

Reading time5 min
Views8.2K

В одной из предыдущих заметок автор пытался рассуждать о том, что при программировании микроконтроллера простой переключатель задач будет полезен в ситуациях, когда использование операционной системы реального времени — это слишком много, а всеобъемлющая петля (super loop) для всех требуемых действий — это слишком мало (Сказал, прямо как граф де Ла Фер). Точнее говоря, не слишком мало, а слишком запутано.


В последующей заметке планировалось упорядочить доступ к общим для нескольких задач ресурсам с помощью очередей на основе кольцевых буферов (FIFO) и специально отведенной для этого отдельной задачи. Разбросав по разным задачам те действия, которые не связаны друг с другом, мы вправе ожидать более обозримый код. А если при этом мы получим некоторое удобство и простоту, то почему бы и не попробовать?


Очевидно, что микроконтроллер не предназначен для решения любой мыслимой задачи пользователя. Тогда, возможно, такого переключателя задач окажется вполне достаточно во многих ситуациях. Короче, небольшой эксперимент вряд ли повредит. Поэтому, чтобы не быть голословным, ваш покорный слуга решил кое-что написать и подвергнуть тестированию свои каракули.


В микроконтроллерах, надо сказать, требование считаться со временем как с чем-то важным и жестко заданным встречается чаще, чем в компьютерах общего назначения. Выход за рамки в первом случае приравнивается к неработоспособности, а во втором случае ведет, всего лишь, к увеличению времени ожидания, что вполне допускается, если нервы в порядке. Есть даже два термина «soft real time» и «hard real time».


Напомню, речь шла о контроллерах с ядром Cortex-M3,4,7. На сегодня — очень распространенное семейство. В примерах, представленных ниже, использовался микроконтроллер STM32F303, входящий в состав платы STM32F3DISCOVERY.


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


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



В этой схеме задачи поочередно получают свою порцию времени и могут лишь отдать остаток своего тика и, если потребуется, следом пропустить несколько своих тиков. Эта логика показала себя хорошо, поскольку размер кванта можно сделать небольшим. А именно это и требуется для того, чтобы не пытаться срочно поднимать задачу, для которой только что случилось прерывание, а также повышать, а потом понижать ее приоритет. Тот пакет, который только что получен спокойно подождет 200-300 микросекунд, пока его задача не получит свой тик. А если у нас Cortex-M7, работающий на частоте 216 МГц, то 20 микросекунд для одного тика — это вполне разумно, поскольку на переключение уйдет меньше половины микросекунды. И любая задача из примера выше никогда не опоздает больше, чем на 140 микросекунд.


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



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


Для разрешения доступа к разделяемым ресурсам используются две похожих схемы. Первая, которая упоминалась в предыдущей заметке, использует несколько FIFO (или циклических буферов по числу производителей сообщений) и отдельную согласующую задачу. Она предназначена для связи с внешним миром и не требует ожидания от задач, генерирующих сообщения. Надо только следить, чтобы очереди не переполнялись.


Вторая схема также использует отдельную задачу для разрешения доступа, но вносит ожидания, поскольку управляет внутренним ресурсом в обоих направлениях. Эти действия не могут быть привязаны жестко ко времени. На рисунке 3 показаны составные части второй схемы.



Основными элементами в ней является буфер запросов, по числу желающих задач, и один индикатор доступа. Работа этой конструкции достаточно проста. Задача слева посылает запрос на доступ в специально отведенное для нее место (например, task 2 записывает 1 в Request 2). Задача – диспетчер выбирает кому разрешить и записывает во флаг разрешения номер выбранной задачи. Задача, получившая разрешение, выполняет свои действия и записывает в запрос признак окончания доступа, значение 0xFF. Планировщик, видя, что запрос снят, обнуляет флаг разрешения, обнуляет прошлый запрос и переходит к запросу от другой задачи.


Два тестовых проекта под IAR и описание используемой платы STM32F3DISCOVERY можно посмотреть здесь. В первом проекте ATS303 просто проверялась работоспособность и проходила отладка. Пригодились все установленные на этой плате светодиоды. Никто не пострадал.


Во втором проекте BTS303 проверялись два упомянутых варианта распределения ресурсов. В нем задачи 1 и 2 генерируют тестовые сообщения, которые поступают оператору. Для связи с оператором пришлось добавить платку с TTL COM портом, как показано на фото ниже.



Со стороны оператора задействован эмулятор терминала. Думаю, читатель извинит автора за мягкий ламповый цвет. Выглядит это так.



Для начала работы всей системы, до разрешения прерываний, необходимы предварительные действия в теле нулевой задачи main(), которые представлены ниже.


void  main_start_task_switcher(U8 border);

    U8  task_run_and_return_task_number((U32)t1_task);
    U8  task_run_and_return_task_number((U32)t2_task);
    U8  task_run_and_return_task_number((U32)t3_human_link);
    U8  task_run_and_return_task_number((U32)t4_human_answer);
    U8  task_run_and_return_task_number((U32)task_5);
    U8  task_run_and_return_task_number((U32)task_6);
    U8  task_run_and_return_task_number((U32)task_7);

В этих строчках происходит сначала запуск переключателя, а затем, по очереди, остальных семи задач.


Вот минимальный набор необходимых для работы вызовов.


  void task_wake_up_action(U8 taskNumber);

Этот вызов используется в прерывании от пользовательского аппаратного таймера. Вызовы из самих задач говорят сами за себя.


 void release_me_and_set_sleep_steps(U32 ticks);

    U8 get_my_number(void);

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


В проекте BTS303 задача 3 получает команды оператора извне и отправляет ему ответы на них, которые идут от задачи 4. Задача 4 получает от задачи 3 команды от оператора и выполняет их с возможными ответами. Задача 3 получает также сообщения от задач 1 и 2 и отправляет по UART на эмулятор терминала (например, putty).


Задача 0 (main) производит некоторую вспомогательную работу, например, проверяет количество оставшихся не затронутыми слов в стековой области каждой задачи. Эту информацию может запросить оператор и получить представление об использовании стека. Первоначально для каждой задачи отводится область стека размером 512 байтов (128 слов) и надо следить (хотя бы на этапе отладки), чтобы эти области не приближались к переполнению.


Задачи 5 и 6 делают вычисления над некоторой общей переменной с плавающей точкой. Для этого они запрашивают к ней доступ у задачи 7.


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


 void wake_me_up_after_milliSeconds(U32 timeMS);

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


Как видим, список всех необходимых вызовов умещается на одной странице.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 9: ↑8 and ↓1+7
Comments9

Articles