ESP32_ChinaDieselHeater_Con.../Arduino/BTCDieselHeater/src/Libraries/esp32-ds18b20/ds18b20.c
Ray Jones 924a079fb2 Using new fantastic RMT based library for DS18B20, banished issues with dodgy readings from sensor.
Persistent variables now used for temperature, pump and cyclic mode enabled settings (not NV)
NV save is now staged, so it can be performed at an appropriate time, after reading DS18B20!
JSONpack, instead of lame JSONloose to allow single line JSON output
2019-06-29 18:08:37 +10:00

498 lines
15 KiB
C

/*
* 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 "..\esp32-owb\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;
// ROM code not required
// 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;
}