:::: MENU ::::

Отправление задачи в режим ожидания (сон) на определенное время

Данная серия статей посвящена описанию работы RTOS – написанной на основе TNKernel. Исходники с дополнением описанным в данной статье можно скачать по ссылке.

Если вы хотите написать свою RTOS или разобраться в TNKernel читайте так-же:

Отправление задачи в режим ожидания (сон) на определенное время

В любой RTOS должен быть механизм отключения задачи на определенное время. Например это необходимо, если нам надо каждые 100 мс измерять на канале АЦП. Каждые 100 мс задача выходит из сна выполняется и засыпает обратно. Во время сна задача не участвует в планирование.

Для этого в нашей RTOS введем еще одну очередь — очередь «по таймеру«. В нее будут добавляться задачи которые ушли в сон.

В самом начале при инициализации RTOS, после сброса очереди приоритетов надо добавить сброс очереди по таймеру.

void start_myrtos(void)
{
    uint8_t i;

    // сброс очереди приоритетов
    for(i = 0; i < NUM_PRIORITY; i++)
    {
        reset_queue_priority(i);
    }
    // сброс очереди по таймеру 
    reset_queue_timeout();
    ...
    ...

Сама очередь по таймеру представляет собой связанный список первый и последний элементы которого указывают сами на себя, поэтому сброс очереди — указать элементу на себя.

// сброс стартовой точки списка по timeout - указывает сам на себя
void reset_queue_timeout(void)
{
    task_queue_timeout.next = &task_queue_timeout;
    task_queue_timeout.prev = &task_queue_timeout;
}

В структуре описывающей задачу есть поле wakeup_count. Эта переменная (wakeup_count) показывает сколько еще времени задача будет находится в спящем режиме. В момент засыпания задачи в ней будет находится число единиц времени планирования (время вызова планировщика timer_task_func). При вызове планировщика данная переменная будет декрементироваться (уменьшается на 1).

Также в момент оправления в засыпание задачи (функция task_sleep(timeout)) — задача удаляется из очереди планирования (по которой происходит основное планирование задач) и добавляется в очередь «по таймеру«.

// задача засыпает на timeout циклов RTOS
void task_sleep(uint32_t timeout)
{
    uint8_t priority;

    arm_disable_interrupts();

    queue_remove_task(&(curr_run_task->task_queue));
    queue_timeout_add(curr_run_task);
    curr_run_task->wakeup_count = timeout;

    priority = queue_find_priority_next_task();
    next_task_to_run = task_param_from_task_queue(task_queue_priority[priority].next);

    switch_context();       
    arm_enable_interrupts();
}

В очередной раз повторю, что поскольку данная функция (task_sleep) является функцией самой RTOS, то ее не должны прерывать прерывания — поэтому в начале ее прерывания отключаются, а в конце включаются.queue_remove_task — удаляет элемент данной задачи из связанного списка — удаление элемента из очереди (см.Чередование задач при помощи отправки задачи в конец очереди для данного приоритета).

Функция queue_timeout_add — добавляет задачу в очередь «по таймеру«. Очередь представляет собой связанный список, в котором первый и последний элементы указывают сами на себя — поэтому при поиске последнего элемента ищется элемент указывающей сам на себя.

// добавление задачи в конец очереди по timeout
void queue_timeout_add(task_param_init* task)
{
    link_init *temp_link;

    // ищем последний элемент в очереди
    temp_link = &task_queue_timeout;
    while((*temp_link).next != temp_link)
    {
        temp_link = temp_link->next;
    }   

    // добавляем в элемент данной задачи в конец очереди
    temp_link->next = &(task->task_queue);
    task->task_queue.prev = temp_link;
    task->task_queue.next = &task->task_queue; 
}

После добавления задачи в очередь «по таймеру» в поле wakeup_count структуры описывающей задачу записывается на сколько засыпает задача. Далее происходит поиск самого высокого приоритета в очереди на планирование и поиск первой задачи в очереди по данному приоритету (см. Работа планировщика RTOS). После контекст задачи переключается.

Каждый раз когда вызывается планировщик timer_task_func — программа проходится по этой очереди «по таймеру» и для каждой задачи в ней декрементирует значение переменной wakeup_count. wakeup_count — переменная указывающая на какое время задача уснула. Каждая единица этой переменной — единица времени планирования. Если переменная  wakeup_count равна нулю, то удаляем задачу из очереди по таймеру и добавляем ее в основную очередь планирования — то есть будим задачу.

Таким образом планировщик timer_task_func будет выглядеть следующим образом:

// планировщик
void timer_task_func(void *par)
{
    uint8_t priority;
    task_param_init *task_temp;
    link_init *link_temp;

    while(1)
    {
        arm_disable_interrupts();

        // считаем время до просыпания каждой задачи - если надо будим задачу
        // конец списка timeout - next указывает сам на себя
        link_temp = &(task_queue_timeout);
        while(link_temp->next != link_temp)
        {
            task_temp = task_param_from_task_queue(link_temp->next);
            link_temp = link_temp->next;

            task_temp->wakeup_count--;
            if(task_temp->wakeup_count == 0)
            {
                queue_remove_task(&(task_temp->task_queue));
                queue_task_add(task_temp);
            }
        }

        // приоритет для сл. готовой задачи
        priority = queue_find_priority_next_task();
        // выбираем сл. задачу из списка задач по приоритетам
        next_task_to_run = task_param_from_task_queue(task_queue_priority[priority].next);

        switch_context();
        arm_enable_interrupts();
    }
}

Для тестирования создадим две такие задачи:

void task_1_func(void *par)
{
    extern uint8_t g_flag;

    while(1)
    {
        GPIO_SetBits(GPIOB, GPIO_Pin_6);
        GPIO_ResetBits(GPIOB, GPIO_Pin_6);

        task_move_tail_queue_priority();
        //task_sleep(200);
    }
}

void task_2_func(void *par)
{
    extern uint8_t g_flag;

    while(1)
    {
        GPIO_SetBits(GPIOB, GPIO_Pin_7);
        GPIO_ResetBits(GPIOB, GPIO_Pin_7);

        //task_move_tail_queue_priority();
        task_sleep(2000);
    }   
}

Первая будет будет выполняться постоянно, вторая раз в 2 секунды.