From 92a23f869937b8d279d13c50a6b0cfb28f3b6208 Mon Sep 17 00:00:00 2001 From: David Antliff Date: Sat, 5 Aug 2017 16:38:22 +1200 Subject: [PATCH] Add support for setting and retrieving temperature measurement resolution. --- README.md | 6 +- main/ds18b20.c | 154 +++++++++++++++++++++++++++++++++++++++++++- main/ds18b20.h | 20 ++++++ main/ds18b20_main.c | 8 ++- 4 files changed, 180 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3d84967..f9d7738 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ It is written and tested for the [ESP-IDF](https://github.com/espressif/esp-idf) * 1-Wire device detection and validation, including search for multiple devices on a single bus. * 1-Wire bus operations including multi-byte read and write operations. * CRC checks on ROM code and temperature data. - * Temperature convertion and retrieval. + * Programmable temperature measurement resolution (9, 10, 11 or 12-bit resolution). + * Temperature conversion and retrieval. ## Documentation @@ -47,9 +48,8 @@ Parts of this code are based on references provided to the public domain by Maxi The following features are anticipated but not yet implemented: * Simultaneous temperature conversion from multiple devices. - * Device configuration including resolution. * Alarm support. + * EEPROM support. * Single device optimisations - avoid ROM addressing when only one device exists. * Parasitic power support. - * EEPROM support. diff --git a/main/ds18b20.c b/main/ds18b20.c index 4744dc4..cea3116 100644 --- a/main/ds18b20.c +++ b/main/ds18b20.c @@ -24,6 +24,11 @@ /** * @file ds18b20.c + * + * Resolution is cached in the DS18B20_Info object to avoid querying the hardware + * every time a temperature conversion is required. However this can result in the + * cached value becoming inconsistent with the hardware value, so care must be taken. + * */ #include @@ -31,6 +36,7 @@ #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -43,7 +49,7 @@ #include "owb.h" static const char * TAG = "ds18b20"; - +static const int T_CONV = 750; // maximum conversion time at 12-bit resolution in milliseconds // Function commands #define DS18B20_FUNCTION_TEMP_CONVERT 0x44 @@ -54,6 +60,16 @@ static const char * TAG = "ds18b20"; #define DS18B20_FUNCTION_POWER_SUPPLY_READ 0xB4 +typedef struct +{ + uint8_t temperature[2]; // [0] is LSB, [1] is MSB + uint8_t trigger_high; + uint8_t trigger_low; + uint8_t configuration; + uint8_t reserved[3]; + uint8_t crc; +} Scratchpad; + static bool _is_init(const DS18B20_Info * ds18b20_info) { bool ok = false; @@ -99,6 +115,58 @@ static float _decode_temp(uint8_t lsb, uint8_t msb, DS18B20_RESOLUTION resolutio return result; } +static size_t _min(size_t x, size_t y) +{ + return x > y ? y : x; +} + +static Scratchpad _read_scratchpad(const DS18B20_Info * ds18b20_info, size_t count) +{ + count = _min(sizeof(Scratchpad), count); // avoid overflow + Scratchpad scratchpad = {0}; + ESP_LOGD(TAG, "scratchpad read %d bytes: ", count); + owb_reset(ds18b20_info->bus); + owb_write_byte(ds18b20_info->bus, OWB_ROM_MATCH); + owb_write_rom_code(ds18b20_info->bus, ds18b20_info->rom_code); + owb_write_byte(ds18b20_info->bus, DS18B20_FUNCTION_SCRATCHPAD_READ); + owb_read_bytes(ds18b20_info->bus, (uint8_t *)&scratchpad, count); + esp_log_buffer_hex(TAG, &scratchpad, count); + return scratchpad; +} + +static bool _write_scratchpad(const DS18B20_Info * ds18b20_info, const Scratchpad * scratchpad, bool verify) +{ + bool result = false; + // Only bytes 2, 3 and 4 (trigger and configuration) can be written. + // All three bytes MUST be written before the next reset to avoid corruption. + if (_is_init(ds18b20_info)) + { + owb_reset(ds18b20_info->bus); + owb_write_byte(ds18b20_info->bus, OWB_ROM_MATCH); + owb_write_rom_code(ds18b20_info->bus, ds18b20_info->rom_code); + owb_write_byte(ds18b20_info->bus, DS18B20_FUNCTION_SCRATCHPAD_WRITE); + owb_write_bytes(ds18b20_info->bus, (uint8_t *)&scratchpad->trigger_high, 3); + ESP_LOGD(TAG, "scratchpad write 3 bytes:"); + esp_log_buffer_hex(TAG, &scratchpad->trigger_high, 3); + result = true; + + if (verify) + { + Scratchpad read = _read_scratchpad(ds18b20_info, offsetof(Scratchpad, configuration) + 1); + if (memcmp(&scratchpad->trigger_high, &read.trigger_high, 3) != 0) + { + ESP_LOGE(TAG, "scratchpad verify failed: " + "wrote {0x%02x, 0x%02x, 0x%02x}, " + "read {0x%02x, 0x%02x, 0x%02x}", + scratchpad->trigger_high, scratchpad->trigger_low, scratchpad->configuration, + read.trigger_high, read.trigger_low, read.configuration); + result = false; + } + } + } + return result; +} + DS18B20_Info * ds18b20_malloc(void) { DS18B20_Info * ds18b20_info = malloc(sizeof(*ds18b20_info)); @@ -132,8 +200,11 @@ void ds18b20_init(DS18B20_Info * ds18b20_info, OneWireBus * bus, OneWireBus_ROMC ds18b20_info->bus = bus; ds18b20_info->rom_code = rom_code; ds18b20_info->use_crc = false; - ds18b20_info->resolution = DS18B20_RESOLUTION_12_BIT; + ds18b20_info->resolution = DS18B20_RESOLUTION_INVALID; ds18b20_info->init = true; + + // read current resolution from device as it may not be power-on or factory default + ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info); } else { @@ -150,6 +221,82 @@ void ds18b20_use_crc(DS18B20_Info * ds18b20_info, bool use_crc) } } +bool ds18b20_set_resolution(DS18B20_Info * ds18b20_info, DS18B20_RESOLUTION resolution) +{ + bool result = false; + if (_is_init(ds18b20_info)) + { + if (_check_resolution(ds18b20_info->resolution)) + { + // read scratchpad up to and including configuration register + Scratchpad scratchpad = _read_scratchpad(ds18b20_info, + offsetof(Scratchpad, configuration) - offsetof(Scratchpad, temperature) + 1); + + // modify configuration register to set resolution + uint8_t value = (((resolution - 1) & 0x03) << 5) | 0x1f; + scratchpad.configuration = value; + ESP_LOGD(TAG, "configuration value 0x%02x", value); + + // write bytes 2, 3 and 4 of scratchpad + result = _write_scratchpad(ds18b20_info, &scratchpad, /* verify */ true); + if (result) + { + ds18b20_info->resolution = resolution; + ESP_LOGI(TAG, "Resolution set to %d bits", (int)resolution); + } + else + { + // Resolution change failed - update the info resolution with the value read from configuration + ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info); + ESP_LOGW(TAG, "Resolution consistency lost - refreshed from device: %d", ds18b20_info->resolution); + } + } + else + { + ESP_LOGE(TAG, "Unsupported resolution %d", resolution); + } + } + return result; +} + +DS18B20_RESOLUTION ds18b20_read_resolution(DS18B20_Info * ds18b20_info) +{ + DS18B20_RESOLUTION resolution = DS18B20_RESOLUTION_INVALID; + if (_is_init(ds18b20_info)) + { + // read scratchpad up to and including configuration register + Scratchpad scratchpad = _read_scratchpad(ds18b20_info, + offsetof(Scratchpad, configuration) - offsetof(Scratchpad, temperature) + 1); + + resolution = ((scratchpad.configuration >> 5) & 0x03) + DS18B20_RESOLUTION_9_BIT; + if (!_check_resolution(resolution)) + { + ESP_LOGE(TAG, "invalid resolution read from device: 0x%02x", scratchpad.configuration); + resolution = DS18B20_RESOLUTION_INVALID; + } + else + { + ESP_LOGI(TAG, "Resolution read as %d", resolution); + } + } + return resolution; +} + +static void _wait_for_conversion(DS18B20_RESOLUTION resolution) +{ + if (_check_resolution(resolution)) + { + int divisor = 1 << (DS18B20_RESOLUTION_12_BIT - resolution); + ESP_LOGD(TAG, "divisor %d", divisor); + float max_conversion_time = (float)T_CONV / (float)divisor; + int ticks = ceil(max_conversion_time / portTICK_PERIOD_MS); + ESP_LOGD(TAG, "wait for conversion: %.3f ms, %d ticks", max_conversion_time, ticks); + + // wait at least this maximum conversion time + vTaskDelay(ticks); + } +} + float ds18b20_get_temp(DS18B20_Info * ds18b20_info) { float temp = 0.0f; @@ -162,7 +309,8 @@ float ds18b20_get_temp(DS18B20_Info * ds18b20_info) owb_write_byte(bus, OWB_ROM_MATCH); owb_write_rom_code(bus, ds18b20_info->rom_code); owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT); - vTaskDelay(750 / portTICK_PERIOD_MS); + + _wait_for_conversion(ds18b20_info->resolution); // reset owb_reset(bus); diff --git a/main/ds18b20.h b/main/ds18b20.h index cbb0e41..1a77c0d 100644 --- a/main/ds18b20.h +++ b/main/ds18b20.h @@ -45,6 +45,7 @@ extern "C" { */ typedef enum { + DS18B20_RESOLUTION_INVALID = -1, ///< Invalid resolution DS18B20_RESOLUTION_9_BIT = 9, ///< 9-bit resolution, LSB bits 2,1,0 undefined DS18B20_RESOLUTION_10_BIT = 10, ///< 10-bit resolution, LSB bits 1,0 undefined DS18B20_RESOLUTION_11_BIT = 11, ///< 11-bit resolution, LSB bit 0 undefined @@ -93,6 +94,25 @@ void ds18b20_init(DS18B20_Info * ds18b20_info, OneWireBus * bus, OneWireBus_ROMC */ void ds18b20_use_crc(DS18B20_Info * ds18b20_info, bool use_crc); +/** + * @brief Set temperature measurement resolution. + * + * This programs the hardware to the specified resolution and sets the cached value to be the same. + * If the program fails, the value currently in hardware is used to refresh the cache. + * + * @param[in] ds18b20_info Pointer to device info instance. + * @param[in] resolution Selected resolution. + * @return True if successful, otherwise false. + */ +bool ds18b20_set_resolution(DS18B20_Info * ds18b20_info, DS18B20_RESOLUTION resolution); + +/** + * @brief Update and return the current temperature measurement resolution from the device. + * @param[in] ds18b20_info Pointer to device info instance. + * @return The currently configured temperature measurement resolution. + */ +DS18B20_RESOLUTION ds18b20_read_resolution(DS18B20_Info * ds18b20_info); + /** * @brief Read 64-bit ROM code from device - only works when there is a single device on the bus. * @param[in] ds18b20_info Pointer to device info instance. diff --git a/main/ds18b20_main.c b/main/ds18b20_main.c index 0604663..47c1a23 100644 --- a/main/ds18b20_main.c +++ b/main/ds18b20_main.c @@ -42,7 +42,7 @@ void app_main() { - esp_log_level_set("*", ESP_LOG_DEBUG); + esp_log_level_set("*", ESP_LOG_INFO); // Create a 1-Wire bus #ifdef USE_STATIC @@ -112,6 +112,10 @@ void app_main() ds18b20_init(ds18b20_info, owb, device_rom_codes[i]); // associate with bus and device //ds18b20_init_solo(ds18b20_info, owb); // only one device on bus ds18b20_use_crc(ds18b20_info, true); // enable CRC check for temperature readings +// ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION_9_BIT); +// ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION_10_BIT); +// ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION_11_BIT); + ds18b20_set_resolution(ds18b20_info, DS18B20_RESOLUTION_12_BIT); } // read temperatures from all sensors @@ -121,7 +125,7 @@ void app_main() for (int i = 0; i < num_devices; ++i) { float temp = ds18b20_get_temp(devices[i]); - printf(" %d: %.2f\n", i, temp); + printf(" %d: %.3f\n", i, temp); } vTaskDelay(1000 / portTICK_PERIOD_MS); }