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

  • Tutorial

На недавно прошедшем Microsoft Build 2020 многократно упоминалась Azure RTOS как специализированная ОС жесткого реального времени для микроконтроллеров.


В данном материале мы последовательно разберемся в том, что это за операционная система, какое место она занимает в продукции Microsoft для встраиваемых систем, а также установим планировщик ОС на один из микроконтроллеров STM32.


Кому не интересен обзор, а нужна практическая часть — переходите сразу к ней.


Что это вообще такое?


Микроконтроллер – это специализированная микросхема, объединяющая микропроцессор, память и периферийные устройства в одном корпусе. В отличие от "большого" компьютера имеет ограниченные объемы собственно этой самой памяти: типовые значения и для RAM, и для ROM – десятки-сотни килобайт.


Как правило, микроконтроллер не имеет MMU (хотя есть и исключения, но это именно исключения, которые правильнее будет уже отнести к совершенно другой категории систем-на-кристалле), то есть отсутствует аппаратная поддержка механизма виртуальной памяти, что не позволяет использовать "полновесные" ОС даже при расширении объема встроенной памяти внешними микросхемами.


В связи с перечисленным, код под микроконтроллеры разрабатывается особым образом, в специализированных IDE, а операционные системы вообще выделены в особый класс. Основной функцией ОС для микроконтроллера является реализация многозадачности, а бонусом обычно идут разные стеки сети, файловых систем и т.д. Ни о каком окружении и вспомогательных утилитах, как в настольных ОС, здесь речи не идет. Так, например, в ОС для микроконтроллеров нет процессов, есть только задачи = нити = потоки, а сама ОС, как правило, компонуется с пользовательским кодом в единую микропрограмму ("прошивку"). Для понимания особенностей таких ОС рекомендуем статью. Отметим, что в ThreadX, несмотря на прямое отсутствие процессов, есть их аналог — модули.


Впрочем, ограниченные объемы ресурсов никак не мешают использовать микроконтроллеры для решения узкоспециализированных задач. Более того, по меркам микроконтроллера, 128 КБ ROM и 64 КБ RAM – уже довольно внушительные цифры. Микроконтроллер, несмотря на отсутствие "большой" ОС, успешно может записывать файлы на USB флешку, обмениваться данными по сети, а некоторые реализации содержат специальные инструкции для цифровой обработки сигналов, то есть могут решать достаточно "тяжелые" задачи.


Зачем вообще нужна ОС в микроконтроллере, ведь есть альтернативные варианты архитектуры типа "суперцикла"? Вопрос не очень простой и до сих пор вызывающий что-то типа религиозных войн. Достаточно подробный ответ на него дан в этой статье. Если совсем вкратце, то это упрощение кода в целом, что позволяет не только разработать, но и успешно модифицировать уже написанный код. Конечно, есть и минусы в виде потребления ресурсов "железа" на саму ОС и необходимости понимания основ написания потокобезопасного кода.


Все изложенное описывает картину достаточно укрупненно, так как везде есть исключения и оговорки. Например, на микроконтроллерах без MMU можно запустить ucLinux – порт "большого" Linux специально для микроконтроллеров без MMU (без защиты памяти, естественно, со всеми вытекающими последствиями). Как правило, для этого потребуются дополнительные микросхемы памяти, так как встроенной хватит только для загрузчика этой самой ucLinux.


Что есть у Microsoft?


Microsoft традиционно занимается "большими" ОС, среди которых тоже есть специализированные решения в виде Windows 10 IoT Enterprise LTSC, значительно дешевле настольных систем и со специальными возможностями встраивания. Windows 10 IoT Enterprise требует практически полноценного (хоть и промышленного и малогабаритного) компьютера для запуска. Впрочем, есть редакция Windows 10 IoT Core, ориентированная только на приложения UWP, где требования к системе ниже: она успешно запускается на Raspberry Pi 2.


Здесь же нельзя не упомянуть класс операционных систем Windows Embedded Compact, которые могут работать на системах, по вычислительным возможностям находящимся где-то между полноценными компьютерами и микроконтроллерами. Compact – отдельный класс ОС, не совместимых с "настольной" Windows, требующих особых средств разработки. Последний выпуск датируется 2013-м годом, далее ОС развития не получила, но все еще продается и поддерживается, как и несколько предыдущих версий.


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


Первым таким решением был .Net Micro Framework, который позволял разрабатывать для микроконтроллеров на языке C#. Вводную информацию можно найти в статье, а репозитории проекта – по ссылке. К сожалению, на всех репозиториях стоит метка "Archive", а последние изменения датируются 2018-м годом. .Net Micro Framework достаточно интересен именно реализацией C#, что позволяет применить все преимущества данного языка на таких ограниченных системах, как микроконтроллеры. Реализация C# с его механизмами управления памятью представляет собой значительный "оверхед" для систем с ограниченными ресурсами, хотя из личного опыта — работает достаточно хорошо и надежно (несмотря на часто встречающиеся едкие комментарии к тематическим статьям). Существуют и коммерческие проекты на .Net Micro Framework.


На данный момент доступны и сторонние реализации среды выполнения для C#: https://www.nanoframework.net/, https://www.wildernesslabs.co/. Отметим, что последняя аппаратная платформа вполне подходит и для запуска ucLinux, так что к выбору ОС следует относиться, как к выбору инструмента для решения задачи: что удобнее, то и применяем.


В 2019 году Microsoft поглощает Express Logic, и среди решений для микроконтроллеров от Microsoft появляется Azure RTOS, которая раньше называлась X-WARE IoT Platform. В Azure RTOS входит ядро ThreadX вместе с дополнительными компонентами, а также добавлены средства подключения к Azure IoT Hub и Azure IoT Central. Само название Azure RTOS подчеркивает применение совместно с сервисами Azure для устройств Интернета вещей.


В состав Azure RTOS входят:


  • сама ОС ThreadX, а именно, ядро, планировщик, реализующий многозадачность и синхронизацию задач;
  • стек TCP/IP NetX/NetX Duo;
  • стек FAT FileX;
  • стек USB Host/Device/OTG USBX;
  • реализация графического интерфейса GUI: GUIX и инструмент разработки (GUIX Studio);
  • реализация равномерного износа флеш-памяти для FileX: LevelX;
  • система трассировки событий TraceX;
  • SDK для Azure IoT поверх NetX Duo – готовые средства для подключения устройства к службам Azure.

Нельзя не отметить одно из специализированных решений высокой готовности: Azure Sphere. Это — безопасная платформа для приложений интернета вещей со встроенными механизмами коммуникаций и безопасности. Она представляет собой микроконтроллер (скорее даже SoC) с установленным ядром Linux, а также готовыми облачными сервисом для доставки обновлений безопасности.


Реальное время


Этот термин используется повсеместно, без уточнения, в нашем же случае важно разобраться, что это такое.


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


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


Среди всех перечисленных продуктов к системам жесткого реального времени относятся:


  • Все семейство Windows Embedded Compact — ориентировано на промышленные компьютеры;
  • Azure RTOS/ThreadX — ориентирована на микроконтроллеры;
  • Azure Sphere — специализированное решение, в котором операционная система (Azure Sphere OS) не является системой реального времени, но тем не менее предоставляются механизмы запуска пользовательских приложений реального времени.

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


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


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


Что делает ThreadX системой реального времени? Время реакции на внешние события в ThreadX строго определено: поток с высоким приоритетом начинает обработку внешнего события за гарантированное время. Например, время переключение контекста всегда гарантированно меньше 100 циклов.


Таким образом, для микроконтоллеров на данный момент Azure RTOS — единственное решение жесткого реального времени от Microsoft. В принципе, сюда же (с некоторыми оговорками) можно было бы отнести и Azure Sphere, но это уникальный продукт с уникальными возможностями, поэтому его мы обсудим в отдельной статье.


Чем уникальна Azure RTOS


Исследование показывает, что данная ОС является одной из наиболее часто применяемых (более 6 миллионов инсталляций). В основном она используется в специализированном оборудовании, таком, как устройства беспроводной связи, принтеры, модемы, устройства хранения данных, медицинские устройства, интеллектуальные датчики.


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


  • Малый размер. Минимальная система занимает 2 КБ ROM. Размер увеличивается автоматически по мере использования возможностей ОС.
  • Поддерживаются различные методы реализации многопоточности, как вытесняющая, так и кооперативная многопоточность.
  • Детерминированное время переключения контекста (меньше 100 циклов), быстрая загрузка (меньше 120 циклов), опциональная проверка ошибок, пикоядро без "слоев".
  • Поддержка большого количества микроконтоллеров и IDE для разработки.
  • Порог вытеснения (Preemption threshold) — порог вытеснения N означает, что данный поток может быть вытеснен только потоками с приоритетом выше N, т.е. от 0 до (N — 1) включительно, а потоки с приоритетом ниже N (т.е. больше N включительно) не могут вытеснять данный поток. Правильное использование данной возможности уменьшает количество переключений контекста, а также уменьшает время реакции на внешние события. Подробную информацию можно найти в статье.
  • Сцепление событий (Event chaining) — позволяет объединить несколько событий в единый сигнал синхронизации для потока, что позволяет синхронизироваться сразу по нескольким событиям, причем в разных комбинациях (И, ИЛИ).
  • Наследование приоритета (Priority inheritance) — позволяет избежать негативных последствий ситуации инверсии приоритетов. Описание ситуации инверсии приоритетов — тема для целой статьи, отдельно с данной проблемой многозадачных систем можно ознакомиться здесь.
  • Оптимизированная обработка прерываний от аппаратных таймеров;
  • Модули (Modules). ThreadX позволяет "обернуть" один или несколько потоков приложения в "модуль", который может быть динамически загружен и запущен на целевом устройстве. Модули позволяют производить обновление "в полях" с целью исправления ошибок. Также при помощи модулей можно разбить микропрограмму на сегменты и динамически определять набор выполняемых потоков, чтобы сэкономить память.
  • Встроенная трассировка событий и аналитика стека. Подбор размера стека потока является одной из самых важных задач при разработке с использованием ОС для микроконтроллера. Нельзя сделать слишком маленький стек, т.к. в отсутствие защиты памяти при переполнении стека — произойдет порча областей памяти других задач. Слишком большой стек также недопустим, т.к. приведет к излишнему расходованию памяти, а она ограничена.

Также рекомендуем интересное сравнение ThreadX с FreeRTOS от инженера, работающего с обеими ОС, а также данную книгу.


Лицензирование


Azure RTOS — коммерческая ОС с соответствующими требованиями к применению в производстве. Однако в ряде случаев платить за ее использование не понадобится.


  • Вам не требуется лицензия, если вы используете код не для производства, а для изучения, разработки, тестирования, портирования, адаптации системы для вашего решения;
  • Лицензия на использование в производстве включена автоматически при развертывании ОС на любой из микроконтроллеров из данного списка. На август 2020 года список еще не заполнен в связи с тем, что процедуры лицензирования еще не завершены, но уже есть соответствующий issue, в котором упомянуты микросхемы Microchip, NXP, Renesas, ST, и Qualcomm;
  • В ином случае вам нужно приобрести платную лицензию.

Во всех случаях ОС поставляется с исходным кодом.


Запуск ThreadX на STM32


Для понимания зависимостей между компонентами Azure RTOS приводим соответствующий рисунок из документации:



Как видим, ThreadX является основой для всего остального. Ядро ThreadX обеспечивает многопоточность, переключение контекста, синхронизацию потоков. Поддерживаются таймеры, очереди сообщений, семафоры, мьютексы, флаги событий, пулы байтов и блоков (в каком-то смысле аналог кучи в C++, но с потокобезопасным управлением).


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


Будем использовать относительно недорогую и популярную плату STM32F4Discovery, но весь процесс можно с успехом повторить на любом микроконтроллере, например, на сверхдешевом и доступном STM32F103C8T6.


STM32F4Discovery удобна тем, что уже имеет встроенный отладчик ST-Link, большое количество периферии для экспериментов, а все выводы микроконтроллера (STM32F407VGT6) выведены на контакты.



Инструменты, которые нам понадобятся


Эксперименты будем проводить на Windows 10 (подойдет также любая, начиная с 7).


Будем также использовать STM32 HAL — набор универсальных API для микроконтроллеров STM32. Есть много мнений и "за", и "против" использования HAL. На наш взгляд, HAL, внося некоторый "оверхед", все же позволяет получить хорошо читаемый и модифицируемый код. HAL не требует скачивания, все необходимые библиотеки будут загружены автоматически при создании проекта.


Скачиваем и устанавливаем STM32CubeIDE — бесплатная IDE от STMicroelectronics на базе открытых инструментов.


Загружаем исходный код ThreadX c GitHub. Существуют, конечно, "правильные" способы использования репозитория с исходным кодом в виде клонирования репозитория или создания форка, но для простоты описания просто скачиваем его как архив: зеленая кнопка "Clone", затем "Download zip". UPD (см. комментарии): для того, чтобы структура исходного кода полностью соответствовала данному руководству, необходимо использовать определенный коммит. Для того, чтобы его получить, сначала установите клиент Git для командной строки, затем откройте командную строку, создайте временную директорию, перейдите в нее и выполните команды:


git clone https://github.com/azure-rtos/threadx.git
git checkout f8e91d4

Затем переименуйте результирующую директорию из threadx в threadx-master.


Теперь подключаем плату STM32F4Discovery через разъем Mini-USB к компьютеру, проверяем наличие устройства "ST Link" в диспетчере устройств. Плата питается по этому же кабелю.



Создание и правка проекта


Запускаем STM32CubeIDE. При запуске нас попросят указать директорию для хранения Workspace — можно оставить директорию по умолчанию.


Создаем новый проект, выбрав на главном экране Start New STM32 Project.



В появившемся окне в поле 1 набираем STM32F4DISCOVERY, выбираем плату в списке плат 2 (она там будет одна) и нажимаем Next (3):



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



Далее выбираем "Copy only necessary files..." и нажимаем Finish.



На появляющиеся вопросы отвечаем Yes:




Открывается окно Project Explorer, в нем находится иерархия файлов проекта. Находим файл threadx_test.ioc и переходим к нему (двойной клик):



В открывшемся окне переходим на вкладку Clock Configuration и убеждаемся, что система тактирования настроена следующим образом:



Заметим, что SYSCLK = 168 МГц, это нам понадобится далее для настройки таймера SysTick.


Вернемся на закладку Pinout & Configuration, где развернем пункт Connectivity, выберем USB_OTG_FS (1) и выключим его (2), затем в группе выводов (3) всем выводам выставим состояние Reset_State (4).



Мы отключаем USB OTG, так как в соответствующем коде используется функция HAL_Delay, которая, выполняя задержку, не позволяет планировщику ОС правильно переключать потоки. Код нужно адаптировать для использования с ThreadX, создав отдельный поток и заменив функцию задержки из HAL на функцию задержки из ThreadX, которая на время задержки передает управление другим потокам. Но для простоты примера мы этого не делаем, а просто отключаем USB OTG.


Аналогично в группе System Core перейдите к SYS и выберите таймер TIM7 в качестве Timebase Source для Serial Wire Debug:



SysTick Timer нам понадобится для ядра ThreadX.


Нам также нужно активировать прерывание на выводе PA0, к которому на плате подключена кнопка. Смотрим в User Manual к плате, как подключена кнопка:



Соответственно, нам понадобится прерывание по восходящему фронту. Подтягивающий вниз резистор на плате уже есть, встроенная подтяжка не потребуется. Соответствующие настройки делаем в группе System Core — GPIO:



В группе System Core — NVIC включаем прерывание EXTI0:



На этой же странице в группе Priority group установите значение "4 bits...":



Важно! В этом же окне выделите строку EXTI line0 interrupt и ниже в поле Preemption priority установите значение 15 (самый низкий приоритет прерывания). Такое изменение связано с тем, что приоритет пользовательских прерываний, из обработчиков которых используются функции RTOS, не должен превышать приоритет, с которым работают служебные обработчики прерывания самой RTOS. В данном случае для простоты мы устанавливаем самый низкий приоритет.


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


Там же, но на вкладке Code Generation, отключим генерацию кода для Pendable request for system serivice, System tick timer, так как соответствующий код уже есть в порте ThreadX для ядра Cortex-M4, причем обработчики определены уже с нужными именами:



Сохраним проект и согласимся с появившимся предложением о генерации кода.


Далее копируем полученный нами ранее каталог threadx-master в каталог threadx_test\Middlewares нашего workspace. В Project Explorer нажимаем F5 и видим, что все необходимые файлы появились в дереве:



Нажимаем на название проекта threadx_test правой кнопкой мыши и выбираем Properties. Переходим к разделу C/C++ General — Paths and Symbols. Нажимаем кнопку Add и добавляем путь


Middlewares/threadx-master/ports/cortex_m4/gnu/inc

Не забыв установить все флажки, как на рисунке:



Если вы делаете эксперименты на микроконтроллере с другим ядром, в вашем случае нужно включить заголовочные файлы для этого ядра, выбирайте путь соответственно (доступны cortex_m0, 3, 4, 7)


Аналогично добавляем путь


Middlewares/threadx-master/common/inc

Важно! Перейдите на вкладку Source Location и убедитесь, что в поле Source folders on build path присутствуют следующие директории:


  • /threadx_test/Core
  • /threadx_test/Drivers
  • /threadx_test/Middlewares

Если какая-либо из них отсутствует — добавьте ее.


Нажимаем Apply and Close и соглашаемся с перестроением индекса.


Теперь по пути Middlewares/threadx-master/ports в Project Explorer исключим из сборки:


  • весь каталог cortex_m0
  • весь каталог cortex_m3
  • весь каталог cortex_m7
  • cortex_m4/gnu/src/tx_vector_table_sample.S (таблица векторов прерываний уже есть в нашем стартовом коде)

Для этого кликаем по каждому каталогу/файлу правой кнопкой мыши, выбираем Properties и устанавливаем галочку Exclude resource from build:



На исключенных таким образом из сборки файлах и директориях появляется значок-перечеркивание.


Также исключите из сборки (или можете просто удалить):


  • Middlewares/threadx-master/cmake
  • Middlewares/threadx-master/docs
  • Middlewares/threadx-master/samples

И убедитесь, что следующие каталоги НЕ исключены из сборки:


  • Middlewares/threadx-master/common
  • Middlewares/threadx-master/ports/cortex_m4

Перейдем к скрипту компоновщика STM32F407VGTX_FLASH.ld и найдем строчки


  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

и после второй (последней, нижней) строки


    . = ALIGN(8);

добавляем строку


    __RAM_segment_used_end__ = .;

Что сообщит ThreadX о первом неиспользуемом участке памяти. В дальнейшем этот участок памяти можно использовать по своему усмотрению.


Если планируете запуск из RAM, можете то же самое сделать в файле STM32F407VGTX_RAM.ld.


Теперь в Project Explorer разворачиваем ветку Middlewares/threadx-master/ports/cortex_m4/gnu/src и открываем файл tx_initialize_low_level_sample.S.


Находим строку


SYSTEM_CLOCK      =   6000000

и меняем значение 6000000 на 168000000 в соответствии с частотой SYSCLK.


В следующей строке


SYSTICK_CYCLES    =   ((SYSTEM_CLOCK / 100) -1)

меняем значение 100 на 1000. Тем самым мы изменим частоту системных тиков со 100 до 1000 Гц: удобнее будет задавать задержки для соответствующих функций ThreadX, задержка в тиках будет равна задержке в миллисекундах.


Конкретное значение частоты тиков планировщика подбирается в зависимости от решаемой задачи.


В Core/Src/main.c в начале файла включим


/* USER CODE BEGIN Includes */
#include "tx_api.h"
/* USER CODE END Includes */

а до кода


  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

добавим вызов


  tx_kernel_enter();

тем самым передав управление планировщику.


Перейдем к Core/Startup/startup_stm32f407vgtx.s


После


.global  Default_Handler

Добавим


.global _vectors

А после


g_pfnVectors:

Также добавим


_vectors:

Тем самым скомпонуем нашу таблицу векторов с кодом ThreadX.


В Core/Src/ создайте файл demo_threadx.c и скопируйте в него код ниже.


#include "tx_api.h"
#include "main.h"

/* Размер стека каждого демо-потока в байтах */
#define DEMO_STACK_SIZE         1024

/* Размер пула памяти, из которого будет выделяться память для демо-потоков, в байтах */
#define DEMO_BYTE_POOL_SIZE     10240

/* Количество демо-потоков */
#define THREAD_COUNT 4

/* Массив структур, каждая из которых хранит информацию о потоке (thread control block) */
TX_THREAD               thread_x[THREAD_COUNT];

/* Структура, хранящая информацию о пуле памяти */
TX_BYTE_POOL            byte_pool_0;

/* Группа флагов событий */
TX_EVENT_FLAGS_GROUP    evt_group;

/* Область памяти для пула TX_BYTE_POOL */
UCHAR                   memory_area[DEMO_BYTE_POOL_SIZE];

/* Флаг события (маска) для потока N */
#define EVT_KEYPRESS_THREAD(n) (1 << n)

/* Время задержки после переключения режима светодиода */
#define LED_PAUSE_AND_DEBOUNCE_TIME_MS 100

/* Описание светодиодов для каждого потока */
static struct
{
    GPIO_TypeDef* GPIOx;
    uint16_t GPIO_Pin;
    uint32_t blink_delay_ms;
    char thread_name[10];
} BoardLedsSettings[THREAD_COUNT] =
{
    { LD3_GPIO_Port, LD3_Pin, 250,  "orange" },
    { LD4_GPIO_Port, LD4_Pin, 500,  "green" },
    { LD5_GPIO_Port, LD5_Pin, 750,  "red" },
    { LD6_GPIO_Port, LD6_Pin, 1000, "blue" }
};

/* Callback внешнего прерывания (кнопки) */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_0)
    {
        for (int i = 0; i < THREAD_COUNT; i++) tx_event_flags_set(&evt_group, EVT_KEYPRESS_THREAD(i), TX_OR);
    }
}

/* Функция-worker каждого из 4 потоков */
void thread_entry(ULONG thread_input)
{
    /* Поток управления одним светодиодом на плате */
    uint8_t led_state = GPIO_PIN_RESET;
    uint32_t cur_delay_ms = BoardLedsSettings[thread_input].blink_delay_ms;
    ULONG actual_flags_ptr;
    while(1)
    {
        HAL_GPIO_WritePin(BoardLedsSettings[thread_input].GPIOx, BoardLedsSettings[thread_input].GPIO_Pin, led_state);
        led_state = !led_state;
        if (TX_SUCCESS == tx_event_flags_get(&evt_group, EVT_KEYPRESS_THREAD(thread_input),
            TX_AND_CLEAR, &actual_flags_ptr, cur_delay_ms))
        {
            /* Установлен флаг события "кнопка нажата". Выключаем светодиод */
            HAL_GPIO_WritePin(BoardLedsSettings[thread_input].GPIOx, BoardLedsSettings[thread_input].GPIO_Pin, GPIO_PIN_RESET);
            /* Пауза, что было видно, что светодиод погас */
            tx_thread_sleep(LED_PAUSE_AND_DEBOUNCE_TIME_MS);
            /* Дополнительно очистим флаг события, на случай, если оно произошло еще раз за время задержки (антидребезг) */
            tx_event_flags_get(&evt_group, EVT_KEYPRESS_THREAD(thread_input), TX_AND_CLEAR, &actual_flags_ptr, TX_NO_WAIT);
            /* Светодиод будет включен в следующей итерации цикла */
            led_state = GPIO_PIN_SET;
            /* Изменяем задержку */
            cur_delay_ms = (cur_delay_ms == BoardLedsSettings[thread_input].blink_delay_ms) ?
                cur_delay_ms * 2 : BoardLedsSettings[thread_input].blink_delay_ms;
        }
    }
}

/* Инициализация приложения */
void tx_application_define(void *first_unused_memory)
{
    CHAR    *pointer = TX_NULL;

    /* Создаем byte memory pool, из которого будем выделять память для стека каждого потока */
    tx_byte_pool_create(&byte_pool_0, "byte pool", memory_area, DEMO_BYTE_POOL_SIZE);

    /* Создаем группу событий */
    tx_event_flags_create(&evt_group, "event group");

    /* Создаем в цикле 4 потока, каждый из которых получает в качестве параметра индекс данных из структуры BoardLedsSettings */
    for (int i = 0; i < THREAD_COUNT; i++)
    {
        /* Выделяем стек для потока i */
        tx_byte_allocate(&byte_pool_0, (void **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
        /* Создаем поток i */
        tx_thread_create(&thread_x[i], BoardLedsSettings[i].thread_name, thread_entry, i, pointer, DEMO_STACK_SIZE,
            1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
    }
}

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


Выберите Project — Build All и убедитесь, что сборка проекта прошла успешно.


После этого выберите Run — Debug Configurations, кликните правой кнопкой мыши на STM32 Cortex-M C/C++ и выберите New Configuration:



Оставьте значения по умолчанию (там выбран ST-LINK уже с нужными параметрами) и нажмите кнопку Debug. Согласитесь с переключением перспективы.


Отладка остановится на строке


  HAL_Init();

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


Что происходит в данном примере


Приведенное приложение — классический пример "лампочки и кнопочки" для RTOS. На плате распаяно 4 светодиода, и задача приложения — мигать ими, причем у каждого из них должна быть своя частота этого мигания. Без RTOS это сделать будет достаточно сложно и неудобно (UPD: см. комментарии).


Также на плате имеется кнопка, и ее мы используем для демонстрации обработки внешнего прерывания в RTOS. Очень плохой практикой является обработка непосредственно в обработчике прерывания (наша функция-callback HAL_GPIO_EXTI_Callback() выполняется непосредственно в контексте прерывания), поэтому в самом обработчике мы устанавливаем флаг соответствующего события. В дальнейшем по этому флагу оно будет обработано в потоке.


Обратите внимание, что задержки между включением и выключением светодиода, как таковой, в коде нет. Вместо этого соответствующее время поток ожидает события. Как только это событие происходит, все светодиоды гаснут (чтобы было видно, что происходит), а время задержки увеличивается в два раза. При следующем нажатии — возвращается к исходному.


Для каждого из четырех потоков используется один и тот же код потока (thread_entry), который на "вход" в качестве параметра получает индекс светодиода, а соответствующая информация (порт, вывод, время задержки, имя потока) будет получена потоком из соответствующей структуры BoardLedsSettings. Это очень удобно: нам не понадобилось писать по функции для каждого потока, вместо этого мы используем единую функцию, просто передавая ей параметр.


В RTOS для микроконтроллеров принято вручную выделять память для стека потока. В некоторых ОС это происходит неявно, но происходит всегда. Стек, как известно, используется для хранения локальных переменных и сохранения контекста при вызове вложенной функции. Важно подобрать размер стека так, чтобы не произошло его переполнение, как правило, это делается экспериментально при помощи средств аналитики стека. В нашем случае выбрано значение в 1 КБ исходя из опыта разработки.


Выделенная область стека передается в функцию tx_thread_create() в виде указателя и размера области памяти в байтах. Обратите внимание, что в нашем примере достаточно было просто объявить массив нужной длины и передать указатель на массив в эту функцию, что означало бы статическое выделение памяти для стека. Но мы пошли более сложным путем, чтобы показать, как в ThreadX устроено динамическое управление памятью. Мы статически создали массив для пула байтов (byte_pool_0), создали сам пул в строке


tx_byte_pool_create(&byte_pool_0, "byte pool", memory_area, DEMO_BYTE_POOL_SIZE);

Затем выделили из этого пула память для стека каждого потока в строке


tx_byte_allocate(&byte_pool_0, (void **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

И передали соответствующий указатель (pointer) в функцию создания потока:


tx_thread_create(&thread_x[i], BoardLedsSettings[i].thread_name, thread_entry, i, pointer, DEMO_STACK_SIZE,
    1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);

Обратим внимание на следующее:


  • Поскольку мы выделяли память динамически, мы также можем ее и освободить, например, после уничтожения потока. Память вернется в пул и может быть в дальнейшем использована повторно. В ThreadX уже решена проблема фрагментации возвращенной в пул памяти, поэтому проблем с повторным выделением не будет.
  • Все созданные потоки запускаются автоматически (параметр TX_AUTO_START).
  • Параметр TX_NO_TIME_SLICE отключает механизм time-slice для создаваемого потока. Это означает, что квант времени на исполнение процесса мы не задаем, а вместо этого полагаемся на планировщик.
  • Данный код не подходит для производства, поскольку для упрощения примера не производится анализ возвращенного значения функций на предмет возникновения ошибок.
  • ThreadX достаточно гибко конфигурируется путем применения директив препроцессора. Для упрощения примера мы их не рассматривали. Подробная информация доступна здесь.

Инициализация потоков производится в теле функции tx_application_define(), которая в качестве параметра получает указатель на первый адрес неиспользуемой памяти. Мы также могли использовать этот адрес для организации пула байтов, вместо статического выделения.


Выводы


Мы рассмотрели лишь базовую часть Azure RTOS и (пока) не использовали расширенных функций, а также стеков FileX, NetX и т.д. Это — тема следующих статей.


Мы убедились, что работу с ThreadX можно начать достаточно быстро, буквально в пределах рабочего дня. Microsoft также предлагает руководство пользователя к ОС, а если у вас еще остались вопросы по Azure RTOS или другим встраиваемым операционным системам Microsoft — обращайтесь к нам в Кварта Технологии.


Автор статьи — Сергей Антонович, ведущий инженер Кварта Технологии. Связаться с ним можно по адресу sergant (at) quarta.ru.

Кварта Технологии
Дистрибутор и интегратор встраиваемых технологий

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                          0
                          Так-то оно так, но они делают медицинское оборудование, а там строгие требования по надёжности.
                      +1
                      Человек просто не умеет в ртос, поэтому выдает свою вкусовщину за серьезные достоинства. Я не против ThreadX, с учетом ее потенциальной бесплатности даже рассматриваю ее в качестве замены FreeRTOS в некоторых проектах. Но исключительно из соображений сертификации, а не потому, что кто-то рассказал мне когда-то, что она чем-то лучше, чем любая другая система.
                        0
                        Вы внимательно читаете ответы? Я же написал, что да, FreeRTOS действительно применяется много где. И за последние 3 года он поменял в качестве контрактора несколько компаний, где использовалась и FreeRTOS.
                        А что касается перехода в некоторых проектах на ThreadX, конечно попробуйте. Благо, автор статьи дал очень хорошую стартовую точку. Может и Вам понравится. С богатым опытом работы в RTOS вообще Вам будет явно легче, чем мне.
                        Благо, у меня есть сотрудник, который имеет опыт с mbed — поможет разобраться в тонкостях программирования. И товарищ недалеко. И мой форум есть, где кто-то уже работал с этой системой.
                      +1
                      Сергей, я скачал библиотеку. Последняя версия 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 исключил из компиляции.
                        0
                        Проблема в том, что за время от написания материала до его публикации ветка master ушла вперед, и структура директорий поменялась, поэтому теперь есть такие несовпадения. Чтобы все было точно как в статье, нужен определенный коммит. Добавил информацию в статью.
                          0
                          А какая версия была при написании статьи? В статье линк ведёт на тот же 6.0.1 Посмотрел разницу с 6.0 — отличия минимальны и явно не приведут к исправлению ситуации.
                          А то, может Вы переправите на последнюю версию? image
                          Последователи наверняка будут чисто на автомате скачивать последнюю версию. Каждый раз объяснять, что надо взять старую…
                          Был бы у меня опыт работы с RTOS — может и сам бы сделал, некоторый опыт портирования есть. Но пока приходится полагаться полностью на чужой.
                            0
                            Здесь дело не в самой версии (я работал с 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.
                        +1
                        Небольшая ошибочка в коде. 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 всё нашлось
                          0
                          Вы правы. Чтобы поправить, лучше всего убрать const в объявлении структуры BoardLedsSettings. Поправил код. У меня, правда, код почему-то нормально собрался и с const, хотя не должен был.
                          По второму вопросу: возьмите тот же коммит, который указан в статье. Причина описана в комментарии выше.
                            0
                            Возможно различные настройки компилятора.
                              +1
                              Обратился к сотруднику за помощью, у него опыта в программировании больше. Хотя не 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 по прерыванию не заходит. Буду разбираться.
                                0
                                1. Исходники в данной IDE должны попадать в сборку по факту присутствия в дереве. Вы обновили дерево после их добавления? Middlewares (вместе с Core и Drivers) по умолчанию уже входит в дерево, соответственно попадание в нее threadx-master должно привести к их включению. Может, в дереве, внутри Middlewares они у вас исключены из компиляции? На всякий случай включу в материал проверку настроек на предмет Source Location, спасибо за замечание.
                                2. Действительно, из контекста может быть неочевидно. Спасибо, поправим.

                                По частоте переключения LED: проверьте в стартовом коде настройки SYSTEM_CLOCK, SYSTICK_CYCLES.
                                По прерыванию: поскольку у вас не та плата, для которой настраивается Cube, посмотрите, есть ли подтяжка вниз на плате, если нет, включите программную. Если подтяжка вверх, измените в Cube фронт прерывания. Убедитесь, что на линии, куда подключена кнопка, есть внешнее прерывание, и что у него нужный номер. Убедитесь с отладчиком, что срабатывает обработчик прерывания и выставляется событие, далее что оно обрабатывается. Где-то в этой цепочке что-то найдете.
                                  0
                                  Исходники в данной 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
                                    0
                                    С прерываниями разобрался. Моя ошибка.

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

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

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

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

                                          Самое читаемое