ESP32 Урок 15. Аппаратные таймеры



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

Помимо 64-разрядного счётчика, каждый из 4 таймеров включает 16-разряюный предделитель и работает по простейшему принципу. Режим работы только один — режим счётчика.

Считать счётчик таймера может и в прямом и в обратном направлении. Переполнение данному счётчику не грозит, так как он 64-разрядный. При тактировании 80 мегагерц до переполнения нужно будет ждать около 60 тысяч лет. Также такой таймер можно остановить. Также значение счётчика можно прочитать и установить программно.

Как же при всех этих условиях получить срабатывание таймера?

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

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

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

Схема останется такая же, как и в прошлом уроке

 

 

Проект был сделан из проекта урока 13 с именем SOFT_TIMER и получил имя TIMER_GROUP.

Откроем наш проект в Espressif IDF и в файле main.h вместо библиотеки

 

#include «esp_timer.h»

 

подключим вот эту

 

#include "driver/timer.h"

 

Перейдём в файл main.c и удалим функции periodic_timer_callback и oneshot_timer_callback вместе с телами.

В функции app_main удалим весь код после строки

 

LCD_ini();

 

до бесконечного цикла.

Мы будем использовать два таймера.

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

 

 

Код для инициализации обоих таймеров будет подобным, поэтому мы его оформим в функцию, а не будем городить в app_main. Добавим такую функцию выше функции app_main

 

 

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

 

 

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

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

 

 

Сбросим начальное значение счётчика в ноль

 

 

Установим интервал срабатывания таймера

 

 

Включим прерывания от таймера

 

 

Объявим глобальную структуру для свойств таймера

 

 

Вернёмся в функцию Объявим, зарезервируем память, а также проинициализируем указатель на переменную структуры свойств таймера

 

 

 

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

 

 

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

Вернёмся в функцию tg_timer_init и зарегистрируем функцию-обработчик прерывания для нашего таймера

 

 

Запустим наш таймер

 

 

В функции app_main вызовем нашу функцию инициализации

 

 

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

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

Поэтому объявим глобальную переменную для такой очереди

 

 

В функции-обработчике timer_group_isr_callback заберём указатель на переменную структуры свойств таймера из параметров

 

 

Затем заберём значение счётчика в переменную

 

 

Объявим ещё одну глобальную структуру для события таймера, в которую включим предыдущую, а также добавим значение счётчика

 

 

Вернёмся в функцию-обработчик timer_group_isr_callback, объявим переменную типа данной структуры и заполним её значениями

 

 

 

Если таймер без перезагрузки, то к значению, взятому из регистра счётчика, добавим интервал и занесём полученное значение в регистр срабатывания

 

 

Отправим указатель на переменную структуры в очередь

 

 

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

 

 

Объявим переменную типа структуры свойств таймера

 

 

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

 

 

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

 

 

Затем отправим в дисплей значение счётчика в секундах

 

 

Такое разделение было применено из-за неуверенной работы с одним символьным массивом.

Создадим очередь в функции app_main

 

 

А затем создадим задачу

 

 

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

 

Давайте попробуем создать ещё один таймер. Для этого в функции app_main немного подождём и инициализируем, например, таймер 0 из группы таймеров 1 без перезагрузки

 

 

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

 

Почему же наш второй таймер тоже считает, а не срабатывает однократно? Всё потому, что мы добавили соответствующий код в обработчике.

Давайте попробуем в функции timer_group_isr_callback данный код закомментировать

 

// if (!info->auto_reload) {
// timer_counter_value += info->alarm_interval * TIMER_SCALE;
// timer_group_set_alarm_value_in_isr(info->timer_group, info->timer_idx, timer_counter_value);
// }

 

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

 

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

Выше функции vLCDTask добавим функцию по выводу значений счётчика в терминал

 

 

Мы отображаем 64-разрядное значение счётчика таймера, а затем на следующей строке вычисляем и отображаем значение в секундах.

Вернёмся в функцию task1 и отобразим в терминале сначала тип нашего таймера (с перезагрузкой или без)

 

 

Далее отобразим номер таймера и номер группы

 

 

Отобразим теперь значение счётчика, которое пришло к нам из обработчика прерывания с помощью очереди

 

 

А теперь заново извлечём из регистра значение счётчика нашего таймера и отобразим его тоже

 

 

Тем самым мы сможем вычислить время, за которое элемент прошел через очередь в задачу.

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

Счётчик наш на дисплее также продолжает считать.

Посмотрим, что у нас в терминале

 

 

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

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

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

Тогда мы получим уже вот такой результат

 

 

Таймеры работают оба, причём у таймера из группы 1 значение счётчика не сбрасывается, а постоянно наращивается.

Итак, сегодня мы познакомились с аппаратными таймерами контроллера ESP32, научились обрабатывать прерывания от них, настраивать их как с перезагрузкой, так и без таковой.

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

 

Данная статья в Дзен.

 

 

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

 

Исходный код

 

 

Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32

Логический анализатор 16 каналов можно приобрести здесь

Дисплей LCD 20×4 можно приобрести здесь Дисплей LCD 20×4

Дисплей LCD 16×2

Переходник I2C to LCD можно приобрести здесь I2C to LCD1602 2004

 

 

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

ESP32 Аппаратные таймеры

 

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

ESP32 Аппаратные таймеры

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

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

*