Урок 6. Таймеры общего назначения. Работа с ними через API-функции. Организация временных задержек, циклов в системе ESP32.

Уроки ESP32

В уроке научимся работать с таймерами ESP32 без использования прерываний. Рассмотрим способы формирования временных задержек и создания временных циклов.

Предыдущий урок     Список уроков     Следующий урок

Первый практический урок был посвящен работе с портами ввода/вывода в минимальном варианте. Порты самый простой способ вывода информации с микроконтроллера. Даже самая примитивная программа должна хотя бы управлять светодиодом или считывать состояние кнопки.

 

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

В этом уроке прерывания таймеров мы не используем.

 

Аппаратные таймеры общего назначения.

Сначала о том, что собой представляют таймеры ESP32.

В системе ESP32 существует 4 таймера общего назначения.

Они разделены на две группы (модуля). В каждой группе по 2 таймера. При работе с ними через API-функции мы будем использовать константы-имена:

  • TIMER_GROUP_0 и TIMER_GROUP_1 для групп;
  • TIMER_0 и TIMER_1 для таймеров.

Давайте в дальнейшем придерживаться этих обозначений.

Таймеры ESP32

Структура таймеров проста, типична для подобных устройств и похожа на схему таймеров STM32 в режиме счетчиков.

Структурная схема таймеров ESP32

По этой схеме без дополнительных объяснений можно понять, как работают таймеры ESP32.

Я выделю главные моменты их функционирования.

  • В стандартной конфигурации таймеры тактируются частотой 80 мГц.
  • На входе каждого счетчика установлен 16ти разрядный предделитель, который снижает частоту тактирования  от 2 до 65536 раз. Коэффициент деления устанавливается программно.
    • При значении предделителя равном 1 или 2  входная частота делится на 2.
    • При значении 0, коэффициент деления равен 65536.
    • Остальные значения предделителя соответствуют коэффициенту деления входной частоты.
  • Основной счетчик 64х разрядный. Можно не заботиться о его переполнении. Например, при частоте тактирования 1 мГц (период 1 мкс) счетчик переполниться через почти 600000 лет.
  • Он может считать в прямом или обратном направлении, быть остановленным.
  • Его значение может быть считано и установлено программно.
  • Счетчик может быть загружен значением из регистра перезагрузки по аппаратному событию перезагрузки. В терминологии ESP32 это называется событием тревоги. Оно происходит при совпадении значения счетчика со значением, заданным программно в регистре тревоги.
  • Событие перезагрузки вырабатывается не только при строгом равенстве значений счетчика и регистра тревоги, а также и при превышении значением счетчика заданного порога для прямого счета  и при уменьшении значения счетчика ниже порога для обратного счета. Это позволяет не пропустить событие перезагрузки в случае опоздания установки регистра тревоги по отношению к состоянию счетчика.
  • Каждый модуль таймеров содержит сторожевой таймер и связанные с ним регистры, но об этом в других уроках.
  • Каждый модуль таймеров может генерировать 3 типа прерываний:
    • прерывание по истечению времени ожидания сторожевого таймера;
    • прерывание перезагрузки (тревоги) таймера 0 (TIMER_0);
    • прерывание перезагрузки (тревоги) таймера 1 (TIMER_1).

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

 

Инициализация таймера.

Для инициализации таймера в общем случае должны быть заданы следующие режимы и параметры:

  • значение предделителя;
  • разрешение/запрет счета;
  • направление счета;
  • разрешение/запрет события перезагрузки;
  • разрешение/запрет аппаратной перезагрузки счетчика;
  • значение порога перезагрузки;
  • значение перезагрузки;
  • параметры прерывания.

Инициализировать таймер можно задавая режимы отдельными функциями, а можно это сделать одним вызовом функции timer_init(). Во втором случае параметры задаются в элементах структуры инициализации. Но в любом варианте аргументы функций имеют одни и те же имена. Поэтому логичнее, сначала перечислить параметры функций работы с  таймерами и соответствующие им константы-значения.

Имя параметра Значения Функция установки
параметра
Назначение
параметра
group_num TIMER_GROUP_0 – группа 0
TIMER_GROUP_1 – группа 1
Все Группа таймеров
timer_num TIMER_0 – таймер 0
TIMER_1 – таймер 1
Все Таймер
alarm_en TIMER_ALARM_DIS – запрет перезагрузки
TIMER_ALARM_EN – разрешение перезагрузки
timer_set_alarm

timer_init

 

Разрешение события перезагрузки
counter_en TIMER_PAUSE – остановка счета
TIMER_START – пуск счетчика
timer_init

timer_start

timer_pause

Разрешение счета
intr_type TIMER_INTR_LEVEL
TIMER_INTR_MAX
timer_init Режим прерывания
counter_dir TIMER_COUNT_UP – прямой счет
TIMER_COUNT_DOWN – обратный счет
timer_set_counter_mode

timer_init

Направление счета
auto_reload

reload

TIMER_AUTORELOAD_DIS – запрет перезагрузки
TIMER_AUTORELOAD_EN – разрешение перезагрузки
timer_set_auto_reload

timer_init

Разрешение перезагрузки счетчика
divider 2 … 65536 timer_set_divider

timer_init

Делитель входной частоты
timer_val 0 … 264 – 1 (uint64_t) timer_get_counter_value Значение счетчика
time (double) timer_get_counter_time_sec Значение счетчика в секундах
load_val 0 … 264 - 1 (uint64_t) timer_set_counter_value Значение счетчика для загрузки
alarm_value 0 … 264 - 1 (uint64_t) timer_set_alarm_value

timer_get_alarm_value

Значение порога перезагрузки

Большую часть параметров инициализации можно установить одним вызовом функции

esp_err_t timer_init(timer_group_t group_num, timer_idx_t timer_num, const timer_config_t *config)

Параметры задаются в структуре config типа timer_config_t.

typedef struct {
timer_alarm_t alarm_en;      /* Разрешение события перезагрузки */
timer_start_t counter_en;    /*Разрешение счета*/
timer_intr_mode_t intr_type; /* Режим прерывания */
timer_count_dir_t counter_dir; /* Направление счета */
timer_autoreload_t auto_reload;   /* Разрешение перезагрузки счетчика */
uint32_t divider;   /* Делитель входной частоты */
} timer_config_t;

  • alarm_en – разрешает или запрещает событие перезагрузки, т.е. то что вызывает собственно перезагрузку и прерывание.
  • counter_en - разрешает или запрещает (останавливает) работу счетчика.
  • intr_type – задает режим прерывания.
  • counter_dir – задает направление счета: прямое или обратное.
  • auto_reload - разрешает или запрещает аппаратную перезагрузку счетчика. Т.е. если запретить это действие и разрешить событие перезагрузки, то при достижении счетчиком порогового значения будет вырабатываться прерывание, а перезагрузка счетчика происходить не будет.
  • divider – делитель входной тактовой частоты.

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

Напишем название программы, подключим API-драйвер для работы с таймерами.

// простой счет таймера
#include "driver/timer.h"

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

void app_main(void)
{
  // инициализация таймера 0, группы 0
  timer_config_t config;
  config.divider = 80; // тактирование счетчика 1 мкс
  config.counter_dir = TIMER_COUNT_UP; // прямой счет
  config.counter_en = TIMER_START; // счетчик работает
  config.alarm_en = TIMER_ALARM_DIS; // событие перезагрузка запрещено
  config.auto_reload = TIMER_AUTORELOAD_DIS ; // аппаратная перезагрузка запрещена
  timer_init(TIMER_GROUP_0, TIMER_0 , &config); // инициализация
}

Значение предделителя я задал 80, что обеспечивает тактирование счетчика частотой 1 мГц. Счетчик будет считать время непосредственно в микросекундах.

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

esp_err_t timer_set_divider(timer_group_t group_num, timer_idx_t timer_num, uint32_t divider)

Устанавливает значение предделителя (divider  = 2 … 65536).

esp_err_t timer_set_counter_mode(timer_group_t group_num, timer_idx_t timer_num, timer_count_dir_t counter_dir)

Задает направления счета (counter_dirTIMER_COUNT_UP или TIMER_COUNT_DOWN).

esp_err_t timer_set_auto_reload(timer_group_t group_num, timer_idx_t timer_num, timer_autoreload_t reload)

Разрешает/запрещает аппаратную перезагрузку счетчика по событию (reload = TIMER_AUTORELOAD_EN или TIMER_AUTORELOAD_DIS).

esp_err_t timer_set_alarm_value(timer_group_t group_num, timer_idx_t timer_num, uint64_t alarm_value)

Устанавливает значение порога перезагрузки (alarm_value = 0 … 264 – 1).

esp_err_t timer_get_alarm_value(timer_group_t group_num, timer_idx_t timer_num, uint64_t *alarm_value)

Считывает значение порога перезагрузки.

esp_err_t timer_set_alarm(timer_group_t group_num, timer_idx_t timer_num, timer_alarm_t alarm_en)

Разрешает/запрещает событие перезагрузки (alarm_en = TIMER_ALARM_EN или TIMER_ALARM_DIS).

esp_err_t timer_start(timer_group_t group_num, timer_idx_t timer_num)

Запуск счетчика (разрешение счета).

esp_err_t timer_pause(timer_group_t group_num, timer_idx_t timer_num)

Остановка счетчика (запрет счета).

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

 

Организация задержек и временных циклов.

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

esp_err_t timer_get_counter_value(timer_group_t group_num, timer_idx_t timer_num, uint64_t *timer_val)

Чтение значения счетчика.

esp_err_t timer_set_counter_value(timer_group_t group_num, timer_idx_t timer_num, uint64_t load_val)

Загрузка значений счетчика и регистра перезагрузки.

esp_err_t timer_get_counter_time_sec(timer_group_t group_num, timer_idx_t timer_num, double *time)

Вариант первой функции, при котором считанное значение счетчика (time) пересчитывается в секунды.

 

Задержка с блокированием программы.

Самый простой вариант.

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Сбрасываем счетчик, в цикле считываем его и ждем, пока его значение достигнет требуемого.

Программа зависает при отработке задержки. Но такое решение приемлемо на практике при отработке коротких задержек.

 

Использование функции ets_delay_us.

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

void ets_delay_us(uint32_t us)

Задержка на время us, заданное в микросекундах.

Для использования функции необходимо включить ее заголовочный файл. Программа с реализацией задержки с помощью ets_delay_us будет выглядеть так.

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Вот проект, который демонстрирует описанные выше 2 способа формирования задержек с блокированием программы.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

 

Временной цикл без блокирования программы.

Если мы инициализируем таймер и разрешим работу счетчика в начале программы, никогда не будем его сбрасывать, то получим счетчик, который ведет отсчет времени работы программы, начиная с ее запуска (включения или сброса микроконтроллера).

Функция чтения значения счетчика timer_get_counter_value()  будет возвращать текущее время работы микроконтроллера в микросекундах. Она будет эквивалентна функции micros() в Ардуино.

Только в отличие от Ардуино счетчики ESP32 имеют разрядность 64 бита, что позволяет реализовывать временные циклы со значительно более длительными периодами, не заботясь о переполнении счетчика.

Логика формирования временного цикла простая.

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

Вот как это выглядит в программе.

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

При этом программа не зависает. В основном цикле while можно выполнять другие операции. В том числе другие временные циклы.

Вот полный проект, демонстрирующий это способ формирования временного цикла.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

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

previousTime += CYCLE_TIME;

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

Но для многих практических приложений такой способ создания временных циклов вполне допустим.

Полностью независимые временные циклы можно реализовать только с использованием прерываний таймера.

 

Этому будет посвящен следующий урок.

 

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 2 недели

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

2 комментария на «Урок 6. Таймеры общего назначения. Работа с ними через API-функции. Организация временных задержек, циклов в системе ESP32.»

  1. Добрый вечер. Обнадеживающий цикл статей. Очень полезный. Хочу спросить. Это все?

    0
    • Здравствуйте!
      Не знаю, как ответить.
      Я писал уроки ESP32 по остаточному принципу. Сейчас времени почти нет. Сайт приносит очень маленький доход. Информации на эту тему практически нет. Приходится добывать ее из официальной документации и, много времени уходит на проверку. Остается надеяться на положительные изменения в стране. Как говорят англичане, темнее всего перед рассветом.

      0

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

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

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".