Урок 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 дня

Эдуард

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

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

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

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