zephyr/drivers/sensor/current_amp/current_amp.c
Jordan Yates 4361c96c48 adc: current_sense_amplifier: resistance in milli-ohms
Change the unit of the sense resistor in the devicetree binding from
micro-ohms to milli-ohms. This is done for three reasons.

Firstly, the maximum value resistor that can currently be represented
is 4.2 kOhms, due to the limitation of devicetree properties to 32 bits.

Secondly, storing the resistance at such a high resolution makes
overflows much more likely when the desired output unit is micro-amps,
not milli-amps.

Finally, micro-ohms, are an unnecessarily precise unit for the purpose
of these calculations, and a resolution that is not realistic to
achieve. The high resistor resolution results in large divisors that
reduce the resolution of outputs. Unlike resistors characterised down to
the micro-ohm, devices wanting to measure micro-amps are actually
realistic.

Signed-off-by: Jordan Yates <jordan@embeint.com>
2024-10-14 13:05:07 +02:00

172 lines
4.5 KiB
C

/*
* Copyright (c) 2023 FTP Technologies
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT current_sense_amplifier
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/adc/current_sense_amplifier.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(current_amp, CONFIG_SENSOR_LOG_LEVEL);
struct current_sense_amplifier_data {
struct adc_sequence sequence;
int16_t raw;
};
static int fetch(const struct device *dev, enum sensor_channel chan)
{
const struct current_sense_amplifier_dt_spec *config = dev->config;
struct current_sense_amplifier_data *data = dev->data;
int ret;
if ((chan != SENSOR_CHAN_CURRENT) && (chan != SENSOR_CHAN_ALL)) {
return -ENOTSUP;
}
ret = adc_read_dt(&config->port, &data->sequence);
if (ret != 0) {
LOG_ERR("adc_read: %d", ret);
}
return ret;
}
static int get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val)
{
const struct current_sense_amplifier_dt_spec *config = dev->config;
struct current_sense_amplifier_data *data = dev->data;
int32_t raw_val = data->raw;
int32_t i_ma;
int ret;
__ASSERT_NO_MSG(val != NULL);
if (chan != SENSOR_CHAN_CURRENT) {
return -ENOTSUP;
}
ret = adc_raw_to_millivolts_dt(&config->port, &raw_val);
if (ret != 0) {
LOG_ERR("raw_to_mv: %d", ret);
return ret;
}
i_ma = raw_val;
current_sense_amplifier_scale_dt(config, &i_ma);
LOG_DBG("%d/%d, %dmV, current:%dmA", data->raw,
(1 << data->sequence.resolution) - 1, raw_val, i_ma);
val->val1 = i_ma / 1000;
val->val2 = (i_ma % 1000) * 1000;
return 0;
}
static const struct sensor_driver_api current_api = {
.sample_fetch = fetch,
.channel_get = get,
};
#ifdef CONFIG_PM_DEVICE
static int pm_action(const struct device *dev, enum pm_device_action action)
{
const struct current_sense_amplifier_dt_spec *config = dev->config;
int ret;
if (config->power_gpio.port == NULL) {
LOG_ERR("PM not supported");
return -ENOTSUP;
}
switch (action) {
case PM_DEVICE_ACTION_RESUME:
ret = gpio_pin_set_dt(&config->power_gpio, 1);
if (ret != 0) {
LOG_ERR("failed to set GPIO for PM resume");
return ret;
}
break;
case PM_DEVICE_ACTION_SUSPEND:
ret = gpio_pin_set_dt(&config->power_gpio, 0);
if (ret != 0) {
LOG_ERR("failed to set GPIO for PM suspend");
return ret;
}
break;
default:
return -ENOTSUP;
}
return 0;
}
#endif
static int current_init(const struct device *dev)
{
const struct current_sense_amplifier_dt_spec *config = dev->config;
struct current_sense_amplifier_data *data = dev->data;
int ret;
__ASSERT(config->sense_milli_ohms != 0, "Milli-ohms must not be 0");
if (!adc_is_ready_dt(&config->port)) {
LOG_ERR("ADC is not ready");
return -ENODEV;
}
#ifdef CONFIG_PM_DEVICE
if (config->power_gpio.port != NULL) {
if (!gpio_is_ready_dt(&config->power_gpio)) {
LOG_ERR("Power GPIO is not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->power_gpio, GPIO_OUTPUT_ACTIVE);
if (ret != 0) {
LOG_ERR("failed to config GPIO: %d", ret);
return ret;
}
}
#endif
ret = adc_channel_setup_dt(&config->port);
if (ret != 0) {
LOG_ERR("setup: %d", ret);
return ret;
}
ret = adc_sequence_init_dt(&config->port, &data->sequence);
if (ret != 0) {
LOG_ERR("sequence init: %d", ret);
return ret;
}
data->sequence.buffer = &data->raw;
data->sequence.buffer_size = sizeof(data->raw);
return 0;
}
#define CURRENT_SENSE_AMPLIFIER_INIT(inst) \
static struct current_sense_amplifier_data current_amp_##inst##_data; \
\
static const struct current_sense_amplifier_dt_spec current_amp_##inst##_config = \
CURRENT_SENSE_AMPLIFIER_DT_SPEC_GET(DT_DRV_INST(inst)); \
\
PM_DEVICE_DT_INST_DEFINE(inst, pm_action); \
\
SENSOR_DEVICE_DT_INST_DEFINE(inst, &current_init, PM_DEVICE_DT_INST_GET(inst), \
&current_amp_##inst##_data, &current_amp_##inst##_config, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &current_api);
DT_INST_FOREACH_STATUS_OKAY(CURRENT_SENSE_AMPLIFIER_INIT)