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