Skip to content

Commit

Permalink
Reimplement support for multiple DS18S20 sensors on separate GPIOs
Browse files Browse the repository at this point in the history
  • Loading branch information
hg committed Nov 7, 2020
1 parent 43d281c commit b3b3897
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 194 deletions.
53 changes: 10 additions & 43 deletions main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -1,46 +1,13 @@
menu "airmon Configuration"

config ONE_WIRE_GPIO
int "OneWire GPIO number"
range 0 33
default 4
help
GPIO number (IOxx) to access One Wire Bus.

Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used.

GPIOs 34-39 are input-only so cannot be used to drive the One Wire Bus.

config ENABLE_STRONG_PULLUP_GPIO
bool "Enable strong pull-up controlled by GPIO (MOSFET)"
default n
help
An external circuit can be used to provide a strong pull-up to the One Wire Bus.
This is useful when the bus has parasitic-powered devices and extra current is
required to power them, such as during temperature ADC conversions.

An example of such a circuit for the ESP32 is a P-channel MOSFET (such as the BS250)
connected Source-to-Drain between a current-limiting resistor (e.g. 270ohm for 12mA
max at 3.3V), itself connected to VCC, and the One Wire Bus data line. The Gate is
connected to the GPIO specified here.

config STRONG_PULLUP_GPIO
int "Strong pull-up GPIO number"
range 0 33
default 5
help
GPIO number (IOxx) to control the strong pull-up on the One Wire Bus, perhaps
via a P-channel MOSFET between VCC and the One Wire Bus data line.

This GPIO will be set as an output and driven high during temperature conversion.
This would enable the MOSFET providing current to the devices.

At all other times it will be driven low, switching off the MOSFET and allowing
the One Wire Bus to operate normally.

Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used.

GPIOs 34-39 are input-only so cannot be used to drive the One Wire Bus.
depends on ENABLE_STRONG_PULLUP_GPIO

config TEMPERATURE_PERIOD_SECONDS
int "Temperature measurement period, seconds"
range 1 3600
default 15

config TEMPERATURE_KEEP_HOURS
int "How many hours of temperature measurements to keep in queue"
range 1 24
default 3
endmenu

297 changes: 146 additions & 151 deletions main/app_main.c
Original file line number Diff line number Diff line change
@@ -1,195 +1,190 @@
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "sys/time.h"

#include "ds18b20.h"
#include "owb.h"
#include "owb_rmt.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
typedef struct {
const char *name;
const int pin;
const rmt_channel_t rx;
const rmt_channel_t tx;
QueueHandle_t queue;
} sensor_config;

_Noreturn void app_main() {
// Override global log level
esp_log_level_set("*", ESP_LOG_INFO);
typedef struct {
time_t time;
float temp;
const char *sensor;
} temp_measurement;

// 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);
// esp_log_level_set("owb_rmt", ESP_LOG_DEBUG);
static sensor_config temp_sensors[] = {
{"room", 5, RMT_CHANNEL_0, RMT_CHANNEL_1, NULL},
// {"street", 12, RMT_CHANNEL_2, RMT_CHANNEL_3, NULL},
};

// Stable readings require a brief period before communication
vTaskDelay(2000.0 / portTICK_PERIOD_MS);
static const int kSensorResolution = (DS18B20_RESOLUTION_12_BIT);

// Create a 1-Wire bus, using the RMT timeslot driver
owb_rmt_driver_info rmt_driver_info;
// one second in ticks
static const int kSecond = 1000 / portTICK_PERIOD_MS;

OneWireBus *owb = owb_rmt_initialize(&rmt_driver_info, GPIO_DS18B20_0,
RMT_CHANNEL_1, RMT_CHANNEL_0);
// delay between two temperature measurements
static const int kTempDelay = CONFIG_TEMPERATURE_PERIOD_SECONDS * kSecond;

owb_use_crc(owb, true); // enable CRC check for ROM code
// keep that many hours of temperature measurements in case internet goes down
static const int kTempQueueLength =
CONFIG_TEMPERATURE_KEEP_HOURS * 60 * 60 / CONFIG_TEMPERATURE_PERIOD_SECONDS;

// Find all connected devices
printf("Find devices:\n");
static const char *const kTag = "airmon";

OneWireBus_ROMCode device_rom_codes[MAX_DEVICES] = {0};
OneWireBus_SearchState search_state = {0};
int devices_found = 0;
bool found = false;
static DS18B20_Info *search_temp_sensor(const OneWireBus *const owb) {
for (;;) {
bool found = false;
OneWireBus_SearchState search_state = {0};

owb_search_first(owb, &search_state, &found);
owb_search_first(owb, &search_state, &found);
if (found) {
break;
}

if (found) {
printf("found device: %d\n", search_state.last_device_flag);
ESP_LOGD(kTag, "temp sensor not found, retrying");
vTaskDelay(500 / portTICK_PERIOD_MS);
}

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", devices_found, rom_code_s);
device_rom_codes[devices_found] = search_state.rom_code;
++devices_found;
owb_search_next(owb, &search_state, &found);
}
printf("Found %d device%s\n", devices_found, devices_found == 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 (devices_found == 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;
OneWireBus_ROMCode rom_code = {0};
const owb_status status = owb_read_rom(owb, &rom_code);

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);
}
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));
ESP_LOGI(kTag, "found device %s", rom_code_s);
} else {
ESP_LOGE(kTag, "could not read ROM code: %d", status);
return NULL;
}

// Create DS18B20 devices on the 1-Wire bus
DS18B20_Info *devices[MAX_DEVICES] = {0};
for (int i = 0; i < devices_found; ++i) {
DS18B20_Info *ds18b20_info = ds18b20_malloc(); // heap allocation
devices[i] = ds18b20_info;

if (devices_found == 1) {
printf("Single device optimisations enabled\n");
ds18b20_init_solo(ds18b20_info, owb); // only one device on bus
DS18B20_Info *const device = ds18b20_malloc(); // heap allocation
ds18b20_init_solo(device, owb); // only one device on bus
ds18b20_use_crc(device, true); // enable CRC check on all reads
ds18b20_set_resolution(device, kSensorResolution);

return device;
}

static OneWireBus *initialize_bus(owb_rmt_driver_info *const driver_info,
const sensor_config *const config) {
OneWireBus *owb =
owb_rmt_initialize(driver_info, config->pin, config->tx, config->rx);

owb_use_crc(owb, true); // enable CRC check for ROM code

return owb;
}

static void run_temp_measurements(const DS18B20_Info *const device,
const sensor_config *const config) {
int error_count = 0;

while (error_count < 10) {
TickType_t last_wake_time = xTaskGetTickCount();

temp_measurement measurement;
const DS18B20_ERROR err =
ds18b20_convert_and_read_temp(device, &measurement.temp);

if (err != DS18B20_OK) {
ESP_LOGW(kTag, "measurement failed in sensor %s", config->name);
++error_count;
} else {
ds18b20_init(ds18b20_info, owb,
device_rom_codes[i]); // associate with bus and device
error_count = 0;

struct timeval tm;
gettimeofday(&tm, NULL);

measurement.sensor = config->name;
measurement.time = tm.tv_sec;

BaseType_t sent;
do {
sent = xQueueSendToBack(config->queue, &measurement, kSecond);
if (sent == errQUEUE_FULL) {
temp_measurement buf;
xQueueReceive(config->queue, &buf, 0);
}
} while (sent == errQUEUE_FULL);
}
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, &parasitic_power);
if (parasitic_power) {
printf("Parasitic-powered devices detected");
vTaskDelayUntil(&last_wake_time, kTempDelay);
}
}

// 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 (devices_found > 0) {
TickType_t last_wake_time = xTaskGetTickCount();
_Noreturn void collect_temps(const sensor_config *const config) {
ESP_LOGI(kTag, "starting temp collection task for %s", config->name);

while (1) {
last_wake_time = xTaskGetTickCount();
for (;;) {
vTaskDelay(2 * kSecond);

ds18b20_convert_all(owb);
owb_rmt_driver_info rmt_driver_info;
initialize_bus(&rmt_driver_info, config);

// In this application all devices use the same resolution,
// so use the first device to determine the delay
ds18b20_wait_for_conversion(devices[0]);
OneWireBus *const owb = &rmt_driver_info.bus;
DS18B20_Info *device = search_temp_sensor(owb);

// 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};
run_temp_measurements(device, config);
ESP_LOGE(kTag, "sensor %s failed, restarting", config->name);

for (int i = 0; i < devices_found; ++i) {
errors[i] = ds18b20_read_temp(devices[i], &readings[i]);
}
// clean up dynamically allocated data
ds18b20_free(&device);
owb_uninitialize(owb);
}
}

// 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 < devices_found; ++i) {
if (errors[i] != DS18B20_OK) {
++errors_count[i];
}
_Noreturn void app_main() {
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);
// esp_log_level_set("owb_rmt", ESP_LOG_DEBUG);

QueueHandle_t temp_queue =
xQueueCreate(kTempQueueLength, sizeof(temp_measurement));

if (!temp_queue) {
ESP_LOGW(kTag, "trying a smaller temp queue");

printf(" %d: %.1f %d errors\n", i, readings[i], errors_count[i]);
}
temp_queue = xQueueCreate(10, sizeof(temp_measurement));

vTaskDelayUntil(&last_wake_time, SAMPLE_PERIOD / portTICK_PERIOD_MS);
if (!temp_queue) {
ESP_LOGE(kTag, "temp queue could not be created");
esp_restart();
}
} else {
printf("\nNo DS18B20 devices detected!\n");
}

// clean up dynamically allocated data
for (int i = 0; i < devices_found; ++i) {
ds18b20_free(&devices[i]);
for (int i = 0; i < sizeof(temp_sensors) / sizeof(*temp_sensors); ++i) {
char name[24];

sensor_config *conf = &temp_sensors[i];
conf->queue = temp_queue;

snprintf(name, sizeof(name), "temperature_%d", conf->pin);

xTaskCreate((TaskFunction_t)collect_temps, name, 2048, conf, 1, NULL);
}
owb_uninitialize(owb);

printf("Restarting now.\n");
fflush(stdout);
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
for (;;) {
temp_measurement temp = {0};

if (xQueueReceive(temp_queue, &temp, portMAX_DELAY)) {
printf("%s: %.1f°C\n", temp.sensor, temp.temp);
}
}
}

0 comments on commit b3b3897

Please sign in to comment.