ESPHome  2024.12.2
cs5460a.cpp
Go to the documentation of this file.
1 #include "cs5460a.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace cs5460a {
6 
7 static const char *const TAG = "cs5460a";
8 
9 void CS5460AComponent::write_register_(enum CS5460ARegister addr, uint32_t value) {
10  this->write_byte(CMD_WRITE | (addr << 1));
11  this->write_byte(value >> 16);
12  this->write_byte(value >> 8);
13  this->write_byte(value >> 0);
14 }
15 
16 uint32_t CS5460AComponent::read_register_(uint8_t addr) {
17  uint32_t value;
18 
19  this->write_byte(CMD_READ | (addr << 1));
20  value = (uint32_t) this->transfer_byte(CMD_SYNC0) << 16;
21  value |= (uint32_t) this->transfer_byte(CMD_SYNC0) << 8;
22  value |= this->transfer_byte(CMD_SYNC0) << 0;
23 
24  return value;
25 }
26 
28  uint32_t pc = ((uint8_t) phase_offset_ & 0x3f) | (phase_offset_ < 0 ? 0x40 : 0);
29  uint32_t config = (1 << 0) | /* K = 0b0001 */
30  (current_hpf_ ? 1 << 5 : 0) | /* IHPF */
31  (voltage_hpf_ ? 1 << 6 : 0) | /* VHPF */
32  (pga_gain_ << 16) | /* Gi */
33  (pc << 17); /* PC */
34  int cnt = 0;
35 
36  /* Serial resynchronization */
37  this->write_byte(CMD_SYNC1);
38  this->write_byte(CMD_SYNC1);
39  this->write_byte(CMD_SYNC1);
40  this->write_byte(CMD_SYNC0);
41 
42  /* Reset */
43  this->write_register_(REG_CONFIG, 1 << 7);
44  delay(10);
45  while (cnt++ < 50 && (this->read_register_(REG_CONFIG) & 0x81) != 0x000001)
46  ;
47  if (cnt > 50)
48  return false;
49 
50  this->write_register_(REG_CONFIG, config);
51  return true;
52 }
53 
55  ESP_LOGCONFIG(TAG, "Setting up CS5460A...");
56 
57  float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
58  float voltage_full_scale = 0.25;
59  current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000);
60  voltage_multiplier_ = voltage_full_scale / (voltage_gain_ * 0x1000000);
61 
62  /*
63  * Calculate power from the Energy register because the Power register
64  * stores instantaneous power which varies a lot in each AC cycle,
65  * while the Energy value is accumulated over the "computation cycle"
66  * which should be an integer number of AC cycles.
67  */
69  (current_full_scale * voltage_full_scale * 4096) / (current_gain_ * voltage_gain_ * samples_ * 0x800000);
70 
71  pulse_freq_ =
72  (current_full_scale * voltage_full_scale) / (fabsf(current_gain_) * voltage_gain_ * pulse_energy_wh_ * 3600);
73 
74  hw_init_();
75 }
76 
78  this->spi_setup();
79  this->enable();
80 
81  if (!this->softreset_()) {
82  this->disable();
83  ESP_LOGE(TAG, "CS5460A reset failed!");
84  this->mark_failed();
85  return;
86  }
87 
88  uint32_t status = this->read_register_(REG_STATUS);
89  ESP_LOGCONFIG(TAG, " Version: %" PRIx32, (status >> 6) & 7);
90 
92  this->write_register_(REG_PULSE_RATE, lroundf(pulse_freq_ * 32.0f));
93 
94  /* Use one of the power saving features (assuming external oscillator), reset other CONTROL bits,
95  * sometimes softreset_() is not enough */
96  this->write_register_(REG_CONTROL, 0x000004);
97 
98  this->restart_();
99  this->disable();
100  ESP_LOGCONFIG(TAG, " Init ok");
101 }
102 
103 /* Doesn't reset the register values etc., just restarts the "computation cycle" */
105  this->enable();
106  /* Stop running conversion, wake up if needed */
107  this->write_byte(CMD_POWER_UP);
108  /* Start continuous conversion */
109  this->write_byte(CMD_START_CONT);
110  this->disable();
111 
112  this->started_();
113 }
114 
116  /*
117  * Try to guess when the next batch of results is going to be ready and
118  * schedule next STATUS check some time before that moment. This assumes
119  * two things:
120  * * a new "computation cycle" started just now. If it started some
121  * time ago we may be a late next time, but hopefully less late in each
122  * iteration -- that's why we schedule the next check in some 0.8 of
123  * the time we actually expect the next reading ready.
124  * * MCLK rate is 4.096MHz and K == 1. If there's a CS5460A module in
125  * use with a different clock this will need to be parametrised.
126  */
127  expect_data_ts_ = millis() + samples_ * 1024 / 4096;
128 
130 }
131 
133  int32_t time_left = expect_data_ts_ - millis();
134 
135  /* First try at 0.8 of the actual expected time (if it's in the future) */
136  if (time_left > 0)
137  time_left -= time_left / 5;
138 
139  if (time_left > -500) {
140  /* But not sooner than in 30ms from now */
141  if (time_left < 30)
142  time_left = 30;
143  } else {
144  /*
145  * If the measurement is more than 0.5s overdue start worrying. The
146  * device may be stuck because of an overcurrent error or similar,
147  * from now on just retry every 1s. After 15s try a reset, if it
148  * fails we give up and mark the component "failed".
149  */
150  if (time_left > -15000) {
151  time_left = 1000;
152  this->status_momentary_warning("warning", 1000);
153  } else {
154  ESP_LOGCONFIG(TAG, "Device officially stuck, resetting");
155  this->cancel_timeout("status-check");
156  this->hw_init_();
157  return;
158  }
159  }
160 
161  this->set_timeout("status-check", time_left, [this]() {
162  if (!this->check_status_())
163  this->schedule_next_check_();
164  });
165 }
166 
168  this->enable();
169  uint32_t status = this->read_register_(REG_STATUS);
170 
171  if (!(status & 0xcbf83c)) {
172  this->disable();
173  return false;
174  }
175 
176  uint32_t clear = 1 << 20;
177 
178  /* TODO: Report if IC=0 but only once as it can't be cleared */
179 
180  if (status & (1 << 2)) {
181  clear |= 1 << 2;
182  ESP_LOGE(TAG, "Low supply detected");
183  this->status_momentary_warning("warning", 500);
184  }
185 
186  if (status & (1 << 3)) {
187  clear |= 1 << 3;
188  ESP_LOGE(TAG, "Modulator oscillation on current channel");
189  this->status_momentary_warning("warning", 500);
190  }
191 
192  if (status & (1 << 4)) {
193  clear |= 1 << 4;
194  ESP_LOGE(TAG, "Modulator oscillation on voltage channel");
195  this->status_momentary_warning("warning", 500);
196  }
197 
198  if (status & (1 << 5)) {
199  clear |= 1 << 5;
200  ESP_LOGE(TAG, "Watch-dog timeout");
201  this->status_momentary_warning("warning", 500);
202  }
203 
204  if (status & (1 << 11)) {
205  clear |= 1 << 11;
206  ESP_LOGE(TAG, "EOUT Energy Accumulation Register out of range");
207  this->status_momentary_warning("warning", 500);
208  }
209 
210  if (status & (1 << 12)) {
211  clear |= 1 << 12;
212  ESP_LOGE(TAG, "Energy out of range");
213  this->status_momentary_warning("warning", 500);
214  }
215 
216  if (status & (1 << 13)) {
217  clear |= 1 << 13;
218  ESP_LOGE(TAG, "RMS voltage out of range");
219  this->status_momentary_warning("warning", 500);
220  }
221 
222  if (status & (1 << 14)) {
223  clear |= 1 << 14;
224  ESP_LOGE(TAG, "RMS current out of range");
225  this->status_momentary_warning("warning", 500);
226  }
227 
228  if (status & (1 << 15)) {
229  clear |= 1 << 15;
230  ESP_LOGE(TAG, "Power calculation out of range");
231  this->status_momentary_warning("warning", 500);
232  }
233 
234  if (status & (1 << 16)) {
235  clear |= 1 << 16;
236  ESP_LOGE(TAG, "Voltage out of range");
237  this->status_momentary_warning("warning", 500);
238  }
239 
240  if (status & (1 << 17)) {
241  clear |= 1 << 17;
242  ESP_LOGE(TAG, "Current out of range");
243  this->status_momentary_warning("warning", 500);
244  }
245 
246  if (status & (1 << 19)) {
247  clear |= 1 << 19;
248  ESP_LOGE(TAG, "Divide overflowed");
249  }
250 
251  if (status & (1 << 22)) {
252  bool dir = status & (1 << 21);
253  if (current_gain_ < 0)
254  dir = !dir;
255  ESP_LOGI(TAG, "Energy counter %s pulse", dir ? "negative" : "positive");
256  clear |= 1 << 22;
257  }
258 
259  uint32_t raw_current = 0; /* Calm the validators */
260  uint32_t raw_voltage = 0;
261  uint32_t raw_energy = 0;
262 
263  if (status & (1 << 23)) {
264  clear |= 1 << 23;
265 
266  if (current_sensor_ != nullptr)
267  raw_current = this->read_register_(REG_IRMS);
268 
269  if (voltage_sensor_ != nullptr)
270  raw_voltage = this->read_register_(REG_VRMS);
271  }
272 
273  if (status & ((1 << 23) | (1 << 5))) {
274  /* Read to clear the WDT bit */
275  raw_energy = this->read_register_(REG_E);
276  }
277 
278  this->write_register_(REG_STATUS, clear);
279  this->disable();
280 
281  /*
282  * Schedule the next STATUS check assuming that DRDY was asserted very
283  * recently, then publish the new values. Do this last for reentrancy in
284  * case the publish triggers a restart() or for whatever reason needs to
285  * cancel the timeout set in schedule_next_check_(), or needs to use SPI.
286  * If the current or power values haven't changed one bit it may be that
287  * the chip somehow forgot to update the registers -- seen happening very
288  * rarely. In that case don't publish them because the user may have
289  * the input connected to a multiplexer and may have switched channels
290  * since the previous reading and we'd be publishing the stale value for
291  * the new channel. If the value *was* updated it's very unlikely that
292  * it wouldn't have changed, especially power/energy which are affected
293  * by the noise on both the current and value channels (in case of energy,
294  * accumulated over many conversion cycles.)
295  */
296  if (status & (1 << 23)) {
297  this->started_();
298 
299  if (current_sensor_ != nullptr && raw_current != prev_raw_current_) {
301  prev_raw_current_ = raw_current;
302  }
303 
304  if (voltage_sensor_ != nullptr)
306 
307  if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) {
308  int32_t raw = (int32_t) (raw_energy << 8) >> 8; /* Sign-extend */
310  prev_raw_energy_ = raw_energy;
311  }
312 
313  return true;
314  }
315 
316  return false;
317 }
318 
320  uint32_t state = this->get_component_state();
321 
322  ESP_LOGCONFIG(TAG, "CS5460A:");
323  ESP_LOGCONFIG(TAG, " Init status: %s",
324  state == COMPONENT_STATE_LOOP ? "OK" : (state == COMPONENT_STATE_FAILED ? "failed" : "other"));
325  LOG_PIN(" CS Pin: ", cs_);
326  ESP_LOGCONFIG(TAG, " Samples / cycle: %" PRIu32, samples_);
327  ESP_LOGCONFIG(TAG, " Phase offset: %i", phase_offset_);
328  ESP_LOGCONFIG(TAG, " PGA Gain: %s", pga_gain_ == CS5460A_PGA_GAIN_50X ? "50x" : "10x");
329  ESP_LOGCONFIG(TAG, " Current gain: %.5f", current_gain_);
330  ESP_LOGCONFIG(TAG, " Voltage gain: %.5f", voltage_gain_);
331  ESP_LOGCONFIG(TAG, " Current HPF: %s", current_hpf_ ? "enabled" : "disabled");
332  ESP_LOGCONFIG(TAG, " Voltage HPF: %s", voltage_hpf_ ? "enabled" : "disabled");
333  ESP_LOGCONFIG(TAG, " Pulse energy: %.2f Wh", pulse_energy_wh_);
334  LOG_SENSOR(" ", "Voltage", voltage_sensor_);
335  LOG_SENSOR(" ", "Current", current_sensor_);
336  LOG_SENSOR(" ", "Power", power_sensor_);
337 }
338 
339 } // namespace cs5460a
340 } // namespace esphome
const uint32_t COMPONENT_STATE_LOOP
Definition: component.cpp:35
const uint32_t COMPONENT_STATE_FAILED
Definition: component.cpp:36
uint8_t raw[35]
Definition: bl0939.h:19
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
Definition: component.cpp:73
void status_momentary_warning(const std::string &name, uint32_t length=5000)
Definition: component.cpp:178
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition: component.cpp:69
u_int8_t raw_voltage
GPIOPin * cs_
Definition: spi.h:378
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
uint32_t read_register_(uint8_t addr)
Definition: cs5460a.cpp:16
uint32_t get_component_state() const
Definition: component.cpp:86
void write_register_(enum CS5460ARegister addr, uint32_t value)
Definition: cs5460a.cpp:9
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint8_t status
Definition: bl0942.h:74
sensor::Sensor * voltage_sensor_
Definition: cs5460a.h:93
sensor::Sensor * current_sensor_
Definition: cs5460a.h:92
sensor::Sensor * power_sensor_
Definition: cs5460a.h:94
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool state
Definition: fan.h:34
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26