1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/ina2xx/ina2xx.c
2021-08-13 19:50:38 +02:00

219 lines
5.8 KiB
C

/*
* Copyright (C) 2015 Eistec AB
* 2019 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_ina2xx
* @{
*
* @file
* @brief Device driver implementation for Texas Instruments INA2XX High
* or Low Side, Bi-Directional CURRENT/POWER MONITOR with Two-Wire
* Interface
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <errno.h>
#include <stdint.h>
#include "ina2xx.h"
#include "ina2xx_defines.h"
#include "periph/i2c.h"
#include "byteorder.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/*
* The value in the current register is obtained by:
*
* I = (V_shunt * C) / 4096
*
* Where V_shunt is the value in the shunt voltage register and C is the
* value programmed (upon driver initialization) into the calibration register
*/
#define CURRENT_QUOTIENT (4096LU)
/** @brief Read one 16 bit register from a INA2XX device and swap byte order, if necessary. */
static int ina2xx_read_reg(const ina2xx_t *dev, uint8_t reg, uint16_t *out)
{
union {
uint8_t c[2];
uint16_t u16;
} tmp;
int status = 0;
i2c_acquire(dev->params.i2c);
status = i2c_read_regs(dev->params.i2c, dev->params.addr, reg, &tmp.c[0],
2, 0);
i2c_release(dev->params.i2c);
if (status < 0) {
return status;
}
*out = ntohs(tmp.u16);
return 0;
}
/** @brief Write one 16 bit register to a INA2XX device and swap byte order, if necessary. */
static int ina2xx_write_reg(const ina2xx_t *dev, uint8_t reg, uint16_t in)
{
union {
uint8_t c[2];
uint16_t u16;
} tmp;
int status = 0;
tmp.u16 = htons(in);
i2c_acquire(dev->params.i2c);
status = i2c_write_regs(dev->params.i2c, dev->params.addr, reg, &tmp.c[0],
2, 0);
i2c_release(dev->params.i2c);
if (status < 0) {
return status;
}
return 0;
}
int ina2xx_init(ina2xx_t *dev, const ina2xx_params_t *params)
{
uint16_t config;
int status;
if (!dev || !params) {
return -EINVAL;
}
dev->params = *params;
/* Reset device */
status = ina2xx_write_reg(dev, INA2XX_REG_CONFIGURATION, INA2XX_RESET);
if (status < 0) {
DEBUG("[ina2xx]: Sending reset (write reg) failed with %d\n", status);
return status;
}
/* Check if default config is preset after reset */
status = ina2xx_read_reg(dev, INA2XX_REG_CONFIGURATION, &config);
if (status < 0) {
DEBUG("[ina2xx]: Verifying device (read reg) failed with %d\n", status);
return status;
}
if (config != INA2XX_DEFCONFIG) {
DEBUG("[ina2xx]: Reset did't restore default config. Wrong device?\n");
return -ENODEV;
}
status = ina2xx_write_reg(dev, INA2XX_REG_CONFIGURATION, params->config);
if (status < 0) {
DEBUG("[ina2xx]: Setting configuration (write reg) with %d\n", status);
return status;
}
/* Set calibration register based on the shunt resistor.
* ==> Current will be in mA
* Multiply by 100
* ==> Current will be in mA
*/
uint32_t calib = (100 * CURRENT_QUOTIENT) / params->rshunt_mohm;
/* Divide by 2^i_range to reduce the resolution by factor 2^i_range */
calib >>= params->i_range;
if (calib > UINT16_MAX) {
return -ERANGE;
}
status = ina2xx_write_reg(dev, INA2XX_REG_CALIBRATION, (uint16_t)calib);
if (status < 0) {
DEBUG("[ina2xx]: Setting calibration (write reg) with %d\n", status);
return status;
}
return status;
}
int ina2xx_read_shunt(const ina2xx_t *dev, int16_t *voltage)
{
return ina2xx_read_reg(dev, INA2XX_REG_SHUNT_VOLTAGE, (uint16_t *)voltage);
}
int ina2xx_read_bus(const ina2xx_t *dev, uint16_t *voltage)
{
uint16_t tmp;
int status = ina2xx_read_reg(dev, INA2XX_REG_BUS_VOLTAGE, &tmp);
if (status < 0) {
return status;
}
/* The voltage given by bits 15-3 in steps of 4 mV
* ==> Take bits 15-3, shift 3 bits right, multiply by 4
* ==> Same as: Take bits 15-3, shift 3 bits right, shift 2 bits left
* ==> Same as: Take bits 15-3, shift 1 bit right
*/
*voltage = (tmp & ~0x7) >> 1;
if (tmp & INA2XX_VBUS_OVF) {
return -EDOM;
}
return (tmp & INA2XX_VBUS_CNVR) ? 1 : 0;
}
int ina2xx_read_current(const ina2xx_t *dev, int32_t *current)
{
int16_t tmp;
int status = ina2xx_read_reg(dev, INA2XX_REG_CURRENT, (uint16_t *)&tmp);
if (status < 0) {
return status;
}
/*
* The calibration is chosen according to the selected value in
* dev->params.i_range, so that tmp * 2^i_range gives us the
* current in E-05 A. We can thus simple use a left shift to convert
* the current into E-05 A.
*/
*current = ((int32_t)tmp) << dev->params.i_range;
return 0;
}
int ina2xx_read_power(const ina2xx_t *dev, uint32_t *power)
{
int status;
uint16_t tmp;
status = ina2xx_read_reg(dev, INA2XX_REG_POWER, &tmp);
if (status < 0) {
return status;
}
/*
* The resolution of the raw power value depends only on the resolution
* of the raw current value, as the bus voltage has a resolution of 4 mV
* regardless of configuration and calibration values. The product of
* bus voltage and raw current value is divided by 5000, this results in
* the following resolutions:
*
* Res current | res power
* 0.01 mA | 0.2 mW
* 0.02 mA | 0.4 mW
* 0.04 mA | 0.8 mW
* ...
*
* ==> multiply by 2^(1 + i_range) to get power in E-04W
*/
*power = ((uint32_t)tmp) << (1 + dev->params.i_range);
return 0;
}