ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
cs5460a.cpp
Go to the documentation of this file.
1#include "cs5460a.h"
3#include "esphome/core/log.h"
4
5namespace esphome::cs5460a {
6
7ESPHOME_LOG_TAG(TAG, "cs5460a");
8
9static constexpr uint32_t STATUS_CHECK_TIMEOUT_ID = 0;
10
12 this->write_byte(CMD_WRITE | (addr << 1));
13 this->write_byte(value >> 16);
14 this->write_byte(value >> 8);
15 this->write_byte(value >> 0);
16}
17
19 uint32_t value;
20
21 this->write_byte(CMD_READ | (addr << 1));
22 value = (uint32_t) this->transfer_byte(CMD_SYNC0) << 16;
23 value |= (uint32_t) this->transfer_byte(CMD_SYNC0) << 8;
24 value |= this->transfer_byte(CMD_SYNC0) << 0;
25
26 return value;
27}
28
30 uint32_t pc = ((uint8_t) phase_offset_ & 0x3f) | (phase_offset_ < 0 ? 0x40 : 0);
31 uint32_t config = (1 << 0) | /* K = 0b0001 */
32 (current_hpf_ ? 1 << 5 : 0) | /* IHPF */
33 (voltage_hpf_ ? 1 << 6 : 0) | /* VHPF */
34 (pga_gain_ << 16) | /* Gi */
35 (pc << 17); /* PC */
36 int cnt = 0;
37
38 /* Serial resynchronization */
39 this->write_byte(CMD_SYNC1);
40 this->write_byte(CMD_SYNC1);
41 this->write_byte(CMD_SYNC1);
42 this->write_byte(CMD_SYNC0);
43
44 /* Reset */
45 this->write_register_(REG_CONFIG, 1 << 7);
46 delay(10);
47 while (cnt++ < 50 && (this->read_register_(REG_CONFIG) & 0x81) != 0x000001)
48 ;
49 if (cnt > 50)
50 return false;
51
52 this->write_register_(REG_CONFIG, config);
53 return true;
54}
55
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
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
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 */
108 /* Start continuous conversion */
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(1000);
153 } else {
154 ESP_LOGCONFIG(TAG, "Device officially stuck, resetting");
155 this->cancel_timeout(STATUS_CHECK_TIMEOUT_ID);
156 this->hw_init_();
157 return;
158 }
159 }
160
161 this->set_timeout(STATUS_CHECK_TIMEOUT_ID, time_left, [this]() {
162 if (!this->check_status_())
163 this->schedule_next_check_();
164 });
165}
166
168 this->enable();
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(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(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(500);
196 }
197
198 if (status & (1 << 5)) {
199 clear |= 1 << 5;
200 ESP_LOGE(TAG, "Watch-dog timeout");
201 this->status_momentary_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(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(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(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(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(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(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(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 ? LOG_STR_LITERAL("negative") : LOG_STR_LITERAL("positive"));
256 clear |= 1 << 22;
257 }
258
259 uint32_t raw_current = 0; /* Calm the validators */
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 = sign_extend<24>(raw_energy);
310 prev_raw_energy_ = raw_energy;
311 }
312
313 return true;
314 }
315
316 return false;
317}
318
321
322 ESP_LOGCONFIG(TAG,
323 "CS5460A:\n"
324 " Init status: %s",
326 ? LOG_STR_LITERAL("OK")
327 : (state == COMPONENT_STATE_FAILED ? LOG_STR_LITERAL("failed") : LOG_STR_LITERAL("other")));
328 LOG_PIN(" CS Pin: ", cs_);
329 ESP_LOGCONFIG(TAG,
330 " Samples / cycle: %" PRIu32 "\n"
331 " Phase offset: %i\n"
332 " PGA Gain: %s\n"
333 " Current gain: %.5f\n"
334 " Voltage gain: %.5f\n"
335 " Current HPF: %s\n"
336 " Voltage HPF: %s\n"
337 " Pulse energy: %.2f Wh",
339 pga_gain_ == CS5460A_PGA_GAIN_50X ? LOG_STR_LITERAL("50x") : LOG_STR_LITERAL("10x"), current_gain_,
340 voltage_gain_, current_hpf_ ? LOG_STR_LITERAL("enabled") : LOG_STR_LITERAL("disabled"),
341 voltage_hpf_ ? LOG_STR_LITERAL("enabled") : LOG_STR_LITERAL("disabled"), pulse_energy_wh_);
342 LOG_SENSOR(" ", "Voltage", voltage_sensor_);
343 LOG_SENSOR(" ", "Current", current_sensor_);
344 LOG_SENSOR(" ", "Power", power_sensor_);
345}
346
347} // 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:195
void status_momentary_warning(uint32_t length=5000)
Set warning status flag and clear it after length ms.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void write_register_(enum CS5460ARegister addr, uint32_t value)
Definition cs5460a.cpp:11
sensor::Sensor * voltage_sensor_
Definition cs5460a.h:89
uint32_t read_register_(uint8_t addr)
Definition cs5460a.cpp:18
sensor::Sensor * power_sensor_
Definition cs5460a.h:90
sensor::Sensor * current_sensor_
Definition cs5460a.h:88
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
bool state
Definition fan.h:2
u_int8_t raw_voltage
ESPHOME_LOG_TAG(TAG, "cs5460a")
constexpr uint8_t COMPONENT_STATE_FAILED
Definition component.h:87
constexpr uint8_t COMPONENT_STATE_LOOP
Definition component.h:86
constexpr int32_t sign_extend(uint32_t value)
Sign-extend the low BITS bits of value, e.g. sign_extend<24>(0xFFFFFE) == -2. Higher bits are ignored...
Definition helpers.h:1012
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t