В предыдущей части урока мы подготовили несколько служебных функций и начали писать функцию инициализации устройства, в которой написали инициализацию приёмника и передатчика RMT.
Выйдем из условия установки флагов канала передатчика и в противном случае выведем соответствующее сообщение в терминал
1 2 3 4 5 6 7 |
rmt_driver_uninstall(rmt_tx.channel); } } else { ESP_LOGE(TAG, "failed to install tx driver"); } |
Затем выйдем из тела условия применения конфигурации передатчика и также в противном случае выведем соответствующее сообщение в терминал
1 2 3 4 5 6 7 |
ESP_LOGE(TAG, "failed to install tx driver"); } } else { ESP_LOGE(TAG, "failed to configure tx"); } |
Включим соответствующий бит в регистре порта для ножки GPIO
1 2 3 4 5 6 7 8 9 10 11 12 |
ESP_LOGE(TAG, "failed to configure tx"); } // attach GPIO to previous pin if (gpio_num < 32) { GPIO.enable_w1ts = (0x1 << gpio_num); } else { GPIO.enable1_w1ts.data = (0x1 << (gpio_num - 32)); } |
Включим режимы RMT приёмника и передатчика для ножек (вернее для одной ножки)
1 2 3 4 5 |
GPIO.enable1_w1ts.data = (0x1 << (gpio_num - 32)); } rmt_set_gpio(info->rx_channel, RMT_MODE_RX, gpio_num, false); rmt_set_gpio(info->tx_channel, RMT_MODE_TX, gpio_num, false); |
Включим направление на вход
1 2 3 4 |
rmt_set_pin(info->tx_channel, RMT_MODE_TX, gpio_num); // force pin direction to input to enable path to RX channel PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[gpio_num]); |
Включим режим открытого коллектора
1 2 3 4 |
PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[gpio_num]); // enable open drain GPIO.pin[gpio_num].pad_driver = 1; |
Ещё раз прошьём и запустим наш проект, а затем посмотрим результат в терминале
Теперь у нас нет никаких ошибок.
Добавим функцию, которая будет проверять, инициализирована ли у нас шина. Проверять она будет это по состоянию поля
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2) //--------------------------------------------------------------------- static bool _is_init(const OneWireBus * bus) { bool ok = false; if (bus != NULL) { if (bus->driver) { // OK ok = true; } else { ESP_LOGE(TAG, "bus is not initialised"); } } else { ESP_LOGE(TAG, "bus is NULL"); } return ok; } //--------------------------------------------------------------------- |
А ниже добавим функцию, которая будет включать или выключать режим проверки контрольной суммы в зависимости от значения второго параметра. Также будет инициализироваться соответствующее поле переменной структуры, но только уже здесь будет проверяться, инициализирована ли у нас шина
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//--------------------------------------------------------------------- owb_status owb_use_crc(OneWireBus * bus, bool use_crc) { owb_status status = OWB_STATUS_NOT_SET; if (!bus) { status = OWB_STATUS_PARAMETER_NULL; } else if (!_is_init(bus)) { status = OWB_STATUS_NOT_INITIALIZED; } else { bus->use_crc = use_crc; ESP_LOGD(TAG, "use_crc %d", bus->use_crc); status = OWB_STATUS_OK; } return status; } //--------------------------------------------------------------------- |
Создадим прототип для данной функции в заголовочном файле и вызовем её в функции app_main файла main.c
1 2 |
owb = owb_rmt_initialize(&rmt_driver_info, CONFIG_ONE_WIRE_GPIO, RMT_CHANNEL_1, RMT_CHANNEL_0); owb_use_crc(owb, true); // enable CRC check for ROM code |
В файле owb.h объявим тип структуры для структурированного хранения ROM-кода устройства
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
extern "C" { #endif //--------------------------------------------------------------------- typedef union { // Provides access via field names struct fields { uint8_t family[1]; ///< family identifier (1 byte, LSB - read/write first) uint8_t serial_number[6]; ///< serial number (6 bytes) uint8_t crc[1]; ///< CRC check byte (1 byte, MSB - read/write last) } fields; ///< Provides access via field names uint8_t bytes[8]; ///< Provides raw byte access } OneWireBus_ROMCode; //--------------------------------------------------------------------- |
Вернёмся в файл main.c и объявим макрос со значением максимального количества устройств на одной шине
1 2 3 4 |
static const char *TAG = "main"; //============================================================== #define MAX_DEVICES (8) //============================================================== |
В функции app_main объявим массив с ROM-кодами, забьём его нулями, а также объявим переменную, которая будет хранить в себе затем реальное количество обнаруженных устройств на шине
1 2 3 4 5 |
owb_use_crc(owb, true); // enable CRC check for ROM code // Find all connected devices printf("Find devices:n"); OneWireBus_ROMCode device_rom_codes[MAX_DEVICES] = {0}; int num_devices = 0; |
В файле owb.h объявим тип структуры, которая будет хранить некоторые параметры найденного устройства, включая и его ROM-код
1 2 3 4 5 6 7 8 9 10 |
} OneWireBus_ROMCode; //--------------------------------------------------------------------- typedef struct { OneWireBus_ROMCode rom_code; ///< Device ROM code int last_discrepancy; ///< Bit index that identifies from which bit the next search discrepancy check should start int last_family_discrepancy; ///< Bit index that identifies the last discrepancy within the first 8-bit family code of the ROM code int last_device_flag; ///< Flag to indicate previous search was the last device detected } OneWireBus_SearchState; //--------------------------------------------------------------------- |
В функции app_main файла main.c мы объявим переменную созданного нами типа, инициализируем её нулями, а также объявим переменную булевого типа
1 2 3 |
int num_devices = 0; OneWireBus_SearchState search_state = {0}; bool found = false; |
В файле owb.c выше функции _uninitialize добавим функцию, которая будет искать первое устройство на шине (или единственное, если оно будет одно)
1 2 3 4 5 6 7 8 9 |
//--------------------------------------------------------------------- owb_status owb_search_first(const OneWireBus * bus, OneWireBus_SearchState * state, bool * found_device) { bool result; owb_status status = OWB_STATUS_NOT_SET; return status; } //--------------------------------------------------------------------- |
К данной функции мы вернёмся позже.
А сейчас создадим на неё прототип в заголовочном файле и вызовем её в функции app_main файла main.c
1 2 |
bool found = false; owb_search_first(owb, &search_state, &found); |
Вернёмся в файл owb.c и выше функции owb_search_first добавим функцию поиска устройства на шине
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//--------------------------------------------------------------------- static owb_status _search(const OneWireBus * bus, OneWireBus_SearchState * state, bool * is_found) { bool search_result = false; owb_status status = OWB_STATUS_NOT_SET; status = OWB_STATUS_OK; *is_found = search_result; return status; } //--------------------------------------------------------------------- |
В файле owb.h объявим макрос функции создания контейнера с информацией об устройстве
1 2 3 4 5 6 7 |
owb_status (*read_bits)(const OneWireBus *bus, uint8_t *in, int number_of_bits_to_read); }; //--------------------------------------------------------------------- #define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );}) //--------------------------------------------------------------------- |
Вернёмся в файл owb.c и объявим подобный макрос для информации об устройстве
1 2 3 4 |
#define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2) //--------------------------------------------------------------------- #define info_of_driver(owb) container_of(owb, owb_rmt_driver_info, bus) //--------------------------------------------------------------------- |
Теперь поработаем с функцией перезагрузки _reset, в теле которой вызовем нашу функцию и результат запишем в переменную, указатель которой объявим
1 2 3 |
int res = OWB_STATUS_OK; owb_rmt_driver_info * i = info_of_driver(bus); |
Выше объявим массив ячеек памяти RMT из одного элемента для передачи
1 2 3 |
static owb_status _reset(const OneWireBus * bus, bool * is_present) { rmt_item32_t tx_items[1] = {0}; |
Объявим макрос для значения времени перезагрузки
1 2 3 |
#define OW_DEBUG //--------------------------------------------------------------------- #define OW_DURATION_RESET 480 |
Вернёмся в функцию _reset и присвоим полям данных ячейки памяти необходимые значения
1 2 3 4 5 6 |
owb_rmt_driver_info * i = info_of_driver(bus); tx_items[0].duration0 = OW_DURATION_RESET; tx_items[0].level0 = 0; tx_items[0].duration1 = 0; tx_items[0].level1 = 1; |
Получим пороговые значения
1 2 3 4 5 |
tx_items[0].level1 = 1; uint16_t old_rx_thresh = 0; rmt_get_rx_idle_thresh(i->rx_channel, &old_rx_thresh); rmt_set_rx_idle_thresh(i->rx_channel, OW_DURATION_RESET + 60); |
Выше функции _search добавим функцию освобождению буфера
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//--------------------------------------------------------------------- static void onewire_flush_rmt_rx_buf(const OneWireBus * bus) { void * p = NULL; size_t s = 0; owb_rmt_driver_info * i = info_of_driver(bus); while ((p = xRingbufferReceive(i->rb, &s, 0))) { ESP_LOGD(TAG, "flushing entry"); vRingbufferReturnItem(i->rb, p); } } //--------------------------------------------------------------------- |
Вернёмся в функцию _reset и вызовем нашу функцию
1 2 3 |
rmt_set_rx_idle_thresh(i->rx_channel, OW_DURATION_RESET + 60); onewire_flush_rmt_rx_buf(bus); |
Запустим канал приёмника
1 2 |
onewire_flush_rmt_rx_buf(bus); rmt_rx_start(i->rx_channel, true); |
Используя данные из ячейки памяти отправим импульс в канал
1 2 3 4 5 6 |
rmt_rx_start(i->rx_channel, true); if (rmt_write_items(i->tx_channel, tx_items, 1, true) == ESP_OK) { size_t rx_size = 0; } |
Вернёмся пока в функцию _search и вызовем функцию _reset, а также заполним параметры состояния
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// if the last call was not the last one if (!state->last_device_flag) { // 1-Wire reset bool is_present; bus->driver->reset(bus, &is_present); if (!is_present) { // reset the search state->last_discrepancy = 0; state->last_device_flag = false; state->last_family_discrepancy = 0; *is_found = false; return OWB_STATUS_OK; } } |
В функции owb_search_first при условии наличия параметров, а также инициализации шины, сохраним также состояние в поля и вызовем функцию _search
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
owb_status status = OWB_STATUS_NOT_SET; if (!bus || !state || !found_device) { status = OWB_STATUS_PARAMETER_NULL; } else if (!_is_init(bus)) { status = OWB_STATUS_NOT_INITIALIZED; } else { memset(&state->rom_code, 0, sizeof(state->rom_code)); state->last_discrepancy = 0; state->last_family_discrepancy = 0; state->last_device_flag = false; _search(bus, state, &result); status = OWB_STATUS_OK; *found_device = result; } |
Это пока не всё. Соберём код, прошьём контроллер, посмотрим, что у нас в терминале
Здесь вроде никаких плохих сообщений нет.
Также посмотрим, что у нас в анализе
Мы видим, что команда RESET у нас ушла, и также датчик тоже на неё ответил.
Значит идём в функцию _reset и будем ловить ответ от датчика.
1 2 |
size_t rx_size = 0; rmt_item32_t * rx_items = (rmt_item32_t *)xRingbufferReceive(i->rb, &rx_size, 100 / portTICK_PERIOD_MS); |
Определим количество записанных ячеек памяти и выведем в терминал содержимое их полей
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
rmt_item32_t * rx_items = (rmt_item32_t *)xRingbufferReceive(i->rb, &rx_size, 100 / portTICK_PERIOD_MS); if (rx_items) { if (rx_size >= (1 * sizeof(rmt_item32_t))) { #ifdef OW_DEBUG ESP_LOGI(TAG, "rx_size: %d", rx_size); for (int i = 0; i < (rx_size / sizeof(rmt_item32_t)); i++) { ESP_LOGI(TAG, "i: %d, level0: %d, duration %d", i, rx_items[i].level0, rx_items[i].duration0); ESP_LOGI(TAG, "i: %d, level1: %d, duration %d", i, rx_items[i].level1, rx_items[i].duration1); } #endif } } |
Соберём код, прошьём контроллер и посмотрим результат в терминале
Всё в пределах таймингов. Ответ от датчика получен.
Объявим локальную переменную булевого типа присутствия устройства на шине
1 2 |
rmt_item32_t tx_items[1] = {0}; bool _is_present = false; |
Определим, соответствуют ли тайминги своим пределам и на основе этого присвоим переменной значение
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
ESP_LOGI(TAG, "i: %d, level1: %d, duration %d", i, rx_items[i].level1, rx_items[i].duration1); } #endif // parse signal and search for presence pulse if ((rx_items[0].level0 == 0) && (rx_items[0].duration0 >= OW_DURATION_RESET - 2)) { if ((rx_items[0].level1 == 1) && (rx_items[0].duration1 > 0)) { if (rx_items[1].level0 == 0) { _is_present = true; } } } |
Выйдем из четырёх условий и после синтаксического анализа данных возвратим пробелы в кольцевой буфер
1 2 3 4 5 6 |
_is_present = true; } } } } vRingbufferReturnItem(i->rb, (void *)rx_items); |
Выйдем ещё из одного условия и в противном случае выведем в терминал соответствующее сообщение об ошибке и установим соответствующий статус
1 2 3 4 5 6 7 8 |
vRingbufferReturnItem(i->rb, (void *)rx_items); } else { // time out occurred, this indicates an unconnected / misconfigured bus ESP_LOGE(TAG, "rx_items == 0"); res = OWB_STATUS_HW_ERROR; } |
Выйдем ещё из двух условий и также в противном установим соответствующий статус
1 2 3 4 5 6 7 8 9 |
res = OWB_STATUS_HW_ERROR; } } else { // error in tx channel ESP_LOGE(TAG, "Error tx"); res = OWB_STATUS_HW_ERROR; } |
Остановим приёмник, установим порог тишины и выведем сообщение в терминал о присутствии устройства в случае отладки
1 2 3 4 5 6 7 8 9 |
res = OWB_STATUS_HW_ERROR; } rmt_rx_stop(i->rx_channel); rmt_set_rx_idle_thresh(i->rx_channel, old_rx_thresh); *is_present = _is_present; ESP_LOGD(TAG, "_is_present %d", _is_present); |
Для порядка ещё заполним тело функции _uninitialize
1 2 3 4 5 6 |
static owb_status _uninitialize(const OneWireBus *bus) { owb_rmt_driver_info * info = info_of_driver(bus); rmt_driver_uninstall(info->tx_channel); rmt_driver_uninstall(info->rx_channel); |
Соберём код, прошьём контроллер и посмотрим результат в терминале
Всё нормально, ошибок нет.
Вешать ещё один датчик на шину смысла нет, так как результат будет тот же самый. Смысл будет, когда начнём читать ROM-код.
Итак, на данном уроке мы, используя модуль RMT, начали работу с шиной 1-Wire. Нам удалось передать команду RESET и дождаться ответа датчика.
Всем спасибо за внимание!
Предыдущая часть Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добрый день! Спасибо за огромную работу!
Возникли сложности с компиляцией кода, выложенного в 41 уроке.
Строка 768 owb.c Не объявлена GPIO! наверное должна использоваться переменная из info->gpio? GPIO упоминается в этой строке первый раз.
И поскольку у меня esp-idf v5.1 получаю сообщение, что driver/rmt.h считается устаревшим с рекомендацией использовать на driver/rmt_rx.h и driver/rmt_tx.h.
Попробую довести довести до компиляции.
Добрый день.
Используем теперь в заголовочном файле для этого
#include «hal/gpio_ll.h»
Добрый день! Спасибо за проделанную просветительскую работу!
При попытке компиляции проекта (исходный код урока 41) появляется сообщение о том, что в owb.c GPIO не объявлена.
Есть ещё вопрос по более свежей версии esp-idf. Cообщение driver/rmt.h устарела, рекомендуют использовать driver/rmt_rx.h и driver/rmt_tx.h. Но простой замены недостаточно, поскольку в новых заголовочных файлах отсутствует тип RingbufHandle_t.
Спасибо ещё раз!