max1720x/lib.rs
1//! An embedded-hal driver for the Maxim MAX17205 fuel gauge.
2//!
3//! Loosely based on
4//! https://github.com/tock/tock/blob/master/capsules/src/max17205.rs
5//! rewritten to use the embedded-hal I2C driver.
6//!
7//! <https://www.maximintegrated.com/en/products/power/battery-management/MAX17205.html>
8//!
9//! > The MAX1720x/MAX1721x are ultra-low power stand-alone fuel gauge ICs that
10//! > implement the Maxim ModelGauge⢠m5 algorithm without requiring host
11//! > interaction for configuration. This feature makes the MAX1720x/MAX1721x
12//! > excellent pack-side fuel gauges. The MAX17201/MAX17211 monitor a single
13//! > cell pack. The MAX17205/MAX17215 monitor and balance a 2S or 3S pack or
14//! > monitor a multiple-series cell pack.
15//!
16//! Usage
17//! -----
18//!
19//! use rppal::i2c::I2c;
20//! fn main() {
21//! let mut i2c = I2c::new().unwrap();
22//! let mut max17205 = MAX1720x::new(&mut i2c);
23//! let soc = max17205.state_of_charge(&mut i2c).unwrap();
24//! let status = max17205.status(&mut i2c).unwrap();
25//! let voltage = max17205.voltage(&mut i2c).unwrap();
26//! let current = max17205.current(&mut i2c).unwrap();
27//! println!("State of charge: {}%", soc);
28//! println!("Voltage: {}V", voltage);
29//! println!("Current: {}A", current);
30//! println!("Status: {:#?}", status);
31//! }
32
33#![no_std]
34
35use embedded_hal as hal;
36use hal::blocking::i2c::{Read, Write, WriteRead};
37use core::marker::PhantomData;
38
39// Addresses 0x000 - 0x0FF, 0x180 - 0x1FF can be written as blocks
40// Addresses 0x100 - 0x17F must be written by word
41
42// Addresses 0x000 - 0x0FF should use the ADDR_LOWER address
43// Addresses 0x100 - 0x1FF should use the ADDR_UPPER address
44
45// Note that the datasheet gives addresses in 0-bit format with the RW bit
46// set to 0. We want the addresses in 7-bit format, i.e. the datasheet ones
47// shifted right by one bit.
48const ADDR_LOWER: u8 = 0x36;
49const ADDR_UPPER: u8 = 0x0b;
50
51#[allow(dead_code)]
52#[repr(u16)]
53enum Registers {
54 Status = 0x000, // Status flags
55 RepCap = 0x005, // Reported capacity, LSB = 0.5 mAh
56 RepSOC = 0x006, // Reported capacity, LSB = %/256
57 Voltage = 0x009, // The lowest reading from all cell voltages, LSB = 0.078125 mV
58 Current = 0x00A, // Instantaneous current, LSB = 156.25 uA
59 Tte = 0x011, // Time To Empty
60 Ttf = 0x020, // Time to Full
61 FullCapRep = 0x035, // Maximum capacity, LSB = 0.5 mAh
62 Coulomb = 0x04D, // Raw coloumb count
63 Batt = 0x0DA, // Pack voltage, LSB = 1.25mV
64 NPackCfg = 0x1B5, // Pack configuration
65 NRomID = 0x1BC, // RomID - 64bit unique
66 NRSense = 0x1CF, // Sense resistor
67}
68
69/// Return the I2C device address used to communicate when accessing this
70/// register
71fn device_addr(reg: Registers) -> u8 {
72 if reg as u16 > 0x100 {
73 ADDR_UPPER
74 } else {
75 ADDR_LOWER
76 }
77}
78
79/// Return the register address used to access this register
80fn reg_addr(reg: Registers) -> u8 {
81 ((reg as u16) & 0xff) as u8
82}
83
84#[allow(dead_code)]
85#[derive(Debug)]
86/// Represents the status of the MAX1720x fuel gauge IC read from the STATUS register
87pub struct Status {
88 /// Power-On Reset
89 por: bool,
90 /// Minimum current alert threshold exceeded
91 imn: bool,
92 /// Battery status
93 bst: bool,
94 /// Maximum currentl alert threshold exceeded
95 imx: bool,
96 /// State of charge 1% change alert
97 dsoci: bool,
98 /// Minimum voltage alert threshold exceeded
99 vmn: bool,
100 /// Minimum temperature alert threshold exceeded
101 tmn: bool,
102 /// Minimum SOC alert threshold exceeded
103 smn: bool,
104 /// Battery insertion
105 bi: bool,
106 /// Maximum voltage alert threshold exceeded
107 vmx: bool,
108 /// Maximum temperature alert threshold exceeded
109 tmx: bool,
110 /// Maximum SOC alert threshold exceeded
111 smx: bool,
112 /// Battery removal
113 br: bool,
114}
115
116pub struct MAX1720x<I2C, E> {
117 phantom: PhantomData<I2C>,
118 phantom_e: PhantomData<E>,
119}
120
121impl<I2C, E> MAX1720x<I2C, E>
122where
123 I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
124{
125 /// Make a new MAX17205 driver
126 pub fn new(_bus: &mut I2C) -> Self {
127 Self {
128 phantom: PhantomData,
129 phantom_e: PhantomData,
130 }
131 }
132
133 /// Get the fuel gauge status
134 pub fn status(&mut self, bus: &mut I2C) -> Result<Status, E> {
135 let mut raw = [0u8; 2];
136 let dev_addr = device_addr(Registers::Status);
137 let reg_addr = reg_addr(Registers::Status);
138 bus.write_read(dev_addr, &[reg_addr], &mut raw)?;
139 let raw = ((raw[1] as u16) << 8) | (raw[0] as u16);
140 Ok(Status {
141 br: raw & (1 << 15) != 0,
142 smx: raw & (1 << 14) != 0,
143 tmx: raw & (1 << 13) != 0,
144 vmx: raw & (1 << 12) != 0,
145 bi: raw & (1 << 11) != 0,
146 smn: raw & (1 << 10) != 0,
147 tmn: raw & (1 << 9) != 0,
148 vmn: raw & (1 << 8) != 0,
149 dsoci: raw & (1 << 7) != 0,
150 imx: raw & (1 << 6) != 0,
151 bst: raw & (1 << 3) != 0,
152 imn: raw & (1 << 2) != 0,
153 por: raw & (1 << 1) != 0,
154 })
155 }
156
157 /// Get the current estimated state of charge as a percentage
158 pub fn state_of_charge(&mut self, bus: &mut I2C) -> Result<f32, E> {
159 let mut raw = [0u8; 2];
160 let dev_addr = device_addr(Registers::RepSOC);
161 let reg_addr = reg_addr(Registers::RepSOC);
162 bus.write_read(dev_addr, &[reg_addr], &mut raw)?;
163 let raw = ((raw[1] as u16) << 8) | (raw[0] as u16);
164 // Conversion ratio from datasheet Table 1
165 Ok((raw as f32) / 256.0)
166 }
167
168 /// Get the current pack voltage in volts
169 pub fn voltage(&mut self, bus: &mut I2C) -> Result<f32, E> {
170 let mut raw = [0u8; 2];
171 let dev_addr = device_addr(Registers::Batt);
172 let reg_addr = reg_addr(Registers::Batt);
173 bus.write_read(dev_addr, &[reg_addr], &mut raw)?;
174 let raw = ((raw[1] as u16) << 8) | (raw[0] as u16);
175 // Conversion ratio from datasheet "Batt Register" register info
176 Ok((raw as f32) * 0.001_25)
177 }
178
179 /// Get the current pack current in amps
180 pub fn current(&mut self, bus: &mut I2C) -> Result<f32, E> {
181 let mut raw = [0u8; 2];
182 let dev_addr = device_addr(Registers::Current);
183 let reg_addr = reg_addr(Registers::Current);
184 bus.write_read(dev_addr, &[reg_addr], &mut raw)?;
185 let raw = ((raw[1] as u16) << 8) | (raw[0] as u16);
186 // Convert from twos complement form into a real signed integer
187 let raw = raw as i16;
188 // Conversion ratio from datasheet Table 1
189 Ok((raw as f32) * 0.000_156_25)
190 }
191}