Продолжаем работать с модулем RMT и в прошлом занятии мы начали при помощи данного модуля работу с устройством, подключенным по однопроводной шине и работающим по протоколу 1-Wire. Но пока мы только произвели инициализацию протокола.
На данном уроке мы попробуем добиться полноправного поиска устройства, причём мы также узнаем, сколько у нас на шине присутствует устройств, работающих с протоколом 1-Wire, если таких устройств будет более одного.
Схема наша будет такая же, как и в прошлом уроке: отладочная плата с контроллером esp32, подключенный к ней датчик температуры DS18B20, а также логический анализатор, подключенный к сигнальной ножке датчика для анализа обмена данными по шине
Проект мы сделаем из проекта прошлого урока с именем RMT_ONEWIRE_INIT и назовём его RMT_ONEWIRE_SEARH_ROM.
Откроем наш проект в Espressif IDE и функции app_main файла main.c добавим цикл, в котором будем определять присутствие каждого устройства на шине
1 2 3 4 5 |
owb_search_first(owb, &search_state, &found); while (found) { char rom_code_s[17]; } |
Если при вызове функции owb_search_first в переменную found запишется false, то мы в данный цикл вообще не попадаем, а если true, то попадаем. Поэтому, если мы оставим всё так, как есть, то мы вообще никогда из цикла не выйдем. Тогда мы пока не будем проект прошивать, только соберём.
Если всё нормально собралось, то в файле owb.c ниже функции owb_search_first добавим ещё одну подобную функцию, которая будет искать следующее устройство на шине
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 |
//--------------------------------------------------------------------- owb_status owb_search_next(const OneWireBus * bus, OneWireBus_SearchState * state, bool * found_device) { owb_status status = OWB_STATUS_NOT_SET; bool result = false; if (!bus || !state || !found_device) { status = OWB_STATUS_PARAMETER_NULL; } else if (!_is_init(bus)) { status = OWB_STATUS_NOT_INITIALIZED; } else { _search(bus, state, &result); status = OWB_STATUS_OK; *found_device = result; } return status; } //--------------------------------------------------------------------- |
Здесь проверяется сначала, инициализирована ли шина, а в случае успеха (здесь это наоборот противный случай) устанавливается статус в OK и записывается в переменную.
В заголовочном файле на данную функцию объявим прототип, вернёмся в owb.c и ниже этой функции добавим ещё одну функцию, которая будет собирать в символьный массив ROM-код устройства
1 2 3 4 5 6 7 8 9 10 11 |
//--------------------------------------------------------------------- char * owb_string_from_rom_code(OneWireBus_ROMCode rom_code, char * buffer, size_t len) { for (int i = sizeof(rom_code.bytes) - 1; i >= 0; i--) { sprintf(buffer, "%02x", rom_code.bytes[i]); buffer += 2; } return buffer; } //--------------------------------------------------------------------- |
На данную функцию также объявим прототип в заголовочном файле и вызовем её в функции app_main файла main.c
1 2 |
char rom_code_s[17]; owb_string_from_rom_code(search_state.rom_code, rom_code_s, sizeof(rom_code_s)); |
В результате данного вызова ROM-код устройства пропишется в символьный массив rom_code_s.
Отобразим в терминале данный код и запишем его также в поле массива устройств
1 2 3 |
owb_string_from_rom_code(search_state.rom_code, rom_code_s, sizeof(rom_code_s)); printf(" %d : %s\n", num_devices, rom_code_s); device_rom_codes[num_devices] = search_state.rom_code; |
Инкрементируем переменную количества устройств на шину и вызовем функцию поиска следующего устройства
1 2 3 |
device_rom_codes[num_devices] = search_state.rom_code; ++num_devices; owb_search_next(owb, &search_state, &found); |
Выйдем из цикла и отобразим в терминале количество найденных устройств на шине
1 2 3 |
owb_search_next(owb, &search_state, &found); } printf("Found %d device%s\n", num_devices, num_devices == 1 ? "" : "s"); |
Сейчас также нет смысла проверять работоспособность написанного кода, так как у нас ещё в owb.c много чего не дописано. Перейдём пока что в файл owb.h и объявим там макрос с кодом команды поиска устройства на шине
1 2 3 4 |
#endif //--------------------------------------------------------------------- #define OWB_ROM_SEARCH 0xF0 ///< Perform Search ROM cycle to identify devices on the bus //--------------------------------------------------------------------- |
Перейдём в файл owb.c и в функции _search выйдем из условия наличия устройства на шине и попытаемся передать код функции в шину
1 2 3 4 5 6 |
*is_found = false; return OWB_STATUS_OK; } // issue the search command bus->driver->write_bits(bus, OWB_ROM_SEARCH, 8); |
Только, конечно же, ничего у нас никуда не передастся, так как функция _write_bits у нас ещё не дописана.
Объявим макрос максимального количество бит в слоте
1 2 |
#define OW_DURATION_RESET 480 #define MAX_BITS_PER_SLOT (8) |
В функции _write_bits объявим массив для содержимого ячеек памяти, который мы будем передавать в шину
1 2 3 |
static owb_status _write_bits(const OneWireBus * bus, uint8_t out, int number_of_bits_to_write) { rmt_item32_t tx_items[MAX_BITS_PER_SLOT + 1] = {0}; |
Объявим указатель на переменную типа структуры информации о шине, присвоив ему адрес из входного параметра
1 2 |
rmt_item32_t tx_items[MAX_BITS_PER_SLOT + 1] = {0}; owb_rmt_driver_info * info = info_of_driver(bus); |
Если мы попытаемся передать больше бит, чем возможно, то выйдем из функции с соответствующим статусом
1 2 3 4 5 6 |
owb_rmt_driver_info * info = info_of_driver(bus); if (number_of_bits_to_write > MAX_BITS_PER_SLOT) { return OWB_STATUS_TOO_MANY_BITS; } |
Объявим макросы с таймингами уровней для передачи бит
1 2 3 4 5 |
#define OW_DURATION_SLOT 75 #define OW_DURATION_1_LOW 2 #define OW_DURATION_1_HIGH (OW_DURATION_SLOT - OW_DURATION_1_LOW) #define OW_DURATION_0_LOW 65 #define OW_DURATION_0_HIGH (OW_DURATION_SLOT - OW_DURATION_0_LOW) |
Выше функции _write_bits добавим функцию , которая будет преобразовывать бит в значение, которое будет заноситься в ячейку памяти для дальнейшей передачи в шину
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//--------------------------------------------------------------------- static rmt_item32_t _encode_write_slot(uint8_t val) { rmt_item32_t item = {0}; item.level0 = 0; item.level1 = 1; if (val) { // write "1" slot item.duration0 = OW_DURATION_1_LOW; item.duration1 = OW_DURATION_1_HIGH; } else { // write "0" slot item.duration0 = OW_DURATION_0_LOW; item.duration1 = OW_DURATION_0_HIGH; } return item; } //--------------------------------------------------------------------- |
В функции _write_bits занесём в цикле значения всех передаваемых бит в соответствующие элементы массива
1 2 3 4 5 6 7 8 9 |
return OWB_STATUS_TOO_MANY_BITS; } // write requested bits as pattern to TX buffer for (int i = 0; i < number_of_bits_to_write; i++) { tx_items[i] = _encode_write_slot(out & 0x01); out >>= 1; } |
Занесём в последнюю ячейку маркер окончания передачи, не зря же мы массив создавали на один элемент больше
1 2 3 4 5 6 |
out >>= 1; } // end marker tx_items[number_of_bits_to_write].level0 = 1; tx_items[number_of_bits_to_write].duration0 = 0; |
Удалим вот эту сроку
owb_status status = OWB_STATUS_OK;
Вместо неё добавим другую
owb_status status = OWB_STATUS_NOT_SET;
Попытаемся передать наши данные в шину
1 2 3 4 5 6 7 8 9 10 11 |
owb_status status = OWB_STATUS_NOT_SET; if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_write+1, true) == ESP_OK) { status = OWB_STATUS_OK; } else { status = OWB_STATUS_HW_ERROR; ESP_LOGE(TAG, "rmt_write_items() failed"); } |
В случае неудачи выведем соответствующее сообщение в терминал.
В принципе, сейчас мы уже можем собрать наш код и прошить его в контроллер и посмотреть, передадутся ли у нас данные в шину
Не обращаем внимание на то, что у нас 0 устройств, это всё потому, что ещё не дописан код, самое главное, что не выскочили ошибки.
Теперь посмотрим логический анализ
Команда успешно передана в шину.
Дальнейшая задача — прочитать код найденного устройства из шины.
В функции _search объявим переменную для номера бита ROM-кода и две переменные для сравнения бит идентификаторов устройств
1 2 3 4 5 |
static owb_status _search(const OneWireBus * bus, OneWireBus_SearchState * state, bool * is_found) { int rom_byte_number = 0; uint8_t id_bit = 0; uint8_t cmp_id_bit = 0; |
Организуем цикл, в котором мы будем считывать и сравнивать биты идентификаторов устройств
1 2 3 4 5 6 7 |
bus->driver->write_bits(bus, OWB_ROM_SEARCH, 8); // loop to do the search do { id_bit = cmp_id_bit = 0; } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 |
Подобным занятием мы занимались в уроке 94 по контроллерам STM32.
Перейдём в функцию _read_bits и также создадим указатель на свойства устройства и присвоим адрес из входного параметра
1 2 3 |
int res = OWB_STATUS_OK; owb_rmt_driver_info *info = info_of_driver(bus); |
Затем также проверим на то, что мы не превысили количество бит в слоте
1 2 3 4 5 6 7 |
owb_rmt_driver_info *info = info_of_driver(bus); if (number_of_bits_to_read > MAX_BITS_PER_SLOT) { ESP_LOGE(TAG, "_read_bits() OWB_STATUS_TOO_MANY_BITS"); return OWB_STATUS_TOO_MANY_BITS; } |
Также объявим переменную для ячеек памяти для передачи в шину
1 2 3 |
static owb_status _read_bits(const OneWireBus * bus, uint8_t *in, int number_of_bits_to_read) { rmt_item32_t tx_items[MAX_BITS_PER_SLOT + 1] = {0}; |
Выше добавим функцию, которая подготовит ячейку памяти
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//--------------------------------------------------------------------- static rmt_item32_t _encode_read_slot(void) { rmt_item32_t item = {0}; // construct pattern for a single read time slot item.level0 = 0; item.duration0 = OW_DURATION_1_LOW; // shortly force 0 item.level1 = 1; item.duration1 = OW_DURATION_1_HIGH; // release high and finish slot return item; } //--------------------------------------------------------------------- |
Вернёмся в функцию _read_bits и подготовим все ячейки памяти
1 2 3 4 5 6 7 8 |
return OWB_STATUS_TOO_MANY_BITS; } // generate requested read slots for (int i = 0; i < number_of_bits_to_read; i++) { tx_items[i] = _encode_read_slot(); } |
Подготовим ячейку концевого маркера
1 2 3 4 5 6 |
tx_items[i] = _encode_read_slot(); } // end marker tx_items[number_of_bits_to_read].level0 = 1; tx_items[number_of_bits_to_read].duration0 = 0; |
Освободим буфер канала чтения
1 2 3 |
tx_items[number_of_bits_to_read].duration0 = 0; onewire_flush_rmt_rx_buf(bus); |
Запустим канал
1 2 3 |
onewire_flush_rmt_rx_buf(bus); rmt_rx_start(info->rx_channel, true); |
Далее попытаемся принять данные. Для этого мы используем также функцию передачи, но затем мы читаем данные из кольцевого буфера. Для начала пока получим указатель на него
1 2 3 4 5 6 7 |
rmt_rx_start(info->rx_channel, true); if (rmt_write_items(info->tx_channel, tx_items, number_of_bits_to_read+1, true) == ESP_OK) { size_t rx_size = 0; rmt_item32_t *rx_items = (rmt_item32_t *)xRingbufferReceive(info->rb, &rx_size, 100 / portTICK_PERIOD_MS); } |
Пока отобразим в терминале данные ячеек буфера в структурированном виде
1 2 3 4 5 6 7 8 9 10 11 12 |
rmt_item32_t *rx_items = (rmt_item32_t *)xRingbufferReceive(info->rb, &rx_size, 100 / portTICK_PERIOD_MS); if (rx_items) { #ifdef OW_DEBUG for (int i = 0; i < rx_size / 4; i++) { ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level0, rx_items[i].duration0); ESP_LOGI(TAG, "level: %d, duration %d", rx_items[i].level1, rx_items[i].duration1); } #endif } |
Объявим переменную для хранения считанных данных из буфера
1 2 |
rmt_item32_t tx_items[MAX_BITS_PER_SLOT + 1] = {0}; uint8_t read_data = 0; |
Объявим макрос длины сэмпла
1 2 |
#define OW_DURATION_RX_IDLE (OW_DURATION_SLOT + 2) #define OW_DURATION_SAMPLE (15-2) |
В функции _read_bits запишем значение побитно из буфера в переменную
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#endif if (rx_size >= number_of_bits_to_read * sizeof(rmt_item32_t)) { for (int i = 0; i < number_of_bits_to_read; i++) { read_data >>= 1; // parse signal and identify logical bit if (rx_items[i].level1 == 1) { if ((rx_items[i].level0 == 0) && (rx_items[i].duration0 < OW_DURATION_SAMPLE)) { // rising edge occured before 15us -> bit 1 read_data |= 0x80; } } } read_data >>= 8 - number_of_bits_to_read; } |
Выйдем из одного условия и возвратим оставшиеся элементы буфера
1 2 3 |
read_data >>= 8 - number_of_bits_to_read; } vRingbufferReturnItem(info->rb, (void *)rx_items); |
Выйдем ещё из тела ещё одного условия и напишем противный данному условию случай, в котором отобразим в терминале соответствующее сообщение и присвоим соответствующий статус
1 2 3 4 5 6 7 8 |
vRingbufferReturnItem(info->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 |
res = OWB_STATUS_HW_ERROR; } rmt_rx_stop(info->rx_channel); *in = read_data; |
В функции _search в цикле прочитаем два бита для сравнения
1 2 3 4 |
id_bit = cmp_id_bit = 0; // read a bit and its complement bus->driver->read_bits(bus, &id_bit, 1); bus->driver->read_bits(bus, &cmp_id_bit, 1); |
Объявим две локальных переменных
1 2 3 4 |
static owb_status _search(const OneWireBus * bus, OneWireBus_SearchState * state, bool * is_found) { int id_bit_number = 1; int last_zero = 0; |
Объявим ещё две
1 2 3 |
uint8_t cmp_id_bit = 0; uint8_t rom_byte_mask = 1; uint8_t search_direction = 0; |
И ещё одну для контрольной суммы
1 2 |
bool search_result = false; uint8_t crc8 = 0; |
Вернёмся в наш цикл и добавим код подобный коду 94 урока по STM32 для считывания битов ROM-кодов
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
bus->driver->read_bits(bus, &cmp_id_bit, 1); // check for no devices on 1-wire (signal level is high in both bit reads) if (id_bit && cmp_id_bit) { break; } else { // all devices coupled have 0 or 1 if (id_bit != cmp_id_bit) { search_direction = (id_bit) ? 1 : 0; // bit write value for search } else { // if this discrepancy if before the Last Discrepancy // on a previous next then pick the same as last time if (id_bit_number < state->last_discrepancy) { search_direction = ((state->rom_code.bytes[rom_byte_number] & rom_byte_mask) > 0); } else { // if equal to last pick 1, if not then pick 0 search_direction = (id_bit_number == state->last_discrepancy); } // if 0 was picked then record its position in LastZero if (search_direction == 0) { last_zero = id_bit_number; // check for Last discrepancy in family if (last_zero < 9) { state->last_family_discrepancy = last_zero; } } } // set or clear the bit in the ROM byte rom_byte_number // with mask rom_byte_mask if (search_direction == 1) { state->rom_code.bytes[rom_byte_number] |= rom_byte_mask; } else { state->rom_code.bytes[rom_byte_number] &= ~rom_byte_mask; } // serial number search direction write bit bus->driver->write_bits(bus, search_direction, 1); // increment the byte counter id_bit_number // and shift the mask rom_byte_mask id_bit_number++; rom_byte_mask <<= 1; // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask if (rom_byte_mask == 0) { crc8 = owb_crc8_byte(crc8, state->rom_code.bytes[rom_byte_number]); // accumulate the CRC rom_byte_number++; rom_byte_mask = 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 25 26 |
#define info_of_driver(owb) container_of(owb, owb_rmt_driver_info, bus) //--------------------------------------------------------------------- static uint8_t _calc_crc(uint8_t crc, uint8_t data) { static const uint8_t table[256] = { 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 }; return table[crc ^ data]; } //--------------------------------------------------------------------- |
Выше функции _search добавим ещё одну подобную функцию, которая будет являться лишь оболочкой для функции, которую мы добавили ранее
1 2 3 4 5 6 |
//--------------------------------------------------------------------- uint8_t owb_crc8_byte(uint8_t crc, uint8_t data) { return _calc_crc(crc, data); } //--------------------------------------------------------------------- |
Вернёмся в функцию _search и, если считанных битов достаточное количество, также если контрольная сумма ненулевая, то запишем в переменную search_result значение true, также в теле условия мы проверим наличие бита несоответствия. Если он нулевой, то установим флаг определения последнего устройства на шине
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
} while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 // if the search was successful then if (!((id_bit_number < 65) || (crc8 != 0))) { // search successful so set LastDiscrepancy,LastDeviceFlag,search_result state->last_discrepancy = last_zero; // check for last device if (state->last_discrepancy == 0) { state->last_device_flag = true; } search_result = true; } |
Выйдем из всех условий и, если устройство не найдено, то сбросим флаги
1 2 3 4 5 6 7 8 9 10 11 12 |
search_result = true; } } // if no device found then reset counters so next 'search' will be like a first if (!search_result || !state->rom_code.bytes[0]) { state->last_discrepancy = 0; state->last_device_flag = false; state->last_family_discrepancy = 0; search_result = false; } |
Соберём код, прошьём контроллер и посмотрим результат
У нас найдено одно устройство и его ROM-код считан и отображён в терминале.
Закомментируем режим отладки, чтобы в терминале не мешали значения полей ячеек памяти, а также чтобы не тормозился процесс работы с шиной
//#define OW_DEBUG
Пересоберём код, прошьём контроллер и снова посмотрим результат в терминале
Теперь нет ничего лишнего.
Посмотрим также результат в программе логического анализа
Там также всё корректно.
Подключим ещё один датчик к контроллеру, обычный, без провода
Перезагрузим контроллер и посмотрим результат
Оба устройства отлично видятся.
Подключим третий датчик
Перезагрузим контроллер и посмотрим результат
Все три устройства видны, коды их считаны.
Итак, на данном уроке нам удалось определить количество устройств на шине 1-Wire и считать их ROM-коды с использованием модуля RMT.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Датчик температуры DS18B20 в экране с проводом можно приобрести здесь DS18B20
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Огромнейшее спасибо за очень познавательные и полезные уроки. Не так давно перешёл на ESP32. Пытаюсь повторить все, что Вы показываете. Были найдены несколько несоответствий в текстовом описании уроков. Если интересно, то могу предложить вариант тестирования (естественно, совершенно безвозмездно и, конечно, тех статей, на которые имею в наличии соответствующее железо, т.е. пока только без TFT дисплея.)
Здравствуйте! Не так давно заинтересовался ESP32. Уроки оказались очень ко времени и полезными, за что отдельное спасибо! Пытаюсь повторить каждый из них по текстовому описанию. Обнаружил несколько несоответствий в описании. Если интересно, то предлагаю проводить тестирование уроков (без коммерции) с предоставлением результатов с целью устранения ошибок и улучшения сайта. Также есть ещё предложения по улучшению подачи информации для лучшего понимания новичками, такими каким являюсь и я.