mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Add max9611 High Side Current Shunt ADC (#2705)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
58fa63ad88
commit
4aeacfd16e
6 changed files with 264 additions and 1 deletions
|
@ -94,6 +94,7 @@ esphome/components/lock/* @esphome/core
|
|||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||
|
|
1
esphome/components/max9611/__init__.py
Normal file
1
esphome/components/max9611/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@mckaymatthew"]
|
93
esphome/components/max9611/max9611.cpp
Normal file
93
esphome/components/max9611/max9611.cpp
Normal 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(®_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
|
62
esphome/components/max9611/max9611.h
Normal file
62
esphome/components/max9611/max9611.h
Normal 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
|
92
esphome/components/max9611/sensor.py
Normal file
92
esphome/components/max9611/sensor.py
Normal 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))
|
|
@ -1077,6 +1077,20 @@ sensor:
|
|||
cs_pin:
|
||||
mcp23xxx: mcp23017_hub
|
||||
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:
|
||||
setup_mode: False
|
||||
|
|
Loading…
Reference in a new issue