FreeRTOS настраивается с помощью файла конфигурации FreeRTOSConfig.h. Каждое приложение FreeRTOS должно иметь такой заголовочный файл, находящийся в путях поиска (include path) препроцессора. FreeRTOSConfig.h настраивает поведение ядра RTOS для собираемого приложения. Таким образом, содержание этого файла зависит от приложения, не от RTOS, и файл FreeRTOSConfig.h должен находиться в директории приложения, не в одном из каталогов исходного кода ядра RTOS.
Примечание: непонятные термины и сокращения ищите в Словарике [2]. Еще см. словарик в статье [3], также полезно ознакомиться с серией статей на этом сайте, описывающих основные принципы работы FreeRTOS.
Каждое demo-приложение, включенное в загружаемый пакет исходного кода RTOS, имеет свой собственный файл FreeRTOSConfig.h. Некоторые демки довольно старые, и поэтому не содержат все доступные опции конфигурации. Те опции, которые пропущены в FreeRTOSConfig.h, автоматически устанавливаются в значения по умолчанию (значения по умолчанию прописаны в файле FreeRTOS.h).
Вот типичный пример файла FreeRTOSConfig.h, с последующим описанием каждого параметра:
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* Здесь хорошее место для подключения заголовочных файлов, которые
потребуются в Вашем приложении. */
#include "something.h"
#define configUSE_PREEMPTION 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ 60000000
#define configTICK_RATE_HZ 250
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_MUTEXES 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_ALTERNATIVE_API 0 /* Устарело! */
#define configQUEUE_REGISTRY_SIZE 10
#define configUSE_QUEUE_SETS 0
#define configUSE_TIME_SLICING 0
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 0
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
#define configSTACK_DEPTH_TYPE uint16_t
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
/* Определения, связанные с выделением памяти. */
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE 10240
#define configAPPLICATION_ALLOCATED_HEAP 1
/* Определения, связанные с функциями перехвата событий
(hook-функции). */
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
/* Определения, настраивающие захват информации
времени выполнения и статистики задач. */
#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
/* Определения конфигурации поведения вложенных прерываний. */
// Примечание (*): зависит от процессора:
#define configKERNEL_INTERRUPT_PRIORITY *
// Примечание (*): зависит от процессора и от приложения:
#define configMAX_SYSCALL_INTERRUPT_PRIORITY *
#define configMAX_API_CALL_INTERRUPT_PRIORITY *
/* Определение ловушки ошибок во время разработки. */
#define configASSERT( ( x ) ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
/* Определения FreeRTOS, специфичные для MPU. */
#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 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
/* Здесь может быть подключен заголовочный файл для определения
макроса трассировки (trace macro). */
#endif /* FREERTOS_CONFIG_H */
[Параметры "config"]
configUSE_PREEMPTION. Установите в 1 для использования планировщика с алгоритмом вытесняющей многозадачности (preemptive RTOS scheduler), или в 0 для использования кооперативной многозадачности (cooperative RTOS scheduler).
configUSE_PORT_OPTIMISED_TASK_SELECTION. У некоторых портов FreeRTOS есть два метода для выбора следующей задачи для выполнения – традиционный (generic) метод и метод, специфический для этого порта.
Generic-метод:
• Используется, когда configUSE_PORT_OPTIMISED_TASK_SELECTION установлено в 0, или когда специфический метод не реализован. • Может использоваться для всех портов FreeRTOS. • Полностью написан на C, что делает его менее эффективным по сравнения со специфическим методом. • Не накладывает предел на максимальное количество доступных приоритетов задач.
Метод, специфичный для порта:
• Недоступен для всех портов. • Используется, когда configUSE_PORT_OPTIMISED_TASK_SELECTION установлено в 1. • Полагается на одну или несколько специфичных для архитектуры ассемблерных инструкций (обычно инструкция подсчета лидирующих нулей Count Leading Zeros [CLZ] или подобная инструкция), поэтому может использоваться только на той архитектуре, для которой этот порт FreeRTOS был специально написан. • Более эффективен, чем generic-метод. • Обычно задает предел 32 для максимального количества доступных приоритетов.
configUSE_TICKLESS_IDLE. Установите configUSE_TICKLESS_IDLE в 1 для использования режима пониженного потребления без тиков, или в 0 для сохранения всегда работающего прерывания тиков. Реализации режима без тиков с пониженным энергопотреблением не предоставляется для всех портов FreeRTOS.
configUSE_IDLE_HOOK. Установите в 1, если хотите использовать перехват состояния ожидания (idle hook), или в 0, если эта функция не нужна.
configUSE_MALLOC_FAILED_HOOK. Ядро использует вызов pvPortMalloc() для выделения памяти из кучи каждый раз, когда создается задача (task), очередь (queue) или семафор (semaphore). Официальный пакет загрузки FreeRTOS для этой цели включает в себя 4 схемы примера выделения памяти, они реализованы соответственно в файлах heap_1.c, heap_2.c, heap_3.c, heap_4.c и heap_5.c. Параметр configUSE_MALLOC_FAILED_HOOK относится только к одной из этих схем, которая используется.
Функция перехвата ошибки malloc() это hook-функция (или callback). Если она определена и сконфигурирована, то будет вызвана каждый раз, когда pvPortMalloc() вернет NULL. Значение NULL возвращается, если нет достаточного объема памяти в куче FreeRTOS, чтобы выполнить запрос приложения на выделение памяти.
Если параметр configUSE_MALLOC_FAILED_HOOK установлен в 1, то приложение должно определить hook-функцию перехвата ошибки malloc(). Если configUSE_MALLOC_FAILED_HOOK установлен в 0, то функция перехвата ошибки malloc() не будет вызываться, даже если она была определена. Функция перехвата ошибки malloc() должна быть определена по следующему прототипу:
void vApplicationMallocFailedHook( void );
configUSE_DAEMON_TASK_STARTUP_HOOK. Если оба параметра configUSE_TIMERS и configUSE_DAEMON_TASK_STARTUP_HOOK установлены в 1, то приложение должно определить hook-функцию точно с таким именем и прототипом, как показано ниже. Эта hook-функция будет вызвана точно только один раз, когда когда задача демона RTOS (также известная как timer service task) выполнится в первый раз. Любой инициализационный код приложения, которому нужен рабочий функционал RTOS, может быть помещен в эту hook-функцию.
void void vApplicationDaemonTaskStartupHook( void );
configUSE_TICK_HOOK. Установите в 1, если хотите использовать перехват тиков, или 0, если это не нужно.
configCPU_CLOCK_HZ. Здесь указывается частота в Гц, с которой тактируется внутреннее ядро микроконтроллера. На основе этой частоты аппаратный таймер будет генерировать прерывание тика. Обычно эта та же самая частота, на которой работает CPU. Это значение необходимо для корректного конфигурирования периферийных устройств таймера.
Примечание переводчика: значение configCPU_CLOCK_HZ совпадает со значением переменной SystemCoreClock библиотеки драйверов HAL STM32.
configTICK_RATE_HZ. Частота прерываний тиков RTOS.
Прерывание тика используется для измерения времени. Таким образом, чем выше частота тика, тем выше разрешающая способность отсчета времени. Однако высокая частота тиков также означает, что ядро RTOS будет больше тратить процессорного времени CPU, и эффективность работы приложения снизится. Все демо-приложения RTOS используют одинаковую частоту тиков 1000 Гц. Это значение использовалось для тестирования ядра RTOS, и большее значение обычно не требуется.
У нескольких задач может быть настроен одинаковый приоритет. Планировщик RTOS будет распределять общее процессорное время между задачами с одинаковым приоритетом путем переключения между задачами на каждом тике RTOS. Таким образом, высокая частота тиков также дает эффект уменьшения слайса времени, выделенного для каждой задачи.
configMAX_PRIORITIES. Количество приоритетов, доступных для задач приложения. Любое количество задач может могут использовать одинаковое значение приоритета. Для сопрограмм обработка приоритетов отдельная, см. configMAX_CO_ROUTINE_PRIORITIES.
Каждый доступный приоритет потребляет RAM в ядре RTOS, поэтому параметр configMAX_PRIORITIES не следует устанавливать в значение больше, чем реально требует приложение.
configMINIMAL_STACK_SIZE. Размер стека, используемого для idle task. Обычно этот параметр не должен указываться меньше, чем значение в файл FreeRTOSConfig.h предоставленного демонстрационного приложения используемого вами порта FreeRTOS.
Как и параметр размера стека для функций xTaskCreate() и xTaskCreateStatic(), размер стека в configMINIMAL_STACK_SIZE указывается в единицах слов, а не в байтах. Каждый элемент в стеке занимает 32-бита, поэтому размер стека 100 означает 400 байт (каждый 32-битный элемент стека занимает 4 байта).
configMAX_TASK_NAME_LEN. Максимально допустимая длина описательного имени, которое дается задаче при её создании. Длина указывается в количестве символов, включая символ-терминатор (нулевой байт).
configUSE_TRACE_FACILITY. Установите в 1, если хотите подключить дополнительные поля структуры и функции, чтобы помочь с визуализацией выполнения задач и трассировкой.
configUSE_STATS_FORMATTING_FUNCTIONS. Установите configUSE_TRACE_FACILITY и configUSE_STATS_FORMATTING_FUNCTIONS в 1, чтобы подключить к сборке функции vTaskList() и vTaskGetRunTimeStats(). Либо установите в 0, чтобы опустить использование vTaskList() и vTaskGetRunTimeStates().
configUSE_16_BIT_TICKS. Время измерятся в "тиках". Количество срабатывания прерываний тиков с момента запуска ядра RTOS хранится в переменной TickType_t.
Если определить configUSE_16_BIT_TICKS в 1, то тип TickType_t будет определен (через typedef) как 16-битное число без знака. Если определить configUSE_16_BIT_TICKS как 0, то TickType_t будет 32-битным числом без знака.
Использование 16-битного типа значительно улучшает производительность на 8-битных и 16-битных архитектурах, но ограничивает при этом максимально доступный период времени в 65535 тиков. Таким образом, если предположить частоту тиков 250 Гц, то максимальная отслеживаемая задержка времени или блокировка при 16-битном счетчике составит 262 секунд (сравните с 17179869 секундами при 32-битном счетчике).
configIDLE_SHOULD_YIELD. Этот параметр управляет поведением задач с приоритетом idle (самый низкий приоритет). Он дает эффект только если:
• Используется планировщик с алгоритмом вытеснения (preemptive scheduler). • Приложение создает задачи, которые работают с приоритетом idle.
Если параметр configUSE_TIME_SLICING установлен в 1 (или не определен), то задачи, которые используют одинаковый приоритет, будут переключаться на каждом слайсе времени (каждый тик). Если ни одна из задач не была вытеснена, то каждая задача с определенным приоритетом получит одинаковое количество процессорного времени, и если приоритет выше приоритета idle, то это действительно такой случай.
Когда задачи используют приоритет idle, то поведение может несколько отличаться. Если сконфигурировать configIDLE_SHOULD_YIELD в 1, то задача idle немедленно уступит (yield) процессорное время, если имеется любая готовая к запуску задача с приоритетом idle. Это приводит к тому, что задаче idle будет предоставлено минимальное процессорное время, когда для планировщика доступны другие задачи. Однако это поведение может иметь нежелательные эффекты (в зависимости от нужд приложения), что показано на картинке:
На этой диаграмме показано выполнение 4 задач, которые все работают с приоритетом idle. Задачи A, B и C это задачи приложения. Задача I это системная задача ожидания (idle task). Переключение контекста происходит с регулярными интервалами, в моменты T0, T1, ..., T6. Когда idle task уступает процессор, запускается задача A – однако idle task уже потратила некоторое время текущего слайса. В результате получается, что задача I и задача A эффективно используют время одного и того же слайса. Таким образом, задачи B и C получат больше процессорного времени, чем задач приложения A.
Попадания в эту ситуацию можно избежать следующим образом:
• Если это допустимо, использовать idle hook вместо отдельных задач с приоритетом idle. • Создание всех задач приложения с приоритетом выше приоритета idle. • Установка configIDLE_SHOULD_YIELD в 0.
Установка configIDLE_SHOULD_YIELD в 0 предотвращает idle task от того, что она будет уступать процессорное время до окончания своего слайса времени. Это гарантирует, что все задачи с приоритетом idle получат одинаковое процессорное время (при условии, что ни одна из этих задач не была вытеснена) – однако ценой большего времени обработки, выделяемого для задачи idle.
configUSE_TASK_NOTIFICATIONS. Установка configUSE_TASK_NOTIFICATIONS в 1 (или если не определять параметр configUSE_TASK_NOTIFICATIONS) включит в сборку прямое уведомление о запуске задачи и связанные с этим API-функции.
Установка configUSE_TASK_NOTIFICATIONS в 0 уберет функционал оповещения задачи и соответствующий API.
Каждая задача будет задействовать дополнительные 8 байт из ОЗУ, когда в сборку включены оповещения задач.
configUSE_MUTEXES. Установите в 1 для подключения функционала мьютексов в сборку, или 0, чтобы опустить этот функционал. Пользователи должны представлять разницу между мьютексами и двоичными семафорами, относящуюся к функциональности FreeRTOS.
configUSE_RECURSIVE_MUTEXES. Установите в 1 для поддержки функционала рекурсивных мьютексов, или 0, чтобы опустить этот функционал.
configUSE_COUNTING_SEMAPHORES. Установите в 1 для включения семафоров со счетчиком в сборку, или 0 для исключения семафоров со счетчиком.
configUSE_ALTERNATIVE_API. Установите в 1 для подключения в сборку "альтернативных" функций очереди, или 0 чтобы опустить это. Альтернативное API описано в заголовочном файле queue.h. Альтернативное API считается устаревшим, и оно не предназначено для использования в новых разработках.
configCHECK_FOR_STACK_OVERFLOW. Использование этого параметра см. в статье [4].
configQUEUE_REGISTRY_SIZE. У реестра очереди (queue registry) две цели, обе связаны с отладкой, учитывающей ядро RTOS:
1. Позволяет связать с очередью текстовое имя, чтобы упростить идентификацию очереди в GUI отладки. 2. Содержит информацию, требуемую отладчиком, чтобы найти каждую зарегистрированную очередь и семафор.
У реестра очереди нет никакой другой цели, кроме как отладка приложения в функционале ядра RTOS.
configQUEUE_REGISTRY_SIZE определяет максимальное количество очередей и семафоров, которое можно зарегистрировать. Необходимо зарегистрировать только те очереди и семафоры, которые требуется просмотреть с помощью отладчика с поддержкой ядра RTOS. См. документацию API для функций vQueueAddToRegistry() и vQueueUnregisterQueue().
configUSE_QUEUE_SETS. Установите в 1, чтобы подключить набор функциональности очередей - возможность блокировки задачи (block), или ожидания на очереди или семафоре (pend), на нескольких очередях и семафорах. Или укажите 0, чтобы опустить функционал очередей.
configUSE_TIME_SLICING. По умолчанию (если параметр configUSE_TIME_SLICING не определен, или если configUSE_TIME_SLICING установлен в 1) FreeRTOS использует вытесняющий алгоритм планировщика по слайсам времени. Это означает, что планировщик RTOS всегда работает как задача с самым высоким приоритетом, которая находится в состоянии Ready, и она будет переключать задачи с одинаковым приоритетом на каждом прерывания тика RTOS. Если сконфигурировать configUSE_TIME_SLICING в 0, то планировщик RTOS будет все еще работать с самым высоким приоритетом в состоянии Ready, однако он не будет переключать задачи с одинаковым приоритетом просто потому, что произошло прерывание тика.
configUSE_NEWLIB_REENTRANT. Если сконфигурировать configUSE_NEWLIB_REENTRANT в 1, то для каждой созданной задачи будет выделяться reent-структура newlib [5].
Примечание: поддержка newlib была добавлена по многочисленным просьбам, однако эта библиотека сама по себе не используется командой развития и поддержки FreeRTOS. FreeRTOS не несет ответственности за результаты работы newlib. Пользователь должен быть знаком с newlib, и должен обеспечивать общесистемные реализации необходимых заглушек кода. Обратите внимание, что (во время написания [1]) в текущей версии newlib реализован системный malloc(), который должен быть снабжен блокировками.
configENABLE_BACKWARD_COMPATIBILITY. Заголовочный файл FreeRTOS.h содержит набор макросов #define, который отображает имена типов данных, используемых в версиях FreeRTOS до версии 8.0.0, на имена типов в FreeRTOS версии 8.0.0. Эти макросы позволяют приложению без модификаций своего кода обновить FreeRTOS, когда приложение собиралось в более старой, чем 8.0.0, версии FreeRTOS. Установка configENABLE_BACKWARD_COMPATIBILITY в 0 в файле FreeRTOSConfig.h исключает эти макросы из сборки, что делает невозможным использования имен типов старых версий (до 8.0.0) FreeRTOS.
configNUM_THREAD_LOCAL_STORAGE_POINTERS. Устанавливает количество индексов массива локального хранилища [6] в каждой задаче.
configSTACK_DEPTH_TYPE. Устанавливает тип, используемый для определения глубины стека в вызовах xTaskCreate(), и различные другие используемые места размеров стека (например, когда возвращается вершина стека, stack high water mark).
Более старые версии FreeRTOS указывали размер стека с использованием переменных типа UBaseType_t, однако оказалось, что это слишком ограничивает порты FreeRTOS на 8-битных микроконтроллерах. configSTACK_DEPTH_TYPE снимает это ограничение, разработчики теперь сами могут указать используемый тип.
configMESSAGE_BUFFER_LENGTH_TYPE. Буферы сообщений FreeRTOS используют переменные типа configMESSAGE_BUFFER_LENGTH_TYPE для хранения длины каждого сообщения. Если параметр configMESSAGE_BUFFER_LENGTH_TYPE не определен, то по умолчанию будет использоваться тип size_t. Если сообщения, сохраненные в буфере сообщений, никогда не будут длиннее 255 байт, то определение configMESSAGE_BUFFER_LENGTH_TYPE как тип uint8_t сохранит 3 байта на каждом сообщении при использовании 32-битного микроконтроллера. Подобным образом, если сохраняемые в буфер сообщения не могут быть больше 65535 байт, то если определить configMESSAGE_BUFFER_LENGTH_TYPE как uint16_t, то это будет экономить 2 байта на сообщение при использовании 32-битного микроконтроллера.
configSUPPORT_STATIC_ALLOCATION. Если параметр configSUPPORT_STATIC_ALLOCATION установлен в 1, то объекты RTOS могут быть созданы в ОЗУ (память RAM), предоставленном разработчиком (т. е. статически, в момент компиляции).
Если configSUPPORT_STATIC_ALLOCATION установлен в 0, то объекты RTOS могут быть созданы только из ОЗУ, выделенного для кучи FreeRTOS (т. е. динамически, во время работы программы).
Если параметр configSUPPORT_STATIC_ALLOCATION не определять, то по умолчанию он будет установлен в 0.
Если configSUPPORT_STATIC_ALLOCATION установлен в 1, то разработчик приложения должен также предоставить две callback-функции: vApplicationGetIdleTaskMemory() для предоставления памяти, используемой задачей ожидания (RTOS Idle task), и (если параметр configUSE_TIMERS установлен в 1) vApplicationGetTimerTaskMemory() для предоставления памяти задаче сервисного демона (RTOS Daemon/Timer Service task). Примеры приведены ниже.
/* configSUPPORT_STATIC_ALLOCATION установлен в 1, поэтому приложение должно
предоставить реализацию vApplicationGetIdleTaskMemory(), чтобы обеспечить
память для Idle task. */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
/* Если буферы, предоставленные для Idle task, были декларированы
внутри этой функции, то они должны быть с атрибутом static - иначе
они будут выделены из стека, и уничтожатся при выходе из этой функции. */
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];
/* Передача наружу указателя на структуру StaticTask_t, в которой
задача Idle будет сохранять свое состояние. */
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
/* Передача наружу размера массива, на который указывает *ppxIdleTaskStackBuffer.
Обратите внимание, что поскольку массив должен быть типа StackType_t, то
configMINIMAL_STACK_SIZE указывается в словах, а не в байтах. */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/* Оба параметра configSUPPORT_STATIC_ALLOCATION и configUSE_TIMERS установлены
в 1, поэтому приложение должно предоставить реализацию vApplicationGetTimerTaskMemory()
чтобы обеспечить памятью задачу Timer service. */
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize )
{
/* Если буферы, предоставленные для Timer task, декларированы внутри этой
функции, то они должны быть с атрибутом static – иначе буферы будут
уничтожены при выходе из этой функции. */
static StaticTask_t xTimerTaskTCB;
static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];
/* Передача наружу указателя на структуру StaticTask_t, в которой будет
сохраняться состояние задачи Timer service. */
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
/* Передача наружу массива, который будет использоваться как стек
задачи Timer service. */
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
/* Передача наружу размера массива, на который указывает *ppxTimerTaskStackBuffer.
Обратите внимание, что поскольку массив должен быть типа StackType_t, то
configTIMER_TASK_STACK_DEPTH указывается в словах, а не в байтах. */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
Для дополнительной информации см. [7].
configSUPPORT_DYNAMIC_ALLOCATION. Если параметр configSUPPORT_DYNAMIC_ALLOCATION установлен в 1, то объекты RTOS могут создаваться с использованием RAM, которые автоматически выделяются из кучи FreeRTOS.
Если configSUPPORT_DYNAMIC_ALLOCATION установлен в 0 то объекты RTOS могут создаваться только в RAM, предоставленном разработчиком (статически, в момент компиляции).
Если configSUPPORT_DYNAMIC_ALLOCATION не определен, то по умолчанию его значение 1.
Для дополнительной информации см. [7].
configTOTAL_HEAP_SIZE. Общее количество памяти RAM, доступной в куче FreeRTOS.
Это значение будет использоваться только если параметр configSUPPORT_DYNAMIC_ALLOCATION установлен в 1, и приложение использует одну из примеров схем выделения памяти, которые есть в загружаемом пакете исходного кода FreeRTOS. Для дополнительной информации см. [8].
configAPPLICATION_ALLOCATED_HEAP. По умолчанию куча FreeRTOS декларируется кодом FreeRTOS, и размещается в памяти линкером. Установка configAPPLICATION_ALLOCATED_HEAP в 1 позволяет вместо этого использовать кучу, декларированную разработчиком приложения, что дает возможность разместить кучу в любом месте памяти.
Если используется схема heap_1.c, heap_2.c или heap_4.c, и configAPPLICATION_ALLOCATED_HEAP установлен в 1, то разработчик должен предоставить массив uint8_t с точным именем и размером, как показано ниже. Этот массив будет использоваться как куча FreeRTOS. Как разместить массив в определенном месте памяти - зависит от используемого компилятора, см. его документацию.
uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
configGENERATE_RUN_TIME_STATS. Этот параметр определяет статистику времени выполнения, см. [9].
configUSE_CO_ROUTINES. Установите в 1 для подключения в сборку функционала сопрограмм, или 0, чтобы опустить этот функционал. Для поддержки сопрограмм в проект должен быть добавлен модуль croutine.c.
configMAX_CO_ROUTINE_PRIORITIES. Количество приоритетов, доступных для сопрограмм приложения. Один и тот же уровень приоритета может использовать любое количество сопрограмм. Приоритеты задач и сопрограмм обслуживаются по отдельности, см. параметр configMAX_PRIORITIES.
configUSE_TIMERS. Установите в 1 для подключения функционала программных таймеров, или 0, чтобы опустить этот функционал. Полное описание программных таймеров FreeRTOS см. в [10].
configTIMER_TASK_PRIORITY. Устанавливает приоритет задачи демона программных таймеров (software timer service/daemon task), см. [10].
configTIMER_QUEUE_LENGTH. Устанавливает длину очереди команд программного таймера, см. [10].
configTIMER_TASK_STACK_DEPTH. Устанавливает глубину стека, выделенного для задачи демона программных таймеров (software timer service/daemon task), см. [10].
configKERNEL_INTERRUPT_PRIORITY, configMAX_SYSCALL_INTERRUPT_PRIORITY и configMAX_API_CALL_INTERRUPT_PRIORITY. Порты FreeRTOS, которые содержат настройку configKERNEL_INTERRUPT_PRIORITY, включают ARM Cortex-M3, PIC24, dsPIC, PIC32, SuperH и RX600. Порты FreeRTOS, которые содержат настройку configMAX_SYSCALL_INTERRUPT_PRIORITY, включают PIC32, RX600, ARM Cortex-A и ARM Cortex-M.
Имейте в виду, что порты ARM Cortex-M3 и ARM Cortex-M4 снабжены специальными замечаниями по использованию, которые приведены в конце этой секции.
configMAX_API_CALL_INTERRUPT_PRIORITY это новое имя для параметра configMAX_SYSCALL_INTERRUPT_PRIORITY, используемое только в новых портах FreeRTOS. Оба этих имени эквивалентны.
Параметр configKERNEL_INTERRUPT_PRIORITY должен быть установлен на самый низкий приоритет.
Обратите внимание в последующем описании, что только API-функции, имена которых оканчиваются на "FromISR" могут быть вызваны из обработчика прерывания (interrupt service routine, ISR).
Для тех портов, которые реализуют только configKERNEL_INTERRUPT_PRIORITY: параметр configKERNEL_INTERRUPT_PRIORITY устанавливает приоритет прерывания, используемый самим ядром RTOS. Прерывания, которые вызывают API-функции FreRTOS, должны также выполняться с этим приоритетом (или более низким). Прерывания, которые не вызывают API-функции, могут выполняться с более высоким приоритетом, и по этой причине их выполнение не задерживается активностью ядра RTOS (в пределах ограничений самой аппаратуры).
Для портов, где реализованы оба параметра configKERNEL_INTERRUPT_PRIORITY и configMAX_SYSCALL_INTERRUPT_PRIORITY: параметр configKERNEL_INTERRUPT_PRIORITY также устанавливает приоритет прерывания, используемый самим ядром RTOS. Параметр configMAX_SYSCALL_INTERRUPT_PRIORITY устанавливает самый высокий приоритет прерывания, из которого можно безопасно вызывать API-функции FreeRTOS.
Полная модель вложенности прерываний достигается установкой значения configMAX_SYSCALL_INTERRUPT_PRIORITY больше (т. е. на более высокий уровень приоритета) чем configKERNEL_INTERRUPT_PRIORITY. Это означает, что ядро FreeRTOS не полностью запрещает прерывания, даже внутри критических секций. Кроме того, это достигается без недостатков архитектуры сегментированного ядра. Однако следует отметить, что некоторые архитектуры микроконтроллеров будут (аппаратно) запрещать прерывания, когда принято новое прерывание – как следствие прерывания будут неизбежно запрещены на короткий промежуток времени между приемом прерывания и моментом, когда код FreeRTOS снова разрешает прерывания.
Прерывания, которые не вызывают API-функции FreeRTOS, могут выполняться с приоритетами выше configMAX_SYSCALL_INTERRUPT_PRIORITY, и таким образом на скорость их выполнения не влияет выполнение кода ядра RTOS.
Например, представим себе гипотетический микроконтроллер, у которого 8 уровней приоритета прерываний – 0 самый низкий приоритет, и 7 самый высокий (см. специальное замечание для пользователей ARM Cortex-M3 в конце этой секции). Рисунок ниже дает пример конкретной конфигурации приоритетов этого микроконтроллера. На нем показано, что можно и что нельзя делать на каждом уровне приоритета, если константы конфигурации configMAX_SYSCALL_INTERRUPT_PRIORITY и configKERNEL_INTERRUPT_PRIORITY соответственно установлены в значения 4 и 0:
Эти два параметра конфигурации позволяют реализовать очень гибкую обработку прерываний:
• "Задачи" обработки прерывания (не ISR) могут быть написаны и им назначены приоритеты так же, как любой другой задаче (task) в системе. Эти задачи пробуждаются по прерыванию. ISR сам по себе должен быть написан как можно более коротким – он просто забирает данные, и затем будит задачу обработки с высоким приоритетом. Тогда ISR вернет управление сразу в разбуженную высокоприоритетную задачу – так что обработка прерывания получается непрерывной по времени, точно так же, как это было бы внутри самого ISR. Достоинство такого метода обработки в том, что все прерывания остаются разрешенными, когда выполняется задача обработки прерывания. • Порты FreeRTOS, которые реализуют configMAX_SYSCALL_INTERRUPT_PRIORITY, дополнительно разрешают использование полноценной модели вложенности, где прерывания между приоритетом прерывания ядра RTOS и configMAX_SYSCALL_INTERRUPT_PRIORITY, могут быть вложенными, и могут делать применимые вызовы API-функций FreeRTOS. Прерывания с приоритетом выше configMAX_SYSCALL_INTERRUPT_PRIORITY никогда не будут откладываться (т. е. не будут вытесняться) активностью ядра RTOS. • ISR, работающие с приоритетом выше максимального системного приоритета (выше configMAX_SYSCALL_INTERRUPT_PRIORITY) никогда не маскируются самим ядром, поэтому на их отзывчивость никак не влияет функционал ядра RTOS. Это идеально подходит для прерываний, которые требуют высокой точности обработки – например, прерывания для управления двигателем, или для оцифровки звука. Однако такие ISR не могут использовать API-функции FreeRTOS.
Чтобы реализовать эту схему, приложение должно придерживаться следующего правила: любое прерывание, которое использует FreeRTOS API, должно использовать такой же приоритет, что и ядро RTOS (который сконфигурирован макросом configKERNEL_INTERRUPT_PRIORITY), или с приоритетом configMAX_SYSCALL_INTERRUPT_PRIORITY или ниже его для портов, которые включают такой функционал.
Специальное замечание для пользователей платформ ARM Cortex-M3 и ARM Cortex-M4: внимательно ознакомьтесь со статьей [11], где объясняются особенности использования приоритетов микроконтроллеров платформы ARM Cortex-M. Как минимум помните о том, что ядра ARM Cortex-M3 обозначают малыми числовыми уровнями приоритета прерывания с высоким логическим приоритетом, что выглядит не очевидно, и о чем легко забыть! Если Вы хотите назначить низкий приоритет прерыванию, то не назначайте ему числовой уровень 0 (или любое другое низкое числовое значение) потому что в результате это приведет к тому, что прерывание получит самый высокий приоритет в системе – и таким образом оно может привести к краху системы, если его приоритет выше configMAX_SYSCALL_INTERRUPT_PRIORITY.
Фактически самый низкий приоритет у ARM Cortex-M3 будет 255 – однако разные производители ARM Cortex-M3 реализуют это по-разному, кодируя приоритет разным количеством бит, и предоставляя библиотечные функции, которые ожидают указание приоритета различными способами. Например, для STM32 можно указать самый низкий приоритет 15 для драйвера ST в библиотечном вызове функции, и для самого высокого приоритета можете указать 0.
configASSERT. Семантика макроса configASSERT() такая же, как и у стандартного макроса assert() языка C. Утверждение (assertion) срабатывает, если переданный в макрос configASSERT(), равен 0.
Макрос configASSERT() вызывается во многих местах модулей исходного кода FreeRTOS, чтобы проверить, как приложение использует FreeRTOS. Настоятельно рекомендуется вести разработку приложений FreeRTOS, когда configASSERT() определен.
В примерах, показанных ниже, вызывается vAssertCalled(), куда передается имя файла и номер строки, где сработал вызов configASSERT() (здесь __FILE__ и __LINE__ это стандартные макросы, предоставляемые большинством компиляторов).
/* Определение configASSERT() для вызова функции vAssertCalled(), если утверждение
в макросе неправильное (равно 0). Утверждение сработает, если параметр x, переданный
в макрос, равен 0. */
#define configASSERT((x)) if( (x) == 0 ) vAssertCalled( __FILE__, __LINE__ )
Этот пример приведен просто для демонстрации, поскольку vAssertCalled() это не функция FreeRTOS, configASSERT() можно определить так, как того пожелает разработчик приложения, чтобы в нем выполнялись необходимые действия (например, бесконечный цикл, останавливающий работу программы).
Если FreeRTOS работает под управлением отладчика, то configASSERT() можно определить для запрета прерываний и входа в бесконечный цикл, как показано ниже. Это даст эффект остановки кода на строке, где сработала проверка макроса configASSERT() – перевод отладчика на паузу выполнения сразу покажет Вам строку, где есть проблема.
/* Определение configASSERT() для запрета прерываний и бесконечного зацикливания. */
#define configASSERT((x)) if( (x) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS. Параметр configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS используется только системой MPU FreeRTOS.
Если параметр configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS установлен в 1, то разработчик приложения должен предоставить заголовочный файл с именем application_defined_privileged_functions.h, в котором программисту можно реализовать функции, которые нужно запускать в привилегированном режиме. Обратите внимание, что несмотря на расширение *.h этого файла, в нем должны содержаться именно реализации C-функций, не просто прототипы функций.
Функции, реализованные в application_defined_privileged_functions.h, должны менять и восстанавливать привилегированное состояние процессора, используя функцию prvRaisePrivilege() и макрос portRESET_PRIVILEGE() соответственно. Например, если библиотека предоставляет функцию печати, которая обращается к RAM, которая находится вне области управления разработчика приложения (защищенная память), и поэтому не может быть выделена для задачи, работающей в режиме пользователя (user mode), то функция печать может быть инкапсулирована в привилегированную функцию с помощью следующего кода:
void MPU_debug_printf( const char *pcMessage )
{
/* Состояние уровня привилегии процессора, когда была вызвана эта функция. */
BaseType_t xRunningPrivileged = prvRaisePrivilege();
/* Вызов библиотечной функции, которая может теперь получить доступ
ко всему RAM. */
debug_printf( pcMessage );
/* Сброс уровня привилегии процессора к первоначальному уровню. */
portRESET_PRIVILEGE( xRunningPrivileged );
}
Эта техника должна использоваться только во время разработки, но не для готовых изделий, где должна быть реализована защита памяти (в этом примере отладочной функции печати реализован обход защиты памяти).
[Параметры "INCLUDE"]
Макросы, имена которых начинаются на INCLUDE, позволяют исключить из сборки те компоненты ядра реального времени, которые не используются приложением. Это гарантирует, что RTOS не будет использовать ROM или RAM больше, чем это необходимо для определенного встраиваемого приложения. Каждый такой макрос имеет форму:
INCLUDE_FunctionName
Здесь FunctionName показывает API-функцию (или набор функций), которую можно опционально исключить. Чтобы подключить API-функцию, установите значение макроса в 1, для исключения функции установите макрос в 0. Например, для того, чтобы API функция vTaskDelete() была включена, используйте:
#define INCLUDE_vTaskDelete 1
Чтобы исключить vTaskDelete() из сборки, используйте:
#define INCLUDE_vTaskDelete 0
[Ссылки]
1. Customisation FreeRTOSConfig.h site:freertos.org. 2. STM32: аббревиатуры и термины. 3. FreeRTOS: практическое применение, дополнения, словарик. 4. FreeRTOS: использование стека и проверка стека на переполнение. 5. Newlib C library for embedded systems site:sourceware.org. 6. Thread Local Storage Pointers site:freertos.org. 7. Static Vs Dynamic Memory Allocation site:freertos.org. 8. Memory Management site:freertos.org. 9. FreeRTOS: как получить информацию о загрузке процессора. 10. Software Timers site:freertos.org. 11. Приоритеты прерываний Cortex-M и приоритеты FreeRTOS. 12. FreeRTOS: управление памятью. |