drivers: sensor: zephyr_thermistor: refactor driver

Refactor driver to align a bit more with its Linux counterpart, ie,
ntc_thermistor. This driver did quite a few _unconventional_ things,
like using "zephyr," compatibles, a dedicated node for pre-computed
compensation table (referenced by the actual pseudo-device node), etc.
The comparison helper function should likely be simplified as well (to
avoid the need for custom wrapper for bsearch), but this can be done
later.

In this refactor, each thermistor gets a compatible, e.g. "epcos,xxxx".
Compatibles are known by the driver, so are compensation tables. This
simplifies devicetree files. There's no need to bother about
compensation tables in **every** board file if Zephyr supports a certain
NTC model.

In general we should respect Linux bindings, which in the end influence
how drivers are implemented. In this case, this principle resulted in
simplified, easier to use code.

For future developers, this is how support for a new NTC can be added:

1. Add to the end of the driver:

```c
 #undef DT_DRV_COMPAT
 #define DT_DRV_COMPAT vnd_model

 static __unused const struct ntc_compensation comp_vnd_model[] = {
     { x, y },
     ...,
 };

 #define DT_INST_FOREACH_STATUS_OKAY_VARGS(NTC_THERMISTOR_DEV_INIT,
                                           DT_DRV_COMPAT, comp_vnd_model)
```
3. In driver's Kconfig make sure it depends on
   DT_HAS_$DT_DRV_COMPAT$_ENABLED

Note: $X$ means _value_ of X.

Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
This commit is contained in:
Gerard Marull-Paretas 2023-04-28 11:36:14 +02:00 committed by Carles Cufí
parent c60e4ec989
commit 44f48f6da7
16 changed files with 194 additions and 266 deletions

View file

@ -5,7 +5,6 @@
*/
#include "tdk_robokit1-pinctrl.dtsi"
#include "tdk_robokit1-thermistor.dtsi"
/ {
aliases {
@ -45,9 +44,8 @@
};
temp_sensor: ambient_temp_sensor {
compatible = "zephyr,ntc-thermistor";
compatible = "epcos,b57861s0103a039";
io-channels = <&spi_adc 0>;
zephyr,rt-table = <&thermistor_R25_10000_B_3974>;
r25-ohm = <10000>;
pullup-uv = <3300000>;
pullup-ohm = <0>;
@ -224,7 +222,3 @@ zephyr_udc0: &usbhs {
};
};
};
&thermistor_R25_10000_B_3974 {
status = "okay";
};

View file

@ -1,31 +0,0 @@
/*
* 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 <temp resistance> */
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>;
};
};

View file

@ -133,7 +133,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)
add_subdirectory_ifdef(CONFIG_NTC_THERMISTOR ntc_thermistor)
add_subdirectory_ifdef(CONFIG_S11059 s11059)
if(CONFIG_USERSPACE OR CONFIG_SENSOR_SHELL OR CONFIG_SENSOR_SHELL_BATTERY)

View file

@ -305,7 +305,7 @@ source "drivers/sensor/xmc4xxx_temp/Kconfig"
source "drivers/sensor/tmd2620/Kconfig"
source "drivers/sensor/zephyr_thermistor/Kconfig"
source "drivers/sensor/ntc_thermistor/Kconfig"
source "drivers/sensor/s11059/Kconfig"

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(ntc_thermistor.c ntc_thermistor_calc.c)

View file

@ -0,0 +1,11 @@
# Copyright (c) 2023 Google LLC
#
# SPDX-License-Identifier: Apache-2.0
config NTC_THERMISTOR
bool "NTC Thermistor"
default y
depends on DT_HAS_EPCOS_B57861S0103A039_ENABLED
select ADC
help
Enable driver for Zephyr NTC Thermistor.

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include "ntc_thermistor.h"
LOG_MODULE_REGISTER(NTC_THERMISTOR, CONFIG_SENSOR_LOG_LEVEL);
struct ntc_thermistor_data {
struct k_mutex mutex;
int16_t raw;
int16_t sample_val;
};
struct ntc_thermistor_config {
const struct adc_dt_spec adc_channel;
const struct ntc_config ntc_cfg;
};
static int ntc_thermistor_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
struct ntc_thermistor_data *data = dev->data;
const struct 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 ntc_thermistor_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct ntc_thermistor_data *data = dev->data;
const struct 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 ntc_thermistor_driver_api = {
.sample_fetch = ntc_thermistor_sample_fetch,
.channel_get = ntc_thermistor_channel_get,
};
static int ntc_thermistor_init(const struct device *dev)
{
const struct 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 NTC_THERMISTOR_DEFINE(inst, id, comp_table) \
BUILD_ASSERT(ARRAY_SIZE(comp_table) % 2 == 0, "Compensation table needs to be even size"); \
\
static int compare_ohm_##id##inst(const void *key, const void *element); \
\
static struct ntc_thermistor_data ntc_thermistor_driver_##id##inst; \
\
static const struct ntc_thermistor_config ntc_thermistor_cfg_##id##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), \
.connected_positive = DT_INST_PROP(inst, connected_positive), \
.type = { \
.comp = (const struct ntc_compensation *)comp_table, \
.n_comp = ARRAY_SIZE(comp_table), \
.ohm_cmp = compare_ohm_##id##inst, \
}, \
}, \
}; \
\
static int compare_ohm_##id##inst(const void *key, const void *element) \
{ \
return ntc_compensation_compare_ohm( \
&ntc_thermistor_cfg_##id##inst.ntc_cfg.type, key, element); \
} \
\
SENSOR_DEVICE_DT_INST_DEFINE( \
inst, ntc_thermistor_init, NULL, &ntc_thermistor_driver_##id##inst, \
&ntc_thermistor_cfg_##id##inst, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
&ntc_thermistor_driver_api);
/* epcos,b57861s0103a039 */
#define DT_DRV_COMPAT epcos_b57861s0103a039
static __unused const struct ntc_compensation comp_epcos_b57861s0103a039[] = {
{ -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 },
};
DT_INST_FOREACH_STATUS_OKAY_VARGS(NTC_THERMISTOR_DEFINE, DT_DRV_COMPAT,
comp_epcos_b57861s0103a039)

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_NTC_THERMISTOR_H
#define ZEPHYR_NTC_THERMISTOR_H
#ifndef NTC_THERMISTOR_H
#define NTC_THERMISTOR_H
#include <zephyr/types.h>
@ -26,11 +26,9 @@ struct ntc_config {
uint32_t pullup_uv;
uint32_t pullup_ohm;
uint32_t pulldown_ohm;
const struct ntc_type *type;
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
@ -65,4 +63,4 @@ int32_t ntc_get_temp_mc(const struct ntc_type *type, unsigned int ohm);
*/
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 */
#endif /* NTC_THERMISTOR_H */

View file

@ -6,7 +6,7 @@
#include <stdlib.h>
#include <zephyr/devicetree.h>
#include "zephyr_ntc_thermistor.h"
#include "ntc_thermistor.h"
/**
* fixp_linear_interpolate() - interpolates a value from two known points
@ -55,10 +55,10 @@ int ntc_compensation_compare_ohm(const struct ntc_type *type, const void *key, c
} 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) {
if (element_idx == (type->n_comp / 2) - 1) {
sgn = 0;
} else {
if (element_idx != type->n_comp - 1 &&
if (element_idx != (type->n_comp / 2) - 1 &&
ntc_key->ohm > type->comp[element_idx + 1].ohm) {
sgn = 0;
} else {
@ -129,23 +129,3 @@ int32_t ntc_get_temp_mc(const struct ntc_type *type, unsigned int ohm)
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)

View file

@ -1,8 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(
zephyr_ntc_thermistor.c
zephyr_ntc_thermistor_rt_table.c
)

View file

@ -1,14 +0,0 @@
# 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.

View file

@ -1,125 +0,0 @@
/*
* Copyright (c) 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_ntc_thermistor
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#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), \
.connected_positive = DT_INST_PROP(inst, connected_positive), \
.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)

View file

@ -0,0 +1,8 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
description: EPCOS B57861S0103A039 thermistor
compatible: "epcos,b57861s0103a039"
include: ntc-thermistor.yaml

View file

@ -3,8 +3,6 @@
description: Generic NTC Thermistor
compatible: "zephyr,ntc-thermistor"
include: [sensor-device.yaml]
properties:
@ -13,11 +11,6 @@ properties:
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: |

View file

@ -1,18 +0,0 @@
# 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
<temp0 resistance0 temp1 resistance1 ...>
See `scripts/utils/ntc_thermistor_table.py` to help generate node.

View file

@ -23,32 +23,9 @@ test_adc_emul: adc {
status = "okay";
};
thermistor_R25_10000_B_3974: thermistor-R25-10000-B-3974 {
compatible = "zephyr,ntc-thermistor-rt-table";
/* Format <temp resistance> */
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>;
test_epcos_b57861s0103a039: epcos-b57861s0103a039 {
compatible = "epcos,b57861s0103a039";
io-channels = <&adc0 0>;
zephyr,rt-table = <&thermistor_R25_10000_B_3974>;
r25-ohm = <10000>;
pullup-uv = <3300000>;
pullup-ohm = <0>;