Skip to content

Commit 22a9dd7

Browse files
authored
Merge pull request #72 from jannic-dev-forks/power_good
Check for 3.3V supply before raising IRQ and continuing boot process
2 parents 6cee4f5 + 02c4135 commit 22a9dd7

File tree

1 file changed

+82
-24
lines changed

1 file changed

+82
-24
lines changed

neotron-bmc-pico/src/main.rs

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ use core::convert::TryFrom;
1616
use heapless::spsc::{Consumer, Producer, Queue};
1717
use rtic::app;
1818
use stm32f0xx_hal::{
19-
gpio::gpioa::{PA10, PA11, PA12, PA15, PA2, PA3, PA4, PA8, PA9},
19+
adc,
20+
adc::Adc,
21+
gpio::gpioa::{PA0, PA10, PA11, PA12, PA15, PA2, PA3, PA4, PA8, PA9},
2022
gpio::gpiob::{PB0, PB3, PB4, PB5},
2123
gpio::gpiof::{PF0, PF1},
22-
gpio::{Alternate, Floating, Input, Output, PullDown, PullUp, PushPull, AF1},
24+
gpio::{Alternate, Analog, Floating, Input, Output, PullDown, PullUp, PushPull, AF1},
2325
pac,
2426
prelude::*,
2527
rcc, serial,
@@ -41,6 +43,10 @@ const DEBOUNCE_POLL_INTERVAL_MS: u64 = 75;
4143
/// Length of a reset pulse, in milliseconds
4244
const RESET_DURATION_MS: u64 = 250;
4345

46+
/// Minimum voltage level of the 3.3V rail before we start
47+
/// driving the IRQ pin high, to avoid back-powering.
48+
const POWER_GOOD_THRESHOLD_MV: u16 = 3200;
49+
4450
/// The states we can be in controlling the DC power
4551
#[derive(Copy, Clone, PartialEq, Eq)]
4652
#[repr(u8)]
@@ -170,6 +176,10 @@ mod app {
170176
rcc: Option<rcc::Rcc>,
171177
/// IRQ pin
172178
pin_irq: PA8<Output<PushPull>>,
179+
/// 3.3V monitor pin
180+
pin_3v3_monitor: PA0<Analog>,
181+
/// ADC
182+
adc: Adc,
173183
}
174184

175185
#[monotonic(binds = SysTick, default = true)]
@@ -204,6 +214,14 @@ mod app {
204214
// Initialize the monotonic timer using the Cortex-M SysTick peripheral
205215
let mono = Systick::new(cp.SYST, rcc.clocks.sysclk().0);
206216

217+
let mut adc = Adc::new(dp.ADC, &mut rcc);
218+
// 8 bit precision should be plenty for our use case
219+
adc.set_precision(adc::AdcPrecision::B_8);
220+
// Approx. sampling time required to charge the 8pF sample & hold
221+
// capacitor through the 10kΩ resistor and to get a reading with 8 bit accuracy:
222+
// 8pF * 10kΩ * ln(2^8) ≈ 440ns ≈ 6 ADC clock cycles
223+
adc.set_sample_time(adc::AdcSampleTime::T_7);
224+
207225
defmt::info!("Creating pins...");
208226
let gpioa = dp.GPIOA.split(&mut rcc);
209227
let gpiob = dp.GPIOB.split(&mut rcc);
@@ -236,6 +254,8 @@ mod app {
236254
pin_cipo,
237255
pin_copi,
238256
mut pin_irq,
257+
pin_3v3_monitor,
258+
adc,
239259
) = cortex_m::interrupt::free(|cs| {
240260
(
241261
// uart_tx,
@@ -281,6 +301,10 @@ mod app {
281301
gpioa.pa7.into_alternate_af0(cs),
282302
// pin_irq
283303
gpioa.pa8.into_push_pull_output(cs),
304+
// pin_3v3_monitor
305+
gpioa.pa0.into_analog(cs),
306+
// ADC
307+
adc,
284308
)
285309
});
286310

@@ -364,6 +388,8 @@ mod app {
364388
press_button_reset_short: debouncr::debounce_2(false),
365389
rcc: Some(rcc),
366390
pin_irq,
391+
pin_3v3_monitor,
392+
adc,
367393
};
368394
let init = init::Monotonics(mono);
369395
(shared_resources, local_resources, init)
@@ -372,7 +398,7 @@ mod app {
372398
/// Our idle task.
373399
///
374400
/// This task is called when there is nothing else to do.
375-
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset, speaker], local = [pin_irq, rcc, speaker_task_handle: Option<speaker_pwm_stop::MyMono::SpawnHandle> = None])]
401+
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset, speaker], local = [pin_irq, rcc, speaker_task_handle: Option<speaker_pwm_stop::MyMono::SpawnHandle> = None, adc, pin_3v3_monitor])]
376402
fn idle(mut ctx: idle::Context) -> ! {
377403
// TODO: Get this from the VERSION static variable or from PKG_VERSION
378404
let mut register_state = RegisterState {
@@ -420,7 +446,8 @@ mod app {
420446
}
421447
}
422448
Some(Message::PowerButtonLongPress) => {
423-
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
449+
let power_state = ctx.shared.state_dc_power_enabled.lock(|r| *r);
450+
if power_state == DcPowerState::On || power_state == DcPowerState::Starting {
424451
defmt::info!("Power off requested!");
425452
ctx.shared
426453
.state_dc_power_enabled
@@ -452,26 +479,13 @@ mod app {
452479
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
453480
// Step 4 - Turn on PSU
454481
ctx.shared.pin_dc_on.set_high().unwrap();
455-
// Step 5 - Leave it in reset for a while.
456-
// TODO: Start monitoring 3.3V and 5.0V rails here
457-
// TODO: Take system out of reset when 3.3V and 5.0V are good
458-
// Returns an error if it's already scheduled (but we don't care)
459-
let _ = exit_reset::spawn_after(RESET_DURATION_MS.millis());
460-
// Set 6 - unmask the IRQ
461-
irq_forced_low = false;
482+
// Taking the system out of reset and enabling the IRQ line happens
483+
// later, when the power rail is settled
484+
defmt::info!("Waiting for power-good");
462485
}
463486
}
464487
Some(Message::PowerButtonRelease) => {
465-
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::Starting {
466-
defmt::info!("Power button released.");
467-
// Button released after power on. Change the power
468-
// state machine t "On". We were in 'Starting' to ignore
469-
// any further button events until the button had been
470-
// released.
471-
ctx.shared
472-
.state_dc_power_enabled
473-
.lock(|r| *r = DcPowerState::On);
474-
}
488+
defmt::info!("Power button released.");
475489
}
476490
Some(Message::ResetButtonShortPress) => {
477491
// Is the board powered on? Don't do a reset if it's powered off.
@@ -493,7 +507,7 @@ mod app {
493507
}
494508
}
495509
Some(Message::SpiEnable) => {
496-
if ctx.shared.state_dc_power_enabled.lock(|r| *r) != DcPowerState::Off {
510+
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
497511
// Turn on the SPI peripheral and expect four bytes (the
498512
// length of a Request).
499513
ctx.shared.spi.lock(|s| s.start(4));
@@ -583,7 +597,51 @@ mod app {
583597
);
584598
}
585599
}
586-
// TODO: Read ADC for 3.3V and 5.0V rails and check good
600+
// TODO: Also monitor 5.0V rail
601+
602+
// Wait for power-good
603+
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::Starting {
604+
// Reads the absolute voltage of the mon_3v3 line in mV.
605+
// This line is connected to the 3v3 supply through a 50% voltage divider,
606+
// so it should read 1650[mV] nominally.
607+
//
608+
// Note that read_abs_mv is relatively slow, as it internally reads
609+
// ADC values from both the pin and an internal voltage reference, and
610+
// does calculations including integer divisions.
611+
//
612+
// As we do not really require an accurate absolute voltage, but only
613+
// need to be sure that the 3v3 rail is reasonably close to the 3.3VP
614+
// rail (the permanent power rail supplying the BMC), this could be
615+
// rewritten using `ctx.local.adc.read(ctx.local.pin_3v3_monitor)`
616+
// in case performance becomes an issue.
617+
let mon_3v3 = ctx.local.adc.read_abs_mv(ctx.local.pin_3v3_monitor);
618+
defmt::trace!("mon_3v3 reading: {} mV", mon_3v3);
619+
if mon_3v3 < POWER_GOOD_THRESHOLD_MV / 2 {
620+
defmt::info!(
621+
"mon_3v3 below threshold of {} mV: {} mV",
622+
POWER_GOOD_THRESHOLD_MV / 2,
623+
mon_3v3
624+
);
625+
} else {
626+
defmt::info!(
627+
"Power good. Mon_3v3 at {} mV. Continue with startup sequence.",
628+
mon_3v3
629+
);
630+
// Change the power state machine to "On". We were in 'Starting' to ignore
631+
// any further button events until the button had been
632+
// released and to wait for power good.
633+
ctx.shared
634+
.state_dc_power_enabled
635+
.lock(|r| *r = DcPowerState::On);
636+
// Wait a bit before taking system out of reset.
637+
// Returns an error if it's already scheduled (but we don't care)
638+
let _ = exit_reset::spawn_after(RESET_DURATION_MS.millis());
639+
// Unmask the IRQ
640+
irq_forced_low = false;
641+
}
642+
}
643+
// TODO: Shutdown system if mon_3v3 falls below threshold
644+
// TODO: Maybe report voltages to CPU?
587645
}
588646
}
589647

@@ -797,7 +855,7 @@ mod app {
797855
#[task(shared = [pin_sys_reset, state_dc_power_enabled])]
798856
fn exit_reset(mut ctx: exit_reset::Context) {
799857
defmt::debug!("End reset");
800-
if ctx.shared.state_dc_power_enabled.lock(|r| *r) != DcPowerState::Off {
858+
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
801859
// Raising the reset line takes the rest of the system out of reset
802860
ctx.shared.pin_sys_reset.lock(|pin| pin.set_high().unwrap());
803861
}

0 commit comments

Comments
 (0)