1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use super::*;
/// Represents multiple ADE7912/ADE7913 3-Channel, Isolated, Sigma-Delta ADCs with SPI configured in
/// a polyphase metering system.
pub struct Ade791x<SPI, CS, const N: usize> {
spi: SPI,
adcs: [ade791x::Ade791x<SPI, CS>; N],
}
impl<SPI, CS, S, P, const N: usize> Ade791x<SPI, CS, N>
where
SPI: spi::Transfer<u8, Error = S>,
CS: OutputPin<Error = P>,
{
/// Creates a new [`Ade791x`] instance, given the SPI peripheral and an array of the CS output
/// pins and chips. The newly created instance must be initialized using [`Self::init()`].
/// # Arguments
/// * `spi` - The SPI interface implementing the [`spi::Transfer`] trait.
/// * `adcs` - The array of tuples containing the CS output pins (implementing the
/// [`OutputPin`]) trait and the chips as [`Chip`].
pub fn new(spi: SPI, adcs: [(CS, Chip); N]) -> Self {
Self {
spi,
adcs: adcs.map(|(cs, chip)| ade791x::Ade791x::new(cs, chip)),
}
}
/// Initializes the ADCs, applying the given configurations. After this method, the ADCs are
/// ready to use.
/// # Arguments
/// * `delay` - The delay source implementing the [`DelayMs`] trait.
/// * `config` - An array of [`Config`] structs containing the configurations for the ADCs.
/// * `calibration` - An array of [`Calibration`] structs containing the calibration values for
/// the ADCs.
/// * `emi_ctrl` - An array of [`EmiCtrl`] structs containing the EMI settings for the ADCs.
pub fn init(
&mut self,
delay: &mut dyn DelayMs<u32>,
config: [Config; N],
calibration: [Calibration; N],
emi_ctrl: [EmiCtrl; N],
) -> Result<(), Error<S, P>> {
for i in 0..N {
self.adcs[i].init(&mut self.spi, delay, config[i], calibration[i], emi_ctrl[i])?;
}
if N > 1 {
self.sync()?;
}
self.lock()?;
Ok(())
}
/// Performs a hardware reset of the ADCs. During a hardware reset, all the registers are set to
/// their default values and the dc-to-dc converters are shut down. After a hardware reset, the
/// ADCs need to be initialized again, using [`Self::init()`].
pub fn hard_reset(&mut self) -> Result<(), Error<S, P>> {
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_listen()?;
}
self.adcs[0].hard_reset(&mut self.spi)?;
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_end()?;
}
Ok(())
}
/// Performs a software reset of the ADCs. During a software reset, all the internal registers
/// are reset to their default values. The dc-to-dc converters continue to function. After a
/// software reset, the ADCs need to be initialized again, using [`Self::init()`].
pub fn soft_reset(&mut self) -> Result<(), Error<S, P>> {
self.unlock()?;
for adc in &mut self.adcs {
adc.soft_reset(&mut self.spi)?;
}
Ok(())
}
/// Powers-down the ADCs by turning off the dc-to-dc converters and shutting down the Σ-Δ
/// modulators. Although the ADE7912/ADE7913 configuration registers maintain their values, the
/// `iwv`, `v1wv`, and `v2wv` [`Measurement`] fields are in an undefined state.
pub fn powerdown(&mut self) -> Result<(), Error<S, P>> {
self.unlock()?;
for adc in &mut self.adcs {
adc.powerdown(&mut self.spi)?;
}
self.lock()?;
Ok(())
}
/// Wakes-up the ADCs by turning on the dc-to-dc converters and activating the Σ-Δ modulators.
pub fn wakeup(&mut self) -> Result<(), Error<S, P>> {
self.unlock()?;
for adc in &mut self.adcs {
adc.wakeup(&mut self.spi)?;
}
self.lock()?;
Ok(())
}
/// Adjusts the synchronization of the ADCs internal counters by following the procedure
/// described in the ADE7912/ADE7913 Datasheet. The method compares the value of the counter of
/// the reference ADC (i.e. the one that is generating the DREADY signal) with the values of the
/// counters of the other ADCs, adjusting the ADCs that are out of sync.
pub fn adjust_sync(&mut self) -> Result<[i16; N], Error<S, P>> {
self.unlock()?;
self.snap()?;
let ref_adc_index = self
.adcs
.iter()
.position(|adc| adc.is_dr_source())
.unwrap_or(0);
let cref = self.adcs[ref_adc_index].get_cnt_snapshot(&mut self.spi)?;
let mut drift = [0; N];
for (i, val) in drift.iter_mut().enumerate() {
if i == ref_adc_index {
continue;
}
*val = self.adcs[i].adjust_sync(&mut self.spi, cref)?;
}
self.lock()?;
Ok(drift)
}
/// Returns the latest available measurement from the ADCs as an array of [`RawMeasurement`]
/// structs. Call this method inside the ISR from the DREADY pin to get a new measurement as
/// soon as it's ready. This method does not convert the received data. To get converted
/// metrics, use [`Self::get_measurement()`] instead. This method does not perform CRC checks on
/// received data.
pub fn get_raw_measurement(&mut self) -> Result<[RawMeasurement; N], Error<S, P>> {
let mut raw_measurement = [RawMeasurement {
iwv: 0,
v1wv: 0,
v2wv: 0,
}; N];
for (i, val) in raw_measurement.iter_mut().enumerate() {
*val = self.adcs[i].get_raw_measurement(&mut self.spi)?;
}
Ok(raw_measurement)
}
/// Returns the latest available measurement from the ADCs as an array of [`Measurement`]
/// structs. Call this method inside the ISR from the DREADY pin to get a new measurement as
/// soon as it's ready. This method converts raw data to voltage and current measurements using
/// the provided calibration values. This method does not perform CRC checks on received data.
pub fn get_measurement(&mut self) -> Result<[Measurement; N], Error<S, P>> {
let mut measurement = [Measurement {
current: 0.0,
voltage: 0.0,
aux: MeasurementAux::Voltage(0.0),
}; N];
for (i, val) in measurement.iter_mut().enumerate() {
*val = self.adcs[i].get_measurement(&mut self.spi)?;
}
Ok(measurement)
}
/// Performs the synchronization procedure for the ADCs. After this procedure, the internal
/// counters of the ADCs are aligned. This method should be called only during initialization,
/// as it invalidates the readings for some ADC cycles. For aligning the counters during
/// operation, use [`Self::adjust_sync()`].
fn sync(&mut self) -> Result<(), Error<S, P>> {
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_listen()?;
}
self.adcs[0].sync(&mut self.spi)?;
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_end()?;
}
Ok(())
}
/// Performs the snap procedure for the ADCs. After this procedure, the values of the internal
/// internal counters of the ADCs is captured at the same time and stored in the CNT_SNAPSHOT
/// register, that can be read using [`Self::get_measurement()`].
fn snap(&mut self) -> Result<(), Error<S, P>> {
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_listen()?;
}
self.adcs[0].snap(&mut self.spi)?;
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_end()?;
}
Ok(())
}
/// Locks the internal register of the ADCs, meaning that they cannot be written.
fn lock(&mut self) -> Result<(), Error<S, P>> {
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_listen()?;
}
self.adcs[0].lock(&mut self.spi)?;
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_end()?;
}
Ok(())
}
/// Unlocks the internal register of the ADC, meaning that they can be written.
fn unlock(&mut self) -> Result<(), Error<S, P>> {
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_listen()?;
}
self.adcs[0].unlock(&mut self.spi)?;
for adc in &mut self.adcs[1.min(N)..] {
adc.broadcast_end()?;
}
Ok(())
}
}