-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement support for multiple DS18S20 sensors on separate GPIOs
- Loading branch information
Showing
2 changed files
with
156 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¶sitic_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); | ||
} | ||
} | ||
} |