Skip to content

Commit

Permalink
Add CS5460A power-meter component
Browse files Browse the repository at this point in the history
CS5460A is a chip that calculates power, RMS current, voltage, energy
etc. and communicates over SPI.  Easy to buy on a breakout board.
  • Loading branch information
balrog-kun committed Jan 17, 2021
1 parent d9a2651 commit f2b2d0e
Show file tree
Hide file tree
Showing 5 changed files with 490 additions and 0 deletions.
Empty file.
249 changes: 249 additions & 0 deletions esphome/components/cs5460a/cs5460a.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#include "cs5460a.h"
#include "esphome/core/log.h"

namespace esphome {
namespace cs5460a {

static const char *TAG = "cs5460a";

void CS5460AComponent::write_register_(enum CS5460ARegister addr, uint32_t value) {
this->write_byte(CMD_WRITE | (addr << 1));
this->write_byte(value >> 16);
this->write_byte(value >> 8);
this->write_byte(value >> 0);
}

uint32_t CS5460AComponent::read_register_(uint8_t addr) {
uint32_t value;

this->write_byte(CMD_READ | (addr << 1));
value = (uint32_t) this->transfer_byte(CMD_SYNC0) << 16;
value |= (uint32_t) this->transfer_byte(CMD_SYNC0) << 8;
value |= this->transfer_byte(CMD_SYNC0) << 0;

return value;
}

bool CS5460AComponent::reset_() {
uint32_t pc = ((uint8_t) phase_offset_ & 0x3f) | (phase_offset_ < 0 ? 0x40 : 0);
uint32_t config = (1 << 0) | /* K = 0b0001 */
(current_hpf_ ? 1 << 5 : 0) | /* IHPF */
(voltage_hpf_ ? 1 << 6 : 0) | /* VHPF */
(pga_gain_ << 16) | /* Gi */
(pc << 17); /* PC */
int cnt = 0;

/* Serial resynchronization */
this->write_byte(CMD_SYNC1);
this->write_byte(CMD_SYNC1);
this->write_byte(CMD_SYNC1);
this->write_byte(CMD_SYNC0);

/* Reset */
this->write_register_(REG_CONFIG, 1 << 7);
delay(10);
while (cnt++ < 50 && (this->read_register_(REG_CONFIG) & 0x81) != 0x000001)
;
if (cnt > 50)
return false;

this->write_register_(REG_CONFIG, config);
return true;
}

/* Doesn't reset the register values etc., just restarts the "computation cycle" */
void CS5460AComponent::restart_() {
int cnt;

/* Stop running conversion, wake up if needed */
this->write_byte(CMD_POWER_UP);
/* Start continuous conversion */
this->write_byte(CMD_START_CONT);
running_ = true;
}

void CS5460AComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CS5460A...");

float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
float voltage_full_scale = 0.25;
current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000);
voltage_multiplier_ = voltage_full_scale / (voltage_gain_ * 0x1000000);

/*
* Calculate power from the Energy register because the Power register
* stores instantaneous power which varies a lot in each AC cycle,
* while the Energy value is accumulated over the "computation cycle"
* which should be an integer number of AC cycles.
*/
power_multiplier_ =
(current_full_scale * voltage_full_scale * 4096) / (current_gain_ * voltage_gain_ * samples_ * 0x800000);

this->spi_setup();
this->enable();
if (!this->reset_()) {
this->disable();
ESP_LOGE(TAG, "CS5460A reset failed!");
init_status_ = "reset failed";
return;
}

uint32_t status = this->read_register_(REG_STATUS);
ESP_LOGCONFIG(TAG, " Version: %x", (status >> 6) & 7);

this->write_register_(REG_CYCLE_COUNT, samples_);

float freq =
(current_full_scale * voltage_full_scale) / (voltage_gain_ * fabsf(current_gain_) * pulse_energy_wh_ * 3600);
this->write_register_(REG_PULSE_RATE, lroundf(freq * 32.0f));

/* Use one of the power saving features (assuming external oscillator), reset other CONTROL bits, sometimes reset_()
* is not enough */
this->write_register_(REG_CONTROL, 0x000004);

this->restart_();
this->disable();
ESP_LOGCONFIG(TAG, " Setup ok");
init_status_ = "OK";
}

void CS5460AComponent::dump_config() {
ESP_LOGCONFIG(TAG, "CS5460A:");
ESP_LOGCONFIG(TAG, " Init status: %s", init_status_);
LOG_PIN(" CS Pin: ", cs_);
ESP_LOGCONFIG(TAG, " Samples / cycle: %u", samples_);
ESP_LOGCONFIG(TAG, " Phase offset: %i", phase_offset_);
ESP_LOGCONFIG(TAG, " PGA Gain: %s", pga_gain_ == CS5460A_PGA_GAIN_50X ? "50x" : "10x");
ESP_LOGCONFIG(TAG, " Current gain: %.5f", current_gain_);
ESP_LOGCONFIG(TAG, " Voltage gain: %.5f", voltage_gain_);
ESP_LOGCONFIG(TAG, " Current HPF: %s", current_hpf_ ? "enabled" : "disabled");
ESP_LOGCONFIG(TAG, " Voltage HPF: %s", voltage_hpf_ ? "enabled" : "disabled");
ESP_LOGCONFIG(TAG, " Pulse energy: %.2f Wh", pulse_energy_wh_);
LOG_SENSOR(" ", "Voltage", voltage_sensor_);
LOG_SENSOR(" ", "Current", current_sensor_);
LOG_SENSOR(" ", "Power", power_sensor_);
}

void CS5460AComponent::loop() {
if (!running_)
return;

this->enable();
uint32_t status = this->read_register_(REG_STATUS);

if (!(status & 0x9bf83c)) {
this->disable();
return;
}

uint32_t clear = 1 << 20;

/* TODO: Report if IC=0 but only once as it can't be cleared */

if (status & (1 << 2)) {
clear |= 1 << 2;
ESP_LOGE(TAG, "Low supply detected");
}

if (status & (1 << 3)) {
clear |= 1 << 3;
ESP_LOGE(TAG, "Modulator oscillation on current channel");
}

if (status & (1 << 4)) {
clear |= 1 << 4;
ESP_LOGE(TAG, "Modulator oscillation on voltage channel");
}

if (status & (1 << 5)) {
clear |= 1 << 5;
ESP_LOGE(TAG, "Watch-dog timeout");
}

if (status & (1 << 11)) {
clear |= 1 << 11;
ESP_LOGE(TAG, "EOUT Energy Accumulation Register out of range");
}

if (status & (1 << 12)) {
clear |= 1 << 12;
ESP_LOGE(TAG, "Energy out of range");
}

if (status & (1 << 13)) {
clear |= 1 << 13;
ESP_LOGE(TAG, "RMS voltage out of range");
}

if (status & (1 << 14)) {
clear |= 1 << 14;
ESP_LOGE(TAG, "RMS current out of range");
}

if (status & (1 << 15)) {
clear |= 1 << 15;
ESP_LOGE(TAG, "Power calculation out of range");
}

if (status & (1 << 16)) {
clear |= 1 << 16;
ESP_LOGE(TAG, "Voltage out of range");
}

if (status & (1 << 17)) {
clear |= 1 << 17;
ESP_LOGE(TAG, "Current out of range");
}

if (status & (1 << 19)) {
clear |= 1 << 19;
ESP_LOGE(TAG, "Divide overflowed");
}

if (status & (1 << 22)) {
bool dir = status & (1 << 21);
if (current_gain_ < 0)
dir = !dir;
ESP_LOGI(TAG, "Energy counter %s pulse", dir ? "negative" : "positive");
clear |= 1 << 22;
}

uint32_t raw_current = 0; /* Calm the validators */
uint32_t raw_voltage = 0;
uint32_t raw_energy = 0;

if (status & (1 << 23)) {
clear |= 1 << 23;

if (current_sensor_ != nullptr)
raw_current = this->read_register_(REG_IRMS);

if (voltage_sensor_ != nullptr)
raw_voltage = this->read_register_(REG_VRMS);
}

if (status & ((1 << 23) | (1 << 5))) {
/* Read to clear the WDT bit */
raw_energy = this->read_register_(REG_E);
}

this->write_register_(REG_STATUS, clear);
this->disable();

/* Publish values last for reentrancy */
if (status & (1 << 23)) {
if (current_sensor_ != nullptr)
current_sensor_->publish_state(raw_current * current_multiplier_);

if (voltage_sensor_ != nullptr)
voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_);

if (power_sensor_ != nullptr) {
int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */
power_sensor_->publish_state(raw * power_multiplier_);
}
}
}

} // namespace cs5460a
} // namespace esphome
116 changes: 116 additions & 0 deletions esphome/components/cs5460a/cs5460a.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#pragma once

#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"

namespace esphome {
namespace cs5460a {

enum CS5460ACommand {
CMD_SYNC0 = 0xfe,
CMD_SYNC1 = 0xff,
CMD_START_SINGLE = 0xe0,
CMD_START_CONT = 0xe8,
CMD_POWER_UP = 0xa0,
CMD_POWER_STANDBY = 0x88,
CMD_POWER_SLEEP = 0x90,
CMD_CALIBRATION = 0xc0,
CMD_READ = 0x00,
CMD_WRITE = 0x40,
};

enum CS5460ARegister {
REG_CONFIG = 0x00,
REG_IDCOFF = 0x01,
REG_IGN = 0x02,
REG_VDCOFF = 0x03,
REG_VGN = 0x04,
REG_CYCLE_COUNT = 0x05,
REG_PULSE_RATE = 0x06,
REG_I = 0x07,
REG_V = 0x08,
REG_P = 0x09,
REG_E = 0x0a,
REG_IRMS = 0x0b,
REG_VRMS = 0x0c,
REG_TBC = 0x0d,
REG_POFF = 0x0e,
REG_STATUS = 0x0f,
REG_IACOFF = 0x10,
REG_VACOFF = 0x11,
REG_MASK = 0x1a,
REG_CONTROL = 0x1c,
};

/** Enum listing the current channel aplifiergain settings for the CS5460A.
*/
enum CS5460APGAGain {
CS5460A_PGA_GAIN_10X = 0b0,
CS5460A_PGA_GAIN_50X = 0b1,
};

class CS5460AComponent : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
void set_samples(uint32_t samples) { samples_ = samples; }
void set_phase_offset(int8_t phase_offset) { phase_offset_ = phase_offset; }
void set_pga_gain(CS5460APGAGain pga_gain) { pga_gain_ = pga_gain; }
void set_gains(float current_gain, float voltage_gain) {
current_gain_ = current_gain;
voltage_gain_ = voltage_gain;
}
void set_hpf_enable(bool current_hpf, bool voltage_hpf) {
current_hpf_ = current_hpf;
voltage_hpf_ = voltage_hpf;
}
void set_pulse_energy_wh(float pulse_energy_wh) { pulse_energy_wh_ = pulse_energy_wh; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }

void restart() { restart_(); }

void setup() override;
void loop() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void dump_config() override;

protected:
void write_register_(enum CS5460ARegister addr, uint32_t value);
uint32_t read_register_(uint8_t addr);
bool reset_();
void restart_();

uint32_t samples_{4000};
int8_t phase_offset_{0};
CS5460APGAGain pga_gain_{CS5460A_PGA_GAIN_10X};
float current_gain_{0.001};
float voltage_gain_{0.001};
bool current_hpf_{true};
bool voltage_hpf_{true};
float pulse_energy_wh_{10.0};
float current_multiplier_;
float voltage_multiplier_;
float power_multiplier_;
sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr};
const char *init_status_{"none"};
bool running_{false};
};

template<typename... Ts> class CS5460ARestartAction : public Action<Ts...> {
public:
CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {}

void play(Ts... x) override { cs5460a_->restart(); }

protected:
CS5460AComponent *cs5460a_;
};

} // namespace cs5460a
} // namespace esphome

0 comments on commit f2b2d0e

Please sign in to comment.