Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В своей предыдущей статье я вскользь упомянул, что использую в проекте операционную систему реального времени собственной разработки vsrtos, которая по внешнему API похожа на FreeRTOS. Так зачем же нужно было ее разрабатывать, и когда стоит сделать выбор в ее пользу вместо FreeRTOS?
В этой статье будут разобраны плюсы и минусы использования vsrtos и FreeRTOS для определенного ряда задач, ради которых vsrtos и была разработана.
Предпосылки к созданию
В один из своих домашних проектов (о нем планирую рассказать в одной из следующих статей), после тщательной оценки требований к аппаратным ресурсам, я заложил микроконтроллер stm32l010k8 (Cortex-M0, 64 KB Flash, 8 KB RAM). По моим расчетам, я мог не задумываясь решить задачу, даже используя десяток потоков FreeRTOS. Собственно, так оно и получилось. Прототип работал хорошо и стабильно, а у меня оставалось 3 килобайта неиспользованной RAM. Далее мне захотелось расширить функционал устройства. Однако в проекте имеется LCD экран на базе контроллера ST7920 с разрешением 128 на 64 пикселя, подключенный по SPI. Для прошлой задачи скорость его обновления была достаточной, но для решения этой уже требовались доработки.
Несколько слов о работе с экранами на основе контроллера ST7920 по SPI
При работе с монохромным экраном на основе контроллера ST7920 по SPI, отправка данных или команд происходит по пакетам. Один пакет - 3 байта. То есть для того, чтобы установить координаты X/Y (обязательно устанавливаются парами), требуется отправить 6 байт (по 3 байта на установку каждой координаты). Устанавливать X/Y необходимо для каждой сдвоенной строки (внутри контроллера на самом деле экран 256x32, которой разорван посередине по оси X на два экрана 128x32, а правая часть находится под левой. Картинка отсюда).
После установки координат на начало сдвоенной строки, необходимо отправить ее содержимое - данные о 256 пикселях. При отправке данных пакетами, последовательно от самого левого пикселя до самого правого сдвоенной строки, они начнут отображаться от начала выставленной строки до конца выбранной строки + 32 (из-за организации экрана, описанной выше). В каждом пакете из трех байт данных содержится 8 бит полезных данных.
Изначально в проекте был выделен буфер под изображение на экране, размером один килобайт (128 * 64 / 8). И буфер под двойную строку (256 * 3) в формате пакетов, пригодных для отправки в экран по DMA. Данные из буфера изображения экрана построчно копировались в буфер для отправки, преобразовывалась на ходу в формат пакетов SPI для ST7920.
В итоге, для обновления экрана требовалось:
Выставить координаты X/Y для каждой двойной строки
Преобразовать строку изображения к формату для отправки
Произвести отправку.
Установка X/Y происходила отправкой независимых пакетов по 3 байта, отправка двойной строки цельным куском. И команды и данные передавались по DMA.
При таком подходе было заметно, как перерисовывается изображение. К тому же, максимальная частота SPI у данного экрана 500 кГц, что еще больше осложняло дело. Поразмыслив над тем, что у меня осталось от решения задачи на прошлом этапе, я решил изменить принцип работы с экраном и его изображением следующим образом:
Хранить изображение не в удобном для рисования формате, а в пригодном для отправки по SPI. Из-за этого объем RAM, требуемый для хранения изображения вырос в 3 раза (на 1 байт полезных данных 3 для отправки).
Держать команды позиционирования курсора на экране вместе с изображением. Это также увеличило потребления RAM.
Отправлять данные не кусками, а цельным блоком. И команды и данные вперемешку.
При таком подходе:
Значительно выросло потребление RAM
Пришлось переписать библиотеку рисования примитивов, чтобы она рисовала сразу в пригодном для отправки формате и игнорировала заголовочные команды в начале каждой сдвоенной строки.
Но, так или иначе, цель была достигнута. Отрисовка начала происходить очень быстро и начала удовлетворять требуемой для расширенной задачи скорости.
Однако, возник нюанс. На решение новой задачи уже не хватало RAM… По моим прикидкам, около 700 байт. Потоки FreeRTOS падали один за другим или происходили зависания в разных частях кода. И, больше всего выводило из себя то, что FreeRTOS не давала толком понять, где именно не хватало памяти. Я начал копаться в ее недрах и понял, что есть множество причин, которые могут не позволить программной защите памяти указать мне, что у задачи закончилось место под стек. В этом случае у меня было два варианта:
Вернуть все как было и сделать конвертацию в формат SPI для ST7920 “на лету”, производя отправку в прерываниях от SPI.
Как-то снизить потребление RAM сервисными функциями.
Переделывать все на работу по прерываниям мне не хотелось. DMA не для того изобретали, чтобы я 20% процессора тратил на конечный автомат в прерывании. А вот идея снизить потребление RAM показалась здравой.
После оценки потребления по map файлу (во FreeRTOS использовалась только статическое выделение памяти, в heap в проекте ни в каком виде не использовался. Даже sbrk), я пришел к выводу, что объекты FreeRTOS занимают примерно 25% проекта (сюда входят как используемые семафоры/мьютексы/очереди, так и внутренние данные самой FreeRTOS, необходимой ей для работы). Остальное же было занято стеками задач и новоиспеченным буфером почти в 3.2 килобайта. Выделенные стеки задач мне также казались сильно большими, но и их не хватало (об этом ниже).
Решив, что примерно 35% RAM - это сильно много, а предупреждения об отказах ОС “только если повезет”, меня не устраивают, я решил разработать свою ОС, лишенную этих недостатков. Но почему они вообще есть в такой популярной операционной системе?
Сравнение операционных систем
Недостатки FreeRTOS выходят из ее достоинств:
Достоинства | Недостатки |
Портирована под множество архитектур | Лишние уровни абстракции, которые потребляют стек при использовании под конкретную архитектуру |
Структуры базовых компонентов универсальны (очереди, мьютексы, семафоры) | Большее потребление RAM |
Большое количество возможностей, которые покрывают широкий спектр задач | Много неиспользуемого кода, который усложняет восприятие при чтении |
Можно создавать задачи как статически, так и динамически | Код сложнее читать из-за двух разных видов инициализации (хотя внутри все сводится к одному механизму). К тому же, связывание между задачами происходит в момент регистрации задачи в процессе выполнения программы. Это ведет к более сложному анализу происходящего при падениях в HardFault |
Все компоненты инициализируются по ходу выполнения программы | Невозможность без выполнения оценить всю связанность в проекте |
Поскольку в 95% случаев мне приходится работать с микроконтроллерами на основе Cortex-M (которые во всей линейке обратно совместимы, за исключением конкретных дополнительных возможностей в каждый более новой линейке), то было принято решение написать ОС, которая была бы максимально оптимизированной и простой для чтения только под одной архитектурой. Однако, реализуя эти принципы, она автоматически лишается описанных выше достоинств FreeRTOS.
Возможности, преимущества, недостатки
Возможности и преимущества:
Реализован API, покрывающий основные потребности в RTOS, совместимый с FreeRTOS:
Запуск планировщика
Вход в критическую секцию/выход из критической секции (только в потоке).
Принудительное переключение задачи (можно вызвать как из потока, так и из прерывания)
Получение времени (из потока или из прерывания), ожидание до метки времени/ожидание в течении заданного периода (только из потока)
Блокировка/разблокировка мьютекса (только из потока)
Получение семафора (только из потока), выдача семафора (из потока и из прерывания)
Получение данных из очереди (только из потока), отправка данных в очередь (из потока и из прерывания)
Инициализация всех потоков происходит в одном месте и описывается пользователем в виде массива константных структур. Это гарантирует, что никакое повреждение стека не сломает конфигурацию задач.
Проверка переполнения стека происходит на этапе каждого переключения контекста относительно данных из неизменной конфигурации задачи. Таким образом, в 100% случаев переполнение будет определено (если только не посыпалась flash, но тогда у меня вообще плохие новости…).
Недостатки
Менее гибкая, чем FreeRTOS.
Приходится вручную задавать все связи между объектами.
Работает на момент написания статьи только с Cortex-M0 (в дальнейшем планирую увеличить список до всей линейки Cortex-M. Там минимальные отличия).
Примеры использования
В качестве примера использования может служить программа из предыдущей статьи. В дальнейшем я планирую дополнять документацию перечнем проектов, в которых данная операционная система была использована.
Выводы: когда стоит сделать выбор в пользу vsrtos вместо FreeRTOS?
Повлиять на решение в пользу vsrtos могут следующие причины:
У вашего микроконтроллера мало памяти, а ОС все же требуется
Вам не очень нравится падать в hardfault по непонятным причинам, с последующим выяснением того, что ваш поток перезаписал часть системных данных ОС, а потом вы упали совершенно в другом месте при попытке воспользоваться этими данными
Вам требуется надежное связывание объектов на этапе компиляции для повышения надежности