Статья описывает этапы адаптации операционной системы Azure RTOS к микроконтроллеру платы управления резервным питанием BACKPMAN v1.0.

Все статьи по проекту

Cсылка на открытый проект: https://github.com/Indemsys/Backup-controller_BACKPMAN-v1.0

Поскольку опыт применения Azure RTOS в прошлом проекте был более чем успешным было решено применять ее везде.

Нельзя сказать что ядро этой RTOS чем-то уникально. Тут имеют значение другие факторы:

  • подтверждённая временем и сертификатами надёжность.

  • богатое программное обеспечение промежуточного уровня включающее: файловую систему, TCP стек, GUI, IoT протоколы, USB классы и т.д.

  • аскетичность и сбалансированность API ядра. Отсюда вытекает простота портирования и освоения.

  • наличие плагинов для отладки сервисов ядра RTOS в среде разработки IAR Embedded Workbench.

  • доступная online документация и книги об этой RTOS.

  • активное развитие. Обновления и исправления идут каждую неделю.

  • поддержка крупными производителей микроконтроллеров: ST, NXP, Cypress, Infineon, Renesas... Они включают эту RTOS в свои SDK.

  • наличие эмуляции API FreeRTOS, OSEK, POSIX.

  • поддержка технологии Symmetric Multi-Processing (SMP)  и динамической загрузки и исполнения программных модулей.

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

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

Старт с hello word без RTOS

Напомню что портировать будем на плату с микроконтроллером MKE18F512VLL16 ( 32-Bit 168MHz ARM​ Cortex​-M4F core, 512KB (512K x 8) FLASH, 64 KB SRAM).

Готовых примеров портирования ThreadX на эти чипы в репозиторий и на сайте NXP нет, но есть директория с портом для архитектуры Cortex-M4F под IAR.

В этом случае все что надо сделать - это сделать для чипов MKE18F простенький проект с пустой функцией main и правильной инициализацией всех тактирующих узлов, подключить к проекту директорию исходников ThreadX, немного откорректировать несколько файлов ThreadX, настроить приоритеты прерываний ядра и все!

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

Правильную инициализацию тактирования легко сделать с помощью утилиты MCUXpresso Config Tools и SDK с сайта NXP.

Несмотря на то что чип может работать на частоте 168 МГц, в конфигураторе выбираем частоту 120 МГц для ядра, поскольку на более высокой частоте будет затруднительно работать с встроенной EEPROM чипа. Встроенная EEPROM может записываться только на системной частоте ниже или равной 120 МГц.

Пины не конфигурируем, только может быть настроим 4-е пина UART0 чтобы передать информацию по UART. UART по умолчанию будет задействован при генерации приложения конфигуратором. Но использовать UART не обязательно. На начальном этапе все потребности в отладке покрывает SWD/JTAG адаптер J-Link.

Выбираем генерацию приложения hello_word и получаем проект с такой структурой директорий:

И среди прочего в директории source будет файл hello_world.c с такой функцией main:

int main(void)
{
    char ch;

    /* Init board hardware. */
    BOARD_InitPins();
    BOARD_InitBootClocks();
    BOARD_InitDebugConsole();

    PRINTF("hello world.\r\n");

    while (1)
    {
        ch = GETCHAR();
        PUTCHAR(ch);
    }
}

Перенос исходников ThreadX в свой проект

Теперь из репозитария threadx переносим в наш проект содержимое директорий common, threadx/ports/cortex_m4/iar/inc, threadx/ports/cortex_m4/iar/src/ и файл tx_initialize_low_level.s. Исключаем из проекта файл tx_misra.s поскольку он вызовет ошибку повторного объявления. Дописываем во вкладке препроцессора компилятора С в IDE IAR пути к добавленным директориям. Перекомпилируем проект. Все должно пойти без ошибок.

Редактирование исходников ThreadX и конфигурирование

Во-первых, надо сделать исправления в файле tx_initialize_low_level.s. Изменим объявление константы:

SYSTICK_CYCLES    EQU   ((SYSTEM_CLOCK / 100) -1)

Эта константа инициализирует генератор системных тиков RTOS.
Мы записываем:

SYSTICK_CYCLES    EQU   ((SYSTEM_CLOCK / TX_TIMER_TICKS_PER_SECOND) -1)

В этом же файле добавляем в начале строку

#include "tx_user.h"

Затем файл tx_user_sample.h переименовываем в tx_user.h

В IDE IAR в закладке препроцессора компилятора добавляем запись: TX_INCLUDE_USER_DEFINE_FILE

Теперь при компиляции будет учитываться файл tx_user.h
В этом файле находятся все важнейшие опции RTOS.

Их надо соответствующим образом выбрать.

В начале файла делаем объявления использованных выше констант:

#define SYSTEM_CLOCK              120000000
#define TX_TIMER_TICKS_PER_SECOND 1000

Системный тик RTOS выбираем равным 1 мс. Это будет удобно при при манипуляциях со временем в автоматах состояний.

Список макросов активизированных в файле конфигурации:

#define TX_MAX_PRIORITIES                       32
#define TX_DISABLE_PREEMPTION_THRESHOLD
#define TX_DISABLE_REDUNDANT_CLEARING
#define TX_DISABLE_NOTIFY_CALLBACKS
#define TX_NO_FILEX_POINTER
#define TX_TIMER_PROCESS_IN_ISR

Такой список обеспечивает минимальные размеры RTOS.

Теперь остается только модифицировать наш файл hello_world.c. Полностью он будет выглядеть так:

#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "board.h"

#include "pin_mux.h"
#include "clock_config.h"

#include "tx_API.h"

#define THREAD_MAIN_STACK_SIZE             1024  // Размер стека выделяемый задаче
#define THREAD_MAIN_PRIORITY               1     // Приоритет первой задачи

TX_THREAD               main_thread;
#pragma data_alignment=8
uint8_t                 thread_main_stack[THREAD_MAIN_STACK_SIZE];
static void             Thread_main(ULONG initial_input);

/*-----------------------------------------------------------------------------------------------------
  Модифицированная функция main из SDK
  
  \param void  
  
  \return int 
-----------------------------------------------------------------------------------------------------*/
int main(void)
{
  /* Init board hardware. */
  BOARD_InitPins();
  BOARD_InitBootClocks();
  BOARD_InitDebugConsole();
  tx_kernel_enter();
}

/*-----------------------------------------------------------------------------------------------------
  Вспомогательная функция инициализирующая и запускающая первую задачу
  
  \param first_unused_memory  
-----------------------------------------------------------------------------------------------------*/
void    tx_application_define(void *first_unused_memory)
{
  tx_thread_create(&main_thread, "Main", Thread_main,
    0,
    (void *)thread_main_stack, // stack_start
    THREAD_MAIN_STACK_SIZE,    // stack_size

    THREAD_MAIN_PRIORITY,     // priority. 
                              // Numerical priority of thread. 
                              // Legal values range from 0 through (TX_MAX_PRIORITES-1), where a value of 0 represents the highest priority.

    THREAD_MAIN_PRIORITY,     // preempt_threshold. 
                              // Highest priority level (0 through (TX_MAX_PRIORITIES-1)) of disabled preemption. 
                              // Only priorities higher than this level are allowed to preempt this thread. 
                              // This value must be less than or equal to the specified priority. 
                              // A value equal to the thread priority disables preemption-threshold.
    TX_NO_TIME_SLICE,
    TX_AUTO_START);
}

/*-----------------------------------------------------------------------------------------------------
  Первая задача
  
  \param initial_input  
-----------------------------------------------------------------------------------------------------*/
static void Thread_main(ULONG initial_input)
{
  char ch;
  PRINTF("hello world.\r\n");
  while (1)
  {
    ch = GETCHAR();
    PUTCHAR(ch);
  }
}

И все! Процесс установки RTOS закончен. Осталось проверить работу. Компилируем, загружаем в контроллер и проверяем вывод в терминал.

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

Из map файла сгенерированного после компиляции видим что вся RTOS заняла 2506 байт памяти программ. Весь размер программы 10198 байт.

Особенности порта Azure RTOS

Надо сказать что эффективность RTOS сильно зависит от того как реализован ее порт (т.е. адаптация к конкретной архитектуре микроконтроллера и компилятора).

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

1. По дефолту не все функции стандартной библиотеки С могут быть безопасно использованы в вытесняющей RTOS . Объявление макроса TX_ENABLE_IAR_LIBRARY_SUPPORT в файле tx_user.h позволяет сделать стандартные библиотеки С в среде IAR мультипоточными. Для этого в ThreadX есть файл tx_iar.c

2. Выделяя массив под стек для задачи следует адрес его начала выравнивать по границе 8. Иначе операции с плавающей точкой в задачах могут приводить к ошибкам.

3. В ThreadX используется так называемая "ленивое" сохранение стека . Это когда регистры сопроцессора операций с плавающей точкой не сохраняются если такие операции не выполнялись в задаче перед переключением контекста. Отсюда следует что переключение контекста может замедлиться на время сохранения 33-х регистров FPU если задача использует операции с плавающей точкой. Но операции с сопроцессором FPU могут использоваться даже когда нет явного использования типов float или double. Это свойство оптимизирующего компилятора IAR. Поэтому для полной уверенности в быстроте ISR всегда следует просматривать ассемблерный код.

4. Общепринято защищать критические секции мьютексами или другими стандартными сервисами RTOS. Но выполнение таких сервисов может занимать сотни тактов. На некоторых этапах выполнения у них запрещаются любые прерывания. Это не хорошо если в системе присутствуют критические к времени отклика прерывания. Например прерывания по сигналам перенапряжения или перегрузки по току, которые должны что-то сделать в течении долей микросекунд от момента появления сигнала. Проблему можно решить используя вместо мьютексов простое поднятие приоритета выше приоритета прерывания системного тика и прерывания по вектору PendSV. Но для этого надо в файле tx_initialize_low_level.s поменять приоритеты этих прерываний как показано ниже:

    LDR     r1, =0xF0000000                         ; SVCl, Rsrv, Rsrv, Rsrv
    STR     r1, [r0, #0xD1C]                        ; Setup System Handlers 8-11 Priority Registers
                                                    ; Note: SVC must be lowest priority, which is 0xFF

    LDR     r1, =0xE0F00000                         ; SysT, PnSV, Rsrv, DbgM
    STR     r1, [r0, #0xD20]                        ; Setup System Handlers 12-15 Priority Registers
                                                    ; Note: PnSV must be lowest priority, which is 0xFF

Здесь системному тику присвоен приоритет 14, а PendSV приоритет 15. Если поднять приоритет выше 14 при входе в критическую секцию, то никакие задачи и сервисы RTOS не смогут прервать ее выполнение.

Код ниже реализует макросы для обеспечения такой функциональности.

#define DISABLE_OS_PRI_LEV    (14 << (8 - __NVIC_PRIO_BITS))  // Маска приоритета в регистре BASEPRI запрещающая прерывания PendSV и  SysTick
#define ENABLE_OS_PRI_LEV     0   // Маска приоритета в регистре BASEPRI разрешающая прерывания с любыми приоритетами

#define  DISABLE_OS_INTERRUPTS __set_BASEPRI(DISABLE_OS_PRI_LEV)
#define  ENABLE_OS_INTERRUPTS  __set_BASEPRI(ENABLE_OS_PRI_LEV)

К слову сказать пока писал эту статью решил посмотреть что произошло за это время в репозитарии Azure RTOS. Оказалось что совсем недавно в версии 6.1.7 ребята пришли к аналогичной мысли и ввели макрос TX_PORT_USE_BASEPRI, а с ним и константу TX_PORT_BASEPRI. Если их объявить, то можно пользоваться стандартными макросами ThreadX для управления прерываниями TX_DISABLE и TX_RESTORE и они будут не запрещать прерывания, а только повышать приоритет до уровня TX_PORT_USE_BASEPRI . При этом в процедурах использующих эти макросы нужно в начале вставлять запись TX_INTERRUPT_SAVE_AREA.

Константа TX_PORT_BASEPRI имеет тот же смысл что и константа DISABLE_OS_PRI_LEV в примере выше.

Однако надо помнить что нельзя делать вызовы сервисов RTOS из прерываний с приоритетом выше TX_PORT_BASEPRI если мы объявили TX_PORT_USE_BASEPRI. Иначе это разрушит работу ядра из-за нарушения защиты критических секций в функциях сервисов.

Проект демонстрационной программы hellow_word находится здесь.

Включение профилирования RTOS

Профилирование дает возможность в среде отладчика IAR видеть статистику о ресурсах процессорного времени занимаемых задачами.

Для этого надо подключить к проекту два файла из репозитария: tx_execution_profile.c и tx_execution_profile.h. После этого в файл tx_user.h добавить объявление:

#define TX_EXECUTION_PROFILE_ENABLE

Целиком файл tx_user.h будет выглядеть так:

#define TX_MAX_PRIORITIES                       32
#define TX_DISABLE_PREEMPTION_THRESHOLD
#define TX_DISABLE_REDUNDANT_CLEARING
#define TX_DISABLE_NOTIFY_CALLBACKS
#define TX_NO_FILEX_POINTER
#define TX_TIMER_PROCESS_IN_ISR
#define TX_ENABLE_IAR_LIBRARY_SUPPORT
#define TX_ENABLE_STACK_CHECKING

#define TX_EXECUTION_PROFILE_ENABLE
#define TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO
#define TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO
#define TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO
#define TX_MUTEX_ENABLE_PERFORMANCE_INFO
#define TX_QUEUE_ENABLE_PERFORMANCE_INFO
#define TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO
#define TX_THREAD_ENABLE_PERFORMANCE_INFO
#define TX_TIMER_ENABLE_PERFORMANCE_INFO

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

Также надо добавить подключение #include "tx_user.h" в файл tx_thread_schedule.s

Убираем файл hello_world.c и вставляем в проект файл demo_threadx.c из репозитория ThreadX. Немного корректируем функцию main, вставив туда вызовы инициализации чипа. Этот демонстрационный файл создает 8 задач и по одной очереди, семафору, событию, пулу байтов, пулу блоков. Это дает возможность наблюдать за работой всех основных сервисов.

После запуска на плате под отладчиком IAR получаем очень подробную статистику по функционированию задач и сервисов RTOS:

Здесь видим сколько задач запущено, сколько каждая задача потребляет процессорного времени, сколько времени занимают прерывания, сколько стека использовано задачами и много другое. Видно что прерывание переключателя контекста PendSV длиться не дольше 1.7 мкс, а прерывание тика системы не дольше 8 мкс. Если период тиков 1 мс то эти все прерывания для 8-и задач занимают всего около 2% процессорного времени. Это и будут накладные на использование RTOS.

Проект демонстрационной программы RTOS_profiling находится здесь.

Итак, первичное исследование на плате контроллера резервного питания подтвердило возможность использования ThreadX из репозитария Azure RTOS в качестве основы для реализации управления с жестким реальным временем.

Ссылка на открытый проект: https://github.com/Indemsys/Backup-controller_BACKPMAN-v1.0

Комментарии (30)


  1. tzlom
    21.08.2021 21:11

    А вы лицензию на это поделие читали? Вот вам выжимка:

    1- данная ОС не лицензирована на тот чип который вы используете, фактически ваша статья является доказательством нарушения лицензии. Можете связяться с микрософтом чтобы узнать что вам за это будет.

    2- вы должны соблюдать экспортные ограничения США, т.к. это требование лицензии то отмазаться по 25% не получится. Сейчас из Ирана ваш репо склонируют и вы потенциально влетите на проблемы. Я правда не знаю с кем- с М$ за нарушение лицензии или с правительством США за нарушение экспортных ограничений, но я думаю вам без разницы.

    Так что уберите тег Open Source, это не он.


    1. Indemsys Автор
      21.08.2021 21:45
      +3

      Немного не так.
      Читатели этой статьи только лишь ограничены в “Distribution and Production Use” rights на изделия с этой RTOS на этом чипе.
      Я же не ограничен портировать и писать статьи про это.
      Но я не произвожу и не продаю. Это исследовательский проект.


      1. tzlom
        21.08.2021 23:07
        -1

        1. INSTALLATION AND USE RIGHTS.

        a) General. You may install and use the software and the included Microsoft applications solely for internal development, testing and evaluation purposes.Any distribution or production use requires a separate license as set forth in Section 2.


        1. Indemsys Автор
          21.08.2021 23:43
          +2

          evaluation purposes


          1. tzlom
            21.08.2021 23:57
            -1

            1. Indemsys Автор
              22.08.2021 00:07
              +4

              1. tzlom
                22.08.2021 11:41

                Угу, согласно этому определению бесплатное распространение вообще не существует т.к. его никто не покупает. Хотите апеллировать к значению слов давайте ссылку на словарь.

                https://www.merriam-webster.com/dictionary/distribution

                https://www.merriam-webster.com/dictionary/distribute


  1. predator86
    22.08.2021 14:40

    прерывание тика системы не дольше 8 мкс
    С чем связано такое долгое прерывание тика?


    1. Indemsys Автор
      22.08.2021 15:25

      Потому что так выбрана опция RTOS.
      Все пользовательские таймеры отрабатываются в этом прерывании.
      Если выставить опцию обработки таймеров в отдельной задаче, то прерывание тика длиться 0.52 мкс.


      1. predator86
        22.08.2021 15:32
        +1

        пользовательские таймеры
        Что они делают?


        1. lamerok
          22.08.2021 15:36

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


          1. predator86
            22.08.2021 15:43

            Просто 8us это приличное время и у этих таймеров должно быть конкретное оправдание.


            1. Indemsys Автор
              22.08.2021 16:05

              Таймера организуются в демо приложении. Я не смотрел сколько точно их там.
              Это не мое приложение и оно специально не оптимизировано, его цель только демонстрация работы сервисов.
              К тому же включен профайлинг и сбор статистики которые тоже потребляют сотни тактов.
              И наконец 8 мкс это худшее время, и оно появляется только когда истекает какой либо из таймеров. А так прерывание тика длится все так же в районе 0.6 мкс.
              Кстати таймера требуются и сервисам с таймаутами. Так что там хватает таймеров.


  1. predator86
    22.08.2021 14:43

    для 8-и задач занимают всего около 2% процессорного времени
    Как Вы это посчитали?


    1. Indemsys Автор
      22.08.2021 15:27

      Сложил длительность всех прерываний ядра за один тик. Не совсем точно, но дает представление о масштабах.


      1. predator86
        22.08.2021 15:35

        Я посчитал так ((8+1.7)/1000)*100=0,97%. А как Вы считали?


        1. Indemsys Автор
          22.08.2021 16:07

          Восемь задач. И восемь прерываний от каждой в каждом тике в худшем случае.
          Я считаю всегда худшие случаи.


          1. predator86
            22.08.2021 18:06

            Тогда если судить по графику Timeline то накладные расходы в худшем случае примерно 25%.


            1. Indemsys Автор
              22.08.2021 23:26

              Тогда опишите этот случай. Я его не вижу.


              1. predator86
                23.08.2021 00:36

                Между переключениями контекста проходит 6.5us из которых само переключение занимает 1.62us, следовательно на работу программ остаётся 4.88us.


                1. Indemsys Автор
                  23.08.2021 10:37

                  Пауза между прерываниями это тоже работа сервисов RTOS.
                  Поэтому накладные будут 100% в этом случае.
                  Да, с такими накладными будет работать программа с багом, например с deadlock или некорректным ISR ядра.
                  RTOS - не средство от багов, а скорее катализатор багов.
                  Но статья рассчитана на тех кто уже знает зачем им RTOS.


                  1. predator86
                    23.08.2021 15:10

                    Пауза между прерываниями это тоже работа сервисов RTOS.
                    Здесь я не понял. А программы когда исполняются?
                    с такими накладными будет работать программа с багом, например с deadlock
                    Разве deadlock может как то поднять накладные расходы? Я думал что просто все участники deadlock'а заснут, а остальные продолжат свою работу.
                    RTOS — не средство от багов, а скорее катализатор багов.
                    Т.е. ОС не упрощает, а усложняет жизнь программистам? Из этого следует вывод, что из более громоздких и сложных проектов необходимо исключить ОС для уменьшения накладных расходов и меньшего возникновения ошибок. Правильно я Вас понял?

                    Просто хочу разобраться.


                    1. Indemsys Автор
                      23.08.2021 15:43

                      Здесь я не понял. А программы когда исполняются?

                      Сначала это я не понял.
                      Не понял как вы по маленьком фрагменту таймлайна определили полный процент накладных.
                      Я решил что вы начали рассматривать гипотетические случаи.
                      В таком разе я вам привел гипотетический случай с двумя залочеными задачами. И в этом случае никакой полезный код не будет выполняться и накладные 100%

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

                      Аргументы за Azure RTOS приведены в начале статьи. И там нет аргумента по поводу уменьшение ошибок.
                      Как я отлаживаю свой проект планирую написать в следующей статье.


                      1. predator86
                        23.08.2021 15:52

                        Скажите тогда что заставляет задачи переключатся: время или события?


                      1. Indemsys Автор
                        23.08.2021 16:13

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


                      1. predator86
                        23.08.2021 16:18

                        Спасибо за разъяснения, но это всё как-то сложновато. Хотелось бы чтобы оно всё работало без заморочек.


                      1. Indemsys Автор
                        23.08.2021 16:27

                        Эх, сложновато....
                        Сложность еще и не началась.
                        Портирование RTOS - самый простой этап в проекте.


                      1. predator86
                        23.08.2021 16:32

                        Я вот просмотрел Ваши предыдущие статьи и заметил что Вы часто используете в них разные RTOS. С чем это связано?


                      1. Indemsys Автор
                        23.08.2021 17:11

                        Нет, одновременно я не использую разные, просто они у меня меняются с течением времени.
                        Сами производители чипов заставляют менять.


                      1. predator86
                        23.08.2021 17:16

                        Понятно, спасибо.