test: sensor: ina237: add emulator unit test

Add emulator unit test of the INA237.

Signed-off-by: Eric Holmberg <eric.holmberg@northriversystems.co.nz>
This commit is contained in:
Eric Holmberg 2023-07-21 12:46:59 +12:00 committed by Maureen Helm
commit 19da119c17
9 changed files with 411 additions and 5 deletions

View file

@ -20,22 +20,29 @@ LOG_MODULE_REGISTER(INA237, CONFIG_SENSOR_LOG_LEVEL);
#define INA237_CAL_SCALING 8192ULL
/** @brief The LSB value for the bus voltage register, in microvolts/LSB. */
#define INA237_BUS_VOLTAGE_UV_LSB 3125
#define INA237_BUS_VOLTAGE_TO_uV(x) ((x) * 3125U)
/** @brief Power scaling (scaled by 10) */
#define INA237_POWER_SCALING 2
/**
* @brief Scale die temperture from 0.125 degC/bit to micro-degrees C
* Note that the bottom 4 bits are reserved and are always zero.
*/
#define INA237_DIETEMP_TO_uDegC(x) (((x) >> 4) * 125000)
static int ina237_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct ina237_data *data = dev->data;
const struct ina237_config *config = dev->config;
uint32_t bus_uv, current_ua, power_uw;
int32_t sign;
int32_t temp_uDegC;
int32_t sign = 1;
switch (chan) {
case SENSOR_CHAN_VOLTAGE:
bus_uv = data->bus_voltage * INA237_BUS_VOLTAGE_UV_LSB;
bus_uv = INA237_BUS_VOLTAGE_TO_uV(data->bus_voltage);
val->val1 = bus_uv / 1000000U;
val->val2 = bus_uv % 1000000U;
@ -67,6 +74,17 @@ static int ina237_channel_get(const struct device *dev, enum sensor_channel chan
val->val2 = (int32_t)(power_uw % 1000000U);
break;
case SENSOR_CHAN_DIE_TEMP:
temp_uDegC = INA237_DIETEMP_TO_uDegC(data->die_temp);
val->val1 = temp_uDegC / 1000000L;
val->val2 = abs(temp_uDegC) % 1000000L;
if (temp_uDegC < 0) {
val->val2 = -val->val2;
}
break;
default:
return -ENOTSUP;
}
@ -155,6 +173,14 @@ static int ina237_read_data(const struct device *dev)
}
}
if ((data->chan == SENSOR_CHAN_ALL) || (data->chan == SENSOR_CHAN_DIE_TEMP)) {
ret = ina23x_reg_read_16(&config->bus, INA237_REG_DIETEMP, &data->die_temp);
if (ret < 0) {
LOG_ERR("Failed to read temperature");
return ret;
}
}
return 0;
}
@ -169,7 +195,7 @@ static int ina237_sample_fetch(const struct device *dev, enum sensor_channel cha
struct ina237_data *data = dev->data;
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_VOLTAGE && chan != SENSOR_CHAN_CURRENT &&
chan != SENSOR_CHAN_POWER) {
chan != SENSOR_CHAN_POWER && chan != SENSOR_CHAN_DIE_TEMP) {
return -ENOTSUP;
}

View file

@ -21,7 +21,7 @@
#define INA237_REG_CALIB 0x02
#define INA237_REG_SHUNT_VOLT 0x04
#define INA237_REG_BUS_VOLT 0x05
#define INA237_REG_MASK 0x06
#define INA237_REG_DIETEMP 0x06
#define INA237_REG_CURRENT 0x07
#define INA237_REG_POWER 0x08
#define INA237_REG_ALERT 0x0B
@ -40,6 +40,7 @@ struct ina237_data {
uint16_t current;
uint16_t bus_voltage;
uint32_t power;
int16_t die_temp;
enum sensor_channel chan;
struct ina23x_trigger trigger;
};

View file

@ -0,0 +1,13 @@
# Copyright (c) 2023 North River Systems Ltd
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(device)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
# Include the INA23x driver path and unit-test path for private header inclusion
zephyr_include_directories(./src)
zephyr_include_directories($ENV{ZEPHYR_BASE}/drivers/sensor/ina23x)

View file

@ -0,0 +1,17 @@
/* Copyright (c) 2023 North River Systems Ltd
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/dt-bindings/sensor/ina237.h>
&i2c0 {
status = "okay";
ina237_default_test: ina237@40 {
compatible = "ti,ina237";
reg = <0x40>;
rshunt-micro-ohms = <400>;
current-lsb-microamps = <123>;
status = "okay";
};
};

View file

@ -0,0 +1,14 @@
# Copyright (c) 2023 North River Systems Ltd
# SPDX-License-Identifier: Apache-2.0
CONFIG_ZTEST=y
CONFIG_ZTEST_NEW_API=y
CONFIG_SENSOR=y
CONFIG_EMUL=y
CONFIG_I2C_EMUL=y
CONFIG_I2C_LOG_LEVEL_DBG=y
CONFIG_INA237=y
CONFIG_CBPRINTF_FP_SUPPORT=y

View file

@ -0,0 +1,174 @@
/*
* Copyright (c) 2023 North River Systems Ltd
*
* SPDX-License-Identifier: Apache-2.0
*
* Emulator for the TI INA237 I2C power monitor
*/
#define DT_DRV_COMPAT ti_ina237
#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <ina237.h>
#include <ina237_emul.h>
LOG_MODULE_REGISTER(INA237_EMUL, CONFIG_SENSOR_LOG_LEVEL);
/* Register ID, size, and value table */
struct ina237_reg {
uint8_t id;
uint8_t bytes;
uint32_t value;
};
/* Emulator configuration passed into driver instance */
struct ina237_emul_cfg {
uint16_t addr;
};
struct ina237_emul_data {
struct ina237_reg ina237_regs[INA237_REGISTER_COUNT];
};
static struct ina237_reg *get_register(struct ina237_emul_data *data, int reg)
{
for (int i = 0; i < INA237_REGISTER_COUNT; i++) {
if (data->ina237_regs[i].id == reg) {
return &data->ina237_regs[i];
}
}
return NULL;
}
int ina237_mock_set_register(void *data_ptr, int reg, uint32_t value)
{
struct ina237_reg *reg_ptr = get_register(data_ptr, reg);
if (reg_ptr == NULL) {
return -EINVAL;
}
reg_ptr->value = value;
return 0;
}
int ina237_mock_get_register(void *data_ptr, int reg, uint32_t *value_ptr)
{
struct ina237_reg *reg_ptr = get_register(data_ptr, reg);
if (reg_ptr == NULL || value_ptr == NULL) {
return -EINVAL;
}
*value_ptr = reg_ptr->value;
return 0;
}
static int ina237_emul_transfer_i2c(const struct emul *target, struct i2c_msg msgs[], int num_msgs,
int addr)
{
struct ina237_emul_data *data = (struct ina237_emul_data *)target->data;
/* The INA237 uses big-endian read 16, read 24, and write 16 transactions */
if (!msgs || num_msgs < 1 || num_msgs > 2) {
LOG_ERR("Invalid number of messages: %d", num_msgs);
return -EIO;
}
if (msgs[0].flags & I2C_MSG_READ) {
LOG_ERR("Expected write");
return -EIO;
}
if (num_msgs == 1) {
/* Write16 Transaction */
if (msgs[0].len != 3) {
LOG_ERR("Expected 3 bytes");
return -EIO;
}
/* Write 2 bytes */
uint8_t reg = msgs[0].buf[0];
uint16_t val = sys_get_be16(&msgs[0].buf[1]);
struct ina237_reg *reg_ptr = get_register(data, reg);
if (!reg_ptr) {
LOG_ERR("Invalid register: %02x", reg);
return -EIO;
}
reg_ptr->value = val;
LOG_DBG("Write reg %02x: %04x", reg, val);
} else {
/* Read 2 or 3 bytes */
if ((msgs[1].flags & I2C_MSG_READ) == I2C_MSG_WRITE) {
LOG_ERR("Expected read");
return -EIO;
}
uint8_t reg = msgs[0].buf[0];
struct ina237_reg *reg_ptr = get_register(data, reg);
if (!reg_ptr) {
LOG_ERR("Invalid register: %02x", reg);
return -EIO;
}
if (msgs[1].len == 2) {
sys_put_be16(reg_ptr->value, msgs[1].buf);
LOG_DBG("Read16 reg %02x: %04x", reg, reg_ptr->value);
} else if (msgs[1].len == 3) {
sys_put_be24(reg_ptr->value, msgs[1].buf);
LOG_DBG("Read24 reg %02x: %06x", reg, reg_ptr->value);
} else {
LOG_ERR("Invalid read length: %d", msgs[1].len);
return -EIO;
}
}
return 0;
}
static int ina237_emul_init(const struct emul *target, const struct device *parent)
{
ARG_UNUSED(target);
ARG_UNUSED(parent);
return 0;
}
static const struct i2c_emul_api ina237_emul_api_i2c = {
.transfer = ina237_emul_transfer_i2c,
};
#define INA237_EMUL(n) \
static const struct ina237_emul_cfg ina237_emul_cfg_##n = { \
.addr = DT_INST_REG_ADDR(n) \
}; \
static struct ina237_emul_data ina237_emul_data_##n = { \
.ina237_regs = { \
{INA237_REG_CONFIG, 2, 0}, \
{INA237_REG_ADC_CONFIG, 2, 0xFB68}, \
{INA237_REG_CALIB, 2, 0x1000}, \
{INA237_REG_SHUNT_VOLT, 2, 0}, \
{INA237_REG_BUS_VOLT, 2, 0}, \
{INA237_REG_DIETEMP, 2, 0}, \
{INA237_REG_CURRENT, 2, 0}, \
{INA237_REG_POWER, 3, 0}, \
{INA237_REG_ALERT, 2, 0x0001}, \
{INA237_REG_SOVL, 2, 0x7FFF}, \
{INA237_REG_SUVL, 2, 0x8000}, \
{INA237_REG_BOVL, 2, 0x7FFF}, \
{INA237_REG_BUVL, 2, 0}, \
{INA237_REG_TEMP_LIMIT, 2, 0x7FFF}, \
{INA237_REG_PWR_LIMIT, 2, 0xFFFF}, \
{INA237_REG_MANUFACTURER_ID, 2, INA237_MANUFACTURER_ID}, \
}}; \
EMUL_DT_INST_DEFINE(n, ina237_emul_init, &ina237_emul_data_##n, &ina237_emul_cfg_##n, \
&ina237_emul_api_i2c, NULL)
DT_INST_FOREACH_STATUS_OKAY(INA237_EMUL)

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2023 North River Systems Ltd
*
* SPDX-License-Identifier: Apache-2.0
*
* Emulator for the TI INA237 I2C power monitor
*/
#ifndef INA237_EMUL_H_
#define INA237_EMUL_H_
#define INA237_REGISTER_COUNT 16
int ina237_mock_set_register(void *data_ptr, int reg, uint32_t value);
int ina237_mock_get_register(void *data_ptr, int reg, uint32_t *value_ptr);
#endif /* INA237_EMUL_H_ */

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2023 North River Systems Ltd
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/ztest.h>
#include <ina237_emul.h>
#include <ina237.h>
struct ina237_fixture {
const struct device *dev;
const struct emul *mock;
const uint16_t current_lsb_uA;
const uint16_t rshunt_uOhms;
const uint16_t config;
};
/**
* @brief Verify devicetree default configuration is correct.
*/
ZTEST(ina237_0, test_default_config)
{
const struct ina237_config *config;
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(ina237_default_test));
zassert_not_null(dev);
config = dev->config;
zassert_not_null(config);
/* confirm default DT settings */
zexpect_equal(0xFB68, config->adc_config,
"0xFB68 != adc_config (0x%x)", config->adc_config);
zexpect_equal(0x0000, config->config);
}
/**
* @brief Verify bus voltages for all ina237 nodes in DT
*
* @param fixture
*/
static void test_bus_voltage(struct ina237_fixture *fixture)
{
zassert_not_null(fixture->mock);
/* 16-bit signed value for voltage register (but always positive) 3.125 mV/bit */
const int16_t voltage_reg_vectors[] = {
32767,
27200, /* 85V maximum voltage */
1000,
100,
1,
0,
};
for (int idx = 0; idx < ARRAY_SIZE(voltage_reg_vectors); idx++) {
struct sensor_value sensor_val;
ina237_mock_set_register(fixture->mock->data, INA237_REG_BUS_VOLT,
voltage_reg_vectors[idx]);
/* Verify sensor value is correct */
zassert_ok(sensor_sample_fetch(fixture->dev));
zassert_ok(sensor_channel_get(fixture->dev, SENSOR_CHAN_VOLTAGE, &sensor_val));
double voltage_actual_V = sensor_value_to_double(&sensor_val);
double voltage_expected_V = voltage_reg_vectors[idx] * 3.125e-3;
zexpect_within(voltage_expected_V, voltage_actual_V, 1e-6,
"Expected %.6f A, got %.6f A", voltage_expected_V, voltage_actual_V);
}
}
static void test_temperature(struct ina237_fixture *fixture)
{
zassert_not_null(fixture->mock);
/* 12-bit signed value with bottom 4-bits reserved - 125 mDegC / bit */
const int16_t temp_reg_vectors[] = {
16000, /* 125C */
1000,
100,
1,
0,
-100,
-1000,
-5120, /* -40C */
};
for (int idx = 0; idx < ARRAY_SIZE(temp_reg_vectors); idx++) {
struct sensor_value sensor_val;
ina237_mock_set_register(fixture->mock->data, INA237_REG_DIETEMP,
temp_reg_vectors[idx]);
/* Verify sensor value is correct */
zassert_ok(sensor_sample_fetch(fixture->dev));
zassert_ok(sensor_channel_get(fixture->dev, SENSOR_CHAN_DIE_TEMP, &sensor_val));
double temp_actual_degC = sensor_value_to_double(&sensor_val);
double temp_expected_degC = (temp_reg_vectors[idx] / 16) * 125e-3;
zexpect_within(temp_expected_degC, temp_actual_degC, 125e-3,
"Expected %.6f A, got %.6f A", temp_expected_degC, temp_actual_degC);
}
}
/* Create a test fixture for each enabled ina237 device node */
#define DT_DRV_COMPAT ti_ina237
#define INA237_FIXTURE_ENTRY(inst) \
{ \
.dev = DEVICE_DT_INST_GET(inst), \
.mock = EMUL_DT_GET(DT_DRV_INST(inst)), \
.current_lsb_uA = DT_INST_PROP(inst, current_lsb_microamps), \
.rshunt_uOhms = DT_INST_PROP(inst, rshunt_micro_ohms), \
.config = DT_INST_PROP(inst, config), \
},
static struct ina237_fixture fixtures[] = {
DT_INST_FOREACH_STATUS_OKAY(INA237_FIXTURE_ENTRY)
};
/* Create a test suite for each enabled ina237 device node */
#define INA237_TESTS(inst) \
ZTEST(ina237_##inst, test_bus_voltage) { test_bus_voltage(&fixtures[inst]); } \
ZTEST(ina237_##inst, test_temperature) { test_temperature(&fixtures[inst]); } \
ZTEST_SUITE(ina237_##inst, NULL, NULL, NULL, NULL, NULL);
DT_INST_FOREACH_STATUS_OKAY(INA237_TESTS)

View file

@ -0,0 +1,10 @@
# Copyright (c) 2023 North River Systems Ltd
# SPDX-License-Identifier: Apache-2.0
tests:
drivers.sensor.ina237:
tags:
- drivers
- sensor
- subsys
platform_allow: native_posix