Как стать автором
Обновить

Azure RTOS. Часть 1: обзор и запуск (STM32 + CubeIDE + HAL)

Время на прочтение19 мин
Количество просмотров25K
Всего голосов 17: ↑17 и ↓0+17
Комментарии52

Комментарии 52

Наконец свершилось!!! Вроде как весьма адекватная RTOS и с подробным объяснением, как её подключить к такому удобному инмтрументу, как CubeMX/STM32CubeIDE со всеми специфическими настройками.
Специально сегодня зарегистрировался, чтобы сказать автору огромное спасибо. Будет, чем заняться на этих выходных :-)
И проект есть, который долго думал как сделать. Плата с OLED дисплейчиком 256х64 лежит, а всё никак не мог найти хорошего решения: либо RTOS без графики, а я никогда сам не начинал проекты с RTOS с нуля и потому непонятно, как подключить, либо графика без RTOS. А здесь есть оба компонента. С нетерпением буду ждать описание графической поддержки.
Вопрос по самой статье. Указано, что выходные коды функций не обрабатываются. Надеюсь, что проверка и реакция системы на возможные ошибки также будет описан.

Ещё раз огромное спасибо Сергею!!!
Спасибо за отзыв, приятно слышать!

Как раз думаем над темой для следующей части.

По поводу обработки ошибок: в учебных примерах возвращаемые коды/ошибки обычно не проверяют (для краткости и чтобы сосредоточиться на сути изучаемого предмета) и упоминают, что код не для продакшена. В рассмотренном примере код довольно простой, и ломаться там особо нечему. Но в следующей части учтем замечание и проверку возвращаемых кодов сделаем.
В рассмотренном примере код довольно простой, и ломаться там особо нечему

Ну да, при записи 0/1 в порт вообще редко, когда возникают ошибки. :-) Просто хотелось бы знать, как их лучше обрабатывать в RTOS вообще, ну и в этой, в частности.

Если думаете о следующей части, хотелось бы про графический дисплей. Сейчас редко какой дизайн обходится без дисплея, не просто так можно найти немало графических библиотек, но так, чтобы это шло в одном пакете с RTOS — вроде только в дорогих коммерческих проектах, как дополнительно оплачиваемый пакет. Причём МК, как правило, используют дисплеи с SPI. Так что, если можно…
Если я правильно понял, то в этой системе вполне можно использовать нормальное обращение через HAL/LL, хоть напрямую в порт, т.е. запись в SPI по DMA, но при этом надо использовать либо семафоры, либо мютексы. Или может лучше сделать отдельную задачу вывода информации и посылать данные в задачу через очередь? Осветить бы этот момент… Это вопрос больше архитектурный, и для человека, переходящего с однозадачной системы на RTOS — не очень прозрачный.
Понравилось: «распаяно 4 светодиода, и задача приложения — мигать ими, причем у каждого из них должна быть своя частота этого мигания. Без RTOS это сделать достаточно сложно и неудобно
))))
Увы, для введения в какую-то тему всегда приходится брать какие-то очень простые, искусственные задачи, и это в общем-то не только в электронике. Если начинать сразу с задачи, в которой вот прямо реально не обойтись без RTOS, есть риск получить довольно громоздкую и малопонятную статью.

Без RTOS именно данная задача вполне решается, но сам прикладной код будет менее изящным. Например, так: делается один таймер, который отсчитывает временные интервалы. Время включения/выключения светодиода должно быть кратным одному временному интервалу. Для каждого светодиода подсчитывается количество интервалов, и в нужный момент светодиод гаснет/зажигается.

Когда мы рассматриваем микроконтроллеры, речь идет о том, что рано или поздно появится задача, где обойтись без RTOS будет как минимум затруднительно. Когда от «лампочек и кнопочек» вы перейдете к чему-то более масштабному, например, TCP/IP, поверх него FTP, и файлы по этому FTP будут записываться на USB флешку — вот тогда могут начаться сложности. Нельзя, конечно, сказать, что такие задачи без RTOS вообще не решаемы, но применение RTOS может очень сильно упростить процесс разработки.

Также RTOS дает некоторые, грубо говоря, архитектурные преимущества. Например, при вызове функции задержки в RTOS задача «усыпляется», и планировщик передает управление другим задачам, а не заставляет процессор крутится в пустом цикле. Т.е. пока задача «ждет», другие задачи могут сделать что-то еще полезное, при этом логика «ждущей» задачи не «разрывается».
Да, тоже улыбнуло :-) Я обычно это решаю с помощью PWM, благо таймеров в STM32 хватает. Но здесь задача не просто поморгать, а показать как работать с асинхронными процессами.
статья интересная, но хотелось бы продолжения про обмен по портам и опрос аналоговых без провалов в опросе и обмене
Так использовать запуск АЦП по таймеру и получение данных в памяти через DMA.
В прерывании от DMA выставить семафор готовности (или просто флаг, Сергей, думаю, поправит), а в задаче по возведению семафора/флага, обработать полученные данные.
А чем она лучше/хуже фриртоса? Тут у человека выше проблемы с графикой и freertos что мне совсем не понятно. Фришка добавляется в проект в кубе нажатием одной кнопки и все работает. Такие библиотеки как tochgfx без проблем с ней работают.
Так все-же зачем нужна Azure RTOS если есть поддерживаемая буквально всеми freertos?
Каждой задаче — свой инструмент. Это как сравнивать PHP и ASP.Net: много факторов, влияющих на выбор. Иногда существенное влияние на выбор оказывает даже то, с чем вы больше знакомы/больше работали, или с чем принято работать в вашей организации.

По сути вопроса: в статье есть описание преимуществ Azure RTOS с технической стороны + ссылка на сравнение с FreeRTOS от инженера, который работает с обеими ОС (если вкратце, то там речь идет о том, что обе ОС хороши, но именно технически ThreadX по ряду причин выглядит несколько более совершенной).

Добавлю, что Azure RTOS сертифирована по нескольким стандартам безопасности. Впрочем, тем же (но в несколько меньшем наборе стандартов) может похвастаться SafeRTOS, но это уже не совсем FreeRTOS. Опять же, приходим к тому, что разных RTOS много, и каждая из них — это инструмент. Знание характеристик разных инструментов позволит подобрать лучший под конкретную задачу.

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

Насчет использования в один клик в CubeMX, я думаю, это дело времени. Надо написать обертку CMSIS-RTOS для ThreadX, и тогда процесс начальной настройки и перехода на систему будет гораздо проще. На данный момент все необходимые операции по интеграции ThreadX в проект (причем не обязательно именно в CubeIDE) достаточно проделать один раз, а потом можно использовать проект как шаблон.
Azure RTOS сертифирована по нескольким стандартам безопасности.

А вот это уже серьёзно
Azure RTOS ThreadX is also DO-178 certified.

Мой проект, про который писал чуть выше, как раз для такого класса задач.

Не могу пока повышать карму, но отдельное огромное спасибо за эту инфу. :-)
Кстати, сейчас есть третье издание книги по ThreadX
Пока только в бумажном варианте.
Но интересно, что в онлайне можно прочитать до 211 страницы, потом разрыв около 100+ страниц и остаток с 3хх до 400+. Т.е начинающий вполне может пройти половину курса без оплаты и понять, надо ли ему покупать эту книгу. :-)
Как указано в постинге инженера (на английском языке) — лучшая детерменированность Azure RTOS.
Я долгое время работал с теперь уже другом. Я как хардверщик с пониманием программирования, он программер с пониманием железа. Он изучал этот вопрос — при заявленном временном промежутке на каждую задачу в 1мс, реально может быть выделено от 1 до 5мс. Причём это величина случайная и от задачи независимая. Плюс длинное контекстное переключение. Следствие этого — потеря прерываний. Он выходил напрямую на автора FreeRTOS и тот подтвердил, что да, потеря прерываний есть. И эта проблема не решена даже в SaveRTOS.
Это вкратце. У меня есть темка на форуме iXBT по этому поводу, где описано немного подробнее. Не знаю, допустимо здесь давать темы на другие форумы или нет.

Подозреваю, что проблема графики в FreeRTOS относится именно к этому же. В одной из компаний, где работал мой друг, из-за FreeRTOS были проблемы с обменом по UART. Доказательством того, что именно из-за системы было увеличение системного тика с 1мс до 10мс, т.е. переключение контекста происходит реже и не занимает кучу времени… И UART заработал. Не знаю, как они дальше решали эту проблему, но факт остаётся фактом.
НЛО прилетело и опубликовало эту надпись здесь
О чем вообще речь может идти??

О надёжности. По каким стандартам сертифицирована FreeRTOS?
НЛО прилетело и опубликовало эту надпись здесь
Повторю ещё раз: мой товарищ выходил напрямую на автора FreeRTOS и тот подтвердил, что да, потеря прерываний есть. А различные time slicing, когда одна и та же задача меняла своё время выполнения от 1 до 5мс он измерял лично, показывал мне на экране скопа.
Я тогда вообще на подхвате в программировании был. В перерывах между HW дизайном.
И я не собираюсь никого ни убеждать, ни уговаривать. Нравится FreeRTOS, ну и пользуйтесь. Мне вот понравилась ThreadX.
Мир, дружба, жвачка :-)
НЛО прилетело и опубликовало эту надпись здесь
Это было лет 7 назад. Вы помните все свои разговоры 7 летней давности? Я не исключаю варианта, что он лично с ним разговаривал на одном из форумов ESC (Embedded Systems Conference), которые тогда ещё проходили каждую весну в San Jose.
Никто и не говорит о 1мкс.
НЛО прилетело и опубликовало эту надпись здесь
Работал. Выгребал код за индусами, которые не могли обеспечить работу по UART по acknowledge, который пришлось ввести из-за потери прерываний от этого самого UART. А перед этим, естественно, прошёл по их коду, чтобы понять, где они накосячили. С точки зрения программирования под FreeRTOS там всё было честно. Достаточно?

Вы перепутали блоги — здесь обсуждается, не FreeRTOS, а Azure ThreadX

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

Не уверен. Вышеупомянутый человек несколько лет работал с FreeRTOS, поскольку реально очень много компаний её используют. Привык, знает, что делать, как обходить проблемы. Но сейчас работает в стартапе, разрабатывающем медицинское оборудование. Используется именно ThreadX. Не без сложностей, поскольку всего пару-тройку месяцев. Но считает её гораздо удобнее и правильней.

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

Так-то оно так, но они делают медицинское оборудование, а там строгие требования по надёжности.
НЛО прилетело и опубликовало эту надпись здесь
Вы внимательно читаете ответы? Я же написал, что да, FreeRTOS действительно применяется много где. И за последние 3 года он поменял в качестве контрактора несколько компаний, где использовалась и FreeRTOS.
А что касается перехода в некоторых проектах на ThreadX, конечно попробуйте. Благо, автор статьи дал очень хорошую стартовую точку. Может и Вам понравится. С богатым опытом работы в RTOS вообще Вам будет явно легче, чем мне.
Благо, у меня есть сотрудник, который имеет опыт с mbed — поможет разобраться в тонкостях программирования. И товарищ недалеко. И мой форум есть, где кто-то уже работал с этой системой.
Сергей, я скачал библиотеку. Последняя версия 6.0.1
Во-первых, в директории ports гораздо больше дополнительных портов. Понятно, что надо удалять не только cortex_m0, cortex_m3, cortex_m7, но и все остальные.
А из папки cortex_m4 явно ещё имеет смысл удалить директории ac5, ac6, iar, keil, поскольку у нас CubeIDE на базе gcc. ac6, кстати, тоже, но идёт отдельной строкой. Про ac5 не знаю. Могу только предположить, что это предтеча ac6.
А вот файла cortex_m4/gnu/src/tx_vector_table_sample.S в этой версии просто нет.
Зато есть ports_module/cortex_m3,m4,m7, но только для IAR и AC5. Видимо их тоже надо удалить.
Нет и файла tx_initialize_low_level_sample.S. Где тогда устанавливать частоту CLKSYS и SYSTICK_CYCLES?

Хм, этот файл нашёлся в Middlewares\threadx-master\ports\cortex_m4\gnu\example_build
Видимо стоит перенести его в gnu\src. Надеюсь, что этим ничего не порушу. Тем более, папку example_build исключил из компиляции.
Проблема в том, что за время от написания материала до его публикации ветка master ушла вперед, и структура директорий поменялась, поэтому теперь есть такие несовпадения. Чтобы все было точно как в статье, нужен определенный коммит. Добавил информацию в статью.
А какая версия была при написании статьи? В статье линк ведёт на тот же 6.0.1 Посмотрел разницу с 6.0 — отличия минимальны и явно не приведут к исправлению ситуации.
А то, может Вы переправите на последнюю версию? image
Последователи наверняка будут чисто на автомате скачивать последнюю версию. Каждый раз объяснять, что надо взять старую…
Был бы у меня опыт работы с RTOS — может и сам бы сделал, некоторый опыт портирования есть. Но пока приходится полагаться полностью на чужой.
Здесь дело не в самой версии (я работал с 6.0), а в коммите. За одну версию, в принципе, можно сделать несколько коммитов. Даже если переправить на новую версию — через некоторое время master опять может накопить изменения, и ситуация повторится. Уже сейчас изменена структура, добавлены порты под новые архитектуры (что очень хорошо!), и примеры кода для разных IDE (а это означает, что там есть уже готовые шаблоны проектов!). Думаю, мы это используем в следующей части.

Суть материала в том, что мы показываем сам процесс запуска ОС «с нуля», и если вы это все проделаете, то поймете сам принцип, что и как устроено.

По сути вопроса:
1) версию можно посмотреть в tx_api.h
2) если все же хотите работать с последним коммитом, то изменится структура директорий, а tx_initialize_low_level_sample.S, действительно, переименовали в tx_initialize_low_level.S, разместили по пути ports\cortex_m4\gnu\example_build (для CubeIDE). Частоту SYSCLK можно задать в нем же. Скопируете куда-то еще — ничего не порушится, главное, чтобы файл участвовал в сборке.
3) поскольку в master добавили новых файлов (это все-таки значительное изменение), придется думать, что включать в сборку, а что нет. Нужен порт под M4.
4) tx_vector_table_sample.S не требуется. Таблица векторов прерываний уже есть в стартовом коде от CubeIDE, мы лишь слегка адаптируем ее для ThreadX.
Небольшая ошибочка в коде. tx_thread_create() требует (CHAR *)thread_name, а поскольку в примере она описана константой, то компилятор недоволен (const char *). Прямое приведение к виду (CHAR *) [можно (char *)], устраняет эту проблемку. Возможно у меня компилятор по умолчанию настроен на более строгое следование типам переменных.
tx_thread_create(&thread_x[i], (CHAR *)BoardLedsSettings[i].thread_name, thread_entry, i, pointer, DEMO_STACK_SIZE, 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);

А вторая проблемка у меня в том, что компилятор не находит объявления _tx_initialize_kernel_enter для вызова tx_kernel_enter() в main. Путь где-то не прописал чтоли? Но для файла demo_threads.c всё нашлось
Вы правы. Чтобы поправить, лучше всего убрать const в объявлении структуры BoardLedsSettings. Поправил код. У меня, правда, код почему-то нормально собрался и с const, хотя не должен был.
По второму вопросу: возьмите тот же коммит, который указан в статье. Причина описана в комментарии выше.
Возможно различные настройки компилятора.
Обратился к сотруднику за помощью, у него опыта в программировании больше. Хотя не Azure RTOS переходить не хочет.
1. Самое основное: в статье не указано, что исходники tрreadx надо поставить в пути для компиляции. Я ставил, но, видимо, не совсем правильно указывал пути для исходников.
Картинку здесь ставить неудобно, попробую текстом:
С.С++ General -> Paths and Symbols -> Source Location.
Туда к Core и Drivers надо добавить
[папка проекта]/Middlewares/threadx-master/common
[папка проекта]/Middlewares/threadx-master/cortex_m4/gnu
После чего в дереве проекта эти пути возникают дважды: один раз, как и было — в Middleware, а второй раз в виде отдельных частей после Drivers.

2. В скрипте компоновщика строку __RAM_segment_used_end__ = .; надо ставить в конце ._user_heap_stack: после второй строки. = ALIGN(8);
Я по неопытности поставил после первой :-) Думаю, стоит поправить в статье.

3. Поскольку здесь другая версия (решил остаться на 6.0.1), то файл tx_initialize_low_level.S имеет смысл поместить в раздел Startup, т.к. ое не относится к собственно системе. Особо непринципиально, но это такое размещение логично.

После этих изменений, особенно после первого, проект стал нормально собираться.
Я использую плату Discovery для L476. Соответственно, там всего 2 LED (две задачи вместо 4) и джойстик вместо кнопки пользователя. Проект запускается, но LED включаются/выключаются очень медленно. Проект сотрудника на этой же плате мигает более энергично. Но вот прерывания от кнопки не приходят ни в моём проекте, ни в его. В callback по прерыванию не заходит. Буду разбираться.
1. Исходники в данной IDE должны попадать в сборку по факту присутствия в дереве. Вы обновили дерево после их добавления? Middlewares (вместе с Core и Drivers) по умолчанию уже входит в дерево, соответственно попадание в нее threadx-master должно привести к их включению. Может, в дереве, внутри Middlewares они у вас исключены из компиляции? На всякий случай включу в материал проверку настроек на предмет Source Location, спасибо за замечание.
2. Действительно, из контекста может быть неочевидно. Спасибо, поправим.

По частоте переключения LED: проверьте в стартовом коде настройки SYSTEM_CLOCK, SYSTICK_CYCLES.
По прерыванию: поскольку у вас не та плата, для которой настраивается Cube, посмотрите, есть ли подтяжка вниз на плате, если нет, включите программную. Если подтяжка вверх, измените в Cube фронт прерывания. Убедитесь, что на линии, куда подключена кнопка, есть внешнее прерывание, и что у него нужный номер. Убедитесь с отладчиком, что срабатывает обработчик прерывания и выставляется событие, далее что оно обрабатывается. Где-то в этой цепочке что-то найдете.
Исходники в данной IDE должны попадать в сборку по факту присутствия в дереве. Вы обновили дерево после их добавления?

Да, естественно, попали (что мне в этом IDE не слишком нравится), но не в список компилируемых. Отсюда и проблемка.
включу в материал проверку настроек на предмет Source Location

image
По частоте переключения LED: проверьте в стартовом коде настройки SYSTEM_CLOCK, SYSTICK_CYCLES.

Это было сделано сразу.
Думаю, разберусь. Это уже ближе к железу и его настройкам :-)
А вообще — Кубик ведь настраивает частоту, sysclk (обычно 1мс) и генерируемый им проект уже имеет информацию о частоте работы процессора. Нельзя ли информацию из генерируемого проекта сразу завязать на частоту, которую использует ThreadX? Хотя бы через ifdef
#ifdef CUBE_SYSCLK  // это здесь в качестве заглушки :)
     #define SYSTEM_CLOCK CUBE_SYSCLK
     #define SYSTICK_CYCLES CUBE_SYSTICK
#else
то что указано сейчас
#endif
С прерываниями разобрался. Моя ошибка.

А вот с системной частотой что-то непонятное. Поставил задержки 50 и 75 вместо 500 и 750 стало ближе к тому, что надо. Где-то нестыковка в 10 раз… Понять бы где.
А где можно найти использование SYSTICK_CYCLES? Поставил там деление на 10000- стало совсем понятно и различие видно на глаз. Исходно переключается быстро, нажали — получили замедление.
Хм, остановил программу после запуска RTOS, в systick правильное значение, задаваемое SYSTICK_CYCLES. У меня при тактовой частоте 80МГц в systick 80000-1. Думал, что исходная инициализация железа не совпадает с системной. Вродже всё правильно Но такое впечатление, что в системе задержки больше получаются
Всё оказалось проще, чем я думал. По образцу и подобию со статьёй, поставил, генерацию частоты от внешнего кварца, а у меня, похоже то ли он неправильно заводится, то ли какая другая проблема. В-общем, поставил использование внутреннего генератора — всё стало работать как часики.
лучше всего убрать const в объявлении структуры BoardLedsSettings.

Тогда эта структура будет размещена в ОЗУ, куда будет копироваться из ПЗУ. Мне кажется более правильно оставить const, а перед употреблением взбалтывать приводить к нужному типу.
Не стоит этого делать, const придуман не просто так. Если указатель «приводить» к чему-то со снятием const, то где-нибудь в недрах кода при попытке записи по этому адресу (компилятор это вполне пропустит, если вы сняли const) могут возникнуть неприятности.
Возможно. У меня обычно были небольшие проекты, с небольшим объёмом памяти/кода и можно было отследить, что в строку ничего не будет писаться. Особенно для различных меню на экране. Расположение текстов этих меню вне поля const приводило к двойному расходованию памяти — в RAM столько же, сколько и в ROM.
Когда памяти много, этим можно не заморачиваться :)
И ещё один такой момент. Проект организуется для C++, но ядро явно написано на чистом С. Выбор C++ для проекта — ошибка или модули используют C++ и поэтому такое назначение было сделано специально с расчётом на дальнейшее развитие этого примера с включением модулей?
Не ошибка, в планах, действительно, есть дальнейшее развитие примера, и там C++ может пригодиться.
Ok, принято.
Спасибо за статью. Всё собирался «пощупать», но ни руки, ни моск не доходили.
Теперь, когда у меня всё заработало :-), хочу отметить очень полезный шаг автора — показ механизма динамического выделения стека под задачу. Обычно такое с самого начала на показывают, чтобы не загружать новичков «лишним», но я считаю, что это очень хороший шаг. Всё остальное достаточно просто, особенно если пользоваться бритвой Оккама, и на фоне этого простого показан один небольшой фрагментик полезной функциональности более высокого уровня.

С нетерпением жду следующего урока. Особенно если это будет графический дисплей :-)
Сергей, есть вопрос по прерываниям. В соответствии с документацией по ThreadX в прерывании необходимо использовать CALL _tx_thread_context_save на входе в прерывании и JUMP _tx_thread_context_restore на выходе (очевидно на ассемблере или ассемблерная вставка). Пишут, что это надо для корректного вызова функций системы. Насколько я понимаю, у Вас в примере этого не сделано в то время, как в callback используется установка флагов.
Подумалось следующее — в обработке прерываний с использованием HAL/LL, понятно функции ThreadX не используются, обработка происходит как бы над системой, а потом вызывается callback, в котором, очевидно будет происходить обработка по алгоритму. Может будет достаточно использовать сохранение/восстановление контекста в callback, а не залезать с этим ближе к аппаратному уровню?
Если посмотрите в tx_thread_context_save.s и tx_thread_context_restore.s, там эти функции представляют собой заглушки, которые ничего не делают, с комментарием о том, что для этого порта (Cortex-M4) они не требуются.
Если хотите бОльшей портируемости, можно добавить этот вызов. Куда именно — зависит от порта, но общее правило — в самое начало обработчика, именно «ближе к аппаратному уровню».
Но не факт, что при портировании на другую архитектуру останется та же обработка прерываний (и те же обработчики), так что эту часть, возможно, все равно придется впоследствии переписывать, так что портируемость выходит немного спорная.
Спасибо.
Благо, сейчас как раз на Cortex-M4 :)
Я закончил свой несколько расширенный относительно примера проект в версии 6.0.1. Апдейтнул до 6.0.2, там, насколько я понял, основное — отредактированы комментарии. Всё, естественно, работает.
Так что мне кажется, что имеет смысл этот блог переделать под 6.0.2, чтобы не путать новичков лишний раз.
Тем более, что кучка примеров, в том числе и для кубика, выложены под версию 6.0.2
Зарегистрируйтесь на Хабре, чтобы оставить комментарий