Сопрограммы

Помимо задач, FreeRTOS позволяет создавать так называемые сопрограммы (англ. co-routines, файл croutine.c). Концептуально они похожи на задачи, но имеют значительные отличия, например: для всех сопрограмм используется единый стек, что значительно снижает требования к ОЗУ; сопрограммы работают в парадигме кооперативного режима, но могут быть запущены в вытесняющей среде.

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

  • Запущена (англ. running) — сопрограмма в данный момент выполняется и использует системные ресурсы.
  • Готова к выполнению (англ. ready) — сопрограмма не выполняется и ожидает своей очереди: это происходит, когда есть хотя бы одна активная задача или есть другая сопрограмма с большим или эквивалентным приоритетом в режиме выполнения.
  • Заблокирована (англ. blocked) — задача находится в ожидании наступления какого-то определенного события, например временного.

Любая сопрограмма, как и задача, имеет определенный приоритет, задаваемый при создании, который находится в диапазоне от 0 до configMAX_CO_ROUTINE_PRIORITIES - 1 (определен в FreeRTOSConfig.h). Чем меньше число, тем ниже приоритет. Приоритет задачи и приоритет сопрограммы — это разные сущности, любая задача имеет больший приоритет, чем любая сопрограмма.

Типичная сопрограмма имеет следующий вид:

void vACoRoutineFunction( CoRoutineHandle_t xHandle, BaseType_t uxIndex ) {
    static uint32_t i = 0;
    crSTART( xHandle );
    i = 1;
    for( ;; ) {
        for (; i < 10; i++) {
            LED_TOGGLE();
            crDELAY( xHandle, (i * 1000) / portTICK_RATE_MS );
        }
    }
    crEND();
}

Все сопрограммы должны быть обернуты в макросы crSTART() и crEND().

// croutine.h
#define crSTART( pxCRCB ) switch( ( ( CRCB_t * )( pxCRCB ) )->uxState ) { case 0:
// ...
#define crEND() }

Правила разработки FreeRTOS запрещают создавать дополнительные switch / case операторы, в которых вызывается функция блокировки (задержки). Также не допускается вызов таких функций внутри вызванных функций.

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

Сопрограммы, как и задачи, управляются планировщиком (отдельным) и должны быть в него добавлены через вызов функции xCoRoutineCreate(). Планировщик — это обычная функция (vCoRoutineSchedule()), которая определяет, какая сопрограмма должна выполняться, но он не работает сам по себе, его нужно периодически запускать.

Как уже говорилось ранее, любая задача имеет более высокий приоритет, чем любая сопрограмма, поэтому пытаться запускать планировщик в любой задаче, кроме idle, — плохая идея. Данная задача создается автоматически и служит для утилитарных целей операционной системы, например для освобождения памяти. Для того, чтобы прицепить вызов планировщика к idle, используется «крюк» / «перехватчик» (англ. hook), т.е. функция, которая вызывается при каждом цикле этой задачи. Чтобы разрешить выполнение перехватчика задачи, необходимо установить 1 в макросе configUSE_IDLE_HOOK конфигурационного файла.

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

void vApplicationIdleHook( void ) {
    vCoRoutineSchedule( void );
}

Изменено: