
Здравствуйте. В короткой серии постов я постараюсь описать возможности, и
подходы работы с одной из наиболее популярной и развивающейся РТОС для
микроконтроллеров – FreeRTOS. Я предпологаю базовое знакомство читателя с
теорией многозадачности, о которой можно почитать в одном из соседних
постов на Хабре или ещё где-то.
Ссылки на остальные части:
FreeRTOS: межпроцессное взаимодействие.
FreeRTOS: мьютексы и критические секции.
Зачем все это? Или введение в многозадачные системы, от создателей FreeRTOS.
Традиционно существует 2 версии многозадачности:
- «Мягкого» реального времени(soft real time)
- «Жесткого» реального времени(hard real time)
К ОСРВ мягкого типа можно отнести наши с Вами компьютеры т.е.
пользователь должен видеть, что, например, нажав кнопку с символом, он
видит введенный символ, а если же он нажал кнопку, и спустя время не
увидел реакции, то ОС будет считать задачу «не отвечающей»( по аналогии с
Windows — «Программа не отвечает»), но ОС остается пригодной для
использования. Таким образом, ОСРВ мягкого времени просто определяет
предполагаемое время ответа, и если оно истекло, то ОС относит таск к не
отвечающим.
К ОСРВ жесткого типа, как раз относят ОСРВ во встраиваемых устройствах. В
чем-то они похоже на ОСРВ на дестопах(многопоточное выполнение на одном
процессоре), но и имеют главное отличие — каждая задача
должна выполняться за отведенный квант времени, не выполнение данного условия ведет к краху все системы.
А все таки зачем?
Если у Вас есть устройство с нетривиальной логикой синхронизации обмена
данными между набором сенсоров, если Вам действительно нужно
гарантировать время отклика, и наконец-то если Вы думаете, что система
может разростись, но не знаете насколько, то РТОС Ваш выбор.
Не стоит применять РТОС, для применения РТОС т.е. не нужно применять
РТОС в слишком тривиальных задачах(получить данные с 1 сенсора, и
отправить дальше, обработать нажатие 1 кнопки и т.д) т.к. это приведет к
ненужной избыточности, как полученного кода, так и решения самой
задачи.
Работа с тасками(или задачами, процессами).
Для начала приведу несколько определений, для того чтобы внести ясность в дальнейшие рассуждения:
"Операционные системы реального времени
(ОСРВ(RTOS)) предназначены для обеспечения интерфейса к ресурсам
критических по времени систем реального времени. Основной задачей в
таких системах является своевременность (timeliness) выполнения
обработки данных".
"FreeRTOS — многозадачная операционная система
реального времени (ОСРВ) для встраиваемых систем. Портирована на
несколько микропроцессорных архитектур.
От хабраюзера andrewsh,
по поводу лицензии: разрешено не публиковать текст приложения, которое
использует FreeRTOS, несмотря на то, что OS линкуется с ним. Исходники
самой же RTOS должны всегда прикладываться, изменения, внесённые в неё —
тоже.".
FreeRTOS написана на Си с небольшим количеством ассемблерного
кода(логика переключения контекста) и ее ядро представлено всего 3-мя C
файлами. Более подробно о поддерживаемых платформах можно почитать на
официальном сайте.
Перейдем к делу.
Любой таск представляет собой Си функцию со следующим прототипом:
void vTask( void *pvParametres );
Каждый таск – это по сути мини подпрограмма, которая имеет свою точку
входу, и исполняется внутри бесконечного цикла и обычно не должна
выходить из него, а также имеет собственный стэк. Одно определение таска
может использоваться для создания нескольких тасков, которые будут
выполняться независимо и также иметь собственный стэк.
Тело таска не должно содержать явных
return;
конструкций, и в случае если таск больше не нужен, его можно удалить с
помощью вызова API функции. Следующий листинг, демонстрирует типичный
скелет таска:
void vTask( void *pvParametres) {
int someVar;
for( ;; ) {
}
vTaskDelete( NULL );
}
Для создания таска, и добавления ее в планировщик используется специальная API функция со следующим прототипом:
portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,
const signed portCHAR * const pcName,
unsigned portSHORT usStackDepth,
void *pvParameters,
unsigned portBASE_TYPE uxPriority,
xTaskHandle *pxCreatedTask
);
pvTaskCode – так как таск – это просто Си функция, то первым параметром идет ее значение.
pcName – имя таска. По сути это нигде не используется, и полезно только при отладке с соответствующими плагинами для IDE.
usStackDepth – так как каждый таск – это мини
подпрограмма, со своим стэком, то данный параметр отвечает за его
глубину. При скачивании RTOS и разворачивания системы для своей
платформы, вы получаете файл FreeRTOSConfig.h настройкой которого можно
конфигурировать поведение самой ОС. В данном файле также объявлена
константная величина
configMINIMAL_STACK_SIZE, которую и стоит передавать в качестве usStackDepth с соответствующим множителем, если это необходимо.
pvParameters – при создании, каждый таск может
принимать некоторые параметры, значения, или ещё что-то что может
понадобиться внутри тела самого таска. С точки зрения инкапсуляции, этот
подход наиболее безопасный, и в качестве pvParameters стоит передавать,
например, некоторую структуру, или NULL, если ничего передавать не
нужно.
uxPriority – каждый таск имеет свой собственный приоритет, от 0(min) до (
configMAX_PRIORITIES –
1). Так как, по сути, нет верхнего предела для данного значения, то
рекомендуется использовать как можно меньше значений, чтобы не было
дополнительно расхода RAM на данную логику.
pxCreatedTask — хэндл созданного таска. При
создании таска, опционально можно передать указатель на хэндл будующего
таска, для последующего управления работой самого таска. Например, для
удаления определенного таска.
Данная функция возвращает
pdTRUE, в случае успешного создания таска, или
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,
в случае если размер стэка был указан слишком большим, т.е.
недостаточно размера хипа для хранения стэка таска, и самого таска.
На следующем листинге я привел, короткий пример законченной программы,
которая создает 2 таска, каждый из которых мигает светодиодом:
void vGreenBlinkTask( void *pvParametrs ) {
for( ;; ) {
P8OUT ^= BIT7;
vTaskDelay( 700 );
}
}
void vRedBlinkTask( void *pvParametrs ) {
for( ;; ) {
P8OUT ^= BIT6;
vTaskDelay( 1000 );
}
}
void main(void) {
vInitSystem();
xTaskCreate( &vGreenBlinkTask,
(signed char *)"GreenBlink",
configMINIMAL_STACK_SIZE,
NULL,
1,
NULL );
xTaskCreate( &vRedBlinkTask,
(signed char *)"RedBlink",
configMINIMAL_STACK_SIZE,
NULL,
1,
NULL );
vTaskStartScheduler();
for( ;; ) { }
}
В следующем посте я планирую написать о взаимодействии между тасками, и работе с прерываниями.
комментарии (30)