STM Урок 112. FreeRTOS. Таймеры



С некоторых пор в операционной системе FreeRTOS появилась возможность использовать программные таймеры. Данная возможность позволяет нам не использовать аппаратные таймеры, если нам, конечно, не нужно отслеживать какие-то очень малые периоды, также периоды, не кратные времени системного кванта. В остальном программные таймеры очень удобны, так как для них возможно использовать отдельную функцию с настраиваемым стеком и приоритетом, а также управляет данными таймерами планировщик, поэтому они лучше интегрируются в операционную систему. Также есть ещё определённые прелести, которых мы возможно не коснёмся. Но обойти данную возможность мы не можем. Поэтому и родился в мыслях такой урок.

Программный таймер мы создаём заранее, то есть до того, как мы будем его использовать.

Таймер может находиться только в двух состояниях — либо в активном, либо в пассивном. В пассивном состоянии таймер не считает период, а в активном считает. Чтобы перевести таймер в активное состояние, нужно вызвать функцию его запуска. Также при создании таймера мы определяем, будет ли таймер автоматически вновь запускаться после отсчёта периода (auto-reload) или нужно это делать каждый раз (one-shot).

Давайте посмотрим вот такую картину из официального документа

 

 

Мы можем здесь наблюдать работу двух таймеров разного типа. Оба таймера стартуют в момент t1, таймер 1 досчитывает свой период в момент t7 и больше не запускается. А таймер второй дорабатывает до момента времени t6 и затем стартует заново и таким же образом стартует автоматически в моменты времени t11 и t17. В эти три момента времени мы попадаем в функцию-обработчик данного таймера и смело можем обрабатывать данное событие.

Теперь поподробней о состоянии таймеров. Рассмотрим для начала таймер с автостартом

 

 

Здесь мы оперируем официальными функциями FreeRTOS (не функциями STM). Поначалу таймер создаётся с помощью функции xTimerCreate() и переходит в пассивное состояние. По крайней мере так мы видим по диаграмме. На самом деле, мне кажется, реально таймер будет создан и перейдёт в такое состояние в момент запуска планировщика. Также у нас создаётся структура для таймера, выделяется память. Затем если мы запускаем одну из функций, написанных в диаграмме, например xTimerStart(), то таймер перейдёт в состояние активное и начнёт отсчёт квантов времени. Как только таймер отсчитает необходимое количество тиков, он не перейдёт в пассивное состояние, а останется в активном. В данный момент вызовется функция-обработчик и таймер заново начнёт отсчёт тиков. Перевести таймер данного типа из активного состояния в пассивное мы сможем лишь с помощью вызова функции xTimerStop().

 

Теперь рассмотрим обычный таймер (нециклический, без автостарта)

 

 

Здесь всё происходит аналогично за исключением того, что по завершении счёта и вызова функции-обработчика таймер перейдёт в неактивное состояние и больше сам по себе не запустится, запускать его придётся принудительно с помощью одной из трёх функций. Также мы можем остановить таймер, а тем самым и перевести его в неактивное состояние досрочно с помощью функции xTimerStop().

 

 

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

Проект мы создадим из проекта урока 111 TASKS_QUEUES и назовём его TIMERS.

Запустим проект в Cube MX. В этот раз нужно будет кое-что добавить.

Зайдём в Configuration и выберем настройку FreeRTOS. Затем на самой первой закладке «Config parameters» разрешим использование таймеров

 

 

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

Применим настройки, сгенерируем проект и откроем его в System Workbench. В свойствах проекта уберём отладочные настройки и включим уровень оптимизации 1.

Закомментируем строки настройки DMA2D в файле main.c, неизвестные компилятору. И попробуем собрать проект.

 

 

Если всё нормально собралось, то сначала добавим глобальную переменную идентификатора таймеров. Мы создадим два таймера. Оба будут циклические

 

osMailQId strout_Queue;

osTimerId osProgTimer1, osProgTimer2;

 

Создадим прототипы функций задач-обработчиков таймеров

 

void TaskStringOut(void const * argument);

static void osProgTimer1Callback(void const *argument);

static void osProgTimer2Callback(void const *argument);

 

Создадим также сами функции задач-обработчиков таймеров

 

/* USER CODE BEGIN 4 */

//---------------------------------------------------------------

static void osProgTimer1Callback(void const *argument)

{

  (void) argument;

  static uint32_t tim_cnt=0;

  sprintf(str1,"%lu", tim_cnt);

  TFT_DisplayString(120, 200, (uint8_t *)str1, LEFT_MODE);

  tim_cnt++;

}

//---------------------------------------------------------------

static void osProgTimer2Callback(void const *argument)

{

  (void) argument;

  static uint32_t tim_cnt=0;

  sprintf(str1,"%lu", tim_cnt);

  TFT_DisplayString(320, 200, (uint8_t *)str1, LEFT_MODE);

  tim_cnt++;

}

//---------------------------------------------------------------

void TaskStringOut(void const * argument)

 

Данные функции почти одинаковые за исключением лишь горизонтальной координаты места вывода строки со значением тиков таймера.

Сначала мы создадим статическую локальную переменную, чтобы её значение сохранялось после того, как мы покинем тело нашей функции, тем самым заодно проверим и работу стека, а затем выведем значение нашей переменной в соответствующее место экрана дисплея. Затем мы значение переменной инкрементируем.

Создадим наши таймеры в функции main(). Для этого есть там определённое место

 

/* USER CODE BEGIN RTOS_TIMERS */

/* start timers, add new ones, ... */

osTimerDef(ProgTimer1, osProgTimer1Callback);

osProgTimer1 = osTimerCreate(osTimer(ProgTimer1), osTimerPeriodic, NULL);

osTimerDef(ProgTimer2, osProgTimer2Callback);

osProgTimer2 = osTimerCreate(osTimer(ProgTimer2), osTimerPeriodic, NULL);

/* USER CODE END RTOS_TIMERS */

 

Процедура создания таймера аналогична процедуре создания задачи. Только во втором параметре у нас стоит тип таймера. Функция обработчика таймера аналогична функции любой задачи, так как тоже предусматривает возможность использования параметров.

Осталось нам всего лишь запустить таймеры. Сделаем это в задаче по умолчанию

 

void StartDefaultTask(void const * argument)

{

  /* USER CODE BEGIN 5 */

  uint32_t syscnt;

  osTimerStart(osProgTimer1, 200);

  osTimerStart(osProgTimer2, 300);

 

Первым параметром в функции запуска таймера является его идентификатор, а вторым — количество милисекунд. Это очень удобно, что величину периода таймера мы задаём именно при его старте а не создании, что позволит нам более широко использовать наши таймеры и на ходу менять период.

Соберём код, прошьём контроллер и посмотрим результат работы кода

 

 

Как мы видим, всё у нас прекрасно работает. Мало того, что наши таймеры отсчитывают корректно периоды времени (первый таймер в 1,5 раза считает быстрее), плюс то. что это практически никак не повлияло на работу нашего основного кода работы с параметрами, задачами, очередями и всем прочим.

Таким образом, мы сегодня изучили ещё одну тему по работе с операционной системой FreeRTOS — использование программных таймеров.

Всем спасибо за внимание!

 

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату можно приобрести здесь 32F746G-DISCOVERY

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM FreeRTOS. Таймеры

2 комментария на “STM Урок 112. FreeRTOS. Таймеры
  1. Владимир:

    Скажите пожалуйста, при выполнении программы в функции-обработчика таймера будет переключение на выполнение программы основной задачи? Т.е. когда выполняется код в таймере я могу резко остановить его работу функцией xTimerStop() в простой задаче?

  2. artem:

    CMSIS-RTOS для которой описаны функции и FreeRTOS это как бы разные RTOS

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*