1use embedded_hal::i2c::I2c;
9
10use crate::devices;
11
12const REG_STATUS1: u8 = 0x00;
13const REG_STATUS2: u8 = 0x01;
14const REG_DATA_BUFFER0: u8 = 0x04;
15const REG_POWER_OFF: u8 = 0x10;
16const REG_LDOS_ON_OFF: u8 = 0x90;
17const REG_ALDO3_VOLTAGE: u8 = 0x94;
18const REG_ALDO4_VOLTAGE: u8 = 0x95;
19const REG_DLDO1_VOLTAGE: u8 = 0x99;
20const REG_BATTERY_VOLTAGE_H: u8 = 0x34;
21const REG_BATTERY_VOLTAGE_L: u8 = 0x35;
22const REG_BATTERY_LEVEL: u8 = 0xA4;
23const STATUS1_BATTERY_PRESENT: u8 = 0x08;
24const STATUS1_VBUS_GOOD: u8 = 0x20;
25const LDO_3V3_CODE: u8 = 33 - 5;
26
27#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub enum ChargeState {
31 Unknown,
32 Discharging,
33 Charging,
34 Full,
35}
36
37#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub enum ExternalPower {
41 Unknown,
42 Disconnected,
43 Connected,
44}
45
46#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub struct BatteryStatus {
50 pub millivolts: u16,
51 pub percentage: u8,
56 pub charge_state: ChargeState,
57 pub external_power: ExternalPower,
58 pub low_battery: bool,
59 pub percentage_estimated: bool,
62 pub state_of_charge: Option<u8>,
64 pub battery_present: Option<bool>,
66}
67
68impl BatteryStatus {
69 pub const fn new(millivolts: u16, charge_state: ChargeState) -> Self {
70 Self {
71 millivolts,
72 percentage: estimate_lipo_percentage(millivolts),
73 charge_state,
74 external_power: ExternalPower::Unknown,
75 low_battery: millivolts <= LowBatteryThreshold::DEFAULT.millivolts,
76 percentage_estimated: true,
77 state_of_charge: None,
78 battery_present: None,
79 }
80 }
81
82 pub const fn with_power(
83 millivolts: u16,
84 charge_state: ChargeState,
85 external_power: ExternalPower,
86 threshold: LowBatteryThreshold,
87 ) -> Self {
88 Self::with_power_and_soc(
89 millivolts,
90 charge_state,
91 external_power,
92 threshold,
93 None,
94 None,
95 )
96 }
97
98 pub const fn with_power_and_soc(
99 millivolts: u16,
100 charge_state: ChargeState,
101 external_power: ExternalPower,
102 threshold: LowBatteryThreshold,
103 state_of_charge: Option<u8>,
104 battery_present: Option<bool>,
105 ) -> Self {
106 let voltage_estimate = estimate_lipo_percentage(millivolts);
107 let percentage = match state_of_charge {
108 Some(value) => value,
109 None => voltage_estimate,
110 };
111 Self {
112 millivolts,
113 percentage,
114 charge_state,
115 external_power,
116 low_battery: millivolts <= threshold.millivolts,
117 percentage_estimated: state_of_charge.is_none(),
118 state_of_charge,
119 battery_present,
120 }
121 }
122}
123
124#[cfg_attr(feature = "defmt", derive(defmt::Format))]
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub struct LowBatteryThreshold {
128 pub millivolts: u16,
129}
130
131impl LowBatteryThreshold {
132 pub const DEFAULT: Self = Self { millivolts: 3_500 };
133}
134
135#[cfg_attr(feature = "defmt", derive(defmt::Format))]
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138pub struct VoltageSmoother {
139 value_mv: Option<u16>,
140 weight_new: u8,
141}
142
143impl VoltageSmoother {
144 pub const fn new(weight_new: u8) -> Self {
145 Self {
146 value_mv: None,
147 weight_new,
148 }
149 }
150
151 pub fn update(&mut self, sample_mv: u16) -> u16 {
152 let weight = self.weight_new.clamp(1, 100) as u32;
153 let next = match self.value_mv {
154 None => sample_mv,
155 Some(current) => {
156 let current = u32::from(current);
157 let sample = u32::from(sample_mv);
158 (((current * (100 - weight)) + (sample * weight)) / 100) as u16
159 }
160 };
161 self.value_mv = Some(next);
162 next
163 }
164
165 pub const fn value(&self) -> Option<u16> {
166 self.value_mv
167 }
168}
169
170pub const fn decode_axp2101_soc(raw: u8) -> Option<u8> {
172 if raw <= 100 { Some(raw) } else { None }
173}
174
175pub const fn decode_axp2101_external_power(status0: u8) -> ExternalPower {
177 if status0 & STATUS1_VBUS_GOOD != 0 {
178 ExternalPower::Connected
179 } else {
180 ExternalPower::Disconnected
181 }
182}
183
184pub const fn decode_axp2101_battery_present(status0: u8) -> Option<bool> {
186 Some(status0 & STATUS1_BATTERY_PRESENT != 0)
187}
188
189pub const fn decode_axp2101_charge_state(status0: u8, status1: u8) -> ChargeState {
191 match (status1 >> 5) & 0b11 {
192 0b01 => ChargeState::Charging,
193 0b10 => ChargeState::Discharging,
194 0b00 => match decode_axp2101_external_power(status0) {
195 ExternalPower::Connected => ChargeState::Full,
196 _ => ChargeState::Unknown,
197 },
198 _ => ChargeState::Unknown,
199 }
200}
201
202pub const fn estimate_lipo_percentage(millivolts: u16) -> u8 {
206 match millivolts {
207 4200..=u16::MAX => 100,
208 4100..=4199 => 90,
209 4000..=4099 => 80,
210 3920..=3999 => 70,
211 3850..=3919 => 60,
212 3790..=3849 => 50,
213 3740..=3789 => 40,
214 3700..=3739 => 30,
215 3610..=3699 => 20,
216 3500..=3609 => 10,
217 3300..=3499 => 5,
218 _ => 0,
219 }
220}
221
222pub struct Axp2101<I2C> {
224 i2c: I2C,
225 address: u8,
226 low_threshold: LowBatteryThreshold,
227}
228
229impl<I2C> Axp2101<I2C> {
230 pub const fn new(i2c: I2C) -> Self {
231 Self {
232 i2c,
233 address: devices::i2c::AXP2101_PMU,
234 low_threshold: LowBatteryThreshold::DEFAULT,
235 }
236 }
237
238 pub fn release(self) -> I2C {
239 self.i2c
240 }
241
242 pub fn set_low_battery_threshold(&mut self, threshold: LowBatteryThreshold) {
243 self.low_threshold = threshold;
244 }
245}
246
247impl<I2C, Error> Axp2101<I2C>
248where
249 I2C: I2c<Error = Error>,
250{
251 pub fn init_core_s3_defaults(&mut self) -> Result<(), Error> {
252 self.write_register(REG_LDOS_ON_OFF, 0xBF)?;
253 self.write_register(REG_ALDO3_VOLTAGE, LDO_3V3_CODE)?;
254 self.write_register(REG_ALDO4_VOLTAGE, LDO_3V3_CODE)
255 }
256
257 pub fn set_display_backlight(&mut self, brightness: u8) -> Result<(), Error> {
258 if brightness == 0 {
259 self.write_bit(REG_LDOS_ON_OFF, 7, false)
260 } else {
261 let voltage = ((u16::from(brightness) + 641) >> 5) as u8;
262 self.write_bit(REG_LDOS_ON_OFF, 7, true)?;
263 self.write_register(REG_DLDO1_VOLTAGE, voltage)
264 }
265 }
266
267 pub fn battery_voltage_mv(&mut self) -> Result<u16, Error> {
268 let high = u16::from(self.read_register(REG_BATTERY_VOLTAGE_H)?);
269 let low = u16::from(self.read_register(REG_BATTERY_VOLTAGE_L)?);
270 Ok(((high & 0x3F) << 8) | low)
271 }
272
273 pub fn battery_level_percent(&mut self) -> Result<Option<u8>, Error> {
278 self.read_register(REG_BATTERY_LEVEL)
279 .map(decode_axp2101_soc)
280 }
281
282 pub fn external_power(&mut self) -> Result<ExternalPower, Error> {
284 self.read_register(REG_STATUS1)
285 .map(decode_axp2101_external_power)
286 }
287
288 pub fn battery_present(&mut self) -> Result<Option<bool>, Error> {
290 self.read_register(REG_STATUS1)
291 .map(decode_axp2101_battery_present)
292 }
293
294 pub fn charge_state(&mut self) -> Result<ChargeState, Error> {
296 let status0 = self.read_register(REG_STATUS1)?;
297 let status1 = self.read_register(REG_STATUS2)?;
298 Ok(decode_axp2101_charge_state(status0, status1))
299 }
300
301 pub fn status(&mut self) -> Result<BatteryStatus, Error> {
302 let status0 = self.read_register(REG_STATUS1)?;
303 let status1 = self.read_register(REG_STATUS2)?;
304 let voltage = self.battery_voltage_mv().unwrap_or(0);
305 let state_of_charge = self.battery_level_percent().unwrap_or(None);
306 let external = decode_axp2101_external_power(status0);
307 let battery_present = decode_axp2101_battery_present(status0);
308 let charge_state = decode_axp2101_charge_state(status0, status1);
309 Ok(BatteryStatus::with_power_and_soc(
310 voltage,
311 charge_state,
312 external,
313 self.low_threshold,
314 state_of_charge,
315 battery_present,
316 ))
317 }
318
319 pub fn prepare_sleep(&mut self, wake_marker: u8) -> Result<(), Error> {
320 self.write_register(REG_DATA_BUFFER0, wake_marker)
321 }
322
323 pub fn shutdown(&mut self) -> Result<(), Error> {
324 self.write_register(REG_POWER_OFF, 0x01)
325 }
326
327 pub fn read_register(&mut self, register: u8) -> Result<u8, Error> {
328 let mut value = [0u8];
329 self.i2c.write_read(self.address, &[register], &mut value)?;
330 Ok(value[0])
331 }
332
333 pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), Error> {
334 self.i2c.write(self.address, &[register, value])
335 }
336
337 pub fn write_bit(&mut self, register: u8, bit: u8, value: bool) -> Result<(), Error> {
338 let current = self.read_register(register)?;
339 let mask = 1u8 << bit;
340 let next = if value {
341 current | mask
342 } else {
343 current & !mask
344 };
345 self.write_register(register, next)
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn decodes_axp2101_soc() {
355 assert_eq!(decode_axp2101_soc(0), Some(0));
356 assert_eq!(decode_axp2101_soc(1), Some(1));
357 assert_eq!(decode_axp2101_soc(50), Some(50));
358 assert_eq!(decode_axp2101_soc(90), Some(90));
359 assert_eq!(decode_axp2101_soc(100), Some(100));
360 assert_eq!(decode_axp2101_soc(101), None);
361 assert_eq!(decode_axp2101_soc(0x7F), None);
362 assert_eq!(decode_axp2101_soc(0xFF), None);
363 }
364
365 #[test]
366 fn decodes_axp2101_charge_state() {
367 assert_eq!(
368 decode_axp2101_charge_state(0, 0b01 << 5),
369 ChargeState::Charging
370 );
371 assert_eq!(
372 decode_axp2101_charge_state(0, 0b10 << 5),
373 ChargeState::Discharging
374 );
375 assert_eq!(
376 decode_axp2101_charge_state(STATUS1_VBUS_GOOD, 0),
377 ChargeState::Full
378 );
379 assert_eq!(decode_axp2101_charge_state(0, 0), ChargeState::Unknown);
380 assert_eq!(
381 decode_axp2101_charge_state(0, 0b11 << 5),
382 ChargeState::Unknown
383 );
384 }
385
386 #[test]
387 fn decodes_axp2101_external_power() {
388 assert_eq!(
389 decode_axp2101_external_power(STATUS1_VBUS_GOOD),
390 ExternalPower::Connected
391 );
392 assert_eq!(
393 decode_axp2101_external_power(0),
394 ExternalPower::Disconnected
395 );
396 }
397
398 #[test]
399 fn decodes_axp2101_battery_present() {
400 assert_eq!(
401 decode_axp2101_battery_present(STATUS1_BATTERY_PRESENT),
402 Some(true)
403 );
404 assert_eq!(decode_axp2101_battery_present(0), Some(false));
405 }
406
407 #[test]
408 fn estimates_voltage_percentage() {
409 assert_eq!(estimate_lipo_percentage(4200), 100);
410 assert_eq!(estimate_lipo_percentage(3750), 40);
411 assert_eq!(estimate_lipo_percentage(3400), 5);
412 }
413
414 #[test]
415 fn battery_status_prefers_gauge_soc() {
416 let status = BatteryStatus::with_power_and_soc(
417 3750,
418 ChargeState::Discharging,
419 ExternalPower::Disconnected,
420 LowBatteryThreshold::DEFAULT,
421 Some(87),
422 Some(true),
423 );
424 assert_eq!(status.percentage, 87);
425 assert!(!status.percentage_estimated);
426 assert_eq!(status.state_of_charge, Some(87));
427 assert_eq!(status.battery_present, Some(true));
428 }
429
430 #[test]
431 fn battery_status_falls_back_to_voltage_estimate() {
432 let status = BatteryStatus::with_power_and_soc(
433 3750,
434 ChargeState::Discharging,
435 ExternalPower::Disconnected,
436 LowBatteryThreshold::DEFAULT,
437 None,
438 Some(true),
439 );
440 assert_eq!(status.percentage, 40);
441 assert!(status.percentage_estimated);
442 assert_eq!(status.state_of_charge, None);
443 }
444
445 #[test]
446 fn smooths_voltage() {
447 let mut smoother = VoltageSmoother::new(25);
448 assert_eq!(smoother.update(4000), 4000);
449 assert_eq!(smoother.update(3800), 3950);
450 }
451}