238 lines
6.2 KiB
C
238 lines
6.2 KiB
C
/*
|
|
* Copyright (c) 2022 Thomas Stranger
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Driver for DS18B20 1-Wire temperature sensors
|
|
* A datasheet is available at:
|
|
* https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf
|
|
*
|
|
* Parasite power configuration is not supported by the driver.
|
|
*/
|
|
#define DT_DRV_COMPAT maxim_ds18b20
|
|
|
|
#include <zephyr/drivers/sensor.h>
|
|
#include <zephyr/drivers/sensor/w1_sensor.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
|
|
#include "ds18b20.h"
|
|
|
|
LOG_MODULE_REGISTER(DS18B20, CONFIG_SENSOR_LOG_LEVEL);
|
|
|
|
static int ds18b20_configure(const struct device *dev);
|
|
|
|
/* measure wait time for 9-bit, 10-bit, 11-bit, 12-bit resolution respectively */
|
|
static const uint16_t measure_wait_ms[4] = { 94, 188, 376, 750 };
|
|
|
|
static inline void ds18b20_temperature_from_raw(uint8_t *temp_raw,
|
|
struct sensor_value *val)
|
|
{
|
|
int16_t temp = sys_get_le16(temp_raw);
|
|
|
|
val->val1 = temp / 16;
|
|
val->val2 = (temp % 16) * 1000000 / 16;
|
|
}
|
|
|
|
/*
|
|
* Write scratch pad, read back, then copy to eeprom
|
|
*/
|
|
static int ds18b20_write_scratchpad(const struct device *dev,
|
|
struct ds18b20_scratchpad scratchpad)
|
|
{
|
|
struct ds18b20_data *data = dev->data;
|
|
const struct device *bus = ds18b20_bus(dev);
|
|
uint8_t sp_data[4] = {
|
|
DS18B20_CMD_WRITE_SCRATCHPAD,
|
|
scratchpad.alarm_temp_high,
|
|
scratchpad.alarm_temp_low,
|
|
scratchpad.config
|
|
};
|
|
|
|
return w1_write_read(bus, &data->config, sp_data, sizeof(sp_data), NULL, 0);
|
|
}
|
|
|
|
static int ds18b20_read_scratchpad(const struct device *dev,
|
|
struct ds18b20_scratchpad *scratchpad)
|
|
{
|
|
struct ds18b20_data *data = dev->data;
|
|
const struct device *bus = ds18b20_bus(dev);
|
|
uint8_t cmd = DS18B20_CMD_READ_SCRATCHPAD;
|
|
|
|
return w1_write_read(bus, &data->config, &cmd, 1,
|
|
(uint8_t *)&scratchpad[0], 9);
|
|
}
|
|
|
|
/* Starts sensor temperature conversion without waiting for completion. */
|
|
static int ds18b20_temperature_convert(const struct device *dev)
|
|
{
|
|
int ret;
|
|
struct ds18b20_data *data = dev->data;
|
|
const struct device *bus = ds18b20_bus(dev);
|
|
|
|
(void)w1_lock_bus(bus);
|
|
ret = w1_reset_select(bus, &data->config);
|
|
if (ret != 0) {
|
|
goto out;
|
|
}
|
|
ret = w1_write_byte(bus, DS18B20_CMD_CONVERT_T);
|
|
out:
|
|
(void)w1_unlock_bus(bus);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Write resolution into configuration struct,
|
|
* but don't write it to the sensor yet.
|
|
*/
|
|
static void ds18b20_set_resolution(const struct device *dev, uint8_t resolution)
|
|
{
|
|
struct ds18b20_data *data = dev->data;
|
|
|
|
data->scratchpad.config &= ~DS18B20_RESOLUTION_MASK;
|
|
data->scratchpad.config |= DS18B20_RESOLUTION(resolution);
|
|
}
|
|
|
|
static int ds18b20_sample_fetch(const struct device *dev,
|
|
enum sensor_channel chan)
|
|
{
|
|
const struct ds18b20_config *cfg = dev->config;
|
|
struct ds18b20_data *data = dev->data;
|
|
int status;
|
|
|
|
__ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL ||
|
|
chan == SENSOR_CHAN_AMBIENT_TEMP);
|
|
|
|
if (!data->lazy_loaded) {
|
|
status = ds18b20_configure(dev);
|
|
if (status < 0) {
|
|
return status;
|
|
}
|
|
data->lazy_loaded = true;
|
|
}
|
|
|
|
status = ds18b20_temperature_convert(dev);
|
|
if (status < 0) {
|
|
LOG_DBG("W1 fetch error");
|
|
return status;
|
|
}
|
|
k_msleep(measure_wait_ms[DS18B20_RESOLUTION_INDEX(cfg->resolution)]);
|
|
return ds18b20_read_scratchpad(dev, &data->scratchpad);
|
|
}
|
|
|
|
static int ds18b20_channel_get(const struct device *dev,
|
|
enum sensor_channel chan,
|
|
struct sensor_value *val)
|
|
{
|
|
struct ds18b20_data *data = dev->data;
|
|
|
|
if (chan != SENSOR_CHAN_AMBIENT_TEMP) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
ds18b20_temperature_from_raw((uint8_t *)&data->scratchpad.temp, val);
|
|
return 0;
|
|
}
|
|
|
|
static int ds18b20_configure(const struct device *dev)
|
|
{
|
|
const struct ds18b20_config *cfg = dev->config;
|
|
struct ds18b20_data *data = dev->data;
|
|
int ret;
|
|
|
|
if (w1_reset_bus(cfg->bus) <= 0) {
|
|
LOG_ERR("No 1-Wire slaves connected");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* In single drop configurations the rom can be read from device */
|
|
if (w1_get_slave_count(cfg->bus) == 1) {
|
|
if (w1_rom_to_uint64(&data->config.rom) == 0ULL) {
|
|
(void)w1_read_rom(cfg->bus, &data->config.rom);
|
|
}
|
|
} else if (w1_rom_to_uint64(&data->config.rom) == 0ULL) {
|
|
LOG_DBG("nr: %d", w1_get_slave_count(cfg->bus));
|
|
LOG_ERR("ROM required, because multiple slaves are on the bus");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((cfg->family != 0) && (cfg->family != data->config.rom.family)) {
|
|
LOG_ERR("Found 1-Wire slave is not a DS18B20");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* write default configuration */
|
|
ds18b20_set_resolution(dev, cfg->resolution);
|
|
ret = ds18b20_write_scratchpad(dev, data->scratchpad);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
LOG_DBG("Init DS18B20: ROM=%016llx\n",
|
|
w1_rom_to_uint64(&data->config.rom));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ds18b20_attr_set(const struct device *dev, enum sensor_channel chan,
|
|
enum sensor_attribute attr, const struct sensor_value *thr)
|
|
{
|
|
struct ds18b20_data *data = dev->data;
|
|
|
|
if ((enum sensor_attribute_w1)attr != SENSOR_ATTR_W1_ROM) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
data->lazy_loaded = false;
|
|
w1_sensor_value_to_rom(thr, &data->config.rom);
|
|
return 0;
|
|
}
|
|
|
|
static const struct sensor_driver_api ds18b20_driver_api = {
|
|
.attr_set = ds18b20_attr_set,
|
|
.sample_fetch = ds18b20_sample_fetch,
|
|
.channel_get = ds18b20_channel_get,
|
|
};
|
|
|
|
static int ds18b20_init(const struct device *dev)
|
|
{
|
|
const struct ds18b20_config *cfg = dev->config;
|
|
struct ds18b20_data *data = dev->data;
|
|
|
|
if (device_is_ready(cfg->bus) == 0) {
|
|
LOG_DBG("w1 bus is not ready");
|
|
return -ENODEV;
|
|
}
|
|
|
|
w1_uint64_to_rom(0ULL, &data->config.rom);
|
|
data->lazy_loaded = false;
|
|
/* in multidrop configurations the rom is need, but is not set during
|
|
* driver initialization, therefore do lazy initialization in all cases.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DS18B20_CONFIG_INIT(inst) \
|
|
{ \
|
|
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \
|
|
.family = (uint8_t)DT_INST_PROP_OR(inst, family_code, 0x28), \
|
|
.resolution = DT_INST_PROP(inst, resolution), \
|
|
}
|
|
|
|
#define DS18B20_DEFINE(inst) \
|
|
static struct ds18b20_data ds18b20_data_##inst; \
|
|
static const struct ds18b20_config ds18b20_config_##inst = \
|
|
DS18B20_CONFIG_INIT(inst); \
|
|
DEVICE_DT_INST_DEFINE(inst, \
|
|
ds18b20_init, \
|
|
NULL, \
|
|
&ds18b20_data_##inst, \
|
|
&ds18b20_config_##inst, \
|
|
POST_KERNEL, \
|
|
CONFIG_SENSOR_INIT_PRIORITY, \
|
|
&ds18b20_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DS18B20_DEFINE)
|