В данной статье мы рассмотрим подключение датчика температуры DS18B20 к ESP32 с помощью библиотеки ESP-IDF. Будет кратко описано устройство датчика DS18B20, его распиновка, подключение к плате ESP32, а также будет показана настройка проекта в VS Code с расширением ESP-IDF для сбора данных с датчика и их отображения как на консоли, так и на OLED-дисплее SSD1306.
Также ранее на нашем сайте мы рассматривали подключение датчика температуры DS18B20 к другим микроконтроллерам и платам:
- к плате Arduino;
- к плате Raspberry Pi Pico;
- к плате Raspberry Pi;
- к плате STM32F103C8 (Blue Pill);
- к модулю ESP8266;
- к микроконтроллеру PIC.
Датчик DS18B20
Датчик температуры DS18B20 широко используется в электронных проектах, где для сбора данных используется один провод, поэтому он позиционируется как программируемый по одному проводу. Он использует один вывод GPIO микроконтроллера для вывода текущего показания температуры окружающей среды. Используя минимальное количество выводов, мы можем удобно получать доступ к нескольким показаниям температуры, подключая датчики DS18B20 к одному и тому же выводу GPIO.
В таблице ниже приведены некоторые ключевые характеристики датчика.
| Рабочее напряжение | 3-5 В |
| Диапазон температур | от -55°C до +125°C |
| Точность | ±0,5°C |
| Разрешение выходного сигнала | 9-12 бит |
Датчик DS18B20 выпускается в нескольких вариантах. Один из них — широко используемый датчик, другой — водонепроницаемая версия, и, наконец, модульная версия.
Нормальный датчик DS18B20 |
Водонепроницаемый датчик DS18B20 |
Модуль DS18B20 |
Обратите внимание, что все три варианта датчика имеют три вывода: два вывода питания (VCC и GND) и один вывод данных.
| VCC | Это вывод питания датчика. Напряжение питания 3,3-5 В. |
| Data | Контакт Data соединен с контактом GPIO микроконтроллера, который затем будет выводить показания датчика. |
| Ground | Общий провод (земля) |
Как видите, контактов у датчика мало, поэтому с точки зрения аппаратной части его подключение обычно не вызывает никаких проблем.
Подключение DS18B20 к ESP32
Для подключения платы ESP32 к датчику DS18B20 нам понадобятся следующие компоненты.
- Плата ESP32.
- Датчик DS18B20.
- Подтягивающий резистор 4,7 кОм.
- Соединительные провода.
- Макетная плата.
Датчик DS18B20 может включаться в двух режимах: обычном и паразитном. В обычном режиме датчик питается от внешнего источника через вывод VCC и резистор 4,7 кОм. Однако в паразитном режиме датчик питается от линии данных, поэтому внешний источник питания не требуется. Мы будем питать наш датчик DS18B20 в обычном режиме.
В таблице ниже показано подключение трех контактов датчика DS18B20 к плате ESP32.
| DS18B20 | ESP32 |
| VCC | 3.3V / Vin |
| Data | GPIO4 |
| GND | GND |
Подключите вывод VCC датчика к выводу 3,3 В или Vin платы ESP32 и соедините заземление обоих устройств. Кроме того, подключите вывод данных датчика к соответствующему выводу GPIO платы ESP32, который можно настроить как выходной вывод. Обратите внимание, что выводы GPIO34-39 являются только входными и не могут использоваться для управления однопроводной шиной. Мы используем GPIO4 для подключения к выводу данных через подтягивающий резистор 4,7 кОм. Один конец резистора будет подключен к выходу данных, а другой — к источнику питания.
Обратите внимание, что если вы используете модуль DS18B20, то вам не нужно добавлять внешний подтягивающий резистор. Это связано с тем, что модуль уже оснащен датчиком и встроенным подтягивающим резистором.
Следуйте приведенной ниже принципиальной схеме, чтобы соединить два устройства.

Измерение показаний DS18B20 с помощью ESP32 и ESP-IDF
Мы создадим проект в VS Code с расширением ESP-IDF. Код будет считывать количество датчиков DS18B20, подключенных к шине OneWire, и выводить показания температуры каждую секунду.
Создание примера проекта
Скачайте код в виде ZIP-архива по следующей ссылке.
Нажмите Code > Download ZIP. Затем распакуйте все файлы. Теперь откройте VS Code, перейдите в меню File > Open Folder и выберите папку с распакованными файлами, чтобы открыть её в VS Code с расширением ESP-IDF.
Это откроет проект во вкладке EXPLORER («Проводник»). Внутри папки нашего проекта находится несколько папок.
Для начала добавим необходимые заголовочные файлы для библиотек, требуемых для этого проекта. Откройте две папки, esp32-ds18b20 и esp32-owb , в папке components и добавьте необходимые файлы.
- В папку esp32-ds18b20 добавьте файлы, доступные по этой ссылке. Это совместимая с ESP32 библиотека на языке C для цифрового термометра Maxim Integrated DS18B20 с программируемым разрешением и однопроводным подключением.
- В папку esp32-owb добавьте все файлы, доступные по этой ссылке. Это драйвер шины Maxim One Wire для ESP32.

Настройки MenuConfig в ESP-IDF для работы с DS18B20
Для начала перейдем к menuconfig. Откройте редактор конфигурации ESP-IDF SDK. Прокрутите вниз и откройте конфигурацию esp32-ds18b20-example. Здесь мы можем задать номер однопроводного GPIO, который будет использоваться для подключения к выводу данных датчика. По умолчанию он установлен как GPIO4, что нас вполне устраивает. Также есть возможность включить сильную подтяжку через GPIO. Если вы хотите внести какие-либо изменения в конфигурацию, вы можете сделать это и затем нажать кнопку «Сохранить», расположенную вверху. Мы оставим настройки по умолчанию.
Код ESP32 для отображения показаний DS18B20
Теперь перейдите к файлу app_main.c. В папке main находится исходный код, то есть файл main.c будет расположен именно там. Перейдите в папку main > app_main.c и откройте его. Откроется следующий код.
|
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
/* * MIT License * * Copyright (c) 2017 David Antliff * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" #include "owb.h" #include "owb_rmt.h" #include "ds18b20.h" #define GPIO_DS18B20_0 (CONFIG_ONE_WIRE_GPIO) #define MAX_DEVICES (8) #define DS18B20_RESOLUTION (DS18B20_RESOLUTION_12_BIT) #define SAMPLE_PERIOD (1000) // milliseconds _Noreturn void app_main() { // Override global log level esp_log_level_set("*", ESP_LOG_INFO); // To debug, use 'make menuconfig' to set default Log level to DEBUG, then uncomment: //esp_log_level_set("owb", ESP_LOG_DEBUG); //esp_log_level_set("ds18b20", ESP_LOG_DEBUG); // Stable readings require a brief period before communication vTaskDelay(2000.0 / portTICK_PERIOD_MS); // Create a 1-Wire bus, using the RMT timeslot driver OneWireBus * owb; owb_rmt_driver_info rmt_driver_info; owb = owb_rmt_initialize(&rmt_driver_info, GPIO_DS18B20_0, RMT_CHANNEL_1, RMT_CHANNEL_0); 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; OneWireBus_SearchState search_state = {0}; bool found = false; owb_search_first(owb, &search_state, &found); while (found) { char rom_code_s[17]; 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; ++num_devices; owb_search_next(owb, &search_state, &found); } printf("Found %d device%s\n", num_devices, num_devices == 1 ? "" : "s"); // In this example, if a single device is present, then the ROM code is probably // not very interesting, so just print it out. If there are multiple devices, // then it may be useful to check that a specific device is present. if (num_devices == 1) { // For a single device only: OneWireBus_ROMCode rom_code; owb_status status = owb_read_rom(owb, &rom_code); if (status == OWB_STATUS_OK) { char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; owb_string_from_rom_code(rom_code, rom_code_s, sizeof(rom_code_s)); printf("Single device %s present\n", rom_code_s); } else { printf("An error occurred reading ROM code: %d", status); } } else { // Search for a known ROM code (LSB first): // For example: 0x1502162ca5b2ee28 OneWireBus_ROMCode known_device = { .fields.family = { 0x28 }, .fields.serial_number = { 0xee, 0xb2, 0xa5, 0x2c, 0x16, 0x02 }, .fields.crc = { 0x15 }, }; char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; owb_string_from_rom_code(known_device, rom_code_s, sizeof(rom_code_s)); bool is_present = false; owb_status search_status = owb_verify_rom(owb, known_device, &is_present); if (search_status == OWB_STATUS_OK) { printf("Device %s is %s\n", rom_code_s, is_present ? "present" : "not present"); } else { printf("An error occurred searching for known device: %d", search_status); } } // Create DS18B20 devices on the 1-Wire bus DS18B20_Info * devices[MAX_DEVICES] = {0}; for (int i = 0; i < num_devices; ++i) { DS18B20_Info * ds18b20_info = ds18b20_malloc(); // heap allocation devices[i] = ds18b20_info; if (num_devices == 1) { printf("Single device optimisations enabled\n"); ds18b20_init_solo(ds18b20_info, owb); // only one device on bus } else { ds18b20_init(ds18b20_info, owb, device_rom_codes[i]); // associate with bus and device } ds18b20_use_crc(ds18b20_info, true); // enable CRC check on all reads ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION); } // // Read temperatures from all sensors sequentially // while (1) // { // printf("\nTemperature readings (degrees C):\n"); // for (int i = 0; i < num_devices; ++i) // { // float temp = ds18b20_get_temp(devices[i]); // printf(" %d: %.3f\n", i, temp); // } // vTaskDelay(1000 / portTICK_PERIOD_MS); // } // Check for parasitic-powered devices bool parasitic_power = false; ds18b20_check_for_parasite_power(owb, ¶sitic_power); if (parasitic_power) { printf("Parasitic-powered devices detected"); } // In parasitic-power mode, devices cannot indicate when conversions are complete, // so waiting for a temperature conversion must be done by waiting a prescribed duration owb_use_parasitic_power(owb, parasitic_power); #ifdef CONFIG_ENABLE_STRONG_PULLUP_GPIO // An external pull-up circuit is used to supply extra current to OneWireBus devices // during temperature conversions. owb_use_strong_pullup_gpio(owb, CONFIG_STRONG_PULLUP_GPIO); #endif // Read temperatures more efficiently by starting conversions on all devices at the same time int errors_count[MAX_DEVICES] = {0}; int sample_count = 0; if (num_devices > 0) { TickType_t last_wake_time = xTaskGetTickCount(); while (1) { ds18b20_convert_all(owb); // In this application all devices use the same resolution, // so use the first device to determine the delay ds18b20_wait_for_conversion(devices[0]); // Read the results immediately after conversion otherwise it may fail // (using printf before reading may take too long) float readings[MAX_DEVICES] = { 0 }; DS18B20_ERROR errors[MAX_DEVICES] = { 0 }; for (int i = 0; i < num_devices; ++i) { errors[i] = ds18b20_read_temp(devices[i], &readings[i]); } // Print results in a separate loop, after all have been read printf("\nTemperature readings (degrees C): sample %d\n", ++sample_count); for (int i = 0; i < num_devices; ++i) { if (errors[i] != DS18B20_OK) { ++errors_count[i]; } printf(" %d: %.1f %d errors\n", i, readings[i], errors_count[i]); } vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS); } } else { printf("\nNo DS18B20 devices detected!\n"); } // clean up dynamically allocated data for (int i = 0; i < num_devices; ++i) { ds18b20_free(&devices[i]); } owb_uninitialize(owb); printf("Restarting now.\n"); fflush(stdout); vTaskDelay(1000 / portTICK_PERIOD_MS); esp_restart(); } |
Как работает код?
Для начала мы подключим необходимые для этого проекта библиотеки. Сюда входят библиотека логирования ESP, библиотека DS18B20, драйвер шины OneWire и библиотеки FreeRTOS.
|
1 2 3 4 5 6 7 8 |
#include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" #include "owb.h" #include "owb_rmt.h" #include "ds18b20.h" |
Следующие переменные определяют некоторые параметры, включая количество однопроводных GPIO, максимальное количество устройств, разрешение датчика и период времени между измерениями. Используются все значения по умолчанию.
|
1 2 3 4 |
#define GPIO_DS18B20_0 (CONFIG_ONE_WIRE_GPIO) #define MAX_DEVICES (8) #define DS18B20_RESOLUTION (DS18B20_RESOLUTION_12_BIT) #define SAMPLE_PERIOD (1000) // milliseconds |
Внутри функции app_main() мы сначала создадим однопроводную шину через драйвер временного слота RMT. Здесь OneWireBus — это структура, которая хранит параметры однопроводной шины для одного экземпляра. owb_rmt_driver_info содержит информацию о драйвере RMT. Затем мы инициализируем драйвер RMT, вызывая функцию owb_rmt_initialize(). Эта функция принимает четыре параметра: указатель на структуру owb_rmt_driver_info, номер однопроводного GPIO, канал RMT для передачи и канал RMT для приема. Наконец, мы включаем проверку CRC для кода ПЗУ, используя функцию owb_use_crc() и указывая в качестве первого параметра результат функции owb_rmt_initialize(), а в качестве второго параметра — значение 'true'.
|
1 2 3 4 |
OneWireBus * owb; owb_rmt_driver_info rmt_driver_info; owb = owb_rmt_initialize(&rmt_driver_info, GPIO_DS18B20_0, RMT_CHANNEL_1, RMT_CHANNEL_0); owb_use_crc(owb, true); |
Следующий шаг — найти все подключенные устройства к однопроводной шине и вывести их номера вместе с кодом ПЗУ на консоль. В нашем случае к однопроводной шине подключен только один датчик, поэтому количество устройств на экране будет равно 1.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
printf("Find devices:\n"); OneWireBus_ROMCode device_rom_codes[MAX_DEVICES] = {0}; int num_devices = 0; OneWireBus_SearchState search_state = {0}; bool found = false; owb_search_first(owb, &search_state, &found); while (found) { char rom_code_s[17]; 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; ++num_devices; owb_search_next(owb, &search_state, &found); } printf("Found %d device%s\n", num_devices, num_devices == 1 ? "" : "s"); |
Далее следует условие if-else для проверки количества устройств: одно устройство или несколько. Если присутствует только одно устройство, оно будет выведено на консоль вместе с его ПЗУ-кодом.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if (num_devices == 1) { // For a single device only: OneWireBus_ROMCode rom_code; owb_status status = owb_read_rom(owb, &rom_code); if (status == OWB_STATUS_OK) { char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; owb_string_from_rom_code(rom_code, rom_code_s, sizeof(rom_code_s)); printf("Single device %s present\n", rom_code_s); } else { printf("An error occurred reading ROM code: %d", status); } } |
Однако, если присутствует несколько устройств, вы можете выполнить поиск конкретного устройства по его ПЗУ, чтобы проверить, присутствует оно или нет.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
else { // Search for a known ROM code (LSB first): // For example: 0x1502162ca5b2ee28 OneWireBus_ROMCode known_device = { .fields.family = { 0x28 }, .fields.serial_number = { 0xee, 0xb2, 0xa5, 0x2c, 0x16, 0x02 }, .fields.crc = { 0x15 }, }; char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; owb_string_from_rom_code(known_device, rom_code_s, sizeof(rom_code_s)); bool is_present = false; owb_status search_status = owb_verify_rom(owb, known_device, &is_present); if (search_status == OWB_STATUS_OK) { printf("Device %s is %s\n", rom_code_s, is_present ? "present" : "not present"); } else { printf("An error occurred searching for known device: %d", search_status); } } |
После определения количества устройств мы создадим устройства DS18B20 на однопроводной шине в соответствии с этим количеством. В случае подключения одного устройства будет включена оптимизация для одного устройства, и датчик будет инициализирован с помощью функции ds18b20_init_solo(). В противном случае, если подключено несколько устройств, будет вызвана функция ds18b20_init() для инициализации датчиков в соответствии с кодами ПЗУ устройств. Кроме того, будет включена проверка CRC для всех операций чтения, а также будет установлено разрешение.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
DS18B20_Info * devices[MAX_DEVICES] = {0}; for (int i = 0; i < num_devices; ++i) { DS18B20_Info * ds18b20_info = ds18b20_malloc(); // heap allocation devices[i] = ds18b20_info; if (num_devices == 1) { printf("Single device optimisations enabled\n"); ds18b20_init_solo(ds18b20_info, owb); // only one device on bus } else { ds18b20_init(ds18b20_info, owb, device_rom_codes[i]); // associate with bus and device } ds18b20_use_crc(ds18b20_info, true); // enable CRC check on all reads ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION); } |
Следующие строки кода используются для обнаружения паразитных источников питания с помощью функции ds18b20_check_for_parasite_power(). Если присутствуют устройства с паразитным питанием, на консоль будет выведено соответствующее сообщение.
|
1 2 3 4 5 6 7 |
bool parasitic_power = false; ds18b20_check_for_parasite_power(owb, ¶sitic_power); if (parasitic_power) { printf("Parasitic-powered devices detected"); } owb_use_parasitic_power(owb, parasitic_power); |
После этого показания температуры считываются с датчика после одновременного выполнения преобразований на всех устройствах. Функция ds18b20_read_temp() используется для считывания последнего показания температуры с датчика. Эта функция принимает два параметра: первый — указатель на экземпляр информации об устройстве, а второй — указатель на измеренное значение в градусах Цельсия, возвращаемое устройством. Она возвращает DS18B20_OK, если все прошло успешно, в противном случае генерируется ошибка. Эта функция вызывается внутри цикла for, который будет выполняться для всего количества присутствующих устройств. После считывания всех показаний температуры значения выводятся на консоль в градусах Цельсия вместе с количеством ошибок, если таковые имеются. Показания будут получаться каждую секунду. Если датчик DS18B20 не найден, на консоль будет выведено соответствующее сообщение.
|
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 |
int errors_count[MAX_DEVICES] = {0}; int sample_count = 0; if (num_devices > 0) { TickType_t last_wake_time = xTaskGetTickCount(); while (1) { ds18b20_convert_all(owb); // In this application all devices use the same resolution, // so use the first device to determine the delay ds18b20_wait_for_conversion(devices[0]); // Read the results immediately after conversion otherwise it may fail // (using printf before reading may take too long) float readings[MAX_DEVICES] = { 0 }; DS18B20_ERROR errors[MAX_DEVICES] = { 0 }; for (int i = 0; i < num_devices; ++i) { errors[i] = ds18b20_read_temp(devices[i], &readings[i]); } // Print results in a separate loop, after all have been read printf("\nTemperature readings (degrees C): sample %d\n", ++sample_count); for (int i = 0; i < num_devices; ++i) { if (errors[i] != DS18B20_OK) { ++errors_count[i]; } printf(" %d: %.1f %d errors\n", i, readings[i], errors_count[i]); } vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS); } } else { printf("\nNo DS18B20 devices detected!\n"); } |
Наконец, все динамические данные очищаются путем удаления существующего экземпляра информации об устройстве, после чего плата перезапускается.
|
1 2 3 4 5 6 7 8 9 10 |
for (int i = 0; i < num_devices; ++i) { ds18b20_free(&devices[i]); } owb_uninitialize(owb); printf("Restarting now.\n"); fflush(stdout); vTaskDelay(1000 / portTICK_PERIOD_MS); esp_restart(); |
Компиляция скетча
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
|
1 |
idf.py -p COMX flash monitor |
После успешной прошивки кода вы можете просмотреть показания температуры на консоли, а также количество устройств, подключенных к одной и той же однопроводной шине, и их ПЗУ-коды. В нашем случае мы используем один датчик DS18B20, поэтому было обнаружено одно устройство.
Отображение показаний с датчика DS18B20 на OLED-дисплее
Теперь давайте создадим ещё один проект, в котором мы запрограммируем нашу плату ESP32 для сбора показаний датчика DS18B20 и отображения их на 0,96-дюймовом OLED-дисплее SSD1306.
Создание проекта
статья в разработке
3 просмотров





