diff --git a/boards/arm/tdk_robokit1/tdk_robokit1-common.dtsi b/boards/arm/tdk_robokit1/tdk_robokit1-common.dtsi index 364356dd565..8eed847204c 100644 --- a/boards/arm/tdk_robokit1/tdk_robokit1-common.dtsi +++ b/boards/arm/tdk_robokit1/tdk_robokit1-common.dtsi @@ -6,6 +6,7 @@ #include #include "tdk_robokit1-pinctrl.dtsi" +#include "tdk_robokit1-thermistor.dtsi" / { aliases { @@ -42,6 +43,17 @@ label = "User LED"; }; }; + + temp_sensor: ambient_temp_sensor { + compatible = "zephyr,ntc-thermistor"; + io-channels = <&spi_adc 0>; + zephyr,rt-table = <&thermistor_R25_10000_B_3974>; + r25-ohm = <10000>; + pullup-uv = <3300000>; + pullup-ohm = <0>; + pulldown-ohm = <10000>; + connection-type = "NTC_CONNECTED_POSITIVE"; + }; }; &cpu0 { @@ -219,3 +231,6 @@ zephyr_udc0: &usbhs { }; }; +&thermistor_R25_10000_B_3974 { + status = "okay"; +}; diff --git a/boards/arm/tdk_robokit1/tdk_robokit1-thermistor.dtsi b/boards/arm/tdk_robokit1/tdk_robokit1-thermistor.dtsi new file mode 100644 index 00000000000..26ed27522ac --- /dev/null +++ b/boards/arm/tdk_robokit1/tdk_robokit1-thermistor.dtsi @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* NTC Thermistor Table Generated with ntc_thermistor_table.py */ + +/ { + thermistor_R25_10000_B_3974: thermistor-R25-10000-B-3974 { + status = "disabled"; + compatible = "zephyr,ntc-thermistor-rt-table"; + /* Format */ + tr-table = <(-25) 146676>, + <(-15) 78875>, + <(-5) 44424>, + <(5) 26075>, + <(15) 15881>, + <(25) 10000>, + <(35) 6488>, + <(45) 4326>, + <(55) 2956>, + <(65) 2066>, + <(75) 1474>, + <(85) 1072>, + <(95) 793>, + <(105) 596>, + <(115) 454>, + <(125) 351>; + }; +}; diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index ba346d851df..098b57fddde 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -127,6 +127,7 @@ add_subdirectory_ifdef(CONFIG_ESP32_TEMP esp32_temp) add_subdirectory_ifdef(CONFIG_RPI_PICO_TEMP rpi_pico_temp) add_subdirectory_ifdef(CONFIG_XMC4XXX_TEMP xmc4xxx_temp) add_subdirectory_ifdef(CONFIG_TMD2620 tmd2620) +add_subdirectory_ifdef(CONFIG_ZEPHYR_NTC_THERMISTOR zephyr_thermistor) if(CONFIG_USERSPACE OR CONFIG_SENSOR_SHELL OR CONFIG_SENSOR_SHELL_BATTERY) # The above if() is needed or else CMake would complain about diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 2e186c8bf5c..58ff420d0e0 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -293,4 +293,6 @@ source "drivers/sensor/xmc4xxx_temp/Kconfig" source "drivers/sensor/tmd2620/Kconfig" +source "drivers/sensor/zephyr_thermistor/Kconfig" + endif # SENSOR diff --git a/drivers/sensor/zephyr_thermistor/CMakeLists.txt b/drivers/sensor/zephyr_thermistor/CMakeLists.txt new file mode 100644 index 00000000000..71cf296a9fd --- /dev/null +++ b/drivers/sensor/zephyr_thermistor/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources( + zephyr_ntc_thermistor.c + zephyr_ntc_thermistor_rt_table.c +) diff --git a/drivers/sensor/zephyr_thermistor/Kconfig b/drivers/sensor/zephyr_thermistor/Kconfig new file mode 100644 index 00000000000..3055cfd8161 --- /dev/null +++ b/drivers/sensor/zephyr_thermistor/Kconfig @@ -0,0 +1,14 @@ +# Zephyr Generic NTC Thermistor +# +# Copyright (c) 2023 Google LLC +# +# SPDX-License-Identifier: Apache-2.0 + +config ZEPHYR_NTC_THERMISTOR + bool "Zephyr Generic NTC Thermistor" + default y + depends on DT_HAS_ZEPHYR_NTC_THERMISTOR_ENABLED + depends on DT_HAS_ZEPHYR_NTC_THERMISTOR_RT_TABLE_ENABLED + select ADC + help + Enable driver for Zephyr NTC Thermistor. diff --git a/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor.c b/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor.c new file mode 100644 index 00000000000..cbcd8176cc0 --- /dev/null +++ b/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor.c @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_ntc_thermistor + +#include +#include +#include +#include "zephyr_ntc_thermistor.h" + +LOG_MODULE_REGISTER(ZEPHYR_NTC_THERMISTOR, CONFIG_SENSOR_LOG_LEVEL); + +struct zephyr_ntc_thermistor_data { + struct k_mutex mutex; + int16_t raw; + int16_t sample_val; +}; + +struct zephyr_ntc_thermistor_config { + const struct adc_dt_spec adc_channel; + const struct ntc_config ntc_cfg; +}; + +static int zephyr_ntc_thermistor_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct zephyr_ntc_thermistor_data *data = dev->data; + const struct zephyr_ntc_thermistor_config *cfg = dev->config; + int32_t val_mv; + int res; + struct adc_sequence sequence = { + .options = NULL, + .buffer = &data->raw, + .buffer_size = sizeof(data->raw), + .calibrate = false, + }; + + k_mutex_lock(&data->mutex, K_FOREVER); + + adc_sequence_init_dt(&cfg->adc_channel, &sequence); + res = adc_read(cfg->adc_channel.dev, &sequence); + if (res) { + val_mv = data->raw; + res = adc_raw_to_millivolts_dt(&cfg->adc_channel, &val_mv); + data->sample_val = val_mv; + } + + k_mutex_unlock(&data->mutex); + + return res; +} + +static int zephyr_ntc_thermistor_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct zephyr_ntc_thermistor_data *data = dev->data; + const struct zephyr_ntc_thermistor_config *cfg = dev->config; + uint32_t ohm, max_adc; + int32_t temp; + + switch (chan) { + case SENSOR_CHAN_AMBIENT_TEMP: + max_adc = (1 << (cfg->adc_channel.resolution - 1)) - 1; + ohm = ntc_get_ohm_of_thermistor(&cfg->ntc_cfg, max_adc, data->raw); + temp = ntc_get_temp_mc(cfg->ntc_cfg.type, ohm); + val->val1 = temp / 1000; + val->val2 = (temp % 1000) * 1000; + LOG_INF("ntc temp says %u", val->val1); + break; + default: + return -ENOTSUP; + } + return 0; +} + +static const struct sensor_driver_api zephyr_ntc_thermistor_driver_api = { + .sample_fetch = zephyr_ntc_thermistor_sample_fetch, + .channel_get = zephyr_ntc_thermistor_channel_get, +}; + +static int zephyr_ntc_thermistor_init(const struct device *dev) +{ + const struct zephyr_ntc_thermistor_config *cfg = dev->config; + int err; + + if (!device_is_ready(cfg->adc_channel.dev)) { + LOG_ERR("ADC controller device is not ready\n"); + return -ENODEV; + } + + err = adc_channel_setup_dt(&cfg->adc_channel); + if (err < 0) { + LOG_ERR("Could not setup channel err(%d)\n", err); + return err; + } + + return 0; +} + +#define ZEPHYR_NTC_THERMISTOR_DEV_INIT(inst) \ + static struct zephyr_ntc_thermistor_data zephyr_ntc_thermistor_driver_##inst; \ + \ + extern const struct ntc_type NTC_TYPE_NAME(DT_INST_PROP(inst, zephyr_rt_table)); \ + \ + static const struct zephyr_ntc_thermistor_config zephyr_ntc_thermistor_cfg_##inst = { \ + .adc_channel = ADC_DT_SPEC_INST_GET(inst), \ + .ntc_cfg = \ + { \ + .r25_ohm = DT_INST_PROP(inst, r25_ohm), \ + .pullup_uv = DT_INST_PROP(inst, pullup_uv), \ + .pullup_ohm = DT_INST_PROP(inst, pullup_ohm), \ + .pulldown_ohm = DT_INST_PROP(inst, pulldown_ohm), \ + .connection_type = DT_INST_STRING_TOKEN(inst, connection_type), \ + .type = &NTC_TYPE_NAME(DT_INST_PROP(inst, zephyr_rt_table)), \ + }, \ + }; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE( \ + inst, zephyr_ntc_thermistor_init, NULL, &zephyr_ntc_thermistor_driver_##inst, \ + &zephyr_ntc_thermistor_cfg_##inst, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \ + &zephyr_ntc_thermistor_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(ZEPHYR_NTC_THERMISTOR_DEV_INIT) diff --git a/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor.h b/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor.h new file mode 100644 index 00000000000..fef0272f658 --- /dev/null +++ b/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_NTC_THERMISTOR_H +#define ZEPHYR_NTC_THERMISTOR_H + +#include + +struct ntc_compensation { + const int32_t temp_c; + const uint32_t ohm; +}; + +enum ntc_type_e { + NTC_CONNECTED_POSITIVE, + NTC_CONNECTED_GROUND +}; + +struct ntc_type { + const struct ntc_compensation *comp; + int n_comp; + int (*ohm_cmp)(const void *key, const void *element); +}; + +struct ntc_config { + enum ntc_type_e connection_type; + uint32_t r25_ohm; + uint32_t pullup_uv; + uint32_t pullup_ohm; + uint32_t pulldown_ohm; + const struct ntc_type *type; +}; + +#define NTC_TYPE_NAME(node_id) DT_CAT(node_id, _type) + +/** + * @brief Helper comparison function for bsearch for specific + * ntc_type + * + * Ohms are sorted in descending order, perform comparison to find + * interval indexes where key falls between + * + * @param type: Pointer to ntc_type table info + * @param key: Key value bsearch is looking for + * @param element: Array element bsearch is searching + */ +int ntc_compensation_compare_ohm(const struct ntc_type *type, const void *key, const void *element); + +/** + * @brief Converts ohm to temperature in milli centigrade + * + * @param type: Pointer to ntc_type table info + * @param ohm: Read resistance of NTC thermistor + * + * @return temperature in milli centigrade + */ +int32_t ntc_get_temp_mc(const struct ntc_type *type, unsigned int ohm); + +/** + * @brief Calculate the resistance read from NTC Thermistor + * + * @param cfg: NTC Thermistor configuration + * @param max_adc: Max ADC value + * @param raw_adc: Raw ADC value read + * + * @return resistance from raw ADC value + */ +uint32_t ntc_get_ohm_of_thermistor(const struct ntc_config *cfg, uint32_t max_adc, int16_t raw_adc); + +#endif /* ZEPHYR_NTC_THERMISTOR_H */ diff --git a/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor_rt_table.c b/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor_rt_table.c new file mode 100644 index 00000000000..381e2926c13 --- /dev/null +++ b/drivers/sensor/zephyr_thermistor/zephyr_ntc_thermistor_rt_table.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "zephyr_ntc_thermistor.h" + +/** + * fixp_linear_interpolate() - interpolates a value from two known points + * + * @x0: x value of point 0 + * @y0: y value of point 0 + * @x1: x value of point 1 + * @y1: y value of point 1 + * @x: the linear interpolant + */ +static int ntc_fixp_linear_interpolate(int x0, int y0, int x1, int y1, int x) +{ + if (y0 == y1 || x == x0) { + return y0; + } + if (x1 == x0 || x == x1) { + return y1; + } + + return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); +} + +/** + * ntc_compensation_compare_ohm() - Helper comparison function for bsearch + * + * Ohms are sorted in descending order, perform comparison to find + * interval indexes where key falls between + * + * @type: Pointer to ntc_type table info + * @key: Key value bsearch is looking for + * @element: Array element bsearch is searching + */ +int ntc_compensation_compare_ohm(const struct ntc_type *type, const void *key, const void *element) +{ + int sgn = 0; + const struct ntc_compensation *ntc_key = key; + const struct ntc_compensation *element_val = element; + int element_idx = element_val - type->comp; + + if (ntc_key->ohm > element_val->ohm) { + if (element_idx == 0) { + sgn = 0; + } else { + sgn = -1; + } + } else if (ntc_key->ohm == element_val->ohm) { + sgn = 0; + } else if (ntc_key->ohm < element_val->ohm) { + if (element_idx == type->n_comp - 1) { + sgn = 0; + } else { + if (element_idx != type->n_comp - 1 && + ntc_key->ohm > type->comp[element_idx + 1].ohm) { + sgn = 0; + } else { + sgn = 1; + } + } + } + + return sgn; +} + +/** + * ntc_lookup_comp() - Finds indicies where ohm falls between + * + * @ohm: key value search is looking for + * @i_low: return Lower interval index value + * @i_high: return Higher interval index value + */ +static void ntc_lookup_comp(const struct ntc_type *type, unsigned int ohm, int *i_low, int *i_high) +{ + const struct ntc_compensation *ptr; + struct ntc_compensation search_ohm_key = {.ohm = ohm}; + + ptr = bsearch(&search_ohm_key, type->comp, type->n_comp, sizeof(type->comp[0]), + type->ohm_cmp); + if (ptr) { + *i_low = ptr - type->comp; + *i_high = *i_low + 1; + } else { + *i_low = 0; + *i_high = 0; + } +} + +/** + * ntc_get_ohm_of_thermistor() - Calculate the resistance read from NTC Thermistor + * + * @cfg: NTC Thermistor configuration + * @max_adc: Max ADC value + * @raw_adc: Raw ADC value read + */ +uint32_t ntc_get_ohm_of_thermistor(const struct ntc_config *cfg, uint32_t max_adc, int16_t raw_adc) +{ + uint32_t ohm; + + if (cfg->connection_type == NTC_CONNECTED_POSITIVE) { + ohm = cfg->pulldown_ohm * max_adc / (raw_adc - 1); + } else { + ohm = cfg->pullup_ohm * (raw_adc - 1) / max_adc; + } + + return ohm; +} + +int32_t ntc_get_temp_mc(const struct ntc_type *type, unsigned int ohm) +{ + int low, high; + int temp; + + ntc_lookup_comp(type, ohm, &low, &high); + /* + * First multiplying the table temperatures with 1000 to get to + * millicentigrades (which is what we want) and then interpolating + * will give the best precision. + */ + temp = ntc_fixp_linear_interpolate(type->comp[low].ohm, type->comp[low].temp_c * 1000, + type->comp[high].ohm, type->comp[high].temp_c * 1000, + ohm); + return temp; +} + +#define THERMISTOR_DATA_NAME(node_id) DT_CAT(node_id, _thermistor_data) + +#define DEFINE_THERMISTOR_TYPE(node_id) \ + static int node_id##_compare_ohm(const void *key, const void *element); \ + BUILD_ASSERT(DT_PROP_LEN(node_id, tr_table) % 2 == 0, "T/R Table needs to be even size"); \ + static const uint32_t node_id##_tr_table[] = DT_PROP(node_id, tr_table); \ + \ + const struct ntc_type NTC_TYPE_NAME(node_id) = { \ + .comp = (struct ntc_compensation *)node_id##_tr_table, \ + .n_comp = (ARRAY_SIZE(node_id##_tr_table) / 2), \ + .ohm_cmp = node_id##_compare_ohm, \ + }; \ + \ + static int node_id##_compare_ohm(const void *key, const void *element) \ + { \ + return ntc_compensation_compare_ohm(&NTC_TYPE_NAME(node_id), key, element); \ + } + +DT_FOREACH_STATUS_OKAY(zephyr_ntc_thermistor_rt_table, DEFINE_THERMISTOR_TYPE) diff --git a/dts/bindings/sensor/zephyr,ntc-thermistor-rt-table.yaml b/dts/bindings/sensor/zephyr,ntc-thermistor-rt-table.yaml new file mode 100644 index 00000000000..287b357562d --- /dev/null +++ b/dts/bindings/sensor/zephyr,ntc-thermistor-rt-table.yaml @@ -0,0 +1,18 @@ +# Copyright 2023 Google LLC +# +# SPDX-License-Identifier: Apache-2.0 + +description: | + Common properties for NTC thermistors + For more information: https://www.electronics-tutorials.ws/io/thermistors.html + +compatible: "zephyr,ntc-thermistor-rt-table" + +properties: + tr-table: + required: true + type: array + description: | + NTC Thermistor Temp/Resistance Table formatted as an array like follows + + See `scripts/utils/ntc_thermistor_table.py` to help generate node. diff --git a/dts/bindings/sensor/zephyr,ntc-thermistor.yaml b/dts/bindings/sensor/zephyr,ntc-thermistor.yaml new file mode 100644 index 00000000000..08585a65706 --- /dev/null +++ b/dts/bindings/sensor/zephyr,ntc-thermistor.yaml @@ -0,0 +1,47 @@ +# Copyright (c) 2022 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Generic NTC Thermistor + +compatible: "zephyr,ntc-thermistor" + +include: [sensor-device.yaml] + +properties: + io-channels: + required: true + description: | + ADC IO channel connected to this NTC thermistor. + + zephyr,rt-table: + type: phandle + required: true + description: R/T table for NTC thermistor. + + r25-ohm: + type: int + description: | + The resistance value of the thermistor at 25 C. + + pullup-uv: + type: int + description: | + The pullup voltage microvoltage in the circuit. + + pullup-ohm: + type: int + description: | + The pullup resistance in the circuit. + + pulldown-ohm: + type: int + description: | + The pulldown resistance in the circuit. + + connection-type: + type: string + description: | + The connection type of the circuit model used. + enum: + - NTC_CONNECTED_POSITIVE + - NTC_CONNECTED_GROUND diff --git a/tests/drivers/build_all/sensor/adc.dtsi b/tests/drivers/build_all/sensor/adc.dtsi index 9336a6d89d3..1b54ec5651f 100644 --- a/tests/drivers/build_all/sensor/adc.dtsi +++ b/tests/drivers/build_all/sensor/adc.dtsi @@ -1,14 +1,59 @@ /* * Copyright (c) 2023 FTP Technologies + * Copyright (c) 2023, Google LLC * * SPDX-License-Identifier: Apache-2.0 * * Application overlay for ADC devices */ - test_adc_mcp9700a: mcp9700a { +#include + +test_adc_mcp9700a: mcp9700a { status = "okay"; compatible = "microchip,mcp970x"; family = "MCP9700/9700A"; io-channels = <&test_adc 0>; }; + +adc0: adc { + compatible = "zephyr,adc-emul"; + nchannels = <2>; + ref-internal-mv = <3300>; + ref-external1-mv = <5000>; + #io-channel-cells = <1>; + status = "okay"; +}; + +thermistor_R25_10000_B_3974: thermistor-R25-10000-B-3974 { + compatible = "zephyr,ntc-thermistor-rt-table"; + /* Format */ + tr-table = <(-25) 146676>, + <(-15) 78875>, + <(-5) 44424>, + <(5) 26075>, + <(15) 15881>, + <(25) 10000>, + <(35) 6488>, + <(45) 4326>, + <(55) 2956>, + <(65) 2066>, + <(75) 1474>, + <(85) 1072>, + <(95) 793>, + <(105) 596>, + <(115) 454>, + <(125) 351>; +}; + +test_adc_b57861s: b57861s@0 { + compatible = "zephyr,ntc-thermistor"; + reg = <0x0 0x1000>; + io-channels = <&adc0 0>; + zephyr,rt-table = <&thermistor_R25_10000_B_3974>; + r25-ohm = <10000>; + pullup-uv = <3300000>; + pullup-ohm = <0>; + pulldown-ohm = <10000>; + connection-type = "NTC_CONNECTED_POSITIVE"; +};