Add max9611 High Side Current Shunt ADC (#2705)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
mckaymatthew 2022-02-08 03:56:40 -06:00 committed by GitHub
parent 58fa63ad88
commit 4aeacfd16e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 264 additions and 1 deletions

View file

@ -94,6 +94,7 @@ esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny esphome/components/ltr390/* @sjtrny
esphome/components/max7219digit/* @rspaargaren esphome/components/max7219digit/* @rspaargaren
esphome/components/max9611/* @mckaymatthew
esphome/components/mcp23008/* @jesserockz esphome/components/mcp23008/* @jesserockz
esphome/components/mcp23017/* @jesserockz esphome/components/mcp23017/* @jesserockz
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz

View file

@ -0,0 +1 @@
CODEOWNERS = ["@mckaymatthew"]

View file

@ -0,0 +1,93 @@
#include "max9611.h"
#include "esphome/core/log.h"
#include "esphome/components/i2c/i2c_bus.h"
namespace esphome {
namespace max9611 {
using namespace esphome::i2c;
// Sign extend
// http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend
template<typename T, unsigned B> inline T signextend(const T x) {
struct {
T x : B;
} s;
return s.x = x;
}
// Map the gain register to in uV/LSB
const float gain_to_lsb(MAX9611Multiplexer gain) {
float lsb = 0.0;
if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) {
lsb = 107.50;
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN4) {
lsb = 26.88;
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN8) {
lsb = 13.44;
}
return lsb;
}
static const char *const TAG = "max9611";
static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods.
static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB
static const float TEMP_LSB = 0.48; // 0.48C/LSB
static const float MICRO_VOLTS_PER_VOLT = 1000000.0;
void MAX9611Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up max9611...");
// Perform dummy-read
uint8_t value;
this->read(&value, 1);
// Configuration Stage.
// First send an integration request with the specified gain
const uint8_t setup_dat[] = {CONTROL_REGISTER_1_ADRR, static_cast<uint8_t>(gain_)};
// Then send a request that samples all channels as fast as possible, using the last provided gain
const uint8_t fast_mode_dat[] = {CONTROL_REGISTER_1_ADRR, MAX9611Multiplexer::MAX9611_MULTIPLEXER_FAST_MODE};
if (this->write(reinterpret_cast<const uint8_t *>(&setup_dat), sizeof(setup_dat)) != ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Failed to setup Max9611 during GAIN SET");
return;
}
delay(SETUP_DELAY);
if (this->write(reinterpret_cast<const uint8_t *>(&fast_mode_dat), sizeof(fast_mode_dat)) != ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Failed to setup Max9611 during FAST MODE SET");
return;
}
}
void MAX9611Component::dump_config() {
ESP_LOGCONFIG(TAG, "Dump Config max9611...");
ESP_LOGCONFIG(TAG, " CSA Gain Register: %x", gain_);
LOG_I2C_DEVICE(this);
}
void MAX9611Component::update() {
// Setup read from 0x0 register base
const uint8_t reg_base = 0x0;
const ErrorCode write_result = this->write(&reg_base, 1);
// Just read the entire register map in a bulk read, faster than individually querying register.
const ErrorCode read_result = this->read(register_map_, sizeof(register_map_));
if (write_result != ErrorCode::ERROR_OK || read_result != ErrorCode::ERROR_OK) {
ESP_LOGW(TAG, "MAX9611 Update FAILED!");
return;
}
uint16_t csa_register = ((register_map_[CSA_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[CSA_DATA_BYTE_LSB_ADRR])) >> 4;
uint16_t rs_register = ((register_map_[RS_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[RS_DATA_BYTE_LSB_ADRR])) >> 4;
uint16_t t_register = ((register_map_[TEMP_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[TEMP_DATA_BYTE_LSB_ADRR])) >> 7;
float voltage = rs_register * VOUT_LSB;
float shunt_voltage = (csa_register * gain_to_lsb(gain_)) / MICRO_VOLTS_PER_VOLT;
float temp = signextend<signed int, 9>(t_register) * TEMP_LSB;
float amps = shunt_voltage / current_resistor_;
float watts = amps * voltage;
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(voltage);
}
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(amps);
}
if (watt_sensor_ != nullptr) {
watt_sensor_->publish_state(watts);
}
if (temperature_sensor_ != nullptr) {
temperature_sensor_->publish_state(temp);
}
ESP_LOGD(TAG, "V: %f, A: %f, W: %f, Deg C: %f", voltage, amps, watts, temp);
}
} // namespace max9611
} // namespace esphome

View file

@ -0,0 +1,62 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace max9611 {
enum MAX9611Multiplexer {
MAX9611_MULTIPLEXER_CSA_GAIN1 = 0b000,
MAX9611_MULTIPLEXER_CSA_GAIN4 = 0b001,
MAX9611_MULTIPLEXER_CSA_GAIN8 = 0b010,
MAX9611_MULTIPLEXER_RS = 0b011,
MAX9611_MULTIPLEXER_OUT = 0b100,
MAX9611_MULTIPLEXER_SET = 0b101,
MAX9611_MULTIPLEXER_TEMP = 0b110,
MAX9611_MULTIPLEXER_FAST_MODE = 0b111,
};
enum MAX9611RegisterMap {
CSA_DATA_BYTE_MSB_ADRR = 0x00,
CSA_DATA_BYTE_LSB_ADRR = 0x01,
RS_DATA_BYTE_MSB_ADRR = 0x02,
RS_DATA_BYTE_LSB_ADRR = 0x03,
OUT_DATA_BYTE_MSB_ADRR = 0x04, // Unused Op-Amp
OUT_DATA_BYTE_LSB_ADRR = 0x05, // Unused Op-Amp
SET_DATA_BYTE_MSB_ADRR = 0x06, // Unused Op-Amp
SET_DATA_BYTE_LSB_ADRR = 0x07, // Unused Op-Amp
TEMP_DATA_BYTE_MSB_ADRR = 0x08,
TEMP_DATA_BYTE_LSB_ADRR = 0x09,
CONTROL_REGISTER_1_ADRR = 0x0A,
CONTROL_REGISTER_2_ADRR = 0x0B,
};
class MAX9611Component : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void update() override;
void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; }
void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; }
void set_watt_sensor(sensor::Sensor *ws) { watt_sensor_ = ws; }
void set_temp_sensor(sensor::Sensor *ts) { temperature_sensor_ = ts; }
void set_current_resistor(float r) { current_resistor_ = r; }
void set_gain(MAX9611Multiplexer g) { gain_ = g; }
protected:
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *watt_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
float current_resistor_;
uint8_t register_map_[0x0C];
MAX9611Multiplexer gain_;
};
} // namespace max9611
} // namespace esphome

View file

@ -0,0 +1,92 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_SHUNT_RESISTANCE,
CONF_GAIN,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_POWER,
CONF_TEMPERATURE,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_CELSIUS,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
max9611_ns = cg.esphome_ns.namespace("max9611")
max9611Gain = max9611_ns.enum("MAX9611Multiplexer")
MAX9611_GAIN = {
"8X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN8,
"4X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN4,
"1X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN1,
}
MAX9611Component = max9611_ns.class_(
"MAX9611Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MAX9611Component),
cv.Required(CONF_SHUNT_RESISTANCE): cv.resistance,
cv.Required(CONF_GAIN): cv.enum(MAX9611_GAIN, upper=True),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x70))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_current_resistor(config[CONF_SHUNT_RESISTANCE]))
cg.add(var.set_gain(config[CONF_GAIN]))
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_watt_sensor(sens))
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_temp_sensor(sens))

View file

@ -1077,6 +1077,20 @@ sensor:
cs_pin: cs_pin:
mcp23xxx: mcp23017_hub mcp23xxx: mcp23017_hub
number: 14 number: 14
- platform: max9611
i2c_id: i2c_bus
shunt_resistance: 0.2 ohm
gain: '1X'
voltage:
name: Max9611 Voltage
current:
name: Max9611 Current
power:
name: Max9611 Watts
temperature:
name: Max9611 Temp
update_interval: 1s
esp32_touch: esp32_touch:
setup_mode: False setup_mode: False