1use embedded_hal::i2c::I2c;
8
9use crate::devices;
10
11const REG_STATUS1: u8 = 0x00;
12const REG_STATUS2: u8 = 0x01;
13const REG_DATA_BUFFER0: u8 = 0x04;
14const REG_POWER_OFF: u8 = 0x10;
15const REG_LDOS_ON_OFF: u8 = 0x90;
16const REG_ALDO3_VOLTAGE: u8 = 0x94;
17const REG_ALDO4_VOLTAGE: u8 = 0x95;
18const REG_DLDO1_VOLTAGE: u8 = 0x99;
19const REG_BATTERY_VOLTAGE_H: u8 = 0x34;
20const REG_BATTERY_VOLTAGE_L: u8 = 0x35;
21const LDO_3V3_CODE: u8 = 33 - 5;
22
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum ChargeState {
27 Unknown,
28 Discharging,
29 Charging,
30 Full,
31}
32
33#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum ExternalPower {
37 Unknown,
38 Disconnected,
39 Connected,
40}
41
42#[cfg_attr(feature = "defmt", derive(defmt::Format))]
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45pub struct BatteryStatus {
46 pub millivolts: u16,
47 pub percentage: u8,
48 pub charge_state: ChargeState,
49 pub external_power: ExternalPower,
50 pub low_battery: bool,
51}
52
53impl BatteryStatus {
54 pub const fn new(millivolts: u16, charge_state: ChargeState) -> Self {
55 Self {
56 millivolts,
57 percentage: estimate_lipo_percentage(millivolts),
58 charge_state,
59 external_power: ExternalPower::Unknown,
60 low_battery: millivolts <= LowBatteryThreshold::DEFAULT.millivolts,
61 }
62 }
63
64 pub const fn with_power(
65 millivolts: u16,
66 charge_state: ChargeState,
67 external_power: ExternalPower,
68 threshold: LowBatteryThreshold,
69 ) -> Self {
70 Self {
71 millivolts,
72 percentage: estimate_lipo_percentage(millivolts),
73 charge_state,
74 external_power,
75 low_battery: millivolts <= threshold.millivolts,
76 }
77 }
78}
79
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub struct LowBatteryThreshold {
84 pub millivolts: u16,
85}
86
87impl LowBatteryThreshold {
88 pub const DEFAULT: Self = Self { millivolts: 3_500 };
89}
90
91#[cfg_attr(feature = "defmt", derive(defmt::Format))]
93#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94pub struct VoltageSmoother {
95 value_mv: Option<u16>,
96 weight_new: u8,
97}
98
99impl VoltageSmoother {
100 pub const fn new(weight_new: u8) -> Self {
101 Self {
102 value_mv: None,
103 weight_new,
104 }
105 }
106
107 pub fn update(&mut self, sample_mv: u16) -> u16 {
108 let weight = self.weight_new.clamp(1, 100) as u32;
109 let next = match self.value_mv {
110 None => sample_mv,
111 Some(current) => {
112 let current = u32::from(current);
113 let sample = u32::from(sample_mv);
114 (((current * (100 - weight)) + (sample * weight)) / 100) as u16
115 }
116 };
117 self.value_mv = Some(next);
118 next
119 }
120
121 pub const fn value(&self) -> Option<u16> {
122 self.value_mv
123 }
124}
125
126pub const fn estimate_lipo_percentage(millivolts: u16) -> u8 {
128 match millivolts {
129 4200..=u16::MAX => 100,
130 4100..=4199 => 90,
131 4000..=4099 => 80,
132 3920..=3999 => 70,
133 3850..=3919 => 60,
134 3790..=3849 => 50,
135 3740..=3789 => 40,
136 3700..=3739 => 30,
137 3610..=3699 => 20,
138 3500..=3609 => 10,
139 3300..=3499 => 5,
140 _ => 0,
141 }
142}
143
144pub struct Axp2101<I2C> {
146 i2c: I2C,
147 address: u8,
148 low_threshold: LowBatteryThreshold,
149}
150
151impl<I2C> Axp2101<I2C> {
152 pub const fn new(i2c: I2C) -> Self {
153 Self {
154 i2c,
155 address: devices::i2c::AXP2101_PMU,
156 low_threshold: LowBatteryThreshold::DEFAULT,
157 }
158 }
159
160 pub fn release(self) -> I2C {
161 self.i2c
162 }
163
164 pub fn set_low_battery_threshold(&mut self, threshold: LowBatteryThreshold) {
165 self.low_threshold = threshold;
166 }
167}
168
169impl<I2C, Error> Axp2101<I2C>
170where
171 I2C: I2c<Error = Error>,
172{
173 pub fn init_core_s3_defaults(&mut self) -> Result<(), Error> {
174 self.write_register(REG_LDOS_ON_OFF, 0xBF)?;
175 self.write_register(REG_ALDO3_VOLTAGE, LDO_3V3_CODE)?;
176 self.write_register(REG_ALDO4_VOLTAGE, LDO_3V3_CODE)
177 }
178
179 pub fn set_display_backlight(&mut self, brightness: u8) -> Result<(), Error> {
180 if brightness == 0 {
181 self.write_bit(REG_LDOS_ON_OFF, 7, false)
182 } else {
183 let voltage = ((u16::from(brightness) + 641) >> 5) as u8;
184 self.write_bit(REG_LDOS_ON_OFF, 7, true)?;
185 self.write_register(REG_DLDO1_VOLTAGE, voltage)
186 }
187 }
188
189 pub fn battery_voltage_mv(&mut self) -> Result<u16, Error> {
190 let high = u16::from(self.read_register(REG_BATTERY_VOLTAGE_H)?);
191 let low = u16::from(self.read_register(REG_BATTERY_VOLTAGE_L)?);
192 Ok(((high & 0x3F) << 8) | low)
193 }
194
195 pub fn status(&mut self) -> Result<BatteryStatus, Error> {
196 let status1 = self.read_register(REG_STATUS1)?;
197 let status2 = self.read_register(REG_STATUS2)?;
198 let voltage = self.battery_voltage_mv().unwrap_or(0);
199 let external = if status1 & 0x20 != 0 {
200 ExternalPower::Connected
201 } else {
202 ExternalPower::Disconnected
203 };
204 let charge_state = if status2 & 0x04 != 0 {
205 ChargeState::Charging
206 } else if external == ExternalPower::Connected {
207 ChargeState::Full
208 } else {
209 ChargeState::Discharging
210 };
211 Ok(BatteryStatus::with_power(
212 voltage,
213 charge_state,
214 external,
215 self.low_threshold,
216 ))
217 }
218
219 pub fn prepare_sleep(&mut self, wake_marker: u8) -> Result<(), Error> {
220 self.write_register(REG_DATA_BUFFER0, wake_marker)
221 }
222
223 pub fn shutdown(&mut self) -> Result<(), Error> {
224 self.write_register(REG_POWER_OFF, 0x01)
225 }
226
227 pub fn read_register(&mut self, register: u8) -> Result<u8, Error> {
228 let mut value = [0u8];
229 self.i2c.write_read(self.address, &[register], &mut value)?;
230 Ok(value[0])
231 }
232
233 pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), Error> {
234 self.i2c.write(self.address, &[register, value])
235 }
236
237 pub fn write_bit(&mut self, register: u8, bit: u8, value: bool) -> Result<(), Error> {
238 let current = self.read_register(register)?;
239 let mask = 1u8 << bit;
240 let next = if value {
241 current | mask
242 } else {
243 current & !mask
244 };
245 self.write_register(register, next)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn estimates_voltage_percentage() {
255 assert_eq!(estimate_lipo_percentage(4200), 100);
256 assert_eq!(estimate_lipo_percentage(3750), 40);
257 assert_eq!(estimate_lipo_percentage(3400), 5);
258 }
259
260 #[test]
261 fn smooths_voltage() {
262 let mut smoother = VoltageSmoother::new(25);
263 assert_eq!(smoother.update(4000), 4000);
264 assert_eq!(smoother.update(3800), 3950);
265 }
266}