Files
ESP-Nodes/ESP-IDF_Robot/main/ina219.c
2025-07-08 00:35:46 -04:00

306 lines
8.2 KiB
C

/*
* Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file ina219.c
*
* ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional
* Current/Power Monitor
*
* Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
*
* BSD Licensed as described in the file LICENSE
*/
#include <esp_log.h>
#include <math.h>
#include <esp_idf_lib_helpers.h>
#include "ina219.h"
#define I2C_FREQ_HZ 1000000 // Max 1 MHz for esp-idf, but supports up to 2.56 MHz
static const char *TAG = "ina219";
#define REG_CONFIG 0
#define REG_SHUNT_U 1
#define REG_BUS_U 2
#define REG_POWER 3
#define REG_CURRENT 4
#define REG_CALIBRATION 5
#define BIT_RST 15
#define BIT_BRNG 13
#define BIT_PG0 11
#define BIT_BADC0 7
#define BIT_SADC0 3
#define BIT_MODE 0
#define MASK_PG (3 << BIT_PG0)
#define MASK_BADC (0xf << BIT_BADC0)
#define MASK_SADC (0xf << BIT_SADC0)
#define MASK_MODE (7 << BIT_MODE)
#define MASK_BRNG (1 << BIT_BRNG)
#define DEF_CONFIG 0x399f
#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
static const float u_shunt_max[] = {
[INA219_GAIN_1] = 0.04,
[INA219_GAIN_0_5] = 0.08,
[INA219_GAIN_0_25] = 0.16,
[INA219_GAIN_0_125] = 0.32,
};
static esp_err_t read_reg_16(ina219_t *dev, uint8_t reg, uint16_t *val)
{
CHECK_ARG(val);
I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2));
I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
*val = (*val >> 8) | (*val << 8);
return ESP_OK;
}
static esp_err_t write_reg_16(ina219_t *dev, uint8_t reg, uint16_t val)
{
uint16_t v = (val >> 8) | (val << 8);
I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2));
I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
return ESP_OK;
}
static esp_err_t read_conf_bits(ina219_t *dev, uint16_t mask, uint8_t bit, uint16_t *res)
{
uint16_t raw;
CHECK(read_reg_16(dev, REG_CONFIG, &raw));
*res = (raw & mask) >> bit;
return ESP_OK;
}
///////////////////////////////////////////////////////////////////////////////
esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio)
{
CHECK_ARG(dev);
if (addr < INA219_ADDR_GND_GND || addr > INA219_ADDR_SCL_SCL)
{
ESP_LOGE(TAG, "Invalid I2C address");
return ESP_ERR_INVALID_ARG;
}
dev->i2c_dev.port = port;
dev->i2c_dev.addr = addr;
dev->i2c_dev.cfg.sda_io_num = sda_gpio;
dev->i2c_dev.cfg.scl_io_num = scl_gpio;
#if HELPER_TARGET_IS_ESP32
dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ;
#endif
return i2c_dev_create_mutex(&dev->i2c_dev);
}
esp_err_t ina219_free_desc(ina219_t *dev)
{
CHECK_ARG(dev);
return i2c_dev_delete_mutex(&dev->i2c_dev);
}
esp_err_t ina219_init(ina219_t *dev)
{
CHECK_ARG(dev);
CHECK(read_reg_16(dev, REG_CONFIG, &dev->config));
ESP_LOGD(TAG, "Initialize, config: 0x%04x", dev->config);
return ESP_OK;
}
esp_err_t ina219_reset(ina219_t *dev)
{
CHECK_ARG(dev);
CHECK(write_reg_16(dev, REG_CONFIG, 1 << BIT_RST));
dev->config = DEF_CONFIG;
ESP_LOGD(TAG, "Device reset");
return ESP_OK;
}
esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range,
ina219_gain_t gain, ina219_resolution_t u_res,
ina219_resolution_t i_res, ina219_mode_t mode)
{
CHECK_ARG(dev);
CHECK_ARG(u_range <= INA219_BUS_RANGE_32V);
CHECK_ARG(gain <= INA219_GAIN_0_125);
CHECK_ARG(u_res <= INA219_RES_12BIT_128S);
CHECK_ARG(i_res <= INA219_RES_12BIT_128S);
CHECK_ARG(mode <= INA219_MODE_CONT_SHUNT_BUS);
dev->config = (u_range << BIT_BRNG) |
(gain << BIT_PG0) |
(u_res << BIT_BADC0) |
(i_res << BIT_SADC0) |
(mode << BIT_MODE);
ESP_LOGD(TAG, "Config: 0x%04x", dev->config);
return write_reg_16(dev, REG_CONFIG, dev->config);
}
esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range)
{
CHECK_ARG(dev && range);
*range = 0;
return read_conf_bits(dev, MASK_BRNG, BIT_BRNG, (uint16_t *)range);
}
esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain)
{
CHECK_ARG(dev && gain);
*gain = 0;
return read_conf_bits(dev, MASK_PG, BIT_PG0, (uint16_t *)gain);
}
esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res)
{
CHECK_ARG(dev && res);
*res = 0;
return read_conf_bits(dev, MASK_BADC, BIT_BADC0, (uint16_t *)res);
}
esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res)
{
CHECK_ARG(dev && res);
*res = 0;
return read_conf_bits(dev, MASK_SADC, BIT_SADC0, (uint16_t *)res);
}
esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode)
{
CHECK_ARG(dev && mode);
*mode = 0;
return read_conf_bits(dev, MASK_MODE, BIT_MODE, (uint16_t *)mode);
}
esp_err_t ina219_calibrate(ina219_t *dev, float r_shunt)
{
CHECK_ARG(dev);
ina219_gain_t gain;
CHECK(ina219_get_gain(dev, &gain));
dev->i_lsb = (uint16_t)(u_shunt_max[gain] / r_shunt / 32767 * 100000000);
dev->i_lsb /= 100000000;
dev->i_lsb /= 0.0001;
dev->i_lsb = ceil(dev->i_lsb);
dev->i_lsb *= 0.0001;
dev->p_lsb = dev->i_lsb * 20;
uint16_t cal = (uint16_t)((0.04096) / (dev->i_lsb * r_shunt));
ESP_LOGD(TAG, "Calibration: %.04f Ohm, 0x%04x", r_shunt, cal);
return write_reg_16(dev, REG_CALIBRATION, cal);
}
esp_err_t ina219_trigger(ina219_t *dev)
{
CHECK_ARG(dev);
uint16_t mode = (dev->config & MASK_MODE) >> BIT_MODE;
if (mode < INA219_MODE_TRIG_SHUNT || mode > INA219_MODE_TRIG_SHUNT_BUS)
{
ESP_LOGE(TAG, "Could not trigger conversion in this mode: %d", mode);
return ESP_ERR_INVALID_STATE;
}
return write_reg_16(dev, REG_CONFIG, dev->config);
}
esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage)
{
CHECK_ARG(dev && voltage);
uint16_t raw;
CHECK(read_reg_16(dev, REG_BUS_U, &raw));
*voltage = (raw >> 3) * 0.004;
return ESP_OK;
}
esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage)
{
CHECK_ARG(dev && voltage);
int16_t raw;
CHECK(read_reg_16(dev, REG_SHUNT_U, (uint16_t *)&raw));
*voltage = raw / 100000.0;
return ESP_OK;
}
esp_err_t ina219_get_current(ina219_t *dev, float *current)
{
CHECK_ARG(dev && current);
int16_t raw;
CHECK(read_reg_16(dev, REG_CURRENT, (uint16_t *)&raw));
*current = raw * dev->i_lsb;
return ESP_OK;
}
esp_err_t ina219_get_power(ina219_t *dev, float *power)
{
CHECK_ARG(dev && power);
int16_t raw;
CHECK(read_reg_16(dev, REG_POWER, (uint16_t *)&raw));
*power = raw * dev->p_lsb;
return ESP_OK;
}