Skip to content

Commit

Permalink
BQ25713: CHRG_OK pin handling
Browse files Browse the repository at this point in the history
Set input current limit to zero if BQ25713 CHRG_OK pin is low
and reprogram the configured value immediately after it goes high.
  • Loading branch information
surban committed Jun 26, 2023
1 parent bfc25d6 commit c0212e0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 34 deletions.
12 changes: 12 additions & 0 deletions openemc-firmware/src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,18 @@ pub trait Board {
None
}

/// Gets the status of the BQ25713 CHRG_OK signal.
fn check_bq25713_chrg_ok(&mut self) -> bool {
false
}

/// Checks whether the state of the BQ25713 CHRG_OK signal changed and clears the pending interrupt.
///
/// Returns `Some(state)` if the state changed and `None` if the state is unchanged.
fn check_bq25713_chrg_ok_changed(&mut self) -> Option<bool> {
None
}

/// Sets the power LED to the specified state.
fn set_power_led(&mut self, _state: bool) {}

Expand Down
122 changes: 103 additions & 19 deletions openemc-firmware/src/bq25713.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ pub struct Bq25713<I2C> {
measurement: Option<Bq25713Measurement>,
status: Bq25713Status,
charge_enabled: bool,
max_input_current_ma: u32,
chrg_ok: bool,
_i2c: PhantomData<I2C>,
}

Expand Down Expand Up @@ -291,6 +293,8 @@ where
measurement: None,
status: Default::default(),
charge_enabled: false,
max_input_current_ma: 0,
chrg_ok: false,
_i2c: PhantomData,
}
}
Expand Down Expand Up @@ -354,9 +358,6 @@ where
// Set watchdog timer to 5s.
self.modify(i2c, REG_CHARGE_OPTION_0_HI, |v| v & !(0b11 << 5) | (0b01 << 5))?;

// Set current limit through registers.
self.modify(i2c, REG_CHARGE_OPTION_2_LO, |v| v & !(1 << 7))?;

// Enable IBAT and PSYS measurements.
self.modify(i2c, REG_CHARGE_OPTION_1_HI, |v| v | (1 << 7) | (1 << 4))?;

Expand All @@ -369,16 +370,25 @@ where
// Read initial status.
self.update_status(i2c)?;

// Program maximum input current limit.
self.program_max_input_current(i2c)?;

defmt::info!("BQ25713 initialized");
Ok(())
}

/// Enters low power and Hi-Z mode.
/// Enters low power mode.
///
/// Afterwards reinitialization is necessary.
pub fn shutdown(self, i2c: &mut I2C) -> Result<()> {
pub fn shutdown(mut self, i2c: &mut I2C) -> Result<()> {
defmt::info!("BQ25713 entering low power mode");

// Disable input current.
self.chrg_ok = false;
self.max_input_current_ma = 0;
self.program_max_input_current(i2c)?;
self.program_charge_enabled(i2c)?;

// Enable low power mode.
self.modify(i2c, REG_CHARGE_OPTION_0_HI, |v| v | (1 << 7))?;

Expand Down Expand Up @@ -449,24 +459,57 @@ where
self.write(i2c, REG_CHARGER_STATUS_LO, &[0x00])
}

/// Sets the input current limit.
/// Sets the input current limit in mA.
pub fn set_max_input_current(&mut self, i2c: &mut I2C, ma: u32) -> Result<()> {
self.check_initialized()?;

defmt::debug!("Setting maximum input current to {} mA", ma);
self.max_input_current_ma = ma;
self.program_max_input_current(i2c)?;

Ok(())
}

/// Gets the input current limit in mA.
pub fn max_input_current(&self) -> u32 {
self.max_input_current_ma
}

/// Programs the maximum input current into the BQ25713.
fn program_max_input_current(&mut self, i2c: &mut I2C) -> Result<()> {
// Enable IDPM.
self.modify(i2c, REG_CHARGE_OPTION_0_LO, |v| v | (1 << 1))?;

// Program input current.
let v = (ma / 50) as u8 & 0b01111111;
self.write(i2c, REG_IIN_HOST, &[v])?;
if self.chrg_ok {
defmt::trace!("Programming maximum input current {} mA", self.max_input_current_ma);

// Program input current.
let v = (self.max_input_current_ma / 50) as u8 & 0b01111111;
self.write(i2c, REG_IIN_HOST, &[v])?;

// Verify programmed current.
let r = self.read(i2c, REG_IIN_HOST, 1)?[0];
let r_dpm = self.read(i2c, REG_IIN_DPM, 1)?[0];
if r != v || r_dpm != v {
defmt::error!(
"Programmed input current {:x}, but read back is {:x} and I_DPM is {:x}",
v,
r,
r_dpm
);
return Err(Error::VerifyFailed);
}

// Verify programmed current.
let r = self.read(i2c, REG_IIN_HOST, 1)?[0];
let r_dpm = self.read(i2c, REG_IIN_DPM, 1)?[0];
if r != v || r_dpm != v {
defmt::error!("Programmed input current {:x}, but read back is {:x} and I_DPM is {:x}", v, r, r_dpm);
return Err(Error::VerifyFailed);
// Ignore ILIM_HIZ limit once input current limit is configured.
self.set_obey_ilim_pin(i2c, false)?;
} else {
defmt::trace!("Programming maximum input current zero");

// Set input current limit to zero.
self.write(i2c, REG_IIN_HOST, &[0])?;

// Obey ILIM_HIZ limit until input current limit is configured.
self.set_obey_ilim_pin(i2c, true)?;
}

// Verify IDPM is enabled.
Expand Down Expand Up @@ -519,12 +562,25 @@ where
self.modify(i2c, REG_CHARGE_OPTION_0_LO, |v| if enable { v & !(1 << 6) } else { v | (1 << 6) })
}

/// Sets whether the input current is also limited by the ILIM_HIZ pins.
fn set_obey_ilim_pin(&mut self, i2c: &mut I2C, obey: bool) -> Result<()> {
defmt::debug!("Setting obey ILIM_HIZ current limit to {}", obey);
self.modify(i2c, REG_CHARGE_OPTION_2_LO, |v| if obey { v | (1 << 7) } else { v & !(1 << 7) })
}

/// Enables or disables charging.
pub fn set_charge_enable(&mut self, i2c: &mut I2C, enable: bool) -> Result<()> {
self.check_initialized()?;
defmt::debug!("Setting charge enabled to {}", enable);
self.modify(i2c, REG_CHARGE_OPTION_0_LO, |v| if enable { v & !(1 << 0) } else { v | (1 << 0) })?;
self.charge_enabled = enable;
self.program_charge_enabled(i2c)
}

/// Programs the charge enabled setting.
fn program_charge_enabled(&mut self, i2c: &mut I2C) -> Result<()> {
let enable = self.charge_enabled && self.chrg_ok;
defmt::trace!("Programming charge enabled to {}", enable);
self.modify(i2c, REG_CHARGE_OPTION_0_LO, |v| if enable { v & !(1 << 0) } else { v | (1 << 0) })?;
Ok(())
}

Expand Down Expand Up @@ -586,25 +642,53 @@ where
})
}

fn do_periodic(&mut self, i2c: &mut I2C) -> Result<()> {
fn do_periodic(&mut self, i2c: &mut I2C, chrg_ok: bool) -> Result<()> {
if self.chrg_ok != chrg_ok {
defmt::info!("BQ25713 CHRG_OK has become {}", chrg_ok);
self.chrg_ok = chrg_ok;
}

if !self.initialized {
self.init(i2c)?;
}

self.update_status(i2c)?;
self.update_adc(i2c)?;
self.set_charge_current(i2c, self.cfg.max_charge_ma)?;
self.program_max_input_current(i2c)?;
self.program_charge_enabled(i2c)?;

Ok(())
}

/// Call this periodically (approx. every second) to handle communication with the device.
pub fn periodic(&mut self, i2c: &mut I2C) {
if let Err(err) = self.do_periodic(i2c) {
pub fn periodic(&mut self, i2c: &mut I2C, chrg_ok: bool) {
if let Err(err) = self.do_periodic(i2c, chrg_ok) {
defmt::warn!("BQ25713 failed: {}", err);
self.initialized = false;
}
}

fn do_chrg_ok_changed(&mut self, i2c: &mut I2C) -> Result<()> {
self.program_max_input_current(i2c)?;
self.program_charge_enabled(i2c)?;
Ok(())
}

/// Notifies the driver of a change of the CHRG_OK signal.
pub fn chrg_ok_changed(&mut self, i2c: &mut I2C, chrg_ok: bool) {
if self.chrg_ok != chrg_ok {
defmt::info!("BQ25713 CHRG_OK has become {}", chrg_ok);
self.chrg_ok = chrg_ok;

if self.initialized {
if let Err(err) = self.do_chrg_ok_changed(i2c) {
defmt::warn!("BQ25713 failed: {}", err);
self.initialized = false;
}
}
}
}
}

// Register definitions.
Expand Down
28 changes: 13 additions & 15 deletions openemc-firmware/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,16 +723,7 @@ mod app {
// Configure battery charger.
match (i2c2, bq25713) {
(Some(i2c2), Some(bq25713)) if !report.is_unknown() => {
let mut max_current = report.max_current_ma();

// BQ25713 has trouble controlling small maximum input currents.
if max_current < 500 {
defmt::info!(
"Disabling BQ25713 charging for maximum input current {} mA < 500 mA",
max_current
);
max_current = 0;
}
let max_current = report.max_current_ma();

defmt::info!("Setting BQ25713 maximum input current to {} mA", max_current);
let res = bq25713.set_max_input_current(i2c2, max_current).and_then(|_| {
Expand Down Expand Up @@ -779,16 +770,16 @@ mod app {
}

/// BQ25713 periodic task.
#[task(shared = [i2c2, bq25713, battery, irq, &power_mode], local = [first: Option<Instant> = None])]
#[task(shared = [i2c2, bq25713, battery, irq, board, &power_mode], local = [first: Option<Instant> = None])]
fn bq25713_periodic(cx: bq25713_periodic::Context) {
let grace_period = 10u64.secs();
let first = cx.local.first.get_or_insert_with(monotonics::now);

(cx.shared.i2c2, cx.shared.bq25713, cx.shared.battery, cx.shared.irq).lock(
|i2c2, bq25713, battery, irq| {
(cx.shared.i2c2, cx.shared.bq25713, cx.shared.battery, cx.shared.irq, cx.shared.board).lock(
|i2c2, bq25713, battery, irq, board| {
if let Some(bq25713) = bq25713.as_mut() {
let i2c2 = defmt::unwrap!(i2c2.as_mut());
bq25713.periodic(i2c2);
bq25713.periodic(i2c2, board.check_bq25713_chrg_ok());

let mut ac = false;
if let Some(status) = bq25713.status().cloned() {
Expand Down Expand Up @@ -925,11 +916,18 @@ mod app {
}

/// External interrupt handler 0, handling EXTI0 - EXTI15.
#[task(binds = EXTI0, shared = [irq, board, i2c2, stusb4500, &power_mode])]
#[task(binds = EXTI0, shared = [irq, board, i2c2, stusb4500, bq25713, &power_mode])]
fn exti0(mut cx: exti0::Context) {
if cx.shared.board.lock(|board| board.check_stusb4500_alerting()) {
defmt::trace!("STUSB4500 alert interrupt!");
unwrap!(stusb4500_alert::spawn());
} else if let Some(chrg_ok) = cx.shared.board.lock(|board| board.check_bq25713_chrg_ok_changed()) {
(cx.shared.i2c2, cx.shared.bq25713).lock(|i2c2, bq25713| {
defmt::trace!("BQ25713 CHRG_OK interrupt!");
if let (Some(i2c2), Some(bq25713)) = (i2c2.as_mut(), bq25713.as_mut()) {
bq25713.chrg_ok_changed(i2c2, chrg_ok);
}
});
} else if *cx.shared.power_mode == PowerMode::Charging
&& cx.shared.board.lock(|board| board.check_power_on_requested())
{
Expand Down

0 comments on commit c0212e0

Please sign in to comment.