components/esp32-ds18b20/ds18b20.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/esp32-ds18b20/ds18b20.c	Sat Oct 20 13:23:15 2018 +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 <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <math.h>
+
+#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
+#define DS18B20_FUNCTION_SCRATCHPAD_WRITE   0x4E
+#define DS18B20_FUNCTION_SCRATCHPAD_READ    0xBE
+#define DS18B20_FUNCTION_SCRATCHPAD_COPY    0x48
+#define DS18B20_FUNCTION_EEPROM_RECALL      0xB8
+#define DS18B20_FUNCTION_POWER_SUPPLY_READ  0xB4
+
+/// @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;
+}
+
+

mercurial