drivers: sensor: INA219: added support

This adds support for the TI INA219 Zero-Drift, Bidirectional
Current/Power Monitor with I2C Interface

Signed-off-by: Leonard Pollak <leonardp@tr-host.de>
This commit is contained in:
Leonard Pollak 2021-05-21 21:42:25 +02:00 committed by Maureen Helm
commit d6eb80ac27
7 changed files with 560 additions and 0 deletions

View file

@ -37,6 +37,7 @@ add_subdirectory_ifdef(CONFIG_IIS2DLPC iis2dlpc)
add_subdirectory_ifdef(CONFIG_IIS2ICLX iis2iclx) add_subdirectory_ifdef(CONFIG_IIS2ICLX iis2iclx)
add_subdirectory_ifdef(CONFIG_IIS2MDC iis2mdc) add_subdirectory_ifdef(CONFIG_IIS2MDC iis2mdc)
add_subdirectory_ifdef(CONFIG_IIS3DHHC iis3dhhc) add_subdirectory_ifdef(CONFIG_IIS3DHHC iis3dhhc)
add_subdirectory_ifdef(CONFIG_INA219 ina219)
add_subdirectory_ifdef(CONFIG_INA23X ina23x) add_subdirectory_ifdef(CONFIG_INA23X ina23x)
add_subdirectory_ifdef(CONFIG_ISL29035 isl29035) add_subdirectory_ifdef(CONFIG_ISL29035 isl29035)
add_subdirectory_ifdef(CONFIG_ISM330DHCX ism330dhcx) add_subdirectory_ifdef(CONFIG_ISM330DHCX ism330dhcx)

View file

@ -116,6 +116,8 @@ source "drivers/sensor/ina23x/Kconfig"
source "drivers/sensor/isl29035/Kconfig" source "drivers/sensor/isl29035/Kconfig"
source "drivers/sensor/ina219/Kconfig"
source "drivers/sensor/ism330dhcx/Kconfig" source "drivers/sensor/ism330dhcx/Kconfig"
source "drivers/sensor/lis2dh/Kconfig" source "drivers/sensor/lis2dh/Kconfig"

View file

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

View file

@ -0,0 +1,10 @@
# INA219 Bidirectional Current/Power Monitor
# Copyright (c) 2021 Leonard Pollak
# SPDX-License-Identifier: Apache-2.0
config INA219
bool "INA219 Current/Power Monitor"
depends on I2C
help
Enable driver for INA219 Bidirectional Current/Power Monitor.

View file

@ -0,0 +1,311 @@
/*
* Copyright (c) 2021 Leonard Pollak
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_ina219
#include <device.h>
#include <drivers/i2c.h>
#include <kernel.h>
#include <drivers/sensor.h>
#include <logging/log.h>
#include <sys/byteorder.h>
#include "ina219.h"
LOG_MODULE_REGISTER(INA219, CONFIG_SENSOR_LOG_LEVEL);
static int ina219_reg_read(const struct device *dev,
uint8_t reg_addr,
uint16_t *reg_data)
{
const struct ina219_config *cfg = dev->config;
uint8_t rx_buf[2];
int rc;
rc = i2c_write_read_dt(&cfg->bus,
&reg_addr, sizeof(reg_addr),
rx_buf, sizeof(rx_buf));
*reg_data = sys_get_be16(rx_buf);
return rc;
}
static int ina219_reg_write(const struct device *dev,
uint8_t addr,
uint16_t reg_data)
{
const struct ina219_config *cfg = dev->config;
uint8_t tx_buf[3];
tx_buf[0] = addr;
sys_put_be16(reg_data, &tx_buf[1]);
return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
}
static int ina219_reg_field_update(const struct device *dev,
uint8_t addr,
uint16_t mask,
uint16_t field)
{
uint16_t reg_data;
int rc;
rc = ina219_reg_read(dev, addr, &reg_data);
if (rc) {
return rc;
}
reg_data = (reg_data & ~mask) | field;
return ina219_reg_write(dev, addr, reg_data);
}
static int ina219_set_msr_delay(const struct device *dev)
{
const struct ina219_config *cfg = dev->config;
struct ina219_data *data = dev->data;
data->msr_delay = ina219_conv_delay(cfg->badc) +
ina219_conv_delay(cfg->sadc);
return 0;
}
static int ina219_set_config(const struct device *dev)
{
const struct ina219_config *cfg = dev->config;
uint16_t reg_data;
reg_data = (cfg->brng & INA219_BRNG_MASK) << INA219_BRNG_SHIFT |
(cfg->pg & INA219_PG_MASK) << INA219_PG_SHIFT |
(cfg->badc & INA219_ADC_MASK) << INA219_BADC_SHIFT |
(cfg->sadc & INA219_ADC_MASK) << INA219_SADC_SHIFT |
(cfg->mode & INA219_MODE_NORMAL);
return ina219_reg_write(dev, INA219_REG_CONF, reg_data);
}
static int ina219_set_calib(const struct device *dev)
{
const struct ina219_config *cfg = dev->config;
uint16_t cal;
cal = INA219_SCALING_FACTOR / ((cfg->r_shunt) * (cfg->current_lsb));
return ina219_reg_write(dev, INA219_REG_CALIB, cal);
}
static int ina219_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
struct ina219_data *data = dev->data;
uint16_t status;
uint16_t tmp;
int rc;
if (chan != SENSOR_CHAN_ALL &&
chan != SENSOR_CHAN_VOLTAGE &&
chan != SENSOR_CHAN_POWER &&
chan != SENSOR_CHAN_CURRENT) {
return -ENOTSUP;
}
/* Trigger measurement and wait for completion */
rc = ina219_reg_field_update(dev,
INA219_REG_CONF,
INA219_MODE_MASK,
INA219_MODE_NORMAL);
if (rc) {
LOG_ERR("Failed to start measurement.");
return rc;
}
k_sleep(K_USEC(data->msr_delay));
rc = ina219_reg_read(dev, INA219_REG_V_BUS, &status);
if (rc) {
LOG_ERR("Failed to read device status.");
return rc;
}
while (!(INA219_CNVR_RDY(status))) {
rc = ina219_reg_read(dev, INA219_REG_V_BUS, &status);
if (rc) {
LOG_ERR("Failed to read device status.");
return rc;
}
k_sleep(K_USEC(INA219_WAIT_MSR_RETRY));
}
/* Check for overflow */
if (INA219_OVF_STATUS(status)) {
LOG_WRN("Power and/or Current calculations are out of range.");
}
if (chan == SENSOR_CHAN_ALL ||
chan == SENSOR_CHAN_VOLTAGE) {
rc = ina219_reg_read(dev, INA219_REG_V_BUS, &tmp);
if (rc) {
LOG_ERR("Error reading bus voltage.");
return rc;
}
data->v_bus = INA219_VBUS_GET(tmp);
}
if (chan == SENSOR_CHAN_ALL ||
chan == SENSOR_CHAN_POWER) {
rc = ina219_reg_read(dev, INA219_REG_POWER, &tmp);
if (rc) {
LOG_ERR("Error reading power register.");
return rc;
}
data->power = tmp;
}
if (chan == SENSOR_CHAN_ALL ||
chan == SENSOR_CHAN_CURRENT) {
rc = ina219_reg_read(dev, INA219_REG_CURRENT, &tmp);
if (rc) {
LOG_ERR("Error reading current register.");
return rc;
}
data->current = tmp;
}
return rc;
}
static int ina219_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
const struct ina219_config *cfg = dev->config;
struct ina219_data *data = dev->data;
double tmp;
int8_t sign = 1;
switch (chan) {
case SENSOR_CHAN_VOLTAGE:
tmp = data->v_bus * INA219_V_BUS_MUL;
sensor_value_from_double(val, tmp);
break;
case SENSOR_CHAN_POWER:
tmp = data->power * cfg->current_lsb * INA219_POWER_MUL * INA219_SI_MUL;
sensor_value_from_double(val, tmp);
break;
case SENSOR_CHAN_CURRENT:
if (INA219_SIGN_BIT(data->current)) {
data->current = ~data->current + 1;
sign = -1;
}
tmp = sign * data->current * cfg->current_lsb * INA219_SI_MUL;
sensor_value_from_double(val, tmp);
break;
default:
LOG_DBG("Channel not supported by device!");
return -ENOTSUP;
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int ina219_pm_ctrl(const struct device *dev, enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_RESUME:
return ina219_init(dev);
case PM_DEVICE_ACTION_SUSPEND:
reg_val = INA219_MODE_SLEEP;
break;
case PM_DEVICE_ACTION_TURN_OFF:
reg_val = INA219_MODE_OFF;
break;
default:
return -ENOTSUP;
}
return ina219_reg_field_update(dev,
INA219_REG_CONF,
INA219_MODE_MASK,
reg_val);
}
#endif /* CONFIG_PM_DEVICE */
static int ina219_init(const struct device *dev)
{
const struct ina219_config *cfg = dev->config;
int rc;
if (!device_is_ready(cfg->bus.bus)) {
LOG_ERR("Device not ready.");
return -ENODEV;
}
rc = ina219_reg_write(dev, INA219_REG_CONF, INA219_RST);
if (rc) {
LOG_ERR("Could not reset device.");
return rc;
}
rc = ina219_set_config(dev);
if (rc) {
LOG_ERR("Could not set configuration data.");
return rc;
}
rc = ina219_set_calib(dev);
if (rc) {
LOG_DBG("Could not set calibration data.");
return rc;
}
/* Set measurement delay */
rc = ina219_set_msr_delay(dev);
if (rc) {
LOG_ERR("Could not get measurement delay.");
return rc;
}
k_sleep(K_USEC(INA219_WAIT_STARTUP));
return 0;
}
static const struct sensor_driver_api ina219_api = {
.sample_fetch = ina219_sample_fetch,
.channel_get = ina219_channel_get,
};
#define INA219_INIT(n) \
static struct ina219_data ina219_data_##n; \
\
static const struct ina219_config ina219_config_##n = { \
.bus = I2C_DT_SPEC_INST_GET(n), \
.current_lsb = DT_INST_PROP(n, lsb_microamp), \
.r_shunt = DT_INST_PROP(n, shunt_milliohm), \
.brng = DT_INST_PROP(n, brng), \
.pg = DT_INST_PROP(n, pg), \
.badc = DT_INST_PROP(n, badc), \
.sadc = DT_INST_PROP(n, sadc), \
.mode = INA219_MODE_NORMAL \
}; \
\
DEVICE_DT_INST_DEFINE(n, \
ina219_init, \
ina219_pm_ctrl,\
&ina219_data_##n, \
&ina219_config_##n, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&ina219_api);
DT_INST_FOREACH_STATUS_OKAY(INA219_INIT)

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2021 Leonard Pollak
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_INA219_INA219_H_
#define ZEPHYR_DRIVERS_SENSOR_INA219_INA219_H_
/* Device register addresses */
#define INA219_REG_CONF 0x00
#define INA219_REG_V_SHUNT 0x01
#define INA219_REG_V_BUS 0x02
#define INA219_REG_POWER 0x03
#define INA219_REG_CURRENT 0x04
#define INA219_REG_CALIB 0x05
/* Config register shifts and masks */
#define INA219_RST BIT(15)
#define INA219_BRNG_MASK 0x1
#define INA219_BRNG_SHIFT 13
#define INA219_PG_MASK 0x3
#define INA219_PG_SHIFT 11
#define INA219_ADC_MASK 0xF
#define INA219_BADC_SHIFT 7
#define INA219_SADC_SHIFT 3
#define INA219_MODE_MASK 0x7
/* Bus voltage register */
#define INA219_VBUS_GET(x) ((x) >> 3) & 0x3FFF
#define INA219_CNVR_RDY(x) ((x) >> 1) & 0x1
#define INA219_OVF_STATUS(x) x & 0x1
/* Mode fields */
#define INA219_MODE_NORMAL 0x3 /* shunt and bus, triggered */
#define INA219_MODE_SLEEP 0x4 /* ADC off */
#define INA219_MODE_OFF 0x0 /* Power off */
/* Others */
#define INA219_SIGN_BIT(x) ((x) >> 15) & 0x1
#define INA219_V_BUS_MUL 0.004f
#define INA219_SI_MUL 0.00001f
#define INA219_POWER_MUL 20
#define INA219_WAIT_STARTUP 40
#define INA219_WAIT_MSR_RETRY 100
#define INA219_SCALING_FACTOR 4096000
struct ina219_config {
struct i2c_dt_spec bus;
uint16_t current_lsb;
uint16_t r_shunt;
uint8_t brng;
uint8_t pg;
uint8_t badc;
uint8_t sadc;
uint8_t mode;
};
struct ina219_data {
uint16_t config;
uint16_t v_bus;
uint16_t power;
uint16_t current;
uint16_t calib;
uint32_t msr_delay;
};
static int ina219_init(const struct device *dev);
static inline int ina219_conv_delay(uint8_t delay_idx)
{
switch (delay_idx) {
case 0:
return 84;
case 1:
return 148;
case 2:
return 276;
case 3:
return 532;
case 9:
return 1060;
case 10:
return 2013;
case 11:
return 4260;
case 12:
return 8510;
case 13:
return 17020;
case 14:
return 34050;
case 15:
return 68100;
default:
return -EINVAL;
}
}
#endif /* ZEPHYR_DRIVERS_SENSOR_INA219_INA219_H_ */

View file

@ -0,0 +1,131 @@
# Copyright (c) 2021 Leonard Pollak
# SPDX-License-Identifier: Apache-2.0
description: Texas Instruments Bidirectional Current/Power Sensor
compatible: "ti,ina219"
include: i2c-device.yaml
properties:
lsb-microamp:
type: int
required: true
description: |
Current LSB in microAmpere
Current LSB = max expected current [A] / 2^15
example: 100 -> ~3A
shunt-milliohm:
type: int
required: true
description: |
Value of the shunt resistor in milliOhm
brng:
type: int
required: false
default: 1
description: |
Bus Voltage Range
0 = 16 V FSR
1 = 32 V FSR
The default of 32V is the power-on reset value of the device.
Should the expected bus voltage be below 16V set this to 0.
enum:
- 0
- 1
pg:
type: int
required: false
default: 3
description: |
Programmable Gain
0 = 1 -> ±40 mV
1 = /2 -> ±80 mV
2 = /4 -> ±160 mV
3 = /8 -> ±320 mV
The default of ±320 mV is the power-on reset value of the device.
In case the expected voltage drop across the shunt resistor is lower
one can adjust this to get more accurate readings.
enum:
- 0
- 1
- 2
- 3
badc:
type: int
required: false
default: 3
description: |
Bus ADC configuration
0 = 9 bit -> 84 µs
1 = 10 bit -> 148 µs
2 = 11 bit -> 276 µs
3 = 12 bit -> 532 µs
9 = 12 bit - 2 sample averaging -> 1.06 ms
10 = 12 bit - 4 sample averaging -> 2.13 ms
11 = 12 bit - 8 sample averaging -> 4.26 ms
12 = 12 bit - 16 sample averaging -> 8.51 ms
13 = 12 bit - 32 sample averaging -> 17.02 ms
14 = 12 bit - 64 sample averaging -> 34.05 ms
15 = 12 bit - 128 sample averaging -> 68.10 ms
The default of 12 bit is the power-on reset value of the device.
Lowering the resolution of the ADC gives less accurate readings but
cuts down on conversion times.
Averaging over multiple samples gives more stable readings but adds
to overall conversion time.
enum:
- 0
- 1
- 2
- 3
- 9
- 10
- 11
- 12
- 13
- 14
- 15
sadc:
type: int
required: false
default: 3
description: |
Shunt ADC configuration
0 = 9 bit -> 84 µs
1 = 10 bit -> 148 µs
2 = 11 bit -> 276 µs
3 = 12 bit -> 532 µs
9 = 12 bit - 2 sample averaging -> 1.06 ms
10 = 12 bit - 4 sample averaging -> 2.13 ms
11 = 12 bit - 8 sample averaging -> 4.26 ms
12 = 12 bit - 16 sample averaging -> 8.51 ms
13 = 12 bit - 32 sample averaging -> 17.02 ms
14 = 12 bit - 64 sample averaging -> 34.05 ms
15 = 12 bit - 128 sample averaging -> 68.10 ms
The default of 12 bit is the power-on reset value of the device.
Lowering the resolution of the ADC gives less accurate readings but
cuts down on conversion times.
Averaging over multiple samples gives more stable readings but adds
to overall conversion time.
enum:
- 0
- 1
- 2
- 3
- 9
- 10
- 11
- 12
- 13
- 14
- 15