Skip to main content

Crate async_ltc681x

Crate async_ltc681x 

Source
Expand description

This is an no_std async device driver for the LTC681x Series of Battery AFE (Analog frontend)

For details check the README.md in the repo.

§Basic Structure

§How to use this crate

First you need to create the Driver Part via its new function. This method consumes a SPI Port implementing the SpiBus trait, the chip select Output Pin implementing the OutputPin trait and the MISO Pin as an additional Input Pin implementing the InputPin trait. For the SPI Port and the CS Pin its straight forward. Use device specific MCU HAL implementing the embedded-hal trait to create the SPI Port and the CS Output Pin.

The reuse of the MISO Pin as an Input Pin is more tricky as it collides with all HAL implementations that model hardware resource with Rusts ownership model. The MISO GPIO Pin is consumed by the SPI Port constructor and is not usable as a free standing Input Pin or vice versa. Before we talk about possible solutions and its safety, we need to understand why we need to reuse the MISO Pin as an Input Pin and why this hassle is worth it.

§Timing and Polling

The LTC681x series performs ADC conversions on command from the host. The maximum time this conversion takes to finish is specified in the datasheet. So it is possible to just hard-code the necessary delay in your program before you read back the results. This has two weaknesses:

  1. There are a lot of different converion modes for different channel configurations. While this is all specified it is ofc a source of error that a wrong timing is used and we try to read back results premature. For this approach you need a appropriate time source in the driver or let the caller deal with it.
  2. Performance wise it is not optimal to always wait the maximum specified time plus some overhead for your piece of mind. In a big High-Voltage batterie application, where many devices are chained together, acquiring all measurement results for all batteries while meeting possible timing requirements (these requirements are also typically FuSa related Safety Requirements) of the application regarding data acquisition rates might become a challenge. So reducing unnecessary waiting time might save you some trouble later!

To adress this the LTC681X series allows to poll the state of the conversion via the MISO line. Details about the specifics how this polling works are out of scope, but the Input State of the MISO line needs to be availble for that. To conclude this digression: This is the reason why we have to use this Pin a second time as an Input Pin for the driver.

§Circumvent the ownership model and safety

How do you circumvent the ownership model of your HAL? Most of HALs (limited research went into this statement) provide an unsafe way to clone a GPIO Pin. By cloning the MISO GPIO Pin before creating the SPI instance another Input Pin can be created.

Example from embassy-stm32

let p = embassy_stm32::init(config);
// SAFETY: Cloned Pin is used by ltc681x device driver
// we use the same Pin as MISO for the SPI also used by the device driver
// Author states the this is safe!
let miso_input_pin = InputPin::new(unsafe{p.PB14.clone_unchecked()});
let cs_pin = OutputPin::new(p.PB15); // Pin used as CS
// Omitted other parameters of SPI new
let spi = Spi::new(p.PB14);
let ltc681x_spi = Ltc681xSpi::new(spi, cs_pin, miso_input_pin);

// Now that we have finally created the driver part we can create the final device
let ltc681x = Ltc681x::new(ltc681x_spi);

// After some hassle to create this device driver it is very easy to use!
let cells = ltc681x.convert_cells(Adcv::default()).await?;

For details about command specifiers like Adcv or Adax see Builder pattern for commands.

Safety

Internally the device driver only uses the MISO line as an Input during conversion waiting. No data is read back during that period until the conversion is done. So it is safe to provide another way to use the same hardware resource as it will not used at the same time.

Remark regarding Timeout detection The driver does not implement a timeout detection. In case of serious hardware defects the async operations will never yield Ready. It is advised to supervise the timing behavior via software timeout detection in the application or via watchdog

§Modules

This crate is split in two parts:

  1. device Module exposes the high level chip functionality like convert_cells or convert_gpios
  2. driver Module implements the low level SPI protocol to communicate with the chip

Why? This allows for layered testing of all functionallity. The driver part is generic over the SpiBus and can be test by providing a SPI mock.

The device part is generic over the Ltc681xDriver Trait. This allows to implement a MockDriver Part for convenient manipulation of test data. Otherwise on every testing layer we would always reach down to SPI transactions. In a safety critical application this is a quite common strategy to allow testing on every level of the software to further increase the trust. A user of this crate can directly use the concrete implementations of Device and Driver in the form of Ltc681xSpi and Ltc681x. The associated traits can be used for host based testing with more convenient mock implementations.

§Builder pattern for Commands

Ltc681x uses a 11Bit Command Header as the two first bytes of every SPI sequence. Every command has a basic command bit sequence with additional optional configuration bits (see datasheet for details). Every command can be generated by default method. Configuration is set by chaining functions like MDOptions. Possible configurations are implemented as traits for the spefic command and are listed in the Trait implementation part of the specific command.

Example

// Adcv defaults to AdcChannelConfig::AllCells but can be reconfigured by set_channel_config
// Adcv implemented the trait HasCellChannelConfig
let cells =
    ltc681x.convert_cells(Adcv::default().set_channel_config(AdcChannelConfig::Cell1_7_13));
let gpio =
    ltc681x.convert_gpios(Adax::default().set_channel_config(GpioChannelConfig::Gpio1_6));

§Defining the number of devices

Ltc61x series can be used as a single device to monitor a single battery pack or it can be chained in daisy chain configuration to monitor several battery packs without need for a MCU in every battery pack. For typical application check the datasheet.

The Ltc681x structure has a const generic parameter named NUM_OF_DEVICES. This defines the expected number of devices connected to the daisy chain. If there a less devices physically connected to the SPI daisy chain then specified by the paramete this will result in methods returning an CRC error. If more are connected this will be not detected as any device that exceeds the given number will not be read from/written to.

Every interaction with one devices will always interact with all devices in the daisy chain. This is part of the daisy chain architecture where you “communicate through other device” Read/Write functions work with arrays generic over the NUM_OF_DEVICES generic parameter. The array index indicates to which physical device the payload is mapped.

Attention:

While reading the index 0 corresponds to the first device in the daisy chain (the one that is closest to the MCU) and last index to the last device. For writing it is the opposite way around and the first index is pushed to the last device As payload is “shiftet through the devices” this makes sense. Disclaimer: I havent tested this fully Might be useful to implement the logic in a way that the payload is reversed for writing so index 0 is always device 0

§Feature flags

async-ltc681x supports three features to define the used chip variant.

  • ltc6811 - Deactivates all functions available only for the LTC6812or LTC6813 variant
  • ltc6812 - Extends the crate by functions and registers available for LTC6812
  • ltc6813 - Extends the crate even further with the functions and registers available for LTC6813

Default feature is set to ltc6811 so the crate compiles without any feature flags. Default feature must be disabled if another ltc6812 or ltc6813 is enabled.

docs.rs are build with the ltc6813 feature flag active.

Modules§

device
High-level chip functions
driver
Basic SPI Driver implementation