ltc681x/monitor.rs
1//! Generic client for LTX681X device family
2//!
3//! This module contains a generic client which supports communication with any LTC681X device.
4//!
5//! # Initialization
6//!
7//! The [client](LTC681X) is based on a SPI bus, which implements the [embedded-hal SPI Transfer trait](<https://docs.rs/embedded-hal/latest/embedded_hal/blocking/spi/trait.Transfer.html>)
8//! and contains the following two generic parameters:
9//! * T: Device specific types ([DeviceTypes] trait). See [LTC6813](crate::ltc6813::LTC6813), [LTC6812](crate::ltc6812::LTC6812), [LTC6811](crate::ltc6811::LTC6811) and [LTC6810](crate::ltc6810::LTC6810)
10//! * L: Number of devices in daisy chain
11//!
12//! ````
13//! use ltc681x::example::ExampleSPIDevice;
14//! use ltc681x::ltc6812::LTC6812;
15//! use ltc681x::ltc6813::LTC6813;
16//! use ltc681x::monitor::LTC681X;
17//!
18//! // Single LTC6813 device
19//! let spi_bus = ExampleSPIDevice::default();
20//! let client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(spi_bus);
21//!
22//! // Three LTC6812 devices in daisy chain
23//! let spi_bus = ExampleSPIDevice::default();
24//! let client: LTC681X<_, _, LTC6812, 3> = LTC681X::ltc6812(spi_bus);
25//! ````
26//!
27//! # Conversion
28//!
29//! The following section describes starting conversion and polling mechanisms.
30//!
31//! ## Cell conversion
32//!
33//! A cell conversion is started using the [LTC681XClient::start_conv_cells](LTC681XClient#tymethod.start_conv_cells) method.
34//! The method takes three arguments:
35//! * **mode**: ADC frequency and filter settings, s. [ADCMode]
36//! * **cells**: Group of cells to be converted, e.g. [LTC6813::CellSelection](crate::ltc6813::CellSelection)
37//! * **dcp**: Allow discharging during conversion?
38//!
39//! ````
40//!# use ltc681x::example::ExampleSPIDevice;
41//!# use ltc681x::ltc6813::{CellSelection, LTC6813};
42//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient};
43//!#
44//!# let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(ExampleSPIDevice::default());
45//!#
46//!#
47//! // Converting first cell group using normal ADC mode
48//! client.start_conv_cells(ADCMode::Normal, CellSelection::Group1, true);
49//!
50//! // Converting all cells using fast ADC mode
51//! client.start_conv_cells(ADCMode::Fast, CellSelection::All, true);
52//! ````
53//!
54//! ### Conversion time
55//!
56//! Execution time of cell conversions is deterministic. The expected timing is returned as [CommandTime].
57//!
58//! ````
59//!# use ltc681x::example::ExampleSPIDevice;
60//!# use ltc681x::ltc6813::{CellSelection, LTC6813};
61//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient};
62//!#
63//!# let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(ExampleSPIDevice::default());
64//!#
65//!#
66//! // Converting first cell group using normal ADC mode
67//! let timing = client.start_conv_cells(ADCMode::Normal, CellSelection::Group1, true).unwrap();
68//!
69//! // 407 us in 7kHz mode (CFGAR0=0)
70//! assert_eq!(407, timing.regular);
71//!
72//! // 523 us in 3kHz mode (CFGAR0=1)
73//! assert_eq!(523, timing.alternative);
74//! ````
75//!
76//! ## GPIO conversion
77//!
78//! A GPIO conversion is started using the [LTC681XClient::start_conv_gpio](LTC681XClient#tymethod.start_conv_gpio) method.
79//! The method takes three arguments:
80//! * **mode**: ADC frequency and filter settings, s. [ADCMode]
81//! * **pins**: Group of GPIO channels to be converted, e.g. [LTC6813::GPIOSelection](crate::ltc6813::GPIOSelection)
82//!
83//! ````
84//!# use ltc681x::example::ExampleSPIDevice;
85//!# use ltc681x::ltc6813::{GPIOSelection, LTC6813};
86//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient};
87//!#
88//!# let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(ExampleSPIDevice::default());
89//!#
90//! // Converting second GPIO group using normal ADC mode
91//! client.start_conv_gpio(ADCMode::Normal, GPIOSelection::Group2);
92//!
93//! // Converting all GPIOs using fast ADC mode
94//! client.start_conv_gpio(ADCMode::Fast, GPIOSelection::All);
95//! ````
96//!
97//! ### Conversion time
98//!
99//! Execution time of GPIO conversions is deterministic. The expected timing is returned as [CommandTime].
100//!
101//! ````
102//!# use ltc681x::example::ExampleSPIDevice;
103//!# use ltc681x::ltc6813::{GPIOSelection, LTC6813};
104//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient};
105//!#
106//!# let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(ExampleSPIDevice::default());
107//!#
108//! // Converting second GPIO group using normal ADC mode
109//! let timing = client.start_conv_gpio(ADCMode::Normal, GPIOSelection::Group2).unwrap();
110//!
111//! // 788 us in 7kHz mode (CFGAR0=0)
112//! assert_eq!(788, timing.regular);
113//!
114//! // 1000 us in 3kHz mode (CFGAR0=1)
115//! assert_eq!(1000, timing.alternative);
116//! ````
117//!
118//! ## Polling
119//!
120//! ADC status may be be polled using the [PollClient::adc_ready](PollClient#tymethod.adc_ready) method.
121//! The following poll methods are currently supported:
122//!
123//! ### SDO line polling
124//!
125//! After entering a conversion command, the SDO line is driven low when the device is busy performing
126//! conversions. SDO is pulled high when the device completes conversions.
127//!
128//! This involves controlling the CS pin, as CS needs to stay low until the ADC conversion is finished.
129//! [LatchingSpiDevice] is implementing this behaviour and is used internally.
130//!
131//! Please note that if this poll method is used, the SPI bus cannot be used by any other device
132//! until the conversion is complete.
133//!
134//! ````
135//!# use ltc681x::example::{ExampleCSPin, ExampleSPIBus, ExampleSPIDevice};
136//!# use ltc681x::ltc6813::{GPIOSelection, LTC6813};
137//!# use ltc681x::monitor::{ADCMode, LTC681X, PollClient};
138//!#
139//!# let spi_bus = ExampleSPIBus::default();
140//!# let cs_pin = ExampleCSPin{};
141//! let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::enable_sdo_polling(spi_bus, cs_pin);
142//!
143//! while !client.adc_ready().unwrap() {
144//! // ADC conversion is not finished yet
145//! }
146//! ````
147//!
148//! ## Reading registers
149//!
150//! The content of registers may be directly read. The client returns an array containing three u16,
151//! one value for each register slot.
152//!
153//! ````
154//!# use ltc681x::example::ExampleSPIDevice;
155//!# use ltc681x::ltc6813::{GPIOSelection, LTC6813, Register};
156//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient, PollClient};
157//!#
158//!# let spi_bus = ExampleSPIDevice::default();
159//! // Single LTC613 device
160//! let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(spi_bus);
161//!
162//! // Reading cell voltage register B (CVBR)
163//! let cell_voltages = client.read_register(Register::CellVoltageB).unwrap();
164//! // Voltage of cell 5 (CVBR2/CVBR3)
165//! assert_eq!(7538, cell_voltages[0][1]);
166//!
167//! // Reading auxiliary voltage register A (AVAR)
168//! let aux_voltages = client.read_register(Register::AuxiliaryA).unwrap();
169//! // Voltage of GPIO1 (AVAR0/AVAR1)
170//! assert_eq!(24979, aux_voltages[0][0]);
171//! ````
172//!
173//! ### Multiple devices in daisy chain
174//!
175//! The `read_register()` method returns one array for each device in daisy chain. So the first array index addresses the device index.
176//! The second index addresses the slot within the register (0, 1, 2).
177//!
178//! ````
179//!# use ltc681x::example::ExampleSPIDevice;
180//!# use ltc681x::ltc6813::{GPIOSelection, LTC6813, Register};
181//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient, PollClient};
182//!#
183//!# let spi_bus = ExampleSPIDevice::default();
184//! // Three LTC613 devices in daisy chain
185//! let mut client: LTC681X<_, _, LTC6813, 3> = LTC681X::ltc6813(spi_bus);
186//!
187//! // Reading cell voltage register A (CVAR)
188//! let cell_voltages = client.read_register(Register::CellVoltageA).unwrap();
189//! // Voltage of cell 1 of third device
190//! assert_eq!(24979, cell_voltages[2][0]);
191//!
192//! // Voltage of cell 3 of second device
193//! assert_eq!(8878, cell_voltages[1][2]);
194//! ````
195//!
196//! # Mapping voltages
197//!
198//! Instead of manually reading voltage registers, the client offers a convenient method for mapping
199//! voltages to cell or GPIO groups.
200//!
201//! ````
202//!# use ltc681x::example::ExampleSPIDevice;
203//!# use ltc681x::ltc6813::{CellSelection, Channel, GPIOSelection, LTC6813, Register};
204//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient, PollClient};
205//!#
206//!# let spi_bus = ExampleSPIDevice::default();
207//!#
208//! // LTC6813 device
209//! let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(spi_bus);
210//!
211//! // Returns the value of cell group A. In case of LTC613: cell 1, 7 and 13
212//! let voltages = client.read_voltages(CellSelection::Group1).unwrap();
213//!
214//! assert_eq!(Channel::Cell1, voltages[0][0].channel);
215//! assert_eq!(24979, voltages[0][0].voltage);
216//!
217//! assert_eq!(Channel::Cell7, voltages[0][1].channel);
218//! assert_eq!(25441, voltages[0][1].voltage);
219//!
220//! assert_eq!(Channel::Cell13, voltages[0][2].channel);
221//! assert_eq!(25822, voltages[0][2].voltage);
222//!
223//! // Returns the value of GPIO group 2. In case of LTC613: GPIO2 and GPIO7
224//! let voltages = client.read_voltages(GPIOSelection::Group2).unwrap();
225//!
226//! assert_eq!(Channel::GPIO2, voltages[0][0].channel);
227//! assert_eq!(7867, voltages[0][0].voltage);
228//!
229//! assert_eq!(Channel::GPIO7, voltages[0][1].channel);
230//! assert_eq!(7869, voltages[0][1].voltage);
231//! ````
232//!
233//! # Self-tests
234//!
235//! The LTC681X family supports a number of verification and fault-tests.
236//!
237//! ## Overlap measurement (ADOL command)
238//!
239//! Starting the ADC overlapping measurement and reading the results:
240//! ````
241//!# use ltc681x::example::ExampleSPIDevice;
242//!# use ltc681x::ltc6813::{CellSelection, LTC6813};
243//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient};
244//!#
245//!# let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(ExampleSPIDevice::default());
246//!#
247//! client.start_overlap_measurement(ADCMode::Normal, true);
248//! // [...] waiting until conversion finished
249//! let data = client.read_overlap_result().unwrap();
250//!
251//! // Voltage of cell 7 measured by ADC2
252//! assert_eq!(25441, data[0][0]);
253//! // Voltage of cell 7 measured by ADC1
254//! assert_eq!(7869, data[0][1]);
255//! // Voltage of cell 13 measured by ADC3
256//! assert_eq!(25822, data[0][2]);
257//! // Voltage of cell 13 measured by ADC2
258//! assert_eq!(8591, data[0][3]);
259//! ````
260//!
261//! ## Internal device parameters (ADSTAT command)
262//!
263//! Measuring internal device parameters and reading the results.
264//!
265//! The expected execution time is returned as [CommandTime], see [command timing of cell conversion](#conversion-time) as example.
266//! ````
267//!# use ltc681x::example::ExampleSPIDevice;
268//!# use ltc681x::ltc6813::{CellSelection, LTC6813};
269//!# use ltc681x::monitor::{ADCMode, LTC681X, LTC681XClient, StatusGroup};
270//!#
271//!# let mut client: LTC681X<_, _, LTC6813, 1> = LTC681X::ltc6813(ExampleSPIDevice::default());
272//!#
273//!#
274//! client.measure_internal_parameters(ADCMode::Normal, StatusGroup::All);
275//! // [...] waiting until conversion finished
276//! let data = client.read_internal_device_parameters().unwrap();
277//!
278//! // Sum of all voltages in uV => 75.318 V
279//! assert_eq!(75_318_000, data[0].total_voltage);
280//! // Die temperature in °C
281//! assert_eq!("56.31578", data[0].temperature.to_string());
282//! // Analog power supply voltage in uV => 3.2 V
283//! assert_eq!(3_200_000, data[0].analog_power);
284//! // Digital power supply voltage in uV => 5.12 V
285//! assert_eq!(5_120_000, data[0].digital_power);
286//! ````
287use crate::config::Configuration;
288use crate::monitor::Error::BusError;
289use crate::pec15::PEC15;
290use crate::spi::LatchingSpiDevice;
291use core::fmt::{Debug, Display, Formatter};
292use core::marker::PhantomData;
293use core::slice::Iter;
294use embedded_hal::digital::OutputPin;
295use embedded_hal::spi::{Operation, SpiBus, SpiDevice};
296use fixed::types::I16F16;
297use heapless::Vec;
298
299/// Poll Strategy
300pub trait PollMethod<B: SpiDevice> {
301 /// Gets called by synchronous commands, which not require any waiting/polling (e.g. writing registers)
302 fn end_sync_command(&self, bus: &mut B) -> Result<(), B::Error>;
303}
304
305/// Leaves CS Low and waits until SDO goes high
306pub struct SDOLinePolling {}
307
308impl<B: SpiBus, CS: OutputPin> PollMethod<LatchingSpiDevice<B, CS>> for SDOLinePolling {
309 fn end_sync_command(&self, bus: &mut LatchingSpiDevice<B, CS>) -> Result<(), crate::spi::Error<B, CS>> {
310 bus.release_cs()
311 }
312}
313
314/// No ADC polling is used
315pub struct NoPolling {}
316
317impl<B: SpiDevice> PollMethod<B> for NoPolling {
318 fn end_sync_command(&self, _bus: &mut B) -> Result<(), B::Error> {
319 Ok(())
320 }
321}
322
323/// ADC frequency and filtering settings
324#[derive(Copy, Clone, Eq, PartialEq, Debug)]
325pub enum ADCMode {
326 /// 27kHz or 14kHz in case of CFGAR0=1 configuration
327 Fast = 0x1,
328 /// 7kHz or 3kHz in case of CFGAR0=1 configuration
329 Normal = 0x2,
330 /// 26Hz or 2kHz in case of CFGAR0=1 configuration
331 Filtered = 0x3,
332 /// 422Hz or 1kHz in case of CFGAR0=1 configuration
333 Other = 0x0,
334}
335
336/// Selection of status group
337#[derive(Copy, Clone, Eq, PartialEq, Debug)]
338pub enum StatusGroup {
339 /// Includes SC, ITMP, VA, VD
340 All = 0x0,
341 /// Measures the total voltage of all cells (SC)
342 CellSum = 0x1,
343 /// Measures the internal die temperature (ITMP)
344 Temperature = 0x2,
345 /// Measure the internal analog voltage supply (VA)
346 AnalogVoltage = 0x3,
347 /// Measures the internal digital voltage supply (VD)
348 DigitalVoltage = 0x4,
349}
350
351impl ToCommandBitmap for StatusGroup {
352 fn to_bitmap(&self) -> u16 {
353 *self as u16
354 }
355}
356
357impl ToCommandTiming for StatusGroup {
358 fn to_conv_command_timing(&self, mode: ADCMode) -> CommandTime {
359 match self {
360 StatusGroup::All => match mode {
361 ADCMode::Fast => CommandTime::new(742, 858),
362 ADCMode::Normal => CommandTime::new(1_600, 2_000),
363 ADCMode::Filtered => CommandTime::new(134_000, 3_000),
364 ADCMode::Other => CommandTime::new(8_500, 4_800),
365 },
366 StatusGroup::CellSum
367 | StatusGroup::Temperature
368 | StatusGroup::AnalogVoltage
369 | StatusGroup::DigitalVoltage => match mode {
370 ADCMode::Fast => CommandTime::new(200, 229),
371 ADCMode::Normal => CommandTime::new(403, 520),
372 ADCMode::Filtered => CommandTime::new(34_000, 753),
373 ADCMode::Other => CommandTime::new(2_100, 1_200),
374 },
375 }
376 }
377}
378
379/// Location of a conversion voltage
380pub struct RegisterAddress<T: DeviceTypes> {
381 /// Either a cell or GPIO
382 pub(crate) channel: T::Channel,
383
384 /// Register which stores the voltage of the channel
385 pub(crate) register: T::Register,
386
387 /// Index within register. Each register has three slots
388 pub(crate) slot: usize,
389}
390
391/// Maps register locations to cell or GPIO groups
392pub trait RegisterLocator<T: DeviceTypes + 'static> {
393 /// Returns the register locations of the given cell or GPIO group
394 fn get_locations(&self) -> Iter<'static, RegisterAddress<T>>;
395}
396
397/// Conversion result of a single channel
398#[derive(PartialEq, Debug)]
399pub struct Voltage<T: DeviceTypes> {
400 /// Channel of the voltage
401 pub channel: T::Channel,
402
403 /// Raw register value
404 /// Real voltage: voltage * 100 uV
405 pub voltage: u16,
406}
407
408impl<T: DeviceTypes> Copy for Voltage<T> {}
409
410impl<T: DeviceTypes> Clone for Voltage<T> {
411 fn clone(&self) -> Self {
412 *self
413 }
414}
415
416/// Error enum of LTC681X
417#[derive(PartialEq)]
418pub enum Error<B: SpiDevice<u8>> {
419 /// SPI transfer error
420 BusError(B::Error),
421
422 /// PEC checksum of returned data was invalid
423 ChecksumMismatch,
424
425 /// Writing to the given register is not supported
426 ReadOnlyRegister,
427}
428
429/// Trait for casting command options to command bitmaps
430pub trait ToCommandBitmap {
431 /// Returns the command bitmap for the given argument.
432 fn to_bitmap(&self) -> u16;
433}
434
435/// Trait for determining the estimated execution time
436pub trait ToCommandTiming {
437 /// Returns the expected execution time of ADCV command based on the ADC mode
438 fn to_conv_command_timing(&self, mode: ADCMode) -> CommandTime;
439}
440
441/// Error in case writing to this register ist not supported and therefore no command exists.
442#[derive(Debug)]
443pub struct NoWriteCommandError {}
444
445impl Display for NoWriteCommandError {
446 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
447 write!(f, "No write command for read-only register")
448 }
449}
450
451/// Trait for casting to constant (precomputed) commands
452pub trait ToFullCommand {
453 /// Returns the full register read command + PEC15
454 fn to_read_command(&self) -> [u8; 4];
455
456 /// Returns the full register write command + PEC15
457 /// Returns error in case writing to this register is not supported
458 fn to_write_command(&self) -> Result<[u8; 4], NoWriteCommandError>;
459}
460
461/// Converts channels (cells or GPIOs) to indexes
462pub trait ChannelIndex {
463 /// Returns the cell index if a cell channel, otherwise None.
464 fn to_cell_index(&self) -> Option<usize>;
465
466 /// Returns the GPIO index if a GPIO channel, otherwise None.
467 fn to_gpio_index(&self) -> Option<usize>;
468}
469
470/// Converts registers to indexes
471pub trait GroupedRegisterIndex {
472 /// Returns a **unique** index within the register group (e.g. auxiliary registers)
473 fn to_index(&self) -> usize;
474}
475
476/// ADC channel type
477pub enum ChannelType {
478 Cell,
479 GPIO,
480 Reference,
481}
482
483/// Expected execution time of the issued command
484#[derive(Copy, Clone, Debug)]
485pub struct CommandTime {
486 /// Regular (CFGAR0=0) execution time in microseconds
487 pub regular: u32,
488
489 /// Alternative (CFGAR0=1) execution time in microseconds
490 pub alternative: u32,
491}
492
493impl CommandTime {
494 pub fn new(regular: u32, alternative: u32) -> Self {
495 Self { regular, alternative }
496 }
497}
498
499/// Collection of internal device parameters, measured by ADSTAT command
500#[derive(Debug)]
501pub struct InternalDeviceParameters {
502 /// Sum of all cells in uV
503 pub total_voltage: u32,
504
505 /// Voltage of analog power supply in uV
506 pub analog_power: u32,
507
508 /// Voltage of digital power supply in uV
509 pub digital_power: u32,
510
511 /// Die temperature in °C as fixed-point number
512 /// In case register value overflows 16-bit integer, this value is set to I16F16::MAX (32767.99998)
513 pub temperature: I16F16,
514}
515
516/// Device specific types
517pub trait DeviceTypes: Send + Sync + Sized + 'static {
518 /// Argument for the identification of cell groups, which depends on the exact device type.
519 type CellSelection: ToCommandBitmap + ToCommandTiming + RegisterLocator<Self> + Copy + Clone + Send + Sync;
520
521 /// Argument for the identification of GPIO groups, which depends on the exact device type.
522 type GPIOSelection: ToCommandBitmap + ToCommandTiming + RegisterLocator<Self> + Copy + Clone + Send + Sync;
523
524 /// Argument for register selection. The available registers depend on the device.
525 type Register: ToFullCommand + GroupedRegisterIndex + Copy + Clone + Send + Sync;
526
527 /// Available cells and GPIOs
528 type Channel: ChannelIndex + Into<ChannelType> + Copy + Clone + Send + Sync;
529
530 /// Number of battery cells supported by the device
531 const CELL_COUNT: usize;
532
533 /// Number of GPIO channels
534 const GPIO_COUNT: usize;
535
536 /// Defines the first register storing the results of overlap measurement.
537 /// None in case overlap test is not supported.
538 const OVERLAP_TEST_REG_1: Option<Self::Register>;
539
540 /// Defines the second register storing the results of overlap measurement.
541 /// None in case just one cell is ued for overlap test or if test is no supported at all.
542 const OVERLAP_TEST_REG_2: Option<Self::Register>;
543
544 /// Status group A register
545 const REG_STATUS_A: Self::Register;
546
547 /// Status group b register
548 const REG_STATUS_B: Self::Register;
549
550 /// Configuration register A
551 const REG_CONF_A: Self::Register;
552
553 /// Configuration register B, None in case device type has no second configuration register
554 const REG_CONF_B: Option<Self::Register>;
555
556 /// Conversion factor for calculating the total voltage based on status register value.
557 /// S. datasheet SC -> Sum of All Cells Measurement (page. 68 of LTC6813 datasheet)
558 const TOTAL_VOLTAGE_FACTOR: u32;
559
560 /// Gain for calculating the internal die temperature in uV.
561 /// S. datasheet ITMP -> Internal Die Temperature calculation (page. 68 of LTC6813 datasheet)
562 const INTERNAL_TEMP_GAIN: i32;
563
564 /// Offset for calculating the internal die temperature in °C.
565 /// S. datasheet ITMP -> Internal Die Temperature calculation (page. 68 of LTC6813 datasheet)
566 const INTERNAL_TEMP_OFFSET: i16;
567}
568
569/// Public LTC681X client interface
570///
571/// L: Number of LTC681X devices in daisy chain
572pub trait LTC681XClient<T: DeviceTypes, const L: usize> {
573 type Error;
574
575 /// Starts ADC conversion of cell voltages
576 ///
577 /// # Arguments
578 ///
579 /// * `mode`: ADC mode
580 /// * `cells`: Measures the given cell group
581 /// * `dcp`: True if discharge is permitted during conversion
582 fn start_conv_cells(
583 &mut self,
584 mode: ADCMode,
585 cells: T::CellSelection,
586 dcp: bool,
587 ) -> Result<CommandTime, Self::Error>;
588
589 /// Starts GPIOs ADC conversion
590 ///
591 /// # Arguments
592 ///
593 /// * `mode`: ADC mode
594 /// * `channels`: Measures t:he given GPIO group
595 fn start_conv_gpio(&mut self, mode: ADCMode, pins: T::GPIOSelection) -> Result<CommandTime, Self::Error>;
596
597 /// Start the Overlap Measurements (ADOL command)
598 /// Note: This command is not available on LTC6810, as this device only includes one ADC
599 ///
600 /// # Arguments
601 ///
602 /// * `mode`: ADC mode
603 /// * `dcp`: True if discharge is permitted during conversion
604 fn start_overlap_measurement(&mut self, mode: ADCMode, dcp: bool) -> Result<(), Self::Error>;
605
606 /// Starts measuring internal device parameters (ADSTAT command)
607 ///
608 /// # Arguments
609 ///
610 /// * `mode`: ADC mode
611 /// * `group`: Selection of status parameter to measure
612 fn measure_internal_parameters(&mut self, mode: ADCMode, group: StatusGroup) -> Result<CommandTime, Self::Error>;
613
614 /// Reads the values of the given register
615 /// Returns one array for each device in daisy chain
616 fn read_register(&mut self, register: T::Register) -> Result<[[u16; 3]; L], Self::Error>;
617
618 /// Writes the values of the given register
619 /// One 3-bytes array per device in daisy chain
620 fn write_register(&mut self, register: T::Register, data: [[u8; 6]; L]) -> Result<(), Self::Error>;
621
622 /// Writes the configuration, one array item per device in daisy chain
623 fn write_configuration(&mut self, config: [Configuration; L]) -> Result<(), Self::Error>;
624
625 /// Reads and returns the conversion result (voltages) of Cell or GPIO group
626 /// Returns one vector for each device in daisy chain
627 ///
628 /// Vector needs to have a fixed capacity until feature [generic_const_exprs](<https://github.com/rust-lang/rust/issues/76560>) is stable
629 fn read_voltages<R: RegisterLocator<T> + 'static>(
630 &mut self,
631 locator: R,
632 ) -> Result<Vec<Vec<Voltage<T>, 18>, L>, Self::Error>
633 where
634 T: 'static;
635
636 /// Reads and returns the results of the overlap measurement
637 ///
638 /// Index 0: Result of ADC A of first cell*
639 /// Index 1: Result of ADC B of first cell*
640 /// Index 2: Result of ADC A of second cell*
641 /// Index 3: Result of ADC B of second cell*
642 ///
643 /// * Number of cells depends on the device type, otherwise 0 value is used
644 fn read_overlap_result(&mut self) -> Result<[[u16; 4]; L], Self::Error>;
645
646 /// Reads internal device parameters measured by ATOL command
647 /// Returns one array item for each device in daisy chain
648 fn read_internal_device_parameters(&mut self) -> Result<Vec<InternalDeviceParameters, L>, Self::Error>;
649}
650
651/// Public LTC681X interface for polling ADC status
652pub trait PollClient {
653 type Error;
654
655 /// Returns true if the ADC is not busy
656 fn adc_ready(&mut self) -> Result<bool, Self::Error>;
657}
658
659/// Client for LTC681X IC
660pub struct LTC681X<B, P, T, const L: usize>
661where
662 B: SpiDevice<u8>,
663 P: PollMethod<B>,
664 T: DeviceTypes,
665{
666 /// SPI bus
667 bus: B,
668
669 /// Poll method used for type state
670 poll_method: P,
671
672 device_types: PhantomData<T>,
673}
674
675impl<B, T, const L: usize> LTC681X<B, NoPolling, T, L>
676where
677 B: SpiDevice<u8>,
678 T: DeviceTypes,
679{
680 pub(crate) fn new(spi_device: B) -> Self {
681 LTC681X {
682 bus: spi_device,
683 poll_method: NoPolling {},
684 device_types: PhantomData,
685 }
686 }
687}
688
689impl<B, P, T, const L: usize> LTC681XClient<T, L> for LTC681X<B, P, T, L>
690where
691 B: SpiDevice<u8>,
692 P: PollMethod<B>,
693 T: DeviceTypes,
694{
695 type Error = Error<B>;
696
697 /// See [LTC681XClient::start_conv_cells](LTC681XClient#tymethod.start_conv_cells)
698 fn start_conv_cells(&mut self, mode: ADCMode, cells: T::CellSelection, dcp: bool) -> Result<CommandTime, Error<B>> {
699 let mut command: u16 = 0b0000_0010_0110_0000;
700
701 command |= (mode as u16) << 7;
702 command |= cells.to_bitmap();
703
704 if dcp {
705 command |= 0b0001_0000;
706 }
707
708 self.send_command(command).map_err(Error::BusError)?;
709
710 Ok(cells.to_conv_command_timing(mode))
711 }
712
713 /// See [LTC681XClient::start_conv_gpio](LTC681XClient#tymethod.start_conv_gpio)
714 fn start_conv_gpio(&mut self, mode: ADCMode, channels: T::GPIOSelection) -> Result<CommandTime, Error<B>> {
715 let mut command: u16 = 0b0000_0100_0110_0000;
716
717 command |= (mode as u16) << 7;
718 command |= channels.to_bitmap();
719
720 self.send_command(command).map_err(Error::BusError)?;
721
722 Ok(channels.to_conv_command_timing(mode))
723 }
724
725 /// See [LTC681XClient::start_conv_gpio](LTC681XClient#tymethod.start_overlap_measurement)
726 fn start_overlap_measurement(&mut self, mode: ADCMode, dcp: bool) -> Result<(), Error<B>> {
727 let mut command: u16 = 0b0000_0010_0000_0001;
728
729 command |= (mode as u16) << 7;
730
731 if dcp {
732 command |= 0b0001_0000;
733 }
734
735 self.send_command(command).map_err(BusError)
736 }
737
738 /// See [LTC681XClient::start_conv_gpio](LTC681XClient#tymethod.measure_internal_parameters)
739 fn measure_internal_parameters(&mut self, mode: ADCMode, group: StatusGroup) -> Result<CommandTime, Error<B>> {
740 let mut command: u16 = 0b0000_0100_0110_1000;
741
742 command |= (mode as u16) << 7;
743 command |= group.to_bitmap();
744
745 self.send_command(command).map_err(Error::BusError)?;
746
747 Ok(group.to_conv_command_timing(mode))
748 }
749
750 /// See [LTC681XClient::read_cell_voltages](LTC681XClient#tymethod.read_register)
751 fn read_register(&mut self, register: T::Register) -> Result<[[u16; 3]; L], Error<B>> {
752 self.read_daisy_chain(register.to_read_command())
753 }
754
755 /// See [LTC681XClient::read_cell_voltages](LTC681XClient#tymethod.write_register)
756 fn write_register(&mut self, register: T::Register, data: [[u8; 6]; L]) -> Result<(), Error<B>> {
757 let pre_command = match register.to_write_command() {
758 Ok(command) => command,
759 Err(_) => return Err(Error::ReadOnlyRegister),
760 };
761
762 // Buffer for first operation
763 let mut first_operation = [0xff_u8; 12];
764
765 // Buffer for operations to daisy-chained devices
766 // As generic_const_exprs feature is not yet supported, the last item is not used (wasted)
767 let mut shifted_data = [[0x0_u8; 8]; L];
768
769 // The first operation includes the pre-command + data bytes of master
770 first_operation[..4].copy_from_slice(&pre_command);
771 first_operation[4..10].copy_from_slice(&data[0]);
772 self.add_pec_checksum(&mut first_operation[4..]);
773
774 let mut operations: Vec<Operation<u8>, L> = Vec::new();
775 let _ = operations.push(Operation::Write(&first_operation));
776
777 // Adding data of daisy-chained devices
778 for (i, item) in shifted_data[..L - 1].iter_mut().enumerate() {
779 item[..6].copy_from_slice(&data[i + 1]);
780 self.add_pec_checksum(item);
781 let _ = operations.push(Operation::Write(item));
782 }
783
784 self.bus.transaction(&mut operations).map_err(BusError)?;
785
786 self.poll_method.end_sync_command(&mut self.bus).map_err(BusError)?;
787 Ok(())
788 }
789
790 /// See [LTC681XClient::read_cell_voltages](LTC681XClient#tymethod.write_configuration)
791 fn write_configuration(&mut self, config: [Configuration; L]) -> Result<(), Self::Error> {
792 let mut register_a = [[0x0u8; 6]; L];
793 let mut register_b = [[0x0u8; 6]; L];
794
795 for item in config.iter().enumerate() {
796 register_a[item.0] = item.1.register_a;
797 register_b[item.0] = item.1.register_b;
798 }
799
800 self.write_register(T::REG_CONF_A, register_a)?;
801
802 if let Some(register) = T::REG_CONF_B {
803 self.write_register(register, register_b)?;
804 }
805
806 Ok(())
807 }
808
809 /// See [LTC681XClient::read_cell_voltages](LTC681XClient#tymethod.read_voltages)
810 fn read_voltages<R: RegisterLocator<T> + 'static>(
811 &mut self,
812 locator: R,
813 ) -> Result<Vec<Vec<Voltage<T>, 18>, L>, Self::Error>
814 where
815 T: 'static,
816 {
817 let mut result: Vec<Vec<Voltage<T>, 18>, L> = Vec::new();
818
819 // One slot for each register
820 // 1. index: register index
821 // 2. index: device index
822 // 3. index: Slot within register
823 let mut register_data = [[[0u16; 3]; L]; 6];
824
825 // Array for flagging loaded registers, 0 = not loaded, 1 = loaded
826 let mut loaded_registers = [0; 6];
827
828 // Map register data
829 for device_index in 0..L {
830 let _ = result.push(Vec::new());
831
832 for address in locator.get_locations() {
833 let register_index = address.register.to_index();
834
835 // Load register if not done yet
836 if loaded_registers[register_index] == 0 {
837 register_data[register_index] = self.read_register(address.register)?;
838 loaded_registers[register_index] = 1;
839 }
840
841 let voltage = Voltage {
842 channel: address.channel,
843 voltage: register_data[register_index][device_index][address.slot],
844 };
845
846 let _ = result[device_index].push(voltage);
847 }
848 }
849
850 Ok(result)
851 }
852
853 /// See [LTC681XClient::read_cell_voltages](LTC681XClient#tymethod.read_overlap_result)
854 fn read_overlap_result(&mut self) -> Result<[[u16; 4]; L], Self::Error> {
855 let mut data = [[0; 4]; L];
856
857 let register_c = if let Some(register) = T::OVERLAP_TEST_REG_1 {
858 self.read_register(register)?
859 } else {
860 [[0; 3]; L]
861 };
862
863 let register_e = if let Some(register) = T::OVERLAP_TEST_REG_2 {
864 self.read_register(register)?
865 } else {
866 [[0; 3]; L]
867 };
868
869 for device_index in 0..L {
870 data[device_index][0] = register_c[device_index][0];
871 data[device_index][1] = register_c[device_index][1];
872 data[device_index][2] = register_e[device_index][0];
873 data[device_index][3] = register_e[device_index][1];
874 }
875
876 Ok(data)
877 }
878
879 /// See [LTC681XClient::read_cell_voltages](LTC681XClient#tymethod.read_internal_device_parameters)
880 fn read_internal_device_parameters(&mut self) -> Result<Vec<InternalDeviceParameters, L>, Self::Error> {
881 let status_a = self.read_register(T::REG_STATUS_A)?;
882 let status_b = self.read_register(T::REG_STATUS_B)?;
883
884 let mut parameters = Vec::new();
885
886 for device_index in 0..L {
887 let temp_fixed = self.calc_temperature(status_a[device_index][1]);
888
889 let _ = parameters.push(InternalDeviceParameters {
890 total_voltage: status_a[device_index][0] as u32 * T::TOTAL_VOLTAGE_FACTOR * 100,
891 analog_power: status_a[device_index][2] as u32 * 100,
892 digital_power: status_b[device_index][0] as u32 * 100,
893 temperature: temp_fixed,
894 });
895 }
896
897 Ok(parameters)
898 }
899}
900
901impl<B, P, T, const L: usize> LTC681X<B, P, T, L>
902where
903 B: SpiDevice<u8>,
904 P: PollMethod<B>,
905 T: DeviceTypes,
906{
907 /// Sends the given command. Calculates and attaches the PEC checksum
908 fn send_command(&mut self, command: u16) -> Result<(), B::Error> {
909 let mut data = [(command >> 8) as u8, command as u8, 0x0, 0x0];
910 self.add_pec_checksum(&mut data);
911
912 self.bus.write(&data)?;
913 Ok(())
914 }
915
916 /// Calculates and attaches the PEC15 checksum
917 fn add_pec_checksum(&self, data: &mut [u8]) {
918 let pec = PEC15::calc(&data[0..data.len() - 2]);
919
920 data[data.len() - 2] = pec[0];
921 data[data.len() - 1] = pec[1];
922 }
923
924 /// Send the given read command and returns the response of all devices in daisy chain
925 fn read_daisy_chain(&mut self, command: [u8; 4]) -> Result<[[u16; 3]; L], Error<B>> {
926 let data = self.read_trans_daisy_chain(command)?;
927
928 let mut result = [[0, 0, 0]; L];
929 for (i, item) in result.iter_mut().take(L).enumerate() {
930 let response = data[i];
931
932 let pec = PEC15::calc(&response[0..6]);
933 if pec[0] != response[6] || pec[1] != response[7] {
934 return Err(Error::ChecksumMismatch);
935 }
936
937 item[0] = response[0] as u16;
938 item[0] |= (response[1] as u16) << 8;
939
940 item[1] = response[2] as u16;
941 item[1] |= (response[3] as u16) << 8;
942
943 item[2] = response[4] as u16;
944 item[2] |= (response[5] as u16) << 8;
945 }
946
947 self.poll_method.end_sync_command(&mut self.bus).map_err(Error::BusError)?;
948 Ok(result)
949 }
950
951 /// Creates SPI transactions for reading from daisy chain and returns the raw data
952 fn read_trans_daisy_chain(&mut self, command: [u8; 4]) -> Result<[[u8; 8]; L], Error<B>> {
953 let command_write = [
954 command[0], command[1], command[2], command[3], 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
955 ];
956 let mut command_read = [0xff_u8; 12];
957
958 // Read buffer for all daisy-chained devices
959 // As generic_const_exprs feature is not yet supported the first buffer item will not be used.
960 // This wastes eight bytes of stack memory
961 let mut buffers = [[0xff_u8; 8]; L];
962
963 // Result from connected device will be directly received on command transaction
964 let mut operations: Vec<Operation<u8>, L> = Vec::new();
965 let _ = operations.push(Operation::Transfer(&mut command_read, &command_write));
966
967 // Read operations for all dasi-chained devices
968 for buffer_item in &mut buffers[1..].iter_mut() {
969 operations.push(Operation::Read(buffer_item)).unwrap()
970 }
971
972 self.bus.transaction(&mut operations).map_err(Error::BusError)?;
973 drop(operations);
974 buffers[0].copy_from_slice(&command_read[4..]);
975
976 Ok(buffers)
977 }
978
979 /// Calculates the temperature in °C based on raw register value
980 fn calc_temperature(&self, value: u16) -> I16F16 {
981 if value >= 53744 {
982 return I16F16::MAX;
983 }
984
985 // Normalize gain from mV to reduce need for FP precision.
986 let gain = I16F16::from_num(T::INTERNAL_TEMP_GAIN) / 100;
987 let offset = I16F16::from_num(T::INTERNAL_TEMP_OFFSET);
988
989 // Die temp = ITMP * 1/gain - offset.
990 I16F16::from_num(value) / gain - offset
991 }
992}
993
994impl<S, CS, T, const L: usize> LTC681X<LatchingSpiDevice<S, CS>, SDOLinePolling, T, L>
995where
996 S: SpiBus<u8>,
997 CS: OutputPin,
998 T: DeviceTypes,
999{
1000 /// Enables SDO ADC polling
1001 ///
1002 /// After entering a conversion command, the SDO line is driven low when the device is busy
1003 /// performing conversions. SDO is pulled high when the device completes conversions.
1004 pub fn enable_sdo_polling(bus: S, cs: CS) -> LTC681X<LatchingSpiDevice<S, CS>, SDOLinePolling, T, L> {
1005 LTC681X {
1006 bus: LatchingSpiDevice::new(bus, cs),
1007 poll_method: SDOLinePolling {},
1008 device_types: PhantomData,
1009 }
1010 }
1011}
1012
1013impl<B, CS, T, const L: usize> PollClient for LTC681X<LatchingSpiDevice<B, CS>, SDOLinePolling, T, L>
1014where
1015 B: SpiBus,
1016 CS: OutputPin,
1017 T: DeviceTypes,
1018{
1019 type Error = crate::spi::Error<B, CS>;
1020
1021 /// Returns false if the ADC is busy
1022 /// If ADC is ready, CS line is pulled high
1023 fn adc_ready(&mut self) -> Result<bool, Self::Error> {
1024 let mut buffer = [0x0];
1025 self.bus.read(&mut buffer)?;
1026
1027 if buffer[0] == 0xff {
1028 self.bus.release_cs()?;
1029 return Ok(true);
1030 }
1031
1032 Ok(false)
1033 }
1034}
1035
1036impl<B: SpiDevice<u8>> Debug for Error<B> {
1037 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
1038 match self {
1039 Error::BusError(_) => f.debug_struct("BusError").finish(),
1040 Error::ChecksumMismatch => f.debug_struct("ChecksumMismatch").finish(),
1041 Error::ReadOnlyRegister => f.debug_struct("ReadOnlyRegister").finish(),
1042 }
1043 }
1044}