Русский
Русский
English
Статистика
Реклама

Rtos

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

06.08.2020 16:13:59 | Автор: admin

На недавно прошедшем 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 не требует скачивания, все необходимые библиотеки будут загружены автоматически при создании проекта.


  1. Скачиваем и устанавливаем STM32CubeIDE бесплатная IDE от STMicroelectronics на базе открытых инструментов.
  2. Загружаем исходный код ThreadX c GitHub. Существуют, конечно, "правильные" способы использования репозитория с исходным кодом в виде клонирования репозитория или создания форка, но для простоты описания просто скачиваем его как архив: зеленая кнопка "Clone", затем "Download zip".
  3. Подключаем плату 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...":



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



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


Далее распаковываем содержимое скачанного архива в каталог 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

Нажимаем 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 const 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 это сделать достаточно сложно и неудобно. Также на плате имеется кнопка, и ее мы используем для демонстрации обработки внешнего прерывания в 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.

Подробнее..

Embox на плате EFM32ZG_STK3200

14.01.2021 22:19:16 | Автор: admin
image
Embox является сильно конфигурируемой RTOS. Основная идея Embox прозрачный запуск Linux программного обеспечения везде, в том числе и на микроконтроллерах. Из достижений стоит привести OpenCV, Qt, PJSIP запущенные на микроконтроллерах STM32F7. Конечно, запуск подразумевает, что в данные проекты не вносились изменения и использовались только опции при конфигурации оригинальных проектов и параметры задаваемые в самой конфигурации Embox. Но возникает естественный вопрос насколько Embox позволяет экономить ресурсы по сравнению с тем же Linux? Ведь последний также достаточно хорошо конфигурируется.

Для ответа на этот вопрос можно подобрать минимально возможную для запуска Embox аппаратную платформу. В качестве такой платформы мы выбрали EMF32ZG_STK3200 от компании SiliconLabs. Данная платформа имеет 32kB ROM и 4kB RAM память. А также процессорное ядро cortex-m0+. Из периферии доступны UART, пользовательские светодиоды, кнопки, а также 128x128 монохромный дисплей. Нашей целью является запуск любого пользовательского приложения, позволяющего убедиться в работоспособности Embox на данной плате.

Для работы с периферией и самой платой нужны драйвера и другой системный код. Данный код можно взять из примеров предоставляемых самим производителем чипа. В нашем случае производитель предлагает использовать SimplifyStudio. Есть также открытый репозиторий на GitHub). Этот код и будем использовать.

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

Пример Makefile для скачивания BSP

PKG_NAME := Gecko_SDKPKG_VER := v5.1.2PKG_ARCHIVE_NAME := $(PKG_NAME)-$(PKG_VER).tar.gzPKG_SOURCES := https://github.com/SiliconLabs/$(PKG_NAME)/archive/v5.1.2.tar.gzPKG_MD5     := 0de78b48a8da80931af1a53d401e74f5include $(EXTBLD_LIB)


Mybuild для сборки BSP
package platform.efm32...@BuildArtifactPath(cppflags="-I$(EXTERNAL_BUILD_DIR)/platform/efm32/bsp_get/Gecko_SDK-5.1.2/hardware/kit/common/bsp/")module bsp_get { }@BuildDepends(bsp_get)@BuildDepends(efm32_conf)static module bsp extends embox.arch.arm.cmsis {    source "platform/emlib/src/em_timer.c",        "platform/emlib/src/em_adc.c",    depends bsp_get    depends efm32_conf}


Mybuild для платы EFM32ZG_STK3200

package platform.efm32.efm32zg_stk3200@BuildArtifactPath(cppflags="-I$(EXTERNAL_BUILD_DIR)/platform/efm32/bsp_get/Gecko_SDK-5.1.2/platform/Device/SiliconLabs/EFM32ZG/Include")@BuildArtifactPath(cppflags="-I$(EXTERNAL_BUILD_DIR)/platform/efm32/bsp_get/Gecko_SDK-5.1.2/hardware/kit/EFM32ZG_STK3200/config")...@BuildArtifactPath(cppflags="-D__CORTEX_SC=0")@BuildArtifactPath(cppflags="-DUART_COUNT=0")@BuildArtifactPath(cppflags="-DEFM32ZG222F32=1")module efm32zg_stk3200_conf extends platform.efm32.efm32_conf {    source "efm32_conf.h"}@BuildDepends(platform.efm32.bsp)@BuildDepends(efm32zg_stk3200_conf)static module bsp extends platform.efm32.efm32_bsp {    @DefineMacro("DOXY_DOC_ONLY=0")    @AddPrefix("^BUILD/extbld/platform/efm32/bsp_get/Gecko_SDK-5.1.2/")    source        "platform/Device/SiliconLabs/EFM32ZG/Source/system_efm32zg.c",        "hardware/kit/common/drivers/displayls013b7dh03.c",...}


После таких достаточно простых действий можно использовать код от производителя. Прежде чем приступить к работе с драйверами необходимо разобраться со средствами разработки и архитектурными частями. В Embox используются обычные средства разработки gcc, gdb, openocd. При запуске openocd нужно указать что мы используем платформу efm32:

sudo openocd -f /usr/share/openocd/scripts/board/efm32.cfg


Для нашей платки нет каких-то специальных архитектурных частей, только специфика cortex-m0+. Это задается компилятором. Поэтому мы можем задать общий код для cotrex-m0 отключив все лишнее, например, работу с плавающей точкой.

     @Runlevel(0) include embox.arch.generic.arch    include embox.arch.arm.libarch    @Runlevel(0) include embox.arch.arm.armmlib.locore    @Runlevel(0) include embox.arch.system(core_freq=8000000)    @Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=256)    @Runlevel(0) include embox.kernel.stack(stack_size=1024,alignment=4)    @Runlevel(0) include embox.arch.arm.fpu.fpu_stub


После этого можно попробовать скомпилить Embox и походить с помощью отладчика по шагам, проверив тем самым правильно ли мы задали параметры в линкер скрипте

/* region (origin, length) */ROM (0x00000000, 32K)RAM (0x20000000, 4K)/* section (region[, lma_region]) */text   (ROM)rodata (ROM)data   (RAM, ROM)bss    (RAM)


Первым драйвером реализуемым для поддержки какой-нибудь платы в Embox обычно является UART. На нашей плате есть LEUART. Для драйвера достаточно реализовать несколько функций. При этом мы можем использовать функции из BSP.

static int efm32_uart_putc(struct uart *dev, int ch) {    LEUART_Tx((void *) dev->base_addr, ch);    return 0;}static int efm32_uart_hasrx(struct uart *dev) {...}static int efm32_uart_getc(struct uart *dev) {    return LEUART_Rx((void *) dev->base_addr);}static int efm32_uart_setup(struct uart *dev, const struct uart_params *params) {    LEUART_TypeDef      *leuart = (void *) dev->base_addr;    LEUART_Init_TypeDef init    = LEUART_INIT_DEFAULT;    /* Enable CORE LE clock in order to access LE modules */    CMU_ClockEnable(cmuClock_HFPER, true);  ...    /* Finally enable it */    LEUART_Enable(leuart, leuartEnable);    return 0;}...DIAG_SERIAL_DEF(&efm32_uart0, &uart_defparams);


Для того чтобы функции BSP были доступны нужно просто указать это в описании драйвера, файл Mybuild
package embox.driver.serial@BuildDepends(platform.efm32.efm32_bsp)module efm32_leuart extends embox.driver.diag.diag_api {    option number baud_rate    source "efm32_leuart.c"    @NoRuntime depends platform.efm32.efm32_bsp    depends core    depends diag}


После реализации драйвера UART вам доступны не только вывод, но и консоль где вы можете вызвать свои пользовательские команды. Для этого вам достаточно добавить в конфигурационный файл Embox маленький командный интерпретатор:
    include embox.cmd.help    include embox.cmd.sys.version    include embox.lib.Tokenizer    include embox.init.setup_tty_diag    @Runlevel(2) include embox.cmd.shell    @Runlevel(3) include embox.init.start_script(shell_name="diag_shell")


А также указать, что нужно использовать не полноценный tty доступный через devfs, а заглушку, которая позволяет обращаться к заданному устройству. Устройство задается также в конфигурационном файле mods.conf
    @Runlevel(1) include embox.driver.serial.efm32_leuart    @Runlevel(1) include embox.driver.diag(impl="embox__driver__serial__efm32_leuart")    include embox.driver.serial.core_notty


Еще один очень простой драйвер это GPIO. Для его реализации мы также можем воспользоваться вызовами из BSP. Для этого в описании драйвера укажем что он зависит от BSP
package embox.driver.gpio@BuildDepends(platform.efm32.efm32_bsp)module efm32_gpio extends api {    option number log_level = 0    option number gpio_chip_id = 0    option number gpio_ports_number = 2    source "efm32_gpio.c"    depends embox.driver.gpio.core    @NoRuntime depends platform.efm32.efm32_bsp}


Сама реализация

static int efm32_gpio_setup_mode(unsigned char port, gpio_mask_t pins, int mode) {...}static void efm32_gpio_set(unsigned char port, gpio_mask_t pins, char level) {    if (level) {        GPIO_PortOutSet(port, pins);    } else {        GPIO_PortOutClear(port, pins);    }}static gpio_mask_t efm32_gpio_get(unsigned char port, gpio_mask_t pins) {    return GPIO_PortOutGet(port) & pins;}...static int efm32_gpio_init(void) {#if (_SILICON_LABS_32B_SERIES < 2)  CMU_ClockEnable(cmuClock_HFPER, true);#endif#if (_SILICON_LABS_32B_SERIES < 2) \  || defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2)  CMU_ClockEnable(cmuClock_GPIO, true);#endif    return gpio_register_chip((struct gpio_chip *)&efm32_gpio_chip, EFM32_GPIO_CHIP_ID);}


Этого достаточно чтобы использовать команду pin из Embox. Данная команда позволяет управлять GPIO. И в частности, может использоваться для проверки мигания светодиодом.

Добавляем саму команду в mods.conf
include embox.cmd.hardware.pin


И сделаем так, чтобы она запускалась при старте. Для этого в конфигурационном файле start_sctpt.inc добавим одну из строчек
<source">pin GPIOC 10 blink,

Или

"pin GPIOC 11 blink",


Команды одинаковые, просто номера светодиодов разные.

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

package embox.driver.video@BuildDepends(platform.efm32.efm32_bsp)module efm32_lcd {...    source "efm32_lcd.c"    @NoRuntime depends platform.efm32.efm32_bsp}


Но как только мы делаем любой вызов связанный с дисплеем например DISPLAY_Init у нас секция .bss увеличивается больше чем на 2 kB, при размерах RAM 4 kB, это очень существенно. После изучения данного вопроса, выяснилось, что в самом BSP выделен фреймбуфер размером под дисплей. То есть 128x128x1 бит или 2048 байт.

В этот момент я даже хотел остановиться на достигнутом, ведь уместить в 4kB RAM вызов пользовательских команд с каким-то простым командным интерпретатором само по себе достижение. Но все-таки решил попробовать.

Первым я убрал командный интерпретатор и оставил только вызов уже упомянутой команды pin. Для этого я изменил конфигурационный файл mods.conf следующим образом
    //@Runlevel(2) include embox.cmd.shell    //@Runlevel(3) include embox.init.start_script(shell_name="diag_shell")    @Runlevel(3) include embox.init.system_start_service(cmd_max_len=32, cmd_max_argv=6)


Поскольку я использовал другой модуль для пользовательского старта, я перенес запуск команд в другой конфигурационный файл. Вместо start_script.inc использовал system_start.inc.

Затем, поскольку уже не требовалось использовать индексные дескрипторы в командном интерпретаторе, а также таймеры, я с помощью опций в mods.config, избавился и от них
    include embox.driver.common(device_name_len=1, max_dev_module_count=0)    include embox.compat.libc.stdio.file_pool(file_quantity=0)    include embox.kernel.task.resource.idesc_table(idesc_table_size=3)    include embox.kernel.task.task_no_table    @Runlevel(1) include embox.kernel.timer.sys_timer(timer_quantity=1)...    @Runlevel(1) include embox.kernel.timer.itimer(itimer_quantity=0)


Поскольку я вызывал команды напрямую, а не через командный интерпретатор, я смог уменьшить размер стека
    @Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=224)    @Runlevel(0) include embox.kernel.stack(stack_size=448,alignment=4)


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

Мне хотелось вывести что нибудь на дисплейю Я подумал, что логотип Embox будет показателен. По хорошему нужно использовать полноценный драйвер фреймбуфера и выводить изображение из файла, ведь все это есть в Embox. Но места совсем не хватало. И для демонстрации я решил вывести логотип прямо в функции инициализации драйвера фреймбуфера. Причем данные конвертировав напрямую в битовый массив. Таким образом мне потребовалось ровно 2048 байт в ROM.

Сам код, как и ранее, использует BSP

extern const uint8_t demo_image_mono_128x128[128][16];static int efm_lcd_init(void) {    DISPLAY_Device_t      displayDevice;    EMSTATUS status;    DISPLAY_PixelMatrix_t pixelMatrixBuffer;    /* Initialize the DISPLAY module. */    status = DISPLAY_Init();    if (DISPLAY_EMSTATUS_OK != status) {        return status;    }    /* Retrieve the properties of the DISPLAY. */    status = DISPLAY_DeviceGet(DISPLAY_DEVICE_NO, &displayDevice);    if (DISPLAY_EMSTATUS_OK != status) {        return status;    }    /* Allocate a framebuffer from the DISPLAY device driver. */    displayDevice.pPixelMatrixAllocate(&displayDevice,            displayDevice.geometry.width,            displayDevice.geometry.height,            &pixelMatrixBuffer);#if START_WITH_LOGO    memcpy(pixelMatrixBuffer, demo_image_mono_128x128,            displayDevice.geometry.width * displayDevice.geometry.height / 8 );    status = displayDevice.pPixelMatrixDraw(&displayDevice,            pixelMatrixBuffer,            0,            displayDevice.geometry.width,            0,            displayDevice.geometry.height);#endif    return 0;}


Собственно все. На коротком видео можно увидеть результат.


Весь код доступен на GitHub. Если есть плата, то же самое можно воспроизвести на ней с помощью инструкции описанной на wiki.

Результат превзошел мои ожидания. Ведь удалось запустить Embox по сути на 2kB RAM. Это означает, что с помощью опций в Embox накладные расходы на использование ОС можно свести к минимуму. При этом в системе присутствует многозадачность. Пусть даже она и кооперативная. Ведь обработчики таймеров вызываются не напрямую в контексте прерывания, а из собственного контекста. Что естественно является плюсом использования ОС. Конечно, данный пример во многом искусственный. Ведь при столь ограниченных ресурсах и функциональность будет ограниченной. Преимущества Embox начинают сказываться на более мощных платформах. Но в то же время это можно считать предельным случаем работы Embox.
Подробнее..

Zephyr в embedded опыт использования на STM32F7-Discovery

15.06.2020 18:18:41 | Автор: admin
image

История о моем опыте использования операционной системы реального времени (ОСРВ) Zephyr для устройства на базе микроконтроллера STM32F7-Discovery.


В статье:


  • Что такое Zephyr и при чем тут Linux?
  • Запуск проекта на STM32. Интересные моменты по работе с драйверами.
  • Фишки этой ОС. Что понравилось, а что нет.

Привет, Хабр, меня зовут Илья. Я студент выпускного курса университета и параллельно прохожу стажировку на позицию embedded-разработчика в компании Третий пин. Мой приход совпал с началом изучения операционной системы реального времени Zephyr. Чтобы не делать исследование на пустом месте, мне и другим стажерам предложили придумать небольшой проект, где можно использовать эту операционную систему. Мы остановились на идее устройства для отладки оборудования, когда отсутствует возможность подключения к компьютеру. Устройство позволяет считывать, хранить и отображать логи тестируемого устройства на дисплее или передавать их на компьютер по Ethernet. Проект получил внутренне название Logovoz. Прототип решили делать на STM32F7-Discovery. О том, что получилось планирую рассказать в следующих статьях. Сегодня про сам Zephyr.


Что еще за Zephyr?


image

Zephyr это сравнительно новая операционная система реального времени с прицелом на embedded и устройства интернета вещей. Она была разработана в 2015 году компанией Wind River Systems, автора другой популярной в авиационной и космической отраслях ОС VxWorks.


Что такое операционная система реального времени?

Операционная система реального времени это такая операционная система, ключевым критерием которой, наравне с корректной работой, является время выполнения операций. Так, если в Windows программа отработает на милисекунду позже, пользователь может даже не обратить внимания, а в ОСРВ эта ситуация является недопустимой. Например, представьте, что будет, если контроллер подушки безопасности автомобиля отработает на пару секунд позже, чем нужно?


Чем хорош Zephyr:


  • Мощная архитектура. Системные вызовы, драйвера, потоки, файловая система, shell и так далее. Всё как во взрослых ОС.
  • Поддержка множества контроллеров от разных вендоров. STM, ESP, Atmel, NXP и т. д. Система позволяет запускать одно приложение на разных платах без переписывания кода.
  • Собственный инструмент командной строки West. Сами создатели называют его перочинным ножом для разработчика. Это одновременно система сборки, менеджер модулей, инструмент прошивки, конфигуратор.
  • Open-source. С 2017 года система поддерживается Linux Foundation и в ней используются наработки из кодовой базы Linux. Например, Kconfig и dts.

Система не лишена минусов. В основном, они связаны с её относительной молодостью. Например, проблемы с документацией в разных версиях системы. Несмотря на это, система имеет активное комьюнити и активно развивается.


Как устроен Zephyr?


Система во многом схожа с Linux. Как и Linux, Zephyr содержит menuconfig или guiconfig (то же самое, но с отдельным GUI, а не в консоли), которые конфигурируют программные части системы на основе файлов Kconfig. Это могут быть различные драйверы, поддержка сетевых функций и т.д. Для описания же аппаратной части используется структура device tree. С помощью неё конфигурируются диапазоны адресов регистров в памяти, периферия, линии прерываний и др.


image

В качестве системы сборки Zephyr иcпользует CMake. Поэтому каждое приложение должно иметь CMakeList.txt в качестве точки входа системы сборки. Сборка проекта осуществляется с помощью West. Команды west упрощают настройку приложения. Например, написав программу, собрать её под STM32F746G-Discovery надо командой:


west build b stm32f746g_disco

Не меняя исходный код, программа под NUCLEO-F207ZG собирается командой:


west build b nucleo_f207zg

Подробнее про доступные команды можно узнать, вызвав подсказку:


west --h

image

Для использования определенной версии Zephyr и подключения сторонних модулей используется файл манифеста west.yml.


Запуск Zephyr


Если у вас есть отладочная плата и она поддерживается Zephyr открываем статью Getting Started Guide на официальном сайте и проделываем 8 нехитрых пунктов для вашей ОС. Для выполнения 7 пункта придётся найти файл Kconfig.defconfig и в нём посмотреть название отладки в параметре BOARD.


image

Расположение для STM32F746G-Discovery:
zephyr/boards/arm/stm32f746g_disco/Kconfig.defconfig


И, voila, вы гордый обладатель отладки с мигающим светодиодом.


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


Что попробовать сделать


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


image

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


Отладка


Для отладки приложения я пользовался связкой VSCode + marus25.cortexdebug. В документации приведена инструкция для использования Eclipse в
качестве IDE.


Работа с драйверами


image

Для хранения логов в проекте планировалось реализовать файловую систему на SD-карте. Смотрю в щедро предлагаемые мне системой возможности, но не обнаруживаю там поддержки SD.
Zephyr на момент версии 2.1 умеет работать с SD-картами SDHC от 2 до 32 Гб ёмкости через SPI. Для работы с ними есть примеры, инструкция, всё замечательно. Хорошо, тогда почему же моя отладка не поддерживает работу с SD в Zephyr? Смотрю reference manual на stm32f7 и в разделе SDMMC нахожу строку.


image

Одно из преимуществ Zephyr это активное комьюнити. Гугл в помощь, и вот найдены братья по несчастью, один из которых даже написал свой драйвер работы с SD. Написанный драйвер отлично, но есть нюанс.


image

На рисунке представлена модель драйверов в Zephyr. Для использования конкретной реализации к ней надо обратиться через обобщенный API. Конечная цель работы с SD картой взаимодействовать с ней, как с файловой системой. Такой интерфейс предоставляется через подсистему disk. Директория с этой подсистемой содержит как обобщенный API, так и API, предоставляющие доступ к файловой системе в ОЗУ, во флеше или на SDкартах через SPI. Соответственно, надо добавить сюда свой интерфейс, который будет обращаться к реализации драйвера работы с SD.


Берём готовые реализации интерфейсов, смотрим, как там всё сделано, и пишем что-то подобное. При написании драйверов и интерфейсов рекомендуется использовать язык Си, а также макросы, которые применяются в подобных файлах Zephyr. В конце создания API не забываем вписать о нём информацию в файлы CMakeList.txt и Kconfig, чтобы драйвер можно было собрать и включить в системе. В итоге, на карточку памяти гордо записан текстовый файл с приветствием миру.


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


Версионирование


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


Бывают и другие досадные моменты. В мажорном обновлении с Zephyr 1.14 до 2.0 сменилась такая незначительная деталь, как спецификация device tree.


image

В итоге поменялся формат статуса в файлах .dts c ok на okay. Вроде бы мелочь, но при создании собственной платы и переходе на другую версию, проект не будет собираться. Поэтому если вы работаете на определенном релизе Zephyr, внимательно следите и за версией документации.




Работа с RTC


В попытках запустить RTC (Real Time Clock) на плате, я находил, что драйвер часов реального времени был, но потом его не стало. Неприятная ситуация. Позже оказалось, что его функциональность осталась, но была переименована и получила интерфейс Counter.


Воспользовавшись примерами, запустить RTC оказалось несложно. И он даже работал. До первого reset-а. А вот потом обнулился, хотя суть часов реального времени и заключается в том, чтобы не сбрасываться во время reset-а. Это могло произойти из-за того, что в отладку нельзя подключить батарейку и проверить работу часов с ней, а в Zephyr всё на самом деле прекрасно работает. Помогла возможность подсмотреть реализацию RTC в HAL. Те драйверы, которые удалось пощупать, были написаны с применением LL. Не найдя чего-то в нужном драйвере, можно узнать реализацию в HAL и дописать это. Выяснилось, что при инициализации часов в системе, сбрасываются регистры RCC. Не делая этого при reset-е можно оставить нетронутыми значения RTC и он будет работать, как и должен.


Вторым найденным недостатком в реализации RTC оказалось отсутствие функций выставления времени и даты. Их можно считать, но каждый раз отсчитывать время от 2000 года оказалось как-то неудобно. Поэтому снова смотрим в HAL, вдохновляемся и добавляем реализацию сеттеров вместе с требуемым интерфейсом.


Выводы


Стоит ли пробовать Zephyr? Кратко да. Zephyr действительно поддерживает много фишек из коробки. Достаточно сделать пару кликов в guiconfig и вот в проекте появляется поддержка UART, SPI, Ethernet. Посмотрел пример, повторил, изменил, оно ещё и работать будет. Возможность не переписывать исходный код при переезде на другую плату тоже подкупает.


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


Если у вас был опыт работы с Zephyr, поделитесь им в комментариях.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru