In this tutorial, we will learn to interface temperature sensor DS18B20 with ESP32 using ESP-IDF library. This guide will include a brief description of DS18B20 sensor, pinout, connection with ESP32 board and then setting up a project in VS Code with ESP-IDF extension to acquire the sensor data and display it both on the console and SSD1306 OLED.
Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.
DS18B20 Sensor Introduction
The DS18B20 temperature sensor is widely used in electronic projects that uses a single wire to acquire data, therefore claiming it as single wire programmable in nature. It uses a single GPIO pin of the microcontroller to output the current temperature reading of its surroundings. By using the least number of pins, we can conveniently access multiple temperature readings by hooking up DS18B20 sensors on the same GPIO pin.
The table below shows some key features of the sensor.
Operating Voltage | 3-5V |
Temperature Range | -55°C to +125°C |
Accuracy | ±0.5°C |
Output Resolution | 9-12 bit |
The DS18B20 sensor comes in various types. One is the commonly used sensor, another is the water proof version and lastly we have the module version.
Note that all three variations of the sensor has three pinouts. Two power supply pins (VCC and GND) and one Data pin.
VCC | This is the power pin of the sensor. Supply voltage of 3.3-5V |
Data | The Data pin is connected with a GPIO pin of the microcontroller which will then output the sensor reading. |
Ground | This is the common ground. |
Interfacing DS18B20 with ESP32
We will need the following components to connect our ESP32 board with the DS18B20 sensor.
- ESP32 board
- DS18B20 Sensor
- 4.7k ohm pull up resistor
- Connecting Wires
- Breadboard
The DS18B20 sensor can be powered on in two different modes. which are the normal mode and the parasite mode. In the normal mode, an external source powers the sensor through the VCC pin and the 4.7k ohm resistor. However, in the parasite mode, the sensor is powered from the data line therefore no external power source is required. We will power our DS18B20 sensor in the normal mode.
The table below shows the connections of the three pins of DS18B20 sensor with the ESP32 board.
DS18B20 | ESP32 |
---|---|
VCC | 3.3V / Vin |
Data | GPIO4 |
GND | GND |
Connect VCC pin of the sensor with 3.3V pin or Vin pin of the ESP32 board and common the grounds of the two devices. Moreover, connect the data pin of the sensor with an appropriate GPIO pin of the ESP32 board which can be set up as an output pin. Note GPIO34-39 are input only pins and can not be used to drive the one wire bus. We are using GPIO4 to connect with the data pin through the 4.7k ohm pull up resistor. One end of the resistor will be connected with the data output and the other end will be connected with the power supply.
Note that if you are using the DS18B20 module instead, then you do not need to add the external pull up resistor. This is because the module already comes up with a sensor and an onboard pull up resistor.
Follow the schematic diagram below to connect the two devices together.
Measure DS18B20 Readings with ESP32 and ESP-IDF
We will build and create a project in VS Code with ESP-IDF extension. The code will read the number of DS18B20 sensor connected with the one wire bus and output the temperature readings after every second.
Create Example Project
Download the Code as a zip file from the following link.
Click Code > Download ZIP. Then extract al the files. Now open your VS Code and head over to File > Open Folder and select the extracted files folder to open it in VS Code with ESP-IDF extension.
This opens the project inside the EXPLORER tab. There are several folders inside our project folder.
- First, let’s add the necessary header files for the libraries required for this project. Open the two folders, esp32-ds18b20 and esp32-owb under components folder and add the required files.
- Under esp32-ds18b20 folder, add the files present at this link. This is the ESP32-compatible C library for Maxim Integrated DS18B20 Programmable Resolution 1-Wire Digital Thermometer.
- Under the esp32-owb folder, add all the files present at this link. This is the Maxim one wire bus driver for ESP32.
DS18B20 MenuConfig Settings ESP-IDF
Let’s first head over to the menuconfig. Open the ESP-IDF SDK Configuration Editor. Scroll down and open the esp32-ds18b20-example Configuration. Here we can set the one wire GPIO number that will be used to connect with the data pin of the sensor. By default, it is set as GPIO4 which works well for us. We also have the option to enable strong pull up though a GPIO. If you want to make any changes in the configuration, you can do so and click the Save button found at the top afterwards. We will leave the settings as default.
ESP32 DS18B20 Display Readings Code
Now head over to the app_main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > app_main.c and open it. The following code opens up.
/*
* 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();
}
How the Code Works?
Firstly, we will start by including the necessary libraries for this project. This includes the ESP logging library, DS18B20 library, one wire bus driver and the FreeRTOS libraries.
#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"
The following variables define some parameters including the one wire GPIO number, the maximum number of devices, resolution of the sensor and the sampling time period between readings. All default values are being used.
#define GPIO_DS18B20_0 (CONFIG_ONE_WIRE_GPIO)
#define MAX_DEVICES (8)
#define DS18B20_RESOLUTION (DS18B20_RESOLUTION_12_BIT)
#define SAMPLE_PERIOD (1000) // milliseconds
Inside the app_main() function, we will first create a one wire bus through the RMT timeslot driver. Here, OneWireBus is a structure that holds the one wire bus parameters for a single instance. The owb_rmt_driver_info holds the RMT driver information. Next, we initialize the RMT driver by calling owb_rmt_initialize(). This function takes in four parameters which are the pointer to the owb_rmt_driver_info structure, the one wire GPIO number, the RMT channel for TX and the RMT channel for RX. Finally, we enable the CRC check for the ROM code using owb_use_crc() function and specifying the output of owb_rmt_initialize() function as the first parameter and ‘true’ as the second parameter.
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);
The next step is to find all the connected devices on the one wire bus and print the number along with its ROM code on the console. In our case, we are only connecting a single sensor with the one wire bus, hence the number of devices printed will be 1.
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");
Next up we have an if-else condition to check the number of devices, either it is a single device or multiple devices. If as single device is present, then it will be printed on the console along with its ROM code.
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);
}
}
However, if multiple devices are present, you can search for a specific device through its ROM code to check whether it is present or not.
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);
}
}
After finding the number of devices, we will create the DS18B20 devices on the one wire bus according to the number of devices present. In the case, if a single device is connected then single device optimization will be enabled and the sensor gets initialized through ds18b20_init_solo() function. Otherwise, if multiple devices are connected, ds18b20_init() function is called to initialize the sensors according to the device ROM codes. Moreover, the CRC check is enabled on all reads and the resolution is also set.
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);
}
The following lines of code are used to detect parasitic power supply using ds18b20_check_for_parasite_power(). If parasitic powered devices are present, a relevant message will get printed on the console.
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);
Afterwards, the temperature reading is read from the sensor after initiating conversions on all the devices simultaneously. The function ds18b20_read_temp() will be used to read the last temperature reading from the sensor. This function takes in two parameter where the first parameter is the pointer to the device info instance and the second parameter is the pointer to the measured value in degree Celsius that is returned by the device. It returns DS18B20_OK if everything is successful, otherwise an error is generated. This function is called inside a for loop which will run for the total number of devices present. After all the temperature readings have been read, the values are printed on the console in degree Celsius along with the number of errors if any. The readings will be acquired after every second. If no DS18B20 sensor is found, a relevant message will be printed on the console.
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");
}
Lastly, all the dynamic data is cleared by deleting the existing device info instance and the board restarts.
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();
Compiling the Sketch
To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.
idf.py -p COMX flash monitor
After the code is successfully flashed, you can view the temperature readings on the console, along with the number of devices connected to the same one wire bus with their ROM codes. In our case, we are using a single DS18B20 sensor, hence one device was detected.
ESP32 ESP-IDF Display DS18B20 Readings on OLED
Now let us create another project where we will program our ESP32 board to acquire DS18B20 sensor readings and display them on a 0.96 SSD1306 OLED display.
Create Project
Follow all the same steps to create a similar project as we did before. This time, however, we have to add library for the OLED as well.
- First, let’s add the necessary header files for the libraries required for this project. After adding the BME280 libraries let us proceed to add ssd1306 libraries. Under the components folder, create a new folder called ‘ssd1306’ and add all the files present at this link. This includes all the files listed below:
OLED MenuConfig Settings ESP-IDF
Let’s first head over to the menuconfig. Open the ESP-IDF SDK Configuration Editor. Scroll down and open the SSD1306 Configuration. Here we can set the SSD1306 configuration parameter according to our needs. This includes the I2C interface, panel type, SCL GPIO pin, SDA GPIO pin, and the reset GPIO pin. Here you can view that by default, ESP-IDF is using the interface as I2C, panel type as 128×64, SCL GPIO pin as 22, SDA GPIO pin as 21 and Reset GPIO pin as 33. You can alter these parameters according to your OLED display and then click the Save button found at the top. We will leave the configuration settings as default as they match our module.
As our BME280 sensor also communicates via I2C interface hence same communication pins will be used for both devices.
ESP32 DS18B20 Display Readings on OLED Code
Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > app_main.c and open it. Copy the code given below in that file and save it.
/*
* 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"
#include "ssd1306.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.
// 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);
}
// 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;
printf("Single device optimisations enabled\n");
ds18b20_init_solo(ds18b20_info, owb); // only one device on bus
ds18b20_use_crc(ds18b20_info, true); // enable CRC check on all reads
ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION);
}
// 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
SSD1306_t dev;
i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);
ssd1306_init(&dev, 128, 64);
ssd1306_clear_screen(&dev, false);
ssd1306_contrast(&dev, 0xff);
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]);
}
float temp = readings[0];
char temperature[12];
sprintf(temperature, "%.2f degC", temp);
ssd1306_display_text(&dev, 0, "Temperature", 12, false);
ssd1306_display_text(&dev, 4, temperature, 12, false);
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();
}
How the Code Works?
Most of the code is the same as in the previous project where we accessed sensor readings of DS18B20 readings after every second. This time however, instead of displaying the readings on the ESP-IDF terminal, our aim is to display them on an OLED. So let us look over the parts where we are incorporating the OLED for a single device. The rest of the functions are the same to acquire the DS18B20 sensor data which can be used for single/multiple devices on the same one wire bus.
After creating the DS18B20 devices on the one wire, we will first initialize the I2C interface. The I2C interface will be configured by calling the i2c_master_init() function and passing the address of the SSD1306_t structure, SDA pin, SCL pin and Reset pin as parameters inside it. This will configure the ESP32 master to listen to slaves on the I2C bus.
SSD1306_t dev;
i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);
After that, we will initialize the OLED display by calling ssd1306_init() function. This takes in three parameters. The first parameter is the address of the SSD1306_t structure, the second parameter is the width and the third parameter is the height of the display in pixels. We are using a 128×64 display hence the width is 128 and the height is 64.
ssd1306_init(&dev, 128, 64);
Let’s clear the screen of the OLED display by using ssd1306_clear_screen(). This function takes in two parameters. The first is the address of the SSD1306_t structure and the second parameter is invert which is a bool variable. It is set as false. This means we have a dark background and the text will be displayed in white.
ssd1306_clear_screen(&dev, false);
Secondly, the contrast of the screen is set using ssd1306_contrast(). This function takes in two parameters. The first is the address of the SSD1306_t structure and the second parameter is the contrast value which is an int variable. It is set as 0xff.
ssd1306_contrast(&dev, 0xff);
After the temperatures are read, then inside the infinite loop we will print the temperature reading for the single device on the OLED. The readings[] array contains the temperature readings for the devices. In our case, we want to access the temperature reading of a single device hence the value of readings[0]. This is a float data type hence we will first convert it to an array of characters, round off to two decimal places and add the units for as well.
To display each sensor reading on the OLED screen, we will call the function ssd1306_display_text. It takes in five parameters. The first parameter is the address of the SSD1306_t structure. The second parameter is the page number of the composition data which is set as ‘0’ for the text “Temperature” and ‘4’ for the actual temperature reading. The third parameter is the text that we want to display. It is the array of characters of the sensor reading. The fourth parameter is the length of the text. Lastly, the fifth parameter is invert which is a bool variable. It is set as false. This means we have a dark background and the text will be displayed in white. This function will be called after every second.
for (int i = 0; i < num_devices; ++i)
{
errors[i] = ds18b20_read_temp(devices[i], &readings[i]);
}
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]);
}
float temp = readings[0];
char temperature[12];
sprintf(temperature, "%.2f degC", temp);
ssd1306_display_text(&dev, 0, "Temperature", 12, false);
ssd1306_display_text(&dev, 4, temperature, 12, false);
vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS);
Hardware Setup OLED with ESP32 and DS18B20
Now let us setup our circuit by connecting the ESP32 development board with ab OLED and a DS18B20 sensor.
An OLED display requires an operating voltage of 3.3-5V, hence we can connect the VCC pin of OLED either with the 3.3V pin or the Vin pin of the ESP32 module. The GND pins will be common. To connect the I2C pins, we will use the default I2C pins of the ESP32 board. By default in ESP32, GPIO22 is set up as SCL pin and GPIO21 is set up as SDA pin. Therefore, we will connect SDA pin of display with GPIO21 and SCL pin of display with GPIO22.
The tables below show the connections that we will be using to patch up our circuit.
OLED Display | ESP32 |
---|---|
VCC | 3.3V |
GND | GND |
SCL | GPIO22 |
SDA | GPIO21 |
DS18B20 | ESP32 |
---|---|
VCC | 3.3V |
Data | GPIO4 |
GND | GND |
Assemble the circuit as shown in the schematic diagram below:
Compiling the Sketch
To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.
idf.py -p COMX flash monitor
After the code is successfully flashed, you can view in the ESP-IDF terminal that the OLED got configured successfully.
The OLED screen will start displaying the DS18B20 sensor readings that include temperature in degree Celsius.
You may also like to read:
- DHT22 with ESP32 ESP-IDF and Display Readings on OLED
- BME280 with ESP32 ESP-IDF and Display Readings on OLED
- ESP32 GPIO Interrupts using ESP-IDF
- ESP32 Timer Interrupt using ESP-IDF
- ESP32 SPIFFS using ESP-IDF – Read, Create and Write to Files
We are a team of experienced Embedded Software Developers with skills in developing Embedded Linux, RTOS, and IoT products from scratch to deployment with a demonstrated history of working in the embedded software industry. Contact us for your projects: admin@esp32tutorials.com