├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── main ├── CMakeLists.txt ├── Kconfig.projbuild ├── app_main.c └── component.mk └── sdkconfig.defaults /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | sdkconfig 3 | sdkconfig.old 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/esp32-owb"] 2 | path = components/esp32-owb 3 | url = ../esp32-owb 4 | [submodule "components/esp32-ds18b20"] 5 | path = components/esp32-ds18b20 6 | url = ../esp32-ds18b20 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: bionic 3 | language: python 4 | python: "3.8" 5 | 6 | jobs: 7 | include: 8 | - stage: # IDF v3.3 9 | install: 10 | # Install ESP32 toochain following steps as described 11 | # in http://esp-idf.readthedocs.io/en/latest/linux-setup.html 12 | # 13 | # Download binary toolchain for the ESP32 14 | - pushd ~ 15 | - wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz 16 | - tar -xzf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz 17 | # Make xtensa-esp32-elf available for all terminal sessions 18 | - export PATH=$PATH:$(pwd)/xtensa-esp32-elf/bin 19 | # Get ESP-IDF from github 20 | - git clone --recursive --branch v3.3 --single-branch --shallow-submodules https://github.com/espressif/esp-idf.git esp-idf 21 | # Set the path to ESP-IDF directory 22 | - export IDF_PATH=$(pwd)/esp-idf 23 | # Install python dependencies 24 | - pip install --requirement $IDF_PATH/requirements.txt 25 | - popd 26 | script: 27 | - $IDF_PATH/tools/idf.py build 28 | 29 | - stage: # IDF v4.1-beta1 30 | install: 31 | - pushd ~ 32 | - git clone --recursive --branch v4.1-beta1 --single-branch --shallow-submodules https://github.com/espressif/esp-idf.git esp-idf 33 | - esp-idf/install.sh 34 | - popd 35 | script: 36 | - source ~/esp-idf/export.sh && idf.py build 37 | 38 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following five lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(esp32-ds18b20-example LANGUAGES C) 7 | 8 | if (NOT IDF_VERSION_MAJOR) 9 | set(IDF_VERSION_MAJOR 3) 10 | endif() 11 | 12 | if (IDF_VERSION_MAJOR GREATER 3) 13 | set(PROJECT_ELF ${project_elf}) 14 | else() 15 | set(PROJECT_ELF ${IDF_PROJECT_EXECUTABLE}) 16 | endif() 17 | 18 | # Ignore false clang warnings about `struct foo = { 0 }` 19 | target_compile_options(${PROJECT_ELF} PRIVATE -Wno-missing-braces -Wmissing-field-initializers) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 David Antliff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := esp32-ds18b20-example 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-ds18b20-example 2 | 3 | [![Platform: ESP-IDF](https://img.shields.io/badge/ESP--IDF-v3.0%2B-blue.svg)](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/) 4 | [![Build Status](https://travis-ci.org/DavidAntliff/esp32-ds18b20-example.svg?branch=master)](https://travis-ci.org/DavidAntliff/esp32-ds18b20-example) 5 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)]() 6 | 7 | ## Introduction 8 | 9 | This is an example application for the Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital Thermometer 10 | device. 11 | 12 | It is intended for the ESP32 (`idf.py` target `esp32`), however with some minor modifications it may work on the other ESP32 devices. 13 | 14 | It supports a single or multiple devices on the same 1-Wire bus. 15 | 16 | It is tested for v4.4.4 and v5.0.1 of the [ESP-IDF](https://github.com/espressif/esp-idf) environment. 17 | 18 | Code for earlier versions of the IDF (v3.3 and v4.1-beta1) may be found on branch [ESP-IDF_v3.3_4.1-beta1](https://github.com/DavidAntliff/esp32-ds18b20-example/tree/ESP-IDF_v3.3_4.1-beta1). 19 | This legacy code is no longer supported but you're welcome to use it if you're restricted to older versions of the IDF. 20 | 21 | ## Clone & Build 22 | 23 | Ensure that submodules are cloned: 24 | 25 | $ git clone --recursive https://github.com/DavidAntliff/esp32-ds18b20-example.git 26 | 27 | Build the application with: 28 | 29 | $ cd esp32-ds18b20-example 30 | $ idf.py menuconfig # set your serial configuration and the 1-Wire GPIO - see below 31 | $ idf.py build 32 | $ idf.py -p (PORT) flash monitor 33 | 34 | The program should detect your connected devices and periodically obtain temperature readings from them, displaying them 35 | on the console. 36 | 37 | ## Dependencies 38 | 39 | This application makes use of the following components (included as submodules): 40 | 41 | * components/[esp32-owb](https://github.com/DavidAntliff/esp32-owb) 42 | * components/[esp32-ds18b20](https://github.com/DavidAntliff/esp32-ds18b20) 43 | 44 | ## Hardware 45 | 46 | To run this example, connect one or more DS18B20 devices to a single GPIO on the ESP32. Use the recommended pull-up 47 | resistor of 4.7 KOhms, connected to the 3.3V supply. 48 | 49 | `idf.py menuconfig` can be used to set the 1-Wire GPIO. 50 | 51 | If you have several devices and see occasional CRC errors, consider using a 2.2 kOhm pull-up resistor instead. Also 52 | consider adding decoupling capacitors between the sensor supply voltage and ground, as close to each sensor as possible. 53 | 54 | If you wish to enable a second GPIO to control an external strong pull-up circuit for parasitic power mode, ensure 55 | `CONFIG_ENABLE_STRONG_PULLUP=y` and `CONFIG_STRONG_PULLUP_GPIO` is set appropriately. 56 | 57 | See documentation for [esp32-ds18b20](https://www.github.com/DavidAntliff/esp32-ds18b20#parasitic-power-mode) 58 | for further information about parasitic power mode, including strong pull-up configuration. 59 | 60 | 61 | ## Features 62 | 63 | This example provides: 64 | 65 | * External power supply detection. 66 | * Parasitic power supply detection. 67 | * Static (stack-based) or dynamic (malloc-based) memory model examples. 68 | * No global variables. 69 | * Device search. 70 | * Addressing optimisation for a single (solo) device on a bus. 71 | * CRC checks on ROM code and temperature data. 72 | * Programmable temperature measurement resolution (9, 10, 11 or 12-bit resolution). 73 | * Temperature conversion and retrieval. 74 | * Simultaneous conversion across multiple devices. 75 | 76 | ## Source Code 77 | 78 | The source is available from [GitHub](https://www.github.com/DavidAntliff/esp32-ds18b20-example). 79 | 80 | ## License 81 | 82 | The code in this project is licensed under the MIT license - see LICENSE for details. 83 | 84 | ## Links 85 | 86 | * [DS18B20 Datasheet](http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf) 87 | * [Espressif IoT Development Framework for ESP32](https://github.com/espressif/esp-idf) 88 | 89 | ## Acknowledgements 90 | 91 | "1-Wire" is a registered trademark of Maxim Integrated. 92 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS ".") 2 | set(COMPONENT_ADD_INCLUDEDIRS ".") 3 | 4 | register_component() 5 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "esp32-ds18b20-example Configuration" 2 | 3 | config ONE_WIRE_GPIO 4 | int "OneWire GPIO number" 5 | range 0 33 6 | default 4 7 | help 8 | GPIO number (IOxx) to access One Wire Bus. 9 | 10 | Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used. 11 | 12 | GPIOs 34-39 are input-only so cannot be used to drive the One Wire Bus. 13 | 14 | config ENABLE_STRONG_PULLUP_GPIO 15 | bool "Enable strong pull-up controlled by GPIO (MOSFET)" 16 | default n 17 | help 18 | An external circuit can be used to provide a strong pull-up to the One Wire Bus. 19 | This is useful when the bus has parasitic-powered devices and extra current is 20 | required to power them, such as during temperature ADC conversions. 21 | 22 | An example of such a circuit for the ESP32 is a P-channel MOSFET (such as the BS250) 23 | connected Source-to-Drain between a current-limiting resistor (e.g. 270ohm for 12mA 24 | max at 3.3V), itself connected to VCC, and the One Wire Bus data line. The Gate is 25 | connected to the GPIO specified here. 26 | 27 | config STRONG_PULLUP_GPIO 28 | int "Strong pull-up GPIO number" 29 | range 0 33 30 | default 5 31 | help 32 | GPIO number (IOxx) to control the strong pull-up on the One Wire Bus, perhaps 33 | via a P-channel MOSFET between VCC and the One Wire Bus data line. 34 | 35 | This GPIO will be set as an output and driven high during temperature conversion. 36 | This would enable the MOSFET providing current to the devices. 37 | 38 | At all other times it will be driven low, switching off the MOSFET and allowing 39 | the One Wire Bus to operate normally. 40 | 41 | Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used. 42 | 43 | GPIOs 34-39 are input-only so cannot be used to drive the One Wire Bus. 44 | depends on ENABLE_STRONG_PULLUP_GPIO 45 | 46 | endmenu 47 | -------------------------------------------------------------------------------- /main/app_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 David Antliff 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "freertos/FreeRTOS.h" 26 | #include "freertos/task.h" 27 | #include "esp_system.h" 28 | #include "esp_log.h" 29 | 30 | #include "owb.h" 31 | #include "owb_rmt.h" 32 | #include "ds18b20.h" 33 | 34 | #define GPIO_DS18B20_0 (CONFIG_ONE_WIRE_GPIO) 35 | #define MAX_DEVICES (8) 36 | #define DS18B20_RESOLUTION (DS18B20_RESOLUTION_12_BIT) 37 | #define SAMPLE_PERIOD (1000) // milliseconds 38 | 39 | _Noreturn void app_main() 40 | { 41 | // Override global log level 42 | esp_log_level_set("*", ESP_LOG_INFO); 43 | 44 | // To debug, use 'make menuconfig' to set default Log level to DEBUG, then uncomment: 45 | //esp_log_level_set("owb", ESP_LOG_DEBUG); 46 | //esp_log_level_set("ds18b20", ESP_LOG_DEBUG); 47 | 48 | // Stable readings require a brief period before communication 49 | vTaskDelay(2000.0 / portTICK_PERIOD_MS); 50 | 51 | // Create a 1-Wire bus, using the RMT timeslot driver 52 | OneWireBus * owb; 53 | owb_rmt_driver_info rmt_driver_info; 54 | owb = owb_rmt_initialize(&rmt_driver_info, GPIO_DS18B20_0, RMT_CHANNEL_1, RMT_CHANNEL_0); 55 | owb_use_crc(owb, true); // enable CRC check for ROM code 56 | 57 | // Find all connected devices 58 | printf("Find devices:\n"); 59 | OneWireBus_ROMCode device_rom_codes[MAX_DEVICES] = {0}; 60 | int num_devices = 0; 61 | OneWireBus_SearchState search_state = {0}; 62 | bool found = false; 63 | owb_search_first(owb, &search_state, &found); 64 | while (found) 65 | { 66 | char rom_code_s[17]; 67 | owb_string_from_rom_code(search_state.rom_code, rom_code_s, sizeof(rom_code_s)); 68 | printf(" %d : %s\n", num_devices, rom_code_s); 69 | device_rom_codes[num_devices] = search_state.rom_code; 70 | ++num_devices; 71 | owb_search_next(owb, &search_state, &found); 72 | } 73 | printf("Found %d device%s\n", num_devices, num_devices == 1 ? "" : "s"); 74 | 75 | // In this example, if a single device is present, then the ROM code is probably 76 | // not very interesting, so just print it out. If there are multiple devices, 77 | // then it may be useful to check that a specific device is present. 78 | 79 | if (num_devices == 1) 80 | { 81 | // For a single device only: 82 | OneWireBus_ROMCode rom_code; 83 | owb_status status = owb_read_rom(owb, &rom_code); 84 | if (status == OWB_STATUS_OK) 85 | { 86 | char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; 87 | owb_string_from_rom_code(rom_code, rom_code_s, sizeof(rom_code_s)); 88 | printf("Single device %s present\n", rom_code_s); 89 | } 90 | else 91 | { 92 | printf("An error occurred reading ROM code: %d", status); 93 | } 94 | } 95 | else 96 | { 97 | // Search for a known ROM code (LSB first): 98 | // For example: 0x1502162ca5b2ee28 99 | OneWireBus_ROMCode known_device = { 100 | .fields.family = { 0x28 }, 101 | .fields.serial_number = { 0xee, 0xb2, 0xa5, 0x2c, 0x16, 0x02 }, 102 | .fields.crc = { 0x15 }, 103 | }; 104 | char rom_code_s[OWB_ROM_CODE_STRING_LENGTH]; 105 | owb_string_from_rom_code(known_device, rom_code_s, sizeof(rom_code_s)); 106 | bool is_present = false; 107 | 108 | owb_status search_status = owb_verify_rom(owb, known_device, &is_present); 109 | if (search_status == OWB_STATUS_OK) 110 | { 111 | printf("Device %s is %s\n", rom_code_s, is_present ? "present" : "not present"); 112 | } 113 | else 114 | { 115 | printf("An error occurred searching for known device: %d", search_status); 116 | } 117 | } 118 | 119 | // Create DS18B20 devices on the 1-Wire bus 120 | DS18B20_Info * devices[MAX_DEVICES] = {0}; 121 | for (int i = 0; i < num_devices; ++i) 122 | { 123 | DS18B20_Info * ds18b20_info = ds18b20_malloc(); // heap allocation 124 | devices[i] = ds18b20_info; 125 | 126 | if (num_devices == 1) 127 | { 128 | printf("Single device optimisations enabled\n"); 129 | ds18b20_init_solo(ds18b20_info, owb); // only one device on bus 130 | } 131 | else 132 | { 133 | ds18b20_init(ds18b20_info, owb, device_rom_codes[i]); // associate with bus and device 134 | } 135 | ds18b20_use_crc(ds18b20_info, true); // enable CRC check on all reads 136 | ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION); 137 | } 138 | 139 | // // Read temperatures from all sensors sequentially 140 | // while (1) 141 | // { 142 | // printf("\nTemperature readings (degrees C):\n"); 143 | // for (int i = 0; i < num_devices; ++i) 144 | // { 145 | // float temp = ds18b20_get_temp(devices[i]); 146 | // printf(" %d: %.3f\n", i, temp); 147 | // } 148 | // vTaskDelay(1000 / portTICK_PERIOD_MS); 149 | // } 150 | 151 | // Check for parasitic-powered devices 152 | bool parasitic_power = false; 153 | ds18b20_check_for_parasite_power(owb, ¶sitic_power); 154 | if (parasitic_power) { 155 | printf("Parasitic-powered devices detected"); 156 | } 157 | 158 | // In parasitic-power mode, devices cannot indicate when conversions are complete, 159 | // so waiting for a temperature conversion must be done by waiting a prescribed duration 160 | owb_use_parasitic_power(owb, parasitic_power); 161 | 162 | #ifdef CONFIG_ENABLE_STRONG_PULLUP_GPIO 163 | // An external pull-up circuit is used to supply extra current to OneWireBus devices 164 | // during temperature conversions. 165 | owb_use_strong_pullup_gpio(owb, CONFIG_STRONG_PULLUP_GPIO); 166 | #endif 167 | 168 | // Read temperatures more efficiently by starting conversions on all devices at the same time 169 | int errors_count[MAX_DEVICES] = {0}; 170 | int sample_count = 0; 171 | if (num_devices > 0) 172 | { 173 | TickType_t last_wake_time = xTaskGetTickCount(); 174 | 175 | while (1) 176 | { 177 | ds18b20_convert_all(owb); 178 | 179 | // In this application all devices use the same resolution, 180 | // so use the first device to determine the delay 181 | ds18b20_wait_for_conversion(devices[0]); 182 | 183 | // Read the results immediately after conversion otherwise it may fail 184 | // (using printf before reading may take too long) 185 | float readings[MAX_DEVICES] = { 0 }; 186 | DS18B20_ERROR errors[MAX_DEVICES] = { 0 }; 187 | 188 | for (int i = 0; i < num_devices; ++i) 189 | { 190 | errors[i] = ds18b20_read_temp(devices[i], &readings[i]); 191 | } 192 | 193 | // Print results in a separate loop, after all have been read 194 | printf("\nTemperature readings (degrees C): sample %d\n", ++sample_count); 195 | for (int i = 0; i < num_devices; ++i) 196 | { 197 | if (errors[i] != DS18B20_OK) 198 | { 199 | ++errors_count[i]; 200 | } 201 | 202 | printf(" %d: %.1f %d errors\n", i, readings[i], errors_count[i]); 203 | } 204 | 205 | vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS); 206 | } 207 | } 208 | else 209 | { 210 | printf("\nNo DS18B20 devices detected!\n"); 211 | } 212 | 213 | // clean up dynamically allocated data 214 | for (int i = 0; i < num_devices; ++i) 215 | { 216 | ds18b20_free(&devices[i]); 217 | } 218 | owb_uninitialize(owb); 219 | 220 | printf("Restarting now.\n"); 221 | fflush(stdout); 222 | vTaskDelay(1000 / portTICK_PERIOD_MS); 223 | esp_restart(); 224 | } 225 | -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # 2 | # Log output 3 | # 4 | CONFIG_LOG_DEFAULT_LEVEL=4 5 | --------------------------------------------------------------------------------