Продолжаем работать с модулем RMT (Remote Control) и с сегодняшнего урока мы попытаемся с использованием данного модуля поработать с однопроводной шиной, по которой будут передаваться данные по протоколу 1-Wire.
С данным протоколом мы уже неоднократно встречались, подключая датчик температуры DS18B20 с использованием других контроллеров. Например, с таким датчиком мы работали, подключая его к контроллеру AVR в уроке 20. Также такой датчик мы подключали и к контроллеру STM32 в уроке 92. А в уроке 94 мы подключали даже несколько таких датчиков, так как на одном проводе по протоколу 1-Wire можно работать и с несколькими устройствами.
Благодаря тому, что мы уже с протоколом 1-Wire столько много работали, нам не придётся изучать сам протокол, команды, а также впоследствии и принцип работы с датчиком температуры, так как это всё мы уже неплохо знаем. Останется лишь только адаптировать наши знания к работе с модулем RMT контроллера ESP32. Хотя не смотря на то, что у нас за плечами такой багаж знаний, работа эта будет очень непростая. Поэтому я решил разделить её на несколько этапов. И сегодня мы произведём только инициализацию протокола 1-Wire.
Схема урока будет состоять из отладочной платы с контроллером ESP32, к которой мы подключим датчик температуры DS18B20, а чуть позже три таких датчика. Также мы помним, что для корректной работы с шиной потребуется резистор на 4,7 килоома, подключенный между сигнальным проводом датчика и шиной питания 3,3 вольта. Сигнальный провод датчика мы подключим к ножке порта GPIO4. Резистор мы установим на макетной плате и для начала подключим макетную плату к отладочной
Подключим датчик к макетной плате. Датчик находится на проводе в металлическом корпусе. С таким датчиком мы уже работали
Перемычки на плате нужны для того, чтобы подключить впоследствии ещё два датчика.
Также для отслеживания результатов нашей работы подключим логический анализатор. Также подключим нашу отладочную плату к компьютеру
Теперь проект.
Проект урока был создан из проекта урока 4 с именем BUTTON01 и получил имя RMT_ONEWIRE_INIT.
Откроем наш проект в ESPRESSIF IDE и из файла Kconfig.projbuild удалим пункт меню по работе с ножкой, к которой была подключена кнопка (BUTTON_GPIO). Также в данном файле ножку порта по умолчанию для светодиода выберем вторую
default 48 if IDF_TARGET_ESP32S3
default 2
В самой первую строке для приличия переименуем пункт меню
menu "ONE WIRE Configuration"
Добавим пункт для ножки датчика
1 2 3 4 5 6 7 8 |
menu "ONE WIRE Configuration" config ONE_WIRE_GPIO int "ONE WIRE GPIO number" range 0 48 default 4 help GPIO number to ONE WIRE. |
Запустим конфигуратор и выберем 4 мегабайта объёма памяти FLASH
В функции app_main файла main.c оставим только вот эти строки
1 2 3 4 5 6 7 8 |
//============================================================== void app_main(void) { gpio_reset_pin(CONFIG_BLINK_GPIO); gpio_set_direction(CONFIG_BLINK_GPIO, GPIO_MODE_OUTPUT); gpio_set_level(CONFIG_BLINK_GPIO, 0); } //============================================================== |
Попробуем собрать наш проект. Если всё нормально собирается, то ещё создадим заголовочный файл main.h, в который соберём все подключенные заголовочные файлы из main.c
1 2 3 4 5 6 7 8 9 10 11 |
#ifndef MAIN_MAIN_H_ #define MAIN_MAIN_H_ //--------------------------------------------------------------------- #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp_log.h" #include "sdkconfig.h" //--------------------------------------------------------------------- #endif /* MAIN_MAIN_H_ */ |
Вернёмся в main.c и удалим подключение всех заголовочных файлов, а подключим только один
1 |
#include "main.h" |
Для работы с протоколом создадим отдельный модуль.
Для начала создадим заголовочный файл с именем owb.h, в котором подключим заголовочный файл для работы с модулем RMT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#ifndef MAIN_OWB_H_ #define MAIN_OWB_H_ //--------------------------------------------------------------------- #include <unistd.h> #include <stdbool.h> #include <string.h> #include "esp_log.h" #include "driver/rmt.h" #include "driver/gpio.h" //--------------------------------------------------------------------- #ifdef __cplusplus extern "C" { #endif //--------------------------------------------------------------------- //--------------------------------------------------------------------- #endif /* MAIN_OWB_H_ */ |
Также создадим файл owb.c следующего содержания
1 2 3 4 |
#include "owb.h" //--------------------------------------------------------------------- static const char *TAG = "owb"; //--------------------------------------------------------------------- |
Имя owb для модуля выбиралось как аббревиатура от One Wire Bus (однопроводная шина).
Не забываем добавить модуль в файле CMakeLists.txt
set(COMPONENT_SRCS "main.c owb.c")
В файле main.c объявим и заполним строковый массив для работы с логами
1 2 3 4 |
#include "main.h" //============================================================== static const char *TAG = "main"; //============================================================== |
В функции app_main подождём 2 секунды для первичной инициализации датчика
1 2 3 |
gpio_set_level(CONFIG_BLINK_GPIO, 0); vTaskDelay(2000 / portTICK_PERIOD_MS); ESP_LOGI(TAG, "Start"); |
Затем в файле owb.h объявим тип структуры для хранения настроек шины
1 2 3 4 5 6 7 8 9 10 11 12 |
extern "C" { #endif //--------------------------------------------------------------------- typedef struct { const struct _OneWireBus_Timing * timing; ///< Pointer to timing information bool use_crc; ///< True if CRC checks are to be used when retrieving information from a device on the bus bool use_parasitic_power; ///< True if parasitic-powered devices are expected on the bus gpio_num_t strong_pullup_gpio; ///< Set if an external strong pull-up circuit is required const struct owb_driver * driver; ///< Pointer to hardware driver instance } OneWireBus; //--------------------------------------------------------------------- |
Ниже объявим структуру для хранения таймингов
1 2 3 4 5 6 7 |
} OneWireBus; //--------------------------------------------------------------------- struct _OneWireBus_Timing { uint32_t A, B, C, D, E, F, G, H, I, J; }; //--------------------------------------------------------------------- |
В файле main.h подключим заголовочный файл owb.h
Вернёмся в функцию app_main файла main.c и объявим указатель на переменную только что объявленного нами типа
1 2 3 |
ESP_LOGI(TAG, "Start"); // Create a 1-Wire bus, using the RMT timeslot driver OneWireBus * owb; |
Опять перейдём в файл owb.h и объявим ещё один тип структуры, более расширенный, также для хранения настроек шины, но уже с использованием модуля RMT
1 2 3 4 5 6 7 8 9 10 11 |
} OneWireBus; //--------------------------------------------------------------------- typedef struct { int tx_channel; ///< RMT channel to use for TX int rx_channel; ///< RMT channel to use for RX RingbufHandle_t rb; ///< Ring buffer handle int gpio; ///< OneWireBus GPIO OneWireBus bus; ///< OneWireBus instance } owb_rmt_driver_info; //--------------------------------------------------------------------- |
Вернёмся в функцию app_main файла main.c и переменную этого типа
1 2 |
OneWireBus * owb; owb_rmt_driver_info rmt_driver_info; |
Перейдём в файл owb.c и добавим функцию инициализации нашей шины, в которой пока что только выведем в терминал в случае, если происходит отладка, информацию о входных параметрах, а также вернём указатель на переменную типа структуры конфигурации шины
1 2 3 4 5 6 7 8 9 10 |
static const char *TAG = "owb"; //--------------------------------------------------------------------- OneWireBus * owb_rmt_initialize(owb_rmt_driver_info * info, gpio_num_t gpio_num, rmt_channel_t tx_channel, rmt_channel_t rx_channel) { ESP_LOGD(TAG, "%s: gpio_num: %d, tx_channel: %d, rx_channel: %d", __func__, gpio_num, tx_channel, rx_channel); return &(info->bus); } //--------------------------------------------------------------------- |
К данной функции мы вернёмся позже.
А пока перейдём в заголовочный файл owb.h и объявим перечисляемый тип для состояния шины
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
} owb_rmt_driver_info; //--------------------------------------------------------------------- typedef enum { OWB_STATUS_NOT_SET = -1, ///< A status value has not been set OWB_STATUS_OK = 0, ///< Operation succeeded OWB_STATUS_NOT_INITIALIZED, ///< Function was passed an uninitialised variable OWB_STATUS_PARAMETER_NULL, ///< Function was passed a null pointer OWB_STATUS_DEVICE_NOT_RESPONDING, ///< No response received from the addressed device or devices OWB_STATUS_CRC_FAILED, ///< CRC failed on data received from a device or devices OWB_STATUS_TOO_MANY_BITS, ///< Attempt to write an incorrect number of bits to the One Wire Bus OWB_STATUS_HW_ERROR ///< A hardware error occurred } owb_status; //--------------------------------------------------------------------- |
Вернёмся в owb.c и добавим ещё одну служебную функцию инициализации шины, которую мы будем впоследствии вызывать из основной функции инициализации
1 2 3 4 5 6 7 8 9 |
static const char *TAG = "owb"; //--------------------------------------------------------------------- static owb_status _init(owb_rmt_driver_info *info, gpio_num_t gpio_num, rmt_channel_t tx_channel, rmt_channel_t rx_channel) { owb_status status = OWB_STATUS_HW_ERROR; return status; } //--------------------------------------------------------------------- |
Выше добавим ещё четыре служебных функции, пока также незавершённые для некоторых операций с шиной
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 |
static const char *TAG = "owb"; //--------------------------------------------------------------------- static owb_status _uninitialize(const OneWireBus *bus) { return OWB_STATUS_OK; } //--------------------------------------------------------------------- static owb_status _reset(const OneWireBus * bus, bool * is_present) { int res = OWB_STATUS_OK; return res; } //--------------------------------------------------------------------- static owb_status _write_bits(const OneWireBus * bus, uint8_t out, int number_of_bits_to_write) { owb_status status = OWB_STATUS_OK; return status; } //--------------------------------------------------------------------- static owb_status _read_bits(const OneWireBus * bus, uint8_t *in, int number_of_bits_to_read) { int res = OWB_STATUS_OK; return res; } //--------------------------------------------------------------------- |
В файле owb.h объявим структуру с указателем на символьный массив, а также с указателями на функции, подобные тем, которые мы только что добавили
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
uint32_t A, B, C, D, E, F, G, H, I, J; }; //--------------------------------------------------------------------- struct owb_driver { /** Driver identification **/ const char* name; /** Pointer to driver uninitialization function **/ owb_status (*uninitialize)(const OneWireBus * bus); /** Pointer to driver reset functio **/ owb_status (*reset)(const OneWireBus * bus, bool *is_present); /** NOTE: The data is shifted out of the low bits, eg. it is written in the order of lsb to msb */ owb_status (*write_bits)(const OneWireBus *bus, uint8_t out, int number_of_bits_to_write); /** NOTE: Data is read into the high bits, eg. each bit read is shifted down before the next bit is read */ owb_status (*read_bits)(const OneWireBus *bus, uint8_t *in, int number_of_bits_to_read); }; //--------------------------------------------------------------------- |
Вернёмся в owb.c и выше функции _init объявим и сразу проинициализируем переменную типа только что объявленной структуры с символьным массивом, хранящим в себе имя шины, и указателями на добавленные выше функции
1 2 3 4 5 6 7 8 9 10 |
//--------------------------------------------------------------------- static struct owb_driver rmt_function_table = { .name = "owb_rmt", .uninitialize = _uninitialize, .reset = _reset, .write_bits = _write_bits, .read_bits = _read_bits }; //--------------------------------------------------------------------- |
В функции _init проинициализируем поля переменной структуры настройки модуля RMT, указатель на которую находится во входном параметре
1 2 3 4 5 6 |
owb_status status = OWB_STATUS_HW_ERROR; info->bus.driver = &rmt_function_table; info->tx_channel = tx_channel; info->rx_channel = rx_channel; info->gpio = gpio_num; |
Объявим макрос для объявления режима отладки
1 2 3 4 |
static const char *TAG = "owb"; //--------------------------------------------------------------------- #define OW_DEBUG //--------------------------------------------------------------------- |
Только это не совсем режим отладки. Это только его эмуляция. Настоящий режим отладки запускается по интерфейсу JTAG. С отладкой мы работали в уроке 10. Вернёмся в функцию _init и отобразим в терминале информацию о каналах
1 2 3 4 5 6 |
info->gpio = gpio_num; #ifdef OW_DEBUG ESP_LOGI(TAG, "RMT TX channel: %d", info->tx_channel); ESP_LOGI(TAG, "RMT RX channel: %d", info->rx_channel); #endif |
Чтобы нам проверить хотя бы этот код, для начала в функции owb_rmt_initialize вызовем функцию _init и в случае удачного возврата выведем соответствующее сообщение в терминале, а затем сохраним ножку порта в переменной структуры настройки шины.
Для функции owb_rmt_initialize создадим прототип в заголовочном файле, затем в функции app_main файла main.c вызовем её
1 2 |
owb_rmt_driver_info rmt_driver_info; owb = owb_rmt_initialize(&rmt_driver_info, CONFIG_ONE_WIRE_GPIO, RMT_CHANNEL_1, RMT_CHANNEL_0); |
Попробуем собрать код, прошить контроллер и посмотреть, что отображается в терминале
Всё правильно. Не надо пугаться ошибочного состояния, так как мы ещё не завершили функцию _init в файле owb.c, в которую мы сейчас вернёмся и объявим указатель на переменную структуры конфигурационных параметров RMT, а затем инициализируем её поля
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ESP_LOGI(TAG, "RMT RX channel: %d", info->rx_channel); #endif rmt_config_t rmt_tx = {0}; rmt_tx.channel = info->tx_channel; rmt_tx.gpio_num = gpio_num; rmt_tx.mem_block_num = 1; rmt_tx.clk_div = 80; rmt_tx.tx_config.loop_en = false; rmt_tx.tx_config.carrier_en = false; rmt_tx.tx_config.idle_level = 1; rmt_tx.tx_config.idle_output_en = true; rmt_tx.rmt_mode = RMT_MODE_TX; |
Прошу обратить внимание на настройку делителя. Мы не случайно ему даём значение 80, так как у нас APB тактируется частотой 80 мегагерц и делитель 80 нам позволит считать, что каждый тик тактирования модуля RMT будет равен 1 микросекунде, что облегчит нам работу с таймингами уровней.
Применим наши конфигурационные параметры и зададим источник тактирования RMT
1 2 3 4 5 |
rmt_tx.rmt_mode = RMT_MODE_TX; if (rmt_config(&rmt_tx) == ESP_OK) { rmt_set_source_clk(info->tx_channel, RMT_BASECLK_APB); // only APB is supported by IDF 4.2 } |
Объявим макрос для значения общей продолжительности слота, а также макрос для длительности порога бездействия, который должен быть больше времени слота
1 2 3 4 5 |
#define OW_DEBUG //--------------------------------------------------------------------- #define OW_DURATION_SLOT 75 #define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2) //--------------------------------------------------------------------- |
Вернёмся в функцию _init, применим флаги к настройке канала, и, если всё нормально, то произведём аналогичным образом настройку приёмника
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
rmt_set_source_clk(info->tx_channel, RMT_BASECLK_APB); // only APB is supported by IDF 4.2 if (rmt_driver_install(rmt_tx.channel, 0, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK) { rmt_config_t rmt_rx = {0}; rmt_rx.channel = info->rx_channel; rmt_rx.gpio_num = gpio_num; rmt_rx.clk_div = 80; rmt_rx.mem_block_num = 1; rmt_rx.rmt_mode = RMT_MODE_RX; rmt_rx.rx_config.filter_en = true; rmt_rx.rx_config.filter_ticks_thresh = 30; rmt_rx.rx_config.idle_threshold = OW_DURATION_RX_IDLE; if (rmt_config(&rmt_rx) == ESP_OK) { rmt_set_source_clk(info->rx_channel, RMT_BASECLK_APB); // only APB is supported by IDF 4.2 } else { status = OWB_STATUS_HW_ERROR; ESP_LOGE(TAG, "failed to configure rx, uninstalling rmt driver on tx channel"); rmt_driver_uninstall(rmt_tx.channel); } } |
Ноль в длине буфера в функции rmt_driver_install гласит о том, что мы не используем кольцевой буфер для передатчика.
Применим также флаги к настройке канала приёмника
1 2 3 4 5 6 7 8 9 10 |
rmt_set_source_clk(info->rx_channel, RMT_BASECLK_APB); // only APB is supported by IDF 4.2 if (rmt_driver_install(rmt_rx.channel, 512, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) == ESP_OK) { rmt_get_ringbuf_handle(info->rx_channel, &info->rb); status = OWB_STATUS_OK; } else { ESP_LOGE(TAG, "failed to install rx driver"); } |
Здесь мы уже назначаем кольцевой буфер.
В следующей части урока мы закончим функцию инициализации, отправим в шину команду RESET и отследим ответ устройства.
Предыдущий урок Программирование МК ESP32 Следующая часть
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий