#threshold #delay

nightly no-std bq769x0

BQ76920, BQ76930 and BQ76940 battery AFE no_std hal

2 releases

0.3.1 Jun 23, 2021
0.3.0 Jun 23, 2021

MIT license

904 lines


TI BQ76920, BQ76930, BQ76940 HAL

Example usage, create instance, choose IC variant and cell count. Variant is chosen at compile time, as hardware is generally not changed on the fly. Cell count can be configured at runtime.

let mut bq76920 = BQ769x0::<{bq769x0::BQ76920}>::new(0x08, 4).unwrap();

new() will return None if cell count is invalid. Valid configurations is:

  • BQ76920 - 3 to 5 cells
  • BQ76930 - 6 to 10 cells
  • BQ76940 - 9 to 15 cells

Due to the use of const generics this is a nightly only crate for now.

Configure thresholds and timeouts:

let bq76920_config = BQ769x0Config {
    shunt: MicroOhms(2000),
    scd_delay: SCDDelay::_400uS,
    scd_threshold: Amperes(100),
    ocd_delay: OCDDelay::_640ms,
    ocd_threshold: Amperes(50),
    uv_delay: UVDelay::_4s,
    uv_threshold: config::CELL_UV_THRESHOLD,
    ov_delay: OVDelay::_4s,
    ov_threshold: config::CELL_OV_THRESHOLD
let values = bq76920.init(i2c, &bq769x0_config).map_err(|e| Error::AfeError(e))?;

values will contain actual OCD & SCD range used as well as under voltage and over voltage thresholds as they depend on ADC calibration values stored in the device.

init() will return an error if:

  • requested under or overvoltage thresholds are unobtainable
  • requested short curcuit and overload current thresholds fall into different ranges (see datasheet, RSNS bit in PROTECT1 register)
  • I2C communication fails (no or bad connection, bad IC, bad CRC or verify mismatch)

Disable DSG and CHG fets (be carefull with CHG=1 && DSG=0 or CHG=0 and DSG=1 configurations):

bq76920.discharge(i2c, false)?;
bq76920.charge(i2c, false)?;

Enable ADC and Coulomd counter for voltage and current measurements:

bq76920.enable_adc(i2c, true)?;
bq76920.coulomb_counter_mode(i2c, bq769x0::CoulombCounterMode::Continuous)?;

Show voltage, current and calculate power as well (fixed point math only):

let i = bq76920.current(i2c);
let v = bq76920.voltage(i2c);
let p: Result<i32, bq769x0::Error> = i.and_then(|i| {
    v.map(|v| i.0 * (v.0 as i32) / 1000)
match p {
    Ok(p) => {
        let intpart = p / 1000;
        let fractpart = p - intpart * 1000;
        writeln!(rtt, "V: {}, I:{}, Power: {}.{}W", v.unwrap(), i.unwrap(), intpart, fractpart.abs()).ok();
    Err(e) => {
        writeln!(rtt, "Read error={:?}", e).ok();

Show cell voltages:

match bq76920.cell_voltages(i2c) {
    Ok(cells) => {
        for (i, cell) in cells.iter().enumerate() {
            writeln!(rtt, "Cell {}: {}", i + 1, cell).ok();
    Err(e) => {
        writeln!(rtt, "Read error={:?}", e).ok();

Show status and reset flags if needed:

let stat = bq76920.sys_stat(i2c);
writeln!(rtt, "{:?}", stat).ok();

let r = bq76920.sys_stat_reset(i2c, bq769x0::SysStat::SHORTCIRCUIT | bq769x0::SysStat::OVERCURRENT);
writeln!(rtt, "SCD|OCD clear: {:?}", r).ok();

Balancing is supported through enable_balancing() and balancing_state(), though is should be improved to not allow consecutive cells to balance.

Choose temperature source:

bq769x0.set_temperature_source(i2c, TemperatureSource::InternalDie);

Keep in mind that reading is not available right away, it will be scheculed internally and available after ?.

Read the temperature:

/// TODO: not finished, also mention and publish no hard float implementation of logarithm function.


~39K SLoC