Pull to refresh

FreeRTOS: введение

Reading time 5 min
Views 198K

Здравствуйте. В короткой серии постов я постараюсь описать возможности, и подходы работы с одной из наиболее популярной и развивающейся РТОС для микроконтроллеров – 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) {
    /* Данный фрагмент кода будет вызван один раз, перед запуском таска.
       Каждый созданный таск будет иметь свою копию someVar.
       Кроме объявления переменных, сюда можно поместить некоторый инициализационный код.    
    */
    int someVar;

    // Так как каждый таск - это по сути бесконечный цикл, то именно здесь начинается тело таска.
    for( ;; ) {
        // Тело таска
    }
    
    // Так как при нормальном поведении мы не должны выходить из тела таска, то в случае если это все таки произошло, мы удаляем таск.
    // Функция vTaskDelete принимает в качестве аргумента хэндл таска, который стоит удалить.
    // Вызов внутри тела таска с параметром NULL,удаляет текущий таск
    
    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;
		
		// Выполнить задержку в 700 FreeRTOS тиков. Величина одного тика задана в FreeRTOSConfig.h и как правило составляет 1мс.
		vTaskDelay( 700 );
	}
}

void vRedBlinkTask( void *pvParametrs ) {
	for( ;; ) {
		P8OUT ^= BIT6;
		
		// Выполнить задержку в 1000 FreeRTOS тиков. Величина одного тика задана в FreeRTOSConfig.h и как правило составляет 1мс.
		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( ;; ) { }
}


В следующем посте я планирую написать о взаимодействии между тасками, и работе с прерываниями.
Tags:
Hubs:
+52
Comments 36
Comments Comments 36

Articles