diff -r 000000000000 -r 88d965579617 components/esp32-ds18b20/ds18b20.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/components/esp32-ds18b20/ds18b20.c Tue Oct 08 12:00:31 2019 +0200 @@ -0,0 +1,496 @@ +/* + * 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. + */ + +/** + * @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 +#include +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "ds18b20.h" +#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 ///< Start temperature conversion +#define DS18B20_FUNCTION_SCRATCHPAD_WRITE 0x4E ///< Write scratchpad +#define DS18B20_FUNCTION_SCRATCHPAD_READ 0xBE ///< Read scratchpad +#define DS18B20_FUNCTION_SCRATCHPAD_COPY 0x48 ///< Copy scratchpad +#define DS18B20_FUNCTION_EEPROM_RECALL 0xB8 ///< EEPROM recall +#define DS18B20_FUNCTION_POWER_SUPPLY_READ 0xB4 ///< Read powersupply mode + +/// @cond ignore +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; +/// @endcond ignore + +static void _init(DS18B20_Info * ds18b20_info, const OneWireBus * bus) +{ + if (ds18b20_info != NULL) + { + ds18b20_info->bus = bus; + memset(&ds18b20_info->rom_code, 0, sizeof(ds18b20_info->rom_code)); + ds18b20_info->use_crc = false; + ds18b20_info->resolution = DS18B20_RESOLUTION_INVALID; + ds18b20_info->solo = false; // assume multiple devices unless told otherwise + ds18b20_info->init = true; + } + else + { + ESP_LOGE(TAG, "ds18b20_info is NULL"); + } +} + +static bool _is_init(const DS18B20_Info * ds18b20_info) +{ + bool ok = false; + if (ds18b20_info != NULL) + { + if (ds18b20_info->init) + { + // OK + ok = true; + } + else + { + ESP_LOGE(TAG, "ds18b20_info is not initialised"); + } + } + else + { + ESP_LOGE(TAG, "ds18b20_info is NULL"); + } + return ok; +} + +static bool _address_device(const DS18B20_Info * ds18b20_info) +{ + bool present = false; + if (_is_init(ds18b20_info)) + { + owb_reset(ds18b20_info->bus, &present); + if (present) + { + if (ds18b20_info->solo) + { + // if there's only one device on the bus, we can skip + // sending the ROM code and instruct it directly + owb_write_byte(ds18b20_info->bus, OWB_ROM_SKIP); + } + else + { + // if there are multiple devices on the bus, a Match ROM command + // must be issued to address a specific slave + owb_write_byte(ds18b20_info->bus, OWB_ROM_MATCH); + owb_write_rom_code(ds18b20_info->bus, ds18b20_info->rom_code); + } + } + else + { + ESP_LOGE(TAG, "ds18b20 device not responding"); + } + } + return present; +} + +static bool _check_resolution(DS18B20_RESOLUTION resolution) +{ + return (resolution >= DS18B20_RESOLUTION_9_BIT) && (resolution <= DS18B20_RESOLUTION_12_BIT); +} + +static float _wait_for_conversion(DS18B20_RESOLUTION resolution) +{ + float elapsed_time = 0.0f; + 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); + + // TODO: measure elapsed time more accurately + elapsed_time = ticks * portTICK_PERIOD_MS; + } + return elapsed_time; +} + +static float _decode_temp(uint8_t lsb, uint8_t msb, DS18B20_RESOLUTION resolution) +{ + float result = 0.0f; + if (_check_resolution(resolution)) + { + // masks to remove undefined bits from result + static const uint8_t lsb_mask[4] = { ~0x03, ~0x02, ~0x01, ~0x00 }; + uint8_t lsb_masked = lsb_mask[resolution - DS18B20_RESOLUTION_9_BIT] & lsb; + int16_t raw = (msb << 8) | lsb_masked; + result = raw / 16.0f; + } + else + { + ESP_LOGE(TAG, "Unsupported resolution %d", resolution); + } + 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); + if (_address_device(ds18b20_info)) + { + 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)) + { + if (_address_device(ds18b20_info)) + { + 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; +} + + +// Public API + +DS18B20_Info * ds18b20_malloc(void) +{ + DS18B20_Info * ds18b20_info = malloc(sizeof(*ds18b20_info)); + if (ds18b20_info != NULL) + { + memset(ds18b20_info, 0, sizeof(*ds18b20_info)); + ESP_LOGD(TAG, "malloc %p", ds18b20_info); + } + else + { + ESP_LOGE(TAG, "malloc failed"); + } + + return ds18b20_info; +} + +void ds18b20_free(DS18B20_Info ** ds18b20_info) +{ + if (ds18b20_info != NULL && (*ds18b20_info != NULL)) + { + ESP_LOGD(TAG, "free %p", *ds18b20_info); + free(*ds18b20_info); + *ds18b20_info = NULL; + } +} + +void ds18b20_init(DS18B20_Info * ds18b20_info, const OneWireBus * bus, OneWireBus_ROMCode rom_code) +{ + if (ds18b20_info != NULL) + { + _init(ds18b20_info, bus); + ds18b20_info->rom_code = rom_code; + + // read current resolution from device as it may not be power-on or factory default + ds18b20_info->resolution = ds18b20_read_resolution(ds18b20_info); + } + else + { + ESP_LOGE(TAG, "ds18b20_info is NULL"); + } +} + +void ds18b20_init_solo(DS18B20_Info * ds18b20_info, const OneWireBus * bus) +{ + if (ds18b20_info != NULL) + { + _init(ds18b20_info, bus); + ds18b20_info->solo = 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 + { + ESP_LOGE(TAG, "ds18b20_info is NULL"); + } +} + +void ds18b20_use_crc(DS18B20_Info * ds18b20_info, bool use_crc) +{ + if (_is_init(ds18b20_info)) + { + ds18b20_info->use_crc = use_crc; + ESP_LOGD(TAG, "use_crc %d", ds18b20_info->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_LOGD(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_LOGD(TAG, "Resolution read as %d", resolution); + } + } + return resolution; +} + +bool ds18b20_convert(const DS18B20_Info * ds18b20_info) +{ + bool result = false; + if (_is_init(ds18b20_info)) + { + const OneWireBus * bus = ds18b20_info->bus; + if (_address_device(ds18b20_info)) + { + // initiate a temperature measurement + owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT); + result = true; + } + else + { + ESP_LOGE(TAG, "ds18b20 device not responding"); + } + } + return result; +} + +void ds18b20_convert_all(const OneWireBus * bus) +{ + bool is_present = false; + owb_reset(bus, &is_present); + owb_write_byte(bus, OWB_ROM_SKIP); + owb_write_byte(bus, DS18B20_FUNCTION_TEMP_CONVERT); +} + +float ds18b20_wait_for_conversion(const DS18B20_Info * ds18b20_info) +{ + float elapsed_time = 0.0f; + if (_is_init(ds18b20_info)) + { + elapsed_time = _wait_for_conversion(ds18b20_info->resolution); + } + return elapsed_time; +} + +DS18B20_ERROR ds18b20_read_temp(const DS18B20_Info * ds18b20_info, float * value) +{ + DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN; + if (_is_init(ds18b20_info)) + { + const OneWireBus * bus = ds18b20_info->bus; + if (_address_device(ds18b20_info)) + { + // read measurement + if (owb_write_byte(bus, DS18B20_FUNCTION_SCRATCHPAD_READ) == OWB_STATUS_OK) + { + err = DS18B20_OK; + + uint8_t temp_LSB = 0; + uint8_t temp_MSB = 0; + if (!ds18b20_info->use_crc) + { + // Without CRC: + owb_read_byte(bus, &temp_LSB); + owb_read_byte(bus, &temp_MSB); + bool is_present = false; + owb_reset(bus, &is_present); // terminate early + } + else + { + // with CRC: + uint8_t buffer[9] = {0}; + owb_read_bytes(bus, buffer, 9); + + temp_LSB = buffer[0]; + temp_MSB = buffer[1]; + + if (owb_crc8_bytes(0, buffer, 9) != 0) + { + ESP_LOGE(TAG, "CRC failed"); + temp_LSB = 0x00; + temp_MSB = 0x80; + err = DS18B20_ERROR_CRC; + } + } + + if (err == DS18B20_OK) + { + float temp = _decode_temp(temp_LSB, temp_MSB, ds18b20_info->resolution); + ESP_LOGD(TAG, "temp_LSB 0x%02x, temp_MSB 0x%02x, temp %f", temp_LSB, temp_MSB, temp); + + if (value) + { + *value = temp; + } + } + } + else + { + ESP_LOGE(TAG, "owb_write_byte failed"); + err = DS18B20_ERROR_OWB; + } + } + else + { + ESP_LOGE(TAG, "ds18b20 device not responding"); + err = DS18B20_ERROR_DEVICE; + } + } + return err; +} + +DS18B20_ERROR ds18b20_convert_and_read_temp(const DS18B20_Info * ds18b20_info, float * value) +{ + DS18B20_ERROR err = DS18B20_ERROR_UNKNOWN; + if (_is_init(ds18b20_info)) + { + if (ds18b20_convert(ds18b20_info)) + { + // wait at least maximum conversion time + _wait_for_conversion(ds18b20_info->resolution); + if (value) + { + *value = 0.0f; + err = ds18b20_read_temp(ds18b20_info, value); + } + else + { + err = DS18B20_ERROR_NULL; + } + } + } + return err; +} + +