Установка и настройка

Рассмотрим процесс настройки проекта в среде IAR. Скачайте архив с последней версией операционной системы, скопируйте в папку с проектом, подключите файлы и настройте FreeRTOS через конфигурационный файл FreeRTOSConfig.h. Первое, с чего стоит начать, это выбор режима работы планировщика.

// configUSE_PREEMPTION = 0 => co-operative mode
// configUSE_PREEMPTION = 1 => pre-emption mode
#define configUSE_PREEMPTION                    1
// allow or deny time slicing for tasks with equal priorities
#define configUSE_TIME_SLICING                  1

Для некоторых платформы имеются специфические возможности по выбору задач. Обычно параметр configUSE_PORT_OPTIMISED_TASK_SELECTION оставляют раным 0.

#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0

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

Не все платформы поддерживают режим низкого энергопотребления.

#define configUSE_TICKLESS_IDLE                 0

Зададим частоту тактирования и системных тиков.

#define configCPU_CLOCK_HZ                      ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ                      ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES                    5
#define configMINIMAL_STACK_SIZE                128
#define configMAX_TASK_NAME_LEN                 16

Далее задается максимальное количество приоритетов, минимальный размер стека и максимальное количество символов для описания задачи.

Так как поддерживаются разные платформы (в том числе 8- и 16-битные), то использование 32-разрядного числа для счетчика — не всегда оптимальное решение.

#define configUSE_16_BIT_TICKS                  0

Если задать этот макрос как 0, система будет использовать 32 бита для счетчика, в противном случае — 16.

#define configIDLE_SHOULD_YIELD                 1

Если планируется использовать задачу с приоритетом idle, то конфигурирированием данного макроса можно добиться разного поведения. Если выставить 1, ядро будет переключать управление сразу же, как только какая-нибудь задача станет готовой к выполнению, т.е. не дожидаясь системного тика. Если выставить 0, в один квант времени будет выполняться системная idle-задача, а в следующий — пользовательская задача с idle-приоритетом.

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

#define configUSE_TASK_NOTIFICATIONS            1 define configUSE_MUTEXES                       0
#define configUSE_RECURSIVE_MUTEXES             0
#define configUSE_COUNTING_SEMAPHORES           0
#define configUSE_ALTERNATIVE_API               0 /* Deprecated! */
#define configQUEUE_REGISTRY_SIZE               10
#define configUSE_QUEUE_SETS                    0
#define configUSE_TIME_SLICING                  1
#define configUSE_NEWLIB_REENTRANT              0

configUSE_TASK_NOTIFICATIONS подключает возможность разблокировать задачу из другой через определенные API-функции. Рассмотрим их позже. Остальные макросы с префиксом USE говорят сами за себя — используются ли мьютексы, семафоры и очереди. configUSE_TIME_SLICING разрешает переключение задач по системному тику, а configUSE_NEWLIB_REENTRANT позволяет использовать версию стандартной библиотеки newlib.

Функциональность разработчики добавили по просьбе пользователей, однако сама FreeRTOS не использует функции из данной библиотеки и не гарантирует их корректное поведение.

Если операционная система была обновлена в старом проекте, могут возникнуть проблемы при переходе. Файл FreeRTOS.h использует макрос для валидации функциональности. Чтобы ее разрешить, следует установить 1. В случае создания нового проекта установите 0.

#define configENABLE_BACKWARD_COMPATIBILITY     0

Для описания задач используется структура TBC (сокр. Task Control Block), где хранится приоритет, идентификатор, название и другие параметры. Программист вправе добавить собственные поля (указатели) для каких-нибудь собственных данных.

#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5

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

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

#define configSUPPORT_STATIC_ALLOCATION         0
#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configTOTAL_HEAP_SIZE                   10240
#define configAPPLICATION_ALLOCATED_HEAP        1

Работа кучи будет рассмотрена позже. Задавать размер кучи имеет смысл только тогда, когда configSUPPORT_DYNAMIC_ALLOCATION выставлен в 1.

Использовать напрямую idle-задачу и обработчик прерывания системного таймера нельзя, однако имеются так называемые перехватчики (англ. hook), позволяющие выполнить некоторый код совместно с ними. Пример использования будет рассмотрен ниже при описании сопрограмм. Для того чтобы разрешить перехватчики, нужно выставить 1 в соответствующие макросы.

#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0

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

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

#define configCHECK_FOR_STACK_OVERFLOW          0
#define configUSE_MALLOC_FAILED_HOOK            0
#define configUSE_DAEMON_TASK_STARTUP_HOOK      0

Они позволяют обрабатывать такие ситуации, как переполнение стека задачи и нехватку места для размещения динамически создаваемого объекта (когда pvPortMalloc() возвращает NULL).

Макрос configUSE_DAEMON_TASK_STARTUP_HOOK разрешает (при наличии 1 в configUSE_TIMERS) создать перехватчик для задачи демона, которая выполнится один раз перед запуском системы.

FreeRTOS позволяет собирать данные о ходе работы и тем самым вести журнал своего внутреннего состояния. Следующий блок макросов настраивает эту возможность.

#define configGENERATE_RUN_TIME_STATS           0
#define configUSE_TRACE_FACILITY                0
#define configUSE_STATS_FORMATTING_FUNCTIONS    0

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

#define configUSE_CO_ROUTINES                   0
#define configMAX_CO_ROUTINE_PRIORITIES         1

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

#define configUSE_TIMERS                        1
#define configTIMER_TASK_PRIORITY               3
#define configTIMER_QUEUE_LENGTH                10
#define configTIMER_TASK_STACK_DEPTH            configMINIMAL_STACK_SIZE

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

Как ни странно, планировщик не самое важное в программе: есть множество других событий, на которые нужно реагировать незамедлительно, т.е. обрабатывать прерывания. По этой причине работа планировщика легко может быть прервана. Допустим, подошло время сменить задачу, запускается планировщик и начинает работать с очередями. В это время происходит что-то важное, и начинает выполняться код для обработки события, который может изменить состояние очереди. Может сложиться так, что при возобновлении работы планировщика данные, записанные прерыванием, будут утеряны. Для избежания таких ситуаций планировщик может запретить часть прерываний с определенным приоритетом. Задача программиста в таком случае — правильно настроить исключения. Например, вот так:

/* Cortex-M specific definitions. */
ifdef __NVIC_PRIO_BITS
  /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
  #define configPRIO_BITS         __NVIC_PRIO_BITS
else
  #define configPRIO_BITS         4
endif

/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

FreeRTOS довольно гибкая система: можно не только настроить поведение, но и подключить или отключить конкретные функции. Например, если программист не планирует удалять задачи, то функция vTaskDelete() не нужна. Установив 0 в соответствующий макрос, вы сделаете ее недоступной для выполнения.

#define INCLUDE_vTaskPrioritySet                1
#define INCLUDE_uxTaskPriorityGet               1
#define INCLUDE_vTaskDelete                     1
#define INCLUDE_vTaskSuspend                    1
#define INCLUDE_xResumeFromISR                  1
#define INCLUDE_vTaskDelayUntil                 1
#define INCLUDE_vTaskDelay                      1
#define INCLUDE_xTaskGetSchedulerState          1
#define INCLUDE_xTaskGetCurrentTaskHandle       1
#define INCLUDE_uxTaskGetStackHighWaterMark     0
#define INCLUDE_xTaskGetIdleTaskHandle          0
#define INCLUDE_eTaskGetState                   0
#define INCLUDE_xEventGroupSetBitFromISR        1
#define INCLUDE_xTimerPendFunctionCall          0
#define INCLUDE_xTaskAbortDelay                 0
#define INCLUDE_xTaskGetHandle                  0
#define INCLUDE_xTaskResumeFromISR              1

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

В Cortex-M ядрах для планировщика принято использовать SysTick в качестве системного таймера, однако если вы генерируете код через STM32CubeMX, то квантующий таймер выбирается другой, например TIM1, так как библиотека HAL использует его для своих целей.

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

#define xPortSysTickHandler SysTick_Handler
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler

На этом настройка ОС завершена, перейдем к другим темам.


Изменено: