-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
d9a2651
commit f2b2d0e
Showing
5 changed files
with
490 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.