Следующая периферия микроконтроллера ESP32, которой мы сегодня коснёмся в нашем занятии — это модуль RMT, чья аббревиатура расшифровывается как Remote Control (дистанционное управление).
Модуль RMT (Remote Control) в первую очередь предназначен для отправки и приема инфракрасных сигналов дистанционного управления, реализующих двухпозиционную манипуляцию на несущей частоте, но благодаря своей конструкции он может использоваться для генерации различных типов сигналов. Передатчик RMT делает это, считывая последовательные значения длительности активного и неактивного выхода из встроенного блока ОЗУ, опционально модулируя его несущей волной. Приемник проверяет входной сигнал, опционально отфильтровав его, и помещает значения промежутков времени, в течение которых сигнал активен и неактивен, в блоке ОЗУ.
Модуль RMT имеет восемь каналов, пронумерованных от нуля до семи. Регистры, сигналы и блоки, которые дублируются в каждом канале, обозначаются буквой n, которая используется в качестве заполнителя для номера канала
Модуль RMT содержит восемь каналов. Каждый канал имеет и передатчик, и приемник, но только один из них может быть активен в каждом канале. Восемь каналов совместно используют блок ОЗУ размером 512×32 бит, который может считываться и записываться ядрами процессора по шине APB, считываться передатчиками и записываться приемниками. Передаваемый сигнал может дополнительно модулироваться несущей волной. Каждый канал тактируется разделённым сигналом, полученным либо из тактового сигнала шины APB, либо из REF_TICK, который также формируется из того же тактового сигнала, но через делитель.
Структура RAM модуля RMT
Досконально изучать данный модуль (прерывания, расчёты, регистры) не входит в планы данного урока, так как мы работаем с комплектом IDF, в котором всё запрятано в функциях. Тем не менее, если что-то потребуется изучить из данной периферии по ходу работы с её реализацией в коде, мы вернёмся к данному вопросу.
Мы попробуем на данном занятии принять сигнал с дистанционного пульта управления и получить код команды.
Схема наша будет состоять из платы с контроллером esp32, к которой мы подключим модуль ИК-приёмника
Хоть на рисунке и видно, к каким ножкам подключен модуль, но на всякий случай покажу нагляднее
Проект урока был создан из проекта урока 4 с именем BUTTON01 и получил имя REMCONTROL.
Откроем наш проект в ESPRESSIF IDE и из файла Kconfig.projbuild удалим пункт меню по работе с ножкой, к которой была подключена кнопка (BUTTON_GPIO). Также в данном файле ножку порта по умолчанию для светодиода выберем вторую
default 48 if IDF_TARGET_ESP32S3
default 2
В самой первую строке для приличия переименуем пункт меню
menu "REMCONTROL Configuration"
Далее добавим пункты, нужные нам впоследствии для работы с модулем, а именно тип протокола IR, также номера ножек и каналов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
menu "REMCONTROL Configuration" choice IR_PROTOCOL prompt "Infrared Protocol" default IR_PROTOCOL_NEC help Choose the IR protocol. config IR_PROTOCOL_NEC bool "NEC" help NEC is a kind of Pulse Distance Protocol. It uses ASK modulation and pulse distance encoding with a carrier frequency of 38 kHz. config IR_PROTOCOL_RC5 bool "RC5" help The RC5 protocol was introduced by Philips. It uses ASK modulation and Manchester encoding with carrier frequency fixed at 36 kHz. endchoice config RMT_TX_GPIO int "RMT TX GPIO" default 18 help Set the GPIO number used for transmitting the RMT signal. config RMT_RX_GPIO int "RMT RX GPIO" default 19 help Set the GPIO number used for receiving the RMT signal. config RMT_TX_CHANNEL int "RMT TX Channel Number" default 0 help Set the RMT TX channel number. config RMT_RX_CHANNEL int "RMT RX Channel Number" default 4 if IDF_TARGET_ESP32S3 default 2 help Set the RMT RX channel number. |
Запустим конфигуратор и выберем 4 мегабайта объёма памяти FLASH
В функции app_main файла main.c оставим только вот эти строки
1 2 3 4 5 6 7 |
//------------------------------------------------------------- void app_main(void) { gpio_reset_pin(CONFIG_BLINK_GPIO); gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); } //------------------------------------------------------------- |
Хочется также напомнить, что с ИК-портом мы работали в уроке 12 по контроллерам PIC, где мы очень подробно рассматривали протоколы NEC и RC5. Поэтому, если кто не знает о данных протоколах, то посмотрите данный урок.
Попробуем собрать наш проект. Если всё нормально собирается, то ещё создадим заголовочный файл main.h, в который соберём все подключенные заголовочные файлы из main.c, а заодно и подключим заголовочные файлы для работы с модулем RMT
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#ifndef MAIN_MAIN_H_ #define MAIN_MAIN_H_ //------------------------------------------------------------- #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "driver/gpio.h" #include "driver/rmt.h" #include "ir_tools.h" #include "sdkconfig.h" //------------------------------------------------------------- #endif /* MAIN_MAIN_H_ */ |
Вернёмся в main.c и удалим подключение всех заголовочных файлов, а подключим только один
1 |
#include "main.h" |
Если теперь мы попробуем собрать код, то он у нас не соберётся. Дело в том, что мы не увидим файла ir_tools.h, который надо будет подключить отдельно вместе с другими файлами, необходимыми для работы с библиотекой.
Из каталога C:\Espressif\frameworks\esp-idf-v4.4\examples\peripherals\rmt\ir_protocols\components\infrared_tools\include комплекта IDF в каталог main нашего проекта скопируем файлы ir_tools.h и ir_timings.h.
Затем из каталога C:\Espressif\frameworks\esp-idf-v4.4\examples\peripherals\rmt\ir_protocols\components\infrared_tools\src комплекта скопируем все файлы также в каталог main нашего проекта.
После копирования проект также может не собраться, поэтому надо будет подправить файл CMakeLists.txt, также находящийся в каталоге main нашего проекта
1 2 3 4 |
idf_component_register(SRCS "main.c" "ir_builder_rmt_nec.c" "ir_builder_rmt_rc5.c" "ir_parser_rmt_nec.c" "ir_parser_rmt_rc5.c" INCLUDE_DIRS ".") |
Теперь точно всё соберётся.
Объявим и заполним строковый массив для работы с логами
1 2 3 4 |
#include "main.h" //============================================================== static const char *TAG = "main"; //============================================================== |
Ниже добавим функцию задачи приёма пакетов от пульта управления
1 2 3 4 5 6 7 8 |
static const char *TAG = "main"; //============================================================== static void ir_rx_task(void *arg) { size_t length = 0; uint32_t addr = 0; uint32_t cmd = 0; } |
В функции app_main установим низкий уровень на ножке синего светодиода и создадим задачу для приёма пакетов
1 2 3 |
gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); gpio_set_level(CONFIG_BLINK_GPIO, 0); xTaskCreate(ir_rx_task, "ir_rx_task", 2048, NULL, 10, NULL); |
Перейдём в функцию ir_rx_task и инициализируем конфигурационные параметры RMT по умолчанию для ножки и канала
1 2 3 |
uint32_t cmd = 0; rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(CONFIG_RMT_RX_GPIO, CONFIG_RMT_RX_CHANNEL); rmt_config(&rmt_rx_config); |
При помощи специальной функции инициализации назначим размер буфера
1 2 |
rmt_config(&rmt_rx_config); rmt_driver_install(CONFIG_RMT_RX_CHANNEL, 1000, 0); |
Ноль в третьем параметре гласит о том, что флаги прерываний используются по умолчанию.
Также по умолчанию инициализируем парсер для входящих пакетов
1 2 |
rmt_driver_install(CONFIG_RMT_RX_CHANNEL, 1000, 0); ir_parser_config_t ir_parser_config = IR_PARSER_DEFAULT_CONFIG((ir_dev_t)CONFIG_RMT_RX_CHANNEL); |
Задействуем флаг использования расширенных ИК-протоколов для NEC и RC5
1 2 |
ir_parser_config_t ir_parser_config = IR_PARSER_DEFAULT_CONFIG((ir_dev_t)CONFIG_RMT_RX_CHANNEL); ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version) |
Объявим указатель для ИК-парсера
1 2 |
ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version) ir_parser_t *ir_parser = NULL; |
Инициализируем парсер в зависимости от используемого протокола
1 2 3 4 5 6 |
ir_parser_t *ir_parser = NULL; #if CONFIG_IR_PROTOCOL_NEC ir_parser = ir_parser_rmt_new_nec(&ir_parser_config); #elif CONFIG_IR_PROTOCOL_RC5 ir_parser = ir_parser_rmt_new_rc5(&ir_parser_config); #endif |
Выше объявим указатель на кольцевой буфер
1 2 |
uint32_t cmd = 0; RingbufHandle_t rb = NULL; |
Привяжем кольцевой буфер к каналу
1 2 3 |
ir_parser = ir_parser_rmt_new_rc5(&ir_parser_config); #endif rmt_get_ringbuf_handle(CONFIG_RMT_RX_CHANNEL, &rb); |
Проверим буфер на ошибки
1 2 |
rmt_get_ringbuf_handle(CONFIG_RMT_RX_CHANNEL, &rb); assert(rb != NULL); |
Запустим модуль
1 2 |
assert(rb != NULL); rmt_rx_start(CONFIG_RMT_RX_CHANNEL, true); |
Выше объявим указатель на переменную типа структуры свойств пакета, а также переменную для хранения состояния ножки светодиода
1 2 3 |
RingbufHandle_t rb = NULL; rmt_item32_t *items = NULL; uint8_t lvl = 1; |
В бесконечном цикле начнём принимать наши пакеты.
Попытаемся принять пакет
1 2 3 4 |
uint8_t lvl = 1; while (1) { items = (rmt_item32_t *) xRingbufferReceive(rb, &length, portMAX_DELAY); |
Если пакет непустой, то изменим уровень ножки светодиода
1 2 3 4 5 |
items = (rmt_item32_t *) xRingbufferReceive(rb, &length, portMAX_DELAY); if (items) { gpio_set_level(CONFIG_BLINK_GPIO, lvl); lvl ^= 1; |
Получим остаток от деления на 4 значения длины пакета
1 2 |
lvl ^= 1; length /= 4; // one RMT = 4 Bytes |
Получим точку входа в пакет
1 2 3 4 |
length /= 4; // one RMT = 4 Bytes if (ir_parser->input(ir_parser, items, length) == ESP_OK) { } |
Выше объявим переменную для хранения булевского значения, повторяется ли пакет
1 2 |
uint8_t lvl = 1; bool repeat = false; |
Получим скан-код после декодирования
1 2 3 4 5 |
if (ir_parser->input(ir_parser, items, length) == ESP_OK) { if (ir_parser->get_scan_code(ir_parser, &addr, &cmd, &repeat) == ESP_OK) { } |
Выведем в терминал адрес и команду в зависимости от типа протокола
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if (ir_parser->get_scan_code(ir_parser, &addr, &cmd, &repeat) == ESP_OK) { #if CONFIG_IR_PROTOCOL_NEC if(((uint8_t)addr == (uint8_t)~(addr>>8)) && ((uint8_t)cmd == (uint8_t)~(cmd>>8))) { ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%02x, cmd: 0x%02x", repeat ? "(repeat)" : "", (uint8_t)addr, (uint8_t)cmd); } else { ESP_LOGE(TAG, "Scan Code XOR Error!!!"); ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd); } #elif CONFIG_IR_PROTOCOL_RC5 ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd); #endif |
Выйдем из двух условий и после синтаксического анализа данных возвратим пробелы в кольцевой буфер
1 2 3 4 5 |
ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd); #endif } } vRingbufferReturnItem(rb, (void *) items); |
Если мы вдруг выйдем из бесконечного цикла (что вряд ли произойдёт, так как мы нигде не использовали выход из него), удалим наш парсер, произведём деинициализацию канала RMT и уничтожим задачу
1 2 3 4 5 6 |
vRingbufferReturnItem(rb, (void *) items); } } ir_parser->del(ir_parser); rmt_driver_uninstall(CONFIG_RMT_RX_CHANNEL); vTaskDelete(NULL); |
Вот, в принципе и весь код.
Соберём код, прошьём контроллер и запустим терминал.
Пульты можно использовать любые из тех, которые работают по протоколам RC5 и NEC. Я будут использовать тот же пульт, который я использовал в уроке 12 по контроллеру PIC
По умолчанию у нас задействован протокол NEC, что видно из активных строк кода. Поэтому переключим пульт на источник SAT1.
Наведём пульт на наш ИК-приёмник и понажимаем различные кнопки.
Мы получим адреса и коды команд
Попробуем принять пакеты по другому протоколу.
Переключим протокол в конфигураторе
Пересоберём код, прошьём контроллер, переключим источник на пульте на TV1 и попробуем также понажимать какие-нибудь кнопки на пульте
Мы также получили коды команд и адресов.
Итак, на данном занятии мы познакомились с работой модуля RMT и нам удалось при помощи функционала комплекта разработчика IDF принять пакеты от пульта управления по двум протоколам и получить команды управления.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Инфракрасные модули приёмника и передатчика ИК можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Hi Sir,
you have done a great job providing this tutorial, I tried this to recieve IR codes of AC remote, but i am not able to receive any codes, it is not going into ir_parser function.
Please help me out