[i] Особенности портирования FreeRTOS. Запуск ОСРВ на МК К1986ВК01GI и К1986ВЕ1QI (К1986ВЕ1FI, К1986ВЕ1GI)

ID статьи: 49937
Дата последнего изменения: 30.11.2023 09:10:39
Материал из настоящей статьи, относящийся к микросхеме К1986ВЕ1QI, распространяется в том числе на микроконтроллеры К1986ВЕ1FI и К1986ВЕ1GI
Настоящая статья информационного портала будет рассматривать особенности портирования ОСРВ FreeRTOS на микроконтроллеры компании Миландр, однако, общие положения из этой статьи позволят успешно портировать операционную систему реального времени и на микроконтроллеры сторонних компаний.

FreeRTOS - самая популярная многозадачная операционная система реального времени для встраиваемых систем (портирована на 35 микропроцессорных архитектур). Как и все другие ОСРВ, FreeRTOS предназначена для cвоевременности выполнения обработки данных, реализации взаимодействия процессов в системе.

В статье не будут рассмотрены детальные сведения о каждом параметре настроек и механизмов работы ОСРВ, вместо этого предлагаем два ресурса, которые желательно рассматривать вкупе с данной статьёй, в частности, речь идёт про настройки параметров в файле FreeRTOSConfig.h (источник 2):

1. FreeRTOS: практическое применение;
2. Настройка параметров FreeRTOS;
3. FreeRTOS: управление памятью.

Итоговые проекты будут приложены в конце статьи. Важно отметить, что в основе проектов лежит применение официальных установочных паков. Поскольку разработка будет вестись в IDE Keil, то потребуется применение установочных паков для данной интегрированной среды разработки, тем не менее, портирование для IDE IAR будет иметь аналогичный подход (для IDE IAR также поставляется установочный пак).

Приступим к портированию FreeRTOS - начнем с К1986ВК01GI. В качестве основы, чтобы не собирать проект с нуля, был взят проект pwm_rgb (он имеет все необходимые функции по инициализации системы, подключает библиотечный файл для удобной работы со светодиодами). Пройдемся по пунктам:

1. Скачаем архив с FreeRTOS с официального сайта. Возьмем самую актуальную версию (на момент конца 2022 года это версия FreeRTOS 202212.00). Версия LTS выпущена несколько раньше, однако, она имеет долгосрочную поддержку (такая ветка FreeRTOS возникла вследствие того, что на производственных устройствах не всегда есть возможность постоянно устанавливать новую версию ОСРВ, вместо этого есть некоторая стабильная версия, в которой исправляются ошибки безопасности без изменения совместимости). На рисунке 1 представлен официальный сайт FreeRTOS, и указана кнопка, по которой можно загрузить операционную систему.


Рисунок 1 - Официальный сайт FreeRTOS

2. Распакуем все файлы из загруженного архива в любой удобное место.

3. Создадим в папке с созданным проектом IDE Keil папку ./include. После этого зайдем в корневую папку с распакованными файлами FreeRTOS и проследуем по пути ./FreeRTOS/Source/include - из этой папки необходимо скопировать все заголовочные .h-файлы в папку ./include проекта.

Сразу же укажем компилятору в настройках проекта путь до заголовочных файлов: необходимо зайти в "Options for Target" и перейти на вкладку "C/C++", где нас будет интересовать поле "Include Paths" - здесь необходимо указать папку ./include. Выглядеть итоговая настройка окна будет так, как представлено на рисунке 2.


Рисунок 2 - Вид окна настроек компилятора в IDE Keil

4. Теперь необходимо проследовать по пути ./FreeRTOS/Source с файлами ОСРВ, где нас будет ожидать следующий список исходных .c файлов:

- croutine.c
- event_groups.c
- list.c
- queue.c
- stream_buffer.c
- tasks.c
- timers.c

Это ключевые файлы ОСРВ. Необходимо все семь файлов скопировать и отправить в корневую папку рабочего проекта. После чего данные файлы необходимо добавить в дерево проекта, чтобы получилось так, как представлено на рисунке 3.


Рисунок 3 - Дерево проекта IDE Keil после добавления основных исходных файлов

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

- port.c
- portmacro.h

Поскольку FreeRTOS портирована на множество архитектур, то из состава ОСРВ мы можем взять нужные. В данном случае работа ведётся с К1986ВК01GI, соответственно, необходимо найти файлы для архитектуры ARM, ядра Cortex-M4F (ядро с поддержкой работы с плавающей точкой) и среды IDE Keil. Все файлы должны лежать по пути ./FreeRTOS/Source/portable/Keil, однако, текстовый файл по данному пути ведёт нас в ./FreeRTOS/Source/portable/RVDS. Уже в данной директории необходимо зайти в папку ./ARM_CM4F, где и будут лежать два нужных нам файла. Файл port.c мы копируем в корневую папку с проектом, файл portmacro.h добавляем в папку ./include ко всем остальным заголовочным файлам.

Важные особенности:

- Если портирование будет производиться в другой интегрированной среде разработки, например, в IDE IAR, то оба файла для нужной архитектуры необходимо будет брать по другому пути, в данном случае по пути ./FreeRTOS/Source/portable/IAR

- В портированных файлах самой FreeRTOS мы никогда не встретим файлы на ядро Cortex-M1. Если мы работаем с микроконтроллером К1986ВЕ1QI, в котором RISC-ядро схоже с системой команд Cortex-M1, то можно взять файлы от ядра Cortex-M0 (у обоих ядер одинаковая система команд).

6. Теперь необходимо подключить к проекту .c-файл, который будет отвечать за работу с памятью. Всего во FreeRTOS имеется пять примеров реализации выделения памяти (описание каждой реализации можно увидеть из ресурса №3, представленного в начале статьи). Это файлы: heap_1.c, heap_2.c, heap_3.c, heap_4.c и heap_5.c. Данные файлы расположены по пути ./FreeRTOS/Source/portable/MemMang. Необходимо взять и добавить в собственный проект только один файл - для удобства будет взят файл heap_1.c.

7. Мы почти завершили портирование нужных файлов ОСРВ. Остался ключевой файл - FreeRTOSConfig.h. Изначально ОСРВ настраивает себя в значения по умолчанию в файле FreeRTOS.h, однако, обязательный файл FreeRTOSConfig.h требуется для переопределения пользовательских настроек ОСРВ. Детально по настройкам данного файла сведения представлены на ресурсе №2 из начала статьи. 

В составе файлов ОСРВ FreeRTOS имеется множество реализаций FreeRTOSConfig.h для конкретных микроконтроллеров, возьмем в качестве базиса файл по пути: ./FreeRTOS/Demo/CORTEX_M4F_STM32F407ZG-SK - добавим заголовочный файл в папку ./include рабочего проекта.

Взятый файл FreeRTOSConfig.h имеет оптимальные настройки ОСРВ, тем не менее, их можно варьировать и настраивать под себя, в случае ошибочных настроек при отладке нас будет останавливать на обработчиках, которые будут пояснять, что именно в настройках выполнено неверно.

На что первостепенно важно обратить внимание при работе с данным файлом FreeRTOSConfig.h (тезисы могут быть применены при рассмотрении любых других реализаций):

- Данный файл в исходном виде предназначен для компилятора IDE IAR, на это указывает следующий фрагмент кода:

Фрагмент кода 1 - Код для ошибочной компиляции в IDE Keil
#ifdef __ICCARM__
   #include <stdint.h>
   extern uint32_t SystemCoreClock;
#endif
Поскольку для работы нам нужна переменная SystemCoreClock, то необходимо использовать определение компилятора IDE Keil: __CC_ARM

- Сборка проекта с данным файлом не будет завершена, поскольку активны следующие определения (см. фрагмент кода 2):

Фрагмент кода 2 - Определения для исправления
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 1
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
Установка данных определений в значения, отличные от нуля, требует реализации дополнительных программных обработчиков, поэтому для базовой работы можно установить данным определениям нулевое значение.

- Необходимо также следить за соотношением значений определений configPRIO_BITS и configMAX_PRIORITIES: значение configMAX_PRIORITIES должно быть больше, чем configPRIO_BITS. Некорректный запуск планировщика (именно с него стартует исполнение потоков в рамках ОСРВ) может быть тесно связан с настройками данных параметров - режим отладки остановит в нужном обработчике, где и будет сообщено о невыполнении данного условия.

8. Все необходимые файлы импортированы в проект. Теперь необходимо вспомнить, что в п.5 были взяты файлы для ядра Cortex-M4F. В таком случае на уровне работы с микроконтроллером необходимо включить поддержку инструкций для работы с плавающей точкой. При использовании установочного пака для IDE Keil это делается настройкой поля "Floating Point Hardware" в окне "Target" настроек проекта. Поле необходимо установить в состояние "Single Precision", как указано на рисунке 4.


Рисунок 4 - Включение режима работы с плавающей точкой в IDE Keil

Если бы были взяты файлы для ядра Cortex-M4, то данная настройка не потребовалась бы. При этом если продолжают возникать ошибки, связанные с определением __FPU_PRESENT, то необходимо указать данное определение компилятору напрямую (см. рисунок 2).

9. Теперь если собрать проект, то никаких ошибок не будет. Однако, используемый файл main.c не описывает никакие потоки, также нигде не запускается ОСРВ (планировщик задач). Поэтому добавим в main.c создание трёх потоков (для каждого светодиода). Пример для красного светодиода приведен во фрагменте кода 3:

Фрагмент кода 3 - Функция создания потока для красного светодиода
xTaskCreate(RedLed, "RED", configMINIMAL_STACK_SIZE, NULL, 1, NULL); Здесь значение "RedLed" указывает на имя функции, которая будет вызываться для данного потока. Реализуем тело функции "RedLed" (см. фрагмент кода 4).

Фрагмент кода 4 - Тело функции RedLed созданного потока
void RedLed (void* parameters)
{
while(1)
   {
   vTaskDelay(50);
   Led_Set(LED_RED);
   vTaskDelay(50);
   Led_Reset(LED_RED);
   }
}
Аналогичные потоки и функции для них создаются для других светодиодов (для разнообразия задержки можно сделать разные: для красного светодиода по 50 мс, для зеленого по 100 мс, для синего по 200 мс - получится гирлянда при запуске).

10. Осталось только запустить планировщик ОСРВ, который будет работать с потоками. Делается это вызовом функции vTaskStartScheduler();

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

Особенности портирования FreeRTOS на микроконтроллер К1986ВЕ1QI

Мы рассмотрели портирование на микроконтроллер К1986ВК01GI, но при выполнении данной операции не рассматривали то, как именно ОСРВ отсчитывает реальное время - делает она это по умолчанию от системного таймера SysTick. ОСРВ отсчитывает тики в соответствии с определением configTICK_RATE_HZ в файле FreeRTOSConfig.h - по умолчанию тики отсчитываются раз в 1 мс, то есть с частотой 1000 Гц. И если в К1986ВК01GI никаких проблем при работе с системным таймером SystTick нет, он считает исправно, то в микроконтроллере К1986ВЕ1QI закралась аппаратная ошибка 0011 "Ошибка системного таймера" (смотреть файл errata), из-за которой системный таймер имеет увеличенное время счёта системного таймера. Соответственно, необходимо перевести счёт в рамках ОСРВ с системного таймера на таймер общего назначения (при рассмотрении RTX RTOS от IDE Keil подобный переход осуществлялся при помощи OS Tick API).

Что необходимо сделать:

1. Портировать ОСРВ FreeRTOS по аналогии с микроконтроллером К1986ВК01GI (не забыв при этом, что файлы port.c и portmacro.h должны быть взяты для ядра Cortex-M0).

2. Указать в файле FreeRTOSConfig.h для обработчика xPortSysTickHandler (определение ОСРВ) не функцию обработчика прерывания от системного таймера SysTick_Handler, а, например, функцию обработчика прерывания от четвертого таймера общего назначения TIMER4_IRQHandler. Когда указан обработчик SysTick_Handler (то есть работа ОСРВ по умолчанию), его вызов раз в мс сопровождает вызов внутренней функции void xPortSysTickHandler(void) (реализация в исходном файле port.c), в которой на уровне работы ОСРВ выполняется инкремент внутреннего счётчика операционной системы. Соответственно, меняя определение функции обработчика прерывания на TIMER4_IRQHandler, мы должны позаботиться о том, чтобы прерывание от таймера общего назначения вызывалось также раз в 1 мс по аналогии с системным таймером SysTick (для этого требуется конфигурация таймера общего назначения - см. фрагмент кода 6).

3. Когда работа ведётся с системным таймером SysTick, его настройка выполняется в файле port.c сразу после строчки кода #if (configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0). Поскольку настройка системного таймера нам неинтересна, закомментируем строчки кода, которые его конфигурируют (см. фрагмент кода 5):

Фрагмент кода 5 - Строки конфигурации системного таймера
/* Stop and clear the SysTick. */
portNVIC_SYSTICK_CTRL_REG = 0UL;
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

/* Configure SysTick to interrupt at the requested rate. */
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
 4. Теперь вставим здесь же строки кода, которые будут конфигурировать таймер общего назначения (в данном случае четвертый, поскольку был выбран его обработчик). Код должен включить тактирование таймера общего назначения, выполнить обновление системной частоты SystemCoreClock, настроить основание счёта, которое будет зависеть от частоты SystemCoreClock и делать его таким, чтобы отсчитывать время 1 мс. Также код должен разрешить вызов прерывания по совпадению счёта с основанием счёта внутри блока таймера, помимо этого должно быть включено прерывание на уровне работы NVIC - в заключении счётчик должен начать считать.

В начале файла port.c необходимо подключить файл MDR32FxQI_config.h, чтобы все названия регистров были доступны для использования. Итоговый код представлен во фрагменте кода 6:

Фрагмент кода 6 - Конфигурация таймера общего назначения
MDR_RST_CLK -> PER_CLOCK |= (1 << 19); // Enable global clock Timer4
MDR_RST_CLK -> UART_CLOCK |= (1 << 26); // Enable local clock Timer4
SystemCoreClockUpdate(); // Update SystemCoreClock
MDR_TIMER4 -> ARR = (SystemCoreClock/1000) - 1; // Set the count period so that the interrupt is generated once every 1 ms
MDR_TIMER4 -> IE |= (1 << 1); // Enable interrupt call every 1ms from the Timer4
NVIC_EnableIRQ(TIMER4_IRQn); // Enable interrupt from the Timer4 in NVIC
MDR_TIMER4 -> CNTRL |= (1 << 0); // Turn on the Timer4
5. Настройка завершена (итоговый проект доступен для загрузки в конце статьи).

Контактная информация

Сайт:https://support.milandr.ru
E-mail:support@milandr.ru
Телефон: +7 495 221-13-55