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