| /* |
| * 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) |