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