Когда мы знакомились с семейством микроконтроллеров ESP32, то мы видели, что в данном контроллере существует несколько аппаратных таймеров.
Но порой бывают такие задачи, когда нет смысла задействовать аппаратный таймер, обрабатывать лишние прерывания. В таких случаях нам на помощь приходят программные таймеры. И начнём мы изучение таймеров именно с них ещё по той причине, что работа с ними несколько попроще.
В использовании программных таймеров есть свои ограничения, например такие:
- Период срабатывания таких таймеров обязательно должен быть кратен системному кванту времени. К примеру, если у нас системный квант 1 милисекунда, то мы не можем запустить таймер с периодом 1500 микросекунд. Либо 1000 либо 2000.
- Обратные вызовы таймера отправляются из задачи с низким приоритетом.
Давайте попробуем такие таймеры реализовать в нашем проекте.
Проект был сделан из проекта урока 7 с именем I2C_LCD2004 и получил имя SOFT_TIMER, так как нам пригодится дисплей для мониторинга работы таймеров.
Подключим сразу схему
Откроем наш проект в Espressif IDE и в функции app_main файла main.c удалим весь код, начинающийся после строки
LCD_ini();
до бесконечного цикла.
А в бесконечном цикле оставим только задержку
1 2 3 |
while (1) { vTaskDelay(100 / portTICK_PERIOD_MS); } |
Также в данной функции удалим переменную для счётчика и массив
uint16_t i=0;
char str01[10];
И ещё удалим переменную для параметров очереди
qLCDData xLCDData;
В файле main.h подключим заголовочный файл для работы с программными таймерами
1 2 |
#include "driver/i2c.h" #include "esp_timer.h" |
Мы создадим один таймер для периодического срабатывания, другой — для однократного.
Вернёмся в файл main.c и выше функции app_main добавим пока пустотелые функции обратного вызова для обоих таймеров
1 2 3 4 5 6 7 8 9 |
//------------------------------------------------ static void periodic_timer_callback(void* arg) { } //------------------------------------------------ static void oneshot_timer_callback(void* arg) { } //------------------------------------------------ |
В функции app_main объявим переменную типа структуры для параметры первого таймера и сразу проинициализируем её
1 2 3 4 5 |
LCD_ini(); const esp_timer_create_args_t periodic_timer_args = { .callback = &periodic_timer_callback, .name = "periodic" }; |
После сборки мы получим ошибку, так как такой указатель должен быть обязательно использован.
Объявим указатель-ссылку для первого таймера
1 2 3 |
.name = "periodic" }; esp_timer_handle_t periodic_timer; |
Создадим наш первый таймер, указав в параметрах всё вышеобъявленное
1 2 3 |
esp_timer_handle_t periodic_timer; ret = esp_timer_create(&periodic_timer_args, &periodic_timer); ESP_LOGI(TAG, "esp_timer_create: %d", ret); |
Теперь проект соберётся без ошибок.
Мы пока проверим работу одного таймера, а уж потом возьмёмся за другой.
Запустим наш таймер, указав в параметре период срабатывания в микросекунднах
1 2 3 |
ESP_LOGI(TAG, "esp_timer_create: %d", ret); ret = esp_timer_start_periodic(periodic_timer, 500000); ESP_LOGI(TAG, "esp_timer_start_periodic: %d", ret); |
Перейдём в функцию обратного вызова данного таймера periodic_timer_callback и объявим там символьный массив, переменную для параметров очереди, проинициализируем в ней координаты вывода на дисплей и приравняем указатель на символьный массив
1 2 3 4 5 6 7 |
static void periodic_timer_callback(void* arg) { char str1[15] = {0}; qLCDData xLCDData; xLCDData.x_pos = 0; xLCDData.y_pos = 0; xLCDData.str = str1; |
Затем с помощью специальной функции узнаем время с момента загрузки таймера и выводим его на экран дисплея с помощью очереди
1 2 3 4 |
xLCDData.str = str1; int64_t time_since_boot = esp_timer_get_time(); sprintf(str1,"%010lld", time_since_boot); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); |
Соберём код, прошьём контроллер.
Мы видим, что счётчик наш постоянно увеличивается, причём младшие пять разрядов не меняются, так как выводится время в микросекундах
В функции app_main создадим переменную типа структуры параметров для второго — однократного таймера
1 2 3 4 5 6 |
ESP_LOGI(TAG, "esp_timer_start_periodic: %d", ret); const esp_timer_create_args_t oneshot_timer_args = { .callback = &oneshot_timer_callback, .arg = (void*) periodic_timer, .name = "one-shot" }; |
Только здесь мы произвели инициализацию ещё одного параметра — это аргумент, который передаётся в функцию обратного вызова. В качестве аргумента мы передадим указатель на первый периодический таймер. Позже мы увидим смысл этого указателя.
Создадим наш таймер
1 2 3 4 5 |
.name = "one-shot" }; esp_timer_handle_t oneshot_timer; ret = esp_timer_create(&oneshot_timer_args, &oneshot_timer); ESP_LOGI(TAG, "esp_timer_create: %d", ret); |
После запуска периодического таймера немного подождём и запустим наш второй таймер
1 2 3 4 |
ESP_LOGI(TAG, "esp_timer_create: %d", ret); vTaskDelay(250 / portTICK_PERIOD_MS); ret = esp_timer_start_once(oneshot_timer, 10000000); ESP_LOGI(TAG, "esp_timer_start_once: %d", ret); |
В функции обратного вызова для однократного таймера oneshot_timer_callback мы объявим переменную для кода ошибки, а также символьный массив и переменную для параметров очереди, проинициализировав в ней также поля позиции вывода текста и указателя на символьный массив
1 2 3 4 5 6 7 8 |
static void oneshot_timer_callback(void* arg) { esp_err_t ret; char str1[15] = {0}; qLCDData xLCDData; xLCDData.x_pos = 0; xLCDData.y_pos = 1; xLCDData.str = str1; |
Выведем на экран дисплея количество микросекунд, прошедших с момента запуска таймера
1 2 3 4 |
xLCDData.str = str1; int64_t time_since_boot = esp_timer_get_time(); sprintf(str1,"%010lld", time_since_boot); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); |
Из аргументов получим указатель на первый таймер и попытаемся его остановить
1 2 3 4 |
xQueueSendToBack(lcd_string_queue, &xLCDData, 0); esp_timer_handle_t periodic_timer_handle = (esp_timer_handle_t) arg; ret = esp_timer_stop(periodic_timer_handle); ESP_LOGI(TAG, "esp_timer_stop: %d", ret); |
Затем запустим его, но уже с другим интервалом
1 2 3 |
ESP_LOGI(TAG, "esp_timer_stop: %d", ret); ret = esp_timer_start_periodic(periodic_timer_handle, 1000000); ESP_LOGI(TAG, "esp_timer_start_periodic: %d", ret); |
Соберём код, прошьём контроллер и увидим, что через 10 секунд у нас на дисплее появится вторая строка с временем, прошедшим с момента запуска второго таймера, и после этого в верхней строке счётчик будет обновляться уже раз не в полсекунды, а раз в секунду (неизменными будут шесть младших разрядов)
Строка с временем второго таймера будет оставаться прежней постоянно, что доказывает нам то, что данный таймер срабатывает только один раз.
Итак, на данном уроке мы познакомились с программными таймерами контроллера ESP32.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Переходник I2C to LCD можно приобрести здесь I2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий