Работа с задачами

Количество задач, которые можно создать в FreeRTOS, ограничено только возможностями железа. Для управления задачами система предоставляет ряд функций: создание задачи vTaskCreate() (osThreadCreate()); уничтожение vTaskDelete() (osThreadTerminate()); управление приоритетом uxTaskPriorityGet() и vTaskPrioritySet(); и управляющие vTaskDelay(), vTaskDelayUntil(), vTaskSuspend(), vTaskResume(), vTaskResumeFromISR().

Сами задачи определяются как обычные Си-функции, они ничего не возвращают и принимают в качестве аргумента указатель на тип void *. Одна функция (тело задачи) — не равно одна задача. Программист вправе создать несколько экземпляров (англ. instance) одной и той же задачи. Из стандарта языка следует, что переменные, созданные в теле функции, для каждого экземпляра будут отдельными, если только не используется модификатор static.

В общем случае задача должна иметь бесконечный цикл (во FreeRTOS принято использовать for вместо while), в котором описывается логика. При помощи функции vTaskDelay() (osDelay()) можно отдать управление операционной системе на ожидаемое время задержки, чтобы та могла запустить другую задачу. Для указания абсолютной задержки следует использовать функцию vTaskDelayUntil() (osDelayUntil()). Если задача по какой-то причине выходит из бесконечного цикла, то ее следует удалить через вызов vTaskDelete(NULL) (osThreadTerminate()). При передаче NULL в качестве аргумента функция сама определит идентификатор задачи.

void task_01( void *pvParameters ) {
    static uint32_t a = 0; // sharable variable
    uint32_t a = 0;        // unique variable
    for( ;; )
    {
        if (smpr_flag)
            break;
        // code here
        vTaskDelay(10);
    }
    
    vTaskDelete( NULL ); 
}

Функция xTaskCreate() позволяет создать задачу и имеет следующие аргументы:

  • pvTaskCode — указатель на функцию с кодом задачи;
  • pcName — имя, заданное программистом, оно нужно только для отладки;
  • usStackDepth — глубина стека в машинных словах (т.е. кратна 4 байтам для stm32);
  • pvParameters — указатель на аргументы задачи;
  • uxPriority — приоритет задачи от 0 до MAX_PRIORITIES;
  • pxCreatedTask — указатель на идентификатор, позволяющий обрабатывать задачу (может быть задан как NULL).
portBASE_TYPE xTaskCreate(
    pdTASK_CODE pvTaskCode,
    const signed portCHAR * const pcName,
    unsigned portSHORT usStackDepth,
    void *pvParameters,
    unsigned portBASE_TYPE uxPriority,
    xTaskHandle *pxCreatedTask
);

Функция вернет pdTRUE в случае, если в куче достаточно места, и pdFALSE, если места нет. Минимальный размер стека определяется через макрос configMINIMAL_STACK_SIZE в файле конфигурации. Вообще говоря, определение размера стека — нетривиальная задача, и чаще устанавливается оценочное значение, нежели точно рассчитанное.

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

typedef struct {
    uint8_t x;
    uint8_t y;
    uint8_t z;
} agr_t;

static arg_t task01_args = { .x = 0, .y = 1, .z = 2 };
// ...
void task_01(void const * argument) {
    volatile arg_t *arg = (arg_t*) argument;
    argument->x = 3;
    // ...

Уничтожить задачу можно, вызвав функцию xTaskDelete(), принимающую в качестве аргумента только заданный при создании идентификатор pxCreatedTask.

void vTaskDelete( xTaskHandle pxTask );

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


Изменено: