drivers: sensor: ina237: add high-precision mode

The current-shunt calibration requires a factor of 4x if high-precision
mode is selected.

Signed-off-by: Eric Holmberg <eric.holmberg@northriversystems.co.nz>
This commit is contained in:
Eric Holmberg 2023-07-23 21:01:55 +12:00 committed by Maureen Helm
commit a70d056513
6 changed files with 112 additions and 7 deletions

View file

@ -22,8 +22,8 @@ LOG_MODULE_REGISTER(INA237, CONFIG_SENSOR_LOG_LEVEL);
/** @brief The LSB value for the bus voltage register, in microvolts/LSB. */
#define INA237_BUS_VOLTAGE_TO_uV(x) ((x) * 3125U)
/** @brief Power scaling (scaled by 10) */
#define INA237_POWER_SCALING 2
/** @brief Power scaling (need factor of 0.2) */
#define INA237_POWER_TO_uW(x) ((x) / 5ULL)
/**
* @brief Scale die temperture from 0.125 degC/bit to micro-degrees C
@ -37,6 +37,12 @@ static void micro_s32_to_sensor_value(struct sensor_value *val, int32_t value_mi
val->val2 = value_microX % 1000000L;
}
static void micro_u64_to_sensor_value(struct sensor_value *val, uint64_t value_microX)
{
val->val1 = value_microX / 1000000U;
val->val2 = value_microX % 1000000U;
}
static int ina237_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
@ -54,9 +60,9 @@ static int ina237_channel_get(const struct device *dev, enum sensor_channel chan
break;
case SENSOR_CHAN_POWER:
/* see datasheet "Current and Power calculations" section */
micro_s32_to_sensor_value(val,
(data->power * INA237_POWER_SCALING * config->current_lsb) / 10000U);
/* power in uW is power_reg * current_lsb * 0.2 */
micro_u64_to_sensor_value(val,
INA237_POWER_TO_uW((uint64_t)data->power * config->current_lsb));
break;
case SENSOR_CHAN_DIE_TEMP:
@ -364,6 +370,10 @@ static const struct sensor_driver_api ina237_driver_api = {
.channel_get = ina237_channel_get,
};
/* Shunt calibration must be muliplied by 4 if high-prevision mode is selected */
#define CAL_PRECISION_MULTIPLIER(config) \
(((config & INA237_CFG_HIGH_PRECISION) >> 4) * 3 + 1)
#define INA237_DRIVER_INIT(inst) \
static struct ina237_data ina237_data_##inst; \
static const struct ina237_config ina237_config_##inst = { \
@ -376,8 +386,9 @@ static const struct sensor_driver_api ina237_driver_api = {
(DT_INST_ENUM_IDX(inst, temp_conversion_time_us) << 3) | \
DT_INST_ENUM_IDX(inst, avg_count), \
.current_lsb = DT_INST_PROP(inst, current_lsb_microamps), \
.cal = INA237_CAL_SCALING * DT_INST_PROP(inst, current_lsb_microamps) * \
DT_INST_PROP(inst, rshunt_micro_ohms) / 10000000000ULL, \
.cal = CAL_PRECISION_MULTIPLIER(DT_INST_PROP(inst, config)) * \
INA237_CAL_SCALING * DT_INST_PROP(inst, current_lsb_microamps) * \
DT_INST_PROP(inst, rshunt_micro_ohms) / 10000000ULL, \
.alert_config = DT_INST_PROP_OR(inst, alert_config, 0x01), \
.alert_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, alert_gpios, {0}), \
}; \

View file

@ -17,6 +17,8 @@
#include <zephyr/drivers/sensor.h>
#define INA237_REG_CONFIG 0x00
#define INA237_CFG_HIGH_PRECISION BIT(4)
#define INA237_REG_ADC_CONFIG 0x01
#define INA237_REG_CALIB 0x02
#define INA237_REG_SHUNT_VOLT 0x04

View file

@ -97,3 +97,8 @@ properties:
Default is the power-on reset value.
default: 1
enum: [1, 4, 16, 64, 128, 256, 512, 1024]
high-precision:
type: boolean
description: |
Enable high precision mode (4x the resolution).

View file

@ -9,6 +9,7 @@
#include <zephyr/dt-bindings/dt-util.h>
/* Operating Mode */
#define INA237_CFG_HIGH_PRECISION BIT(4)
#define INA237_OPER_MODE_SHUTDOWN 0x00
#define INA237_OPER_MODE_BUS_VOLTAGE_TRIG 0x01
#define INA237_OPER_MODE_SHUNT_VOLTAGE_TRIG 0x02

View file

@ -7,6 +7,7 @@
&i2c0 {
status = "okay";
/* standard precision mode */
ina237_default_test: ina237@40 {
compatible = "ti,ina237";
reg = <0x40>;
@ -14,4 +15,34 @@
current-lsb-microamps = <123>;
status = "okay";
};
/* high precision mode */
ina237@41 {
compatible = "ti,ina237";
reg = <0x41>;
high-precision;
rshunt-micro-ohms = <400>;
current-lsb-microamps = <123>;
status = "okay";
};
/* high precision mode, maximum current lsb */
ina237@42 {
compatible = "ti,ina237";
reg = <0x42>;
high-precision;
rshunt-micro-ohms = <0x00ff>;
current-lsb-microamps = <0xffff>;
status = "okay";
};
/* high precision mode, maximum rshunt */
ina237@43 {
compatible = "ti,ina237";
reg = <0x43>;
high-precision;
rshunt-micro-ohms = <0xffff>;
current-lsb-microamps = <0x00ff>;
status = "okay";
};
};

View file

@ -8,6 +8,7 @@
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/dt-bindings/sensor/ina237.h>
#include <zephyr/ztest.h>
#include <ina237_emul.h>
@ -44,6 +45,26 @@ ZTEST(ina237_0, test_default_config)
*
* @param fixture
*/
static void test_shunt_cal(struct ina237_fixture *fixture)
{
/* Confirm SHUNT_CAL register which is 819.2e6 * Current_LSB * Rshunt */
double shunt_cal = 819.2e6 * fixture->current_lsb_uA * 1e-6 * fixture->rshunt_uOhms * 1e-6;
if (fixture->config & INA237_CFG_HIGH_PRECISION) {
/* High precision mode */
shunt_cal *= 4;
}
uint32_t shunt_register_actual;
uint16_t shunt_register_expected = (uint16_t)shunt_cal;
zassert_ok(ina237_mock_get_register(fixture->mock->data, INA237_REG_CALIB,
&shunt_register_actual));
zexpect_within(shunt_register_expected, shunt_register_actual, 1,
"Expected %d, got %d", shunt_register_expected, shunt_register_actual);
}
static void test_current(struct ina237_fixture *fixture)
{
/* 16-bit signed value for current register */
@ -109,6 +130,38 @@ static void test_bus_voltage(struct ina237_fixture *fixture)
}
}
static void test_power(struct ina237_fixture *fixture)
{
/* 24-bit unsigned value for power register */
const uint32_t power_reg_vectors[] = {
16777215,
65535,
32767,
1000,
100,
1,
0,
};
for (int idx = 0; idx < ARRAY_SIZE(power_reg_vectors); idx++) {
struct sensor_value sensor_val;
uint32_t power_register = power_reg_vectors[idx];
/* power is 0.2 * current_lsb * register */
double power_expected_W = 0.2 * fixture->current_lsb_uA * 1e-6 * power_register;
/* set current reading */
ina237_mock_set_register(fixture->mock->data, INA237_REG_POWER, power_register);
/* Verify sensor value is correct */
zassert_ok(sensor_sample_fetch(fixture->dev));
zassert_ok(sensor_channel_get(fixture->dev, SENSOR_CHAN_POWER, &sensor_val));
double power_actual_W = sensor_value_to_double(&sensor_val);
zexpect_within(power_expected_W, power_actual_W, 1e-6,
"Expected %.6f C, got %.6f C", power_expected_W, power_actual_W);
}
}
static void test_temperature(struct ina237_fixture *fixture)
{
@ -161,8 +214,10 @@ static struct ina237_fixture fixtures[] = {
/* Create a test suite for each enabled ina237 device node */
#define INA237_TESTS(inst) \
ZTEST(ina237_##inst, test_shunt_cal) { test_shunt_cal(&fixtures[inst]); } \
ZTEST(ina237_##inst, test_current) { test_current(&fixtures[inst]); } \
ZTEST(ina237_##inst, test_bus_voltage) { test_bus_voltage(&fixtures[inst]); } \
ZTEST(ina237_##inst, test_power) { test_power(&fixtures[inst]); } \
ZTEST(ina237_##inst, test_temperature) { test_temperature(&fixtures[inst]); } \
ZTEST_SUITE(ina237_##inst, NULL, NULL, NULL, NULL, NULL);