Skip to content

Commit 6515b1f

Browse files
authored
Merge pull request #13 from Neotron-Compute/feature/power-button-support
Feature/power button support
2 parents 715b6f8 + fe93a16 commit 6515b1f

2 files changed

Lines changed: 167 additions & 36 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defmt-rtt = "0.2.0"
1515
cortex-m-rtic = "0.5"
1616
panic-probe = { version = "0.2.0", features = ["print-defmt"] }
1717
stm32f0xx-hal = { version = "0.17", features = ["stm32f031", "rt"] }
18-
18+
debouncr = "0.2"
1919

2020
[features]
2121
# set logging levels here

src/main.rs

Lines changed: 166 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,81 @@
11
#![no_main]
22
#![no_std]
33

4-
use neotron_bmc as _;
5-
6-
use neotron_bmc::monotonic::{Instant, Tim3Monotonic, U16Ext};
7-
8-
use cortex_m_rt::exception;
9-
4+
///! Neotron BMC Firmware
5+
///!
6+
///! This is the firmware for the Neotron Board Management Controller (BMC). It controls the power, reset, UART and PS/2 ports on a Neotron mainboard.
7+
///! For more details, see the `README.md` file.
8+
///!
9+
///! # Licence
10+
///! This source code as a whole is licensed under the GPL v3. Third-party crates are covered by their respective licences.
11+
use cortex_m::interrupt::free as disable_interrupts;
1012
use rtic::app;
11-
1213
use stm32f0xx_hal::{
1314
gpio::gpioa::{PA10, PA11, PA12, PA9},
1415
gpio::gpiob::{PB0, PB1},
15-
gpio::{Alternate, Output, PushPull, AF1},
16+
gpio::gpiof::{PF0, PF1},
17+
gpio::{Alternate, Input, Output, PullUp, PushPull, AF1},
1618
pac,
1719
prelude::*,
1820
serial,
1921
};
2022

21-
use cortex_m::interrupt::free as disable_interrupts;
23+
use neotron_bmc as _;
24+
use neotron_bmc::monotonic::{Instant, Tim3Monotonic, U16Ext};
2225

26+
/// Version string auto-generated by git.
2327
static VERSION: &'static str = include_str!(concat!(env!("OUT_DIR"), "/version.txt"));
2428

25-
const PERIOD_MS: u16 = 1000;
29+
/// At what rate do we blink the status LED when we're running?
30+
const LED_PERIOD_MS: u16 = 1000;
31+
32+
/// How often we poll the power and reset buttons in milliseconds.
33+
const DEBOUNCE_POLL_INTERVAL_MS: u16 = 75;
34+
35+
/// The states we can be in controlling the DC power
36+
#[derive(Copy, Clone, PartialEq, Eq)]
37+
#[repr(u8)]
38+
pub enum DcPowerState {
39+
/// We've just enabled the DC power (so ignore any incoming long presses!)
40+
Starting = 1,
41+
/// We are now fully on. Look for a long press to turn off.
42+
On = 2,
43+
/// We are fully off.
44+
Off = 0,
45+
}
2646

2747
#[app(device = crate::pac, peripherals = true, monotonic = crate::Tim3Monotonic)]
2848
const APP: () = {
2949
struct Resources {
30-
uart_cts: PA11<Alternate<AF1>>,
31-
uart_rts: PA12<Alternate<AF1>>,
50+
/// The power LED (D1101)
3251
led_power: PB0<Output<PushPull>>,
52+
/// The status LED (D1102)
3353
led_status: PB1<Output<PushPull>>,
54+
/// The FTDI UART header (J105)
3455
serial: serial::Serial<pac::USART1, PA9<Alternate<AF1>>, PA10<Alternate<AF1>>>,
56+
/// The Clear-To-Send line on the FTDI UART header (which the serial object can't handle)
57+
uart_cts: PA11<Alternate<AF1>>,
58+
/// The Ready-To-Receive line on the FTDI UART header (which the serial object can't handle)
59+
uart_rts: PA12<Alternate<AF1>>,
60+
/// The power button
61+
button_power: PF0<Input<PullUp>>,
62+
/// The reset button
63+
button_reset: PF1<Input<PullUp>>,
64+
/// Tracks power button state for short presses. 75ms x 2 = 150ms is a short press
65+
button_power_short_press: debouncr::Debouncer<u8, debouncr::Repeat2>,
66+
/// Tracks power button state for long presses. 75ms x 16 = 1200ms is a long press
67+
button_power_long_press: debouncr::Debouncer<u16, debouncr::Repeat16>,
68+
/// Tracks DC power state
69+
dc_power_enabled: DcPowerState,
3570
}
3671

37-
#[init(spawn = [led_status_blink])]
72+
/// The entry point to our application.
73+
///
74+
/// Sets up the hardware and spawns the regular tasks.
75+
///
76+
/// * Task `led_status_blink` - blinks the LED
77+
/// * Task `button_poll` - checks the power and reset buttons
78+
#[init(spawn = [led_status_blink, button_poll])]
3879
fn init(ctx: init::Context) -> init::LateResources {
3980
defmt::info!("Neotron BMC version {:?} booting", VERSION);
4081

@@ -55,17 +96,28 @@ const APP: () = {
5596
defmt::info!("Creating pins...");
5697
let gpioa = dp.GPIOA.split(&mut rcc);
5798
let gpiob = dp.GPIOB.split(&mut rcc);
58-
let (uart_tx, uart_rx, uart_cts, uart_rts, mut led_power, led_status) =
59-
disable_interrupts(|cs| {
60-
(
61-
gpioa.pa9.into_alternate_af1(cs),
62-
gpioa.pa10.into_alternate_af1(cs),
63-
gpioa.pa11.into_alternate_af1(cs),
64-
gpioa.pa12.into_alternate_af1(cs),
65-
gpiob.pb0.into_push_pull_output(cs),
66-
gpiob.pb1.into_push_pull_output(cs),
67-
)
68-
});
99+
let gpiof = dp.GPIOF.split(&mut rcc);
100+
let (
101+
uart_tx,
102+
uart_rx,
103+
uart_cts,
104+
uart_rts,
105+
mut led_power,
106+
led_status,
107+
button_power,
108+
button_reset,
109+
) = disable_interrupts(|cs| {
110+
(
111+
gpioa.pa9.into_alternate_af1(cs),
112+
gpioa.pa10.into_alternate_af1(cs),
113+
gpioa.pa11.into_alternate_af1(cs),
114+
gpioa.pa12.into_alternate_af1(cs),
115+
gpiob.pb0.into_push_pull_output(cs),
116+
gpiob.pb1.into_push_pull_output(cs),
117+
gpiof.pf0.into_pull_up_input(cs),
118+
gpiof.pf1.into_pull_up_input(cs),
119+
)
120+
});
69121

70122
defmt::info!("Creating UART...");
71123

@@ -76,7 +128,9 @@ const APP: () = {
76128

77129
ctx.spawn.led_status_blink().unwrap();
78130

79-
led_power.set_high().unwrap();
131+
ctx.spawn.button_poll().unwrap();
132+
133+
led_power.set_low().unwrap();
80134

81135
defmt::info!("Init complete!");
82136

@@ -86,18 +140,31 @@ const APP: () = {
86140
uart_rts,
87141
led_power,
88142
led_status,
143+
button_power,
144+
button_reset,
145+
button_power_short_press: debouncr::debounce_2(false),
146+
button_power_long_press: debouncr::debounce_16(false),
147+
dc_power_enabled: DcPowerState::Off,
89148
}
90149
}
91150

151+
/// Our idle task.
152+
///
153+
/// This task is called when there is nothing else to do. We
154+
/// do a little logging, then put the CPU to sleep waiting for an interrupt.
92155
#[idle(resources = [])]
93-
fn idle(_: idle::Context) -> ! {
156+
fn idle(_ctx: idle::Context) -> ! {
94157
defmt::info!("Idle is running...");
95158
loop {
96159
cortex_m::asm::wfi();
97-
defmt::info!("It is now {}", crate::Instant::now().counts());
160+
defmt::trace!("It is now {}", crate::Instant::now().counts());
98161
}
99162
}
100163

164+
/// This is the USART1 task.
165+
///
166+
/// It fires whenever there is new data received on USART1. We should flag to the host
167+
/// that data is available.
101168
#[task(binds = USART1, resources=[serial])]
102169
fn usart1_interrupt(ctx: usart1_interrupt::Context) {
103170
// Reading the register clears the RX-Not-Empty-Interrupt flag.
@@ -111,12 +178,16 @@ const APP: () = {
111178
}
112179
}
113180

181+
/// This is the LED blink task.
182+
///
183+
/// This task is called periodically. We check whether the status LED is currently on or off,
184+
/// and set it to the opposite. This makes the LED blink.
114185
#[task(schedule = [led_status_blink], resources = [led_status])]
115186
fn led_status_blink(ctx: led_status_blink::Context) {
116187
// Use the safe local `static mut` of RTIC
117188
static mut LED_STATE: bool = false;
118189

119-
defmt::info!("blink time {}", ctx.scheduled.counts());
190+
defmt::trace!("blink time {}", ctx.scheduled.counts());
120191

121192
if *LED_STATE {
122193
ctx.resources.led_status.set_low().unwrap();
@@ -125,22 +196,82 @@ const APP: () = {
125196
ctx.resources.led_status.set_high().unwrap();
126197
*LED_STATE = true;
127198
}
128-
let next = ctx.scheduled + PERIOD_MS.millis();
129-
defmt::info!("Next blink at {}", next.counts());
199+
let next = ctx.scheduled + LED_PERIOD_MS.millis();
200+
defmt::trace!("Next blink at {}", next.counts());
130201
ctx.schedule.led_status_blink(next).unwrap();
131202
}
132203

204+
/// This task polls our power and reset buttons.
205+
///
206+
/// We poll them rather than setting up an interrupt as we need to debounce them, which involves waiting a short period and checking them again. Given that we have to do that, we might as well not bother with the interrupt.
207+
#[task(schedule = [button_poll], resources = [led_power, button_power, button_power_short_press, button_power_long_press, dc_power_enabled])]
208+
fn button_poll(ctx: button_poll::Context) {
209+
// Poll button
210+
let pressed: bool = ctx.resources.button_power.is_low().unwrap();
211+
212+
// Update state
213+
let short_edge = ctx.resources.button_power_short_press.update(pressed);
214+
let long_edge = ctx.resources.button_power_long_press.update(pressed);
215+
216+
// Dispatch event
217+
if short_edge == Some(debouncr::Edge::Rising) {
218+
defmt::trace!(
219+
"Power short press in! {}",
220+
*ctx.resources.dc_power_enabled as u8
221+
);
222+
if *ctx.resources.dc_power_enabled == DcPowerState::Off {
223+
*ctx.resources.dc_power_enabled = DcPowerState::Starting;
224+
ctx.resources.led_power.set_high().unwrap();
225+
defmt::info!("Power on!");
226+
// TODO: Enable DC PSU here
227+
// TODO: Start monitoring 3.3V and 5.0V rails here
228+
// TODO: Take system out of reset when 3.3V and 5.0V are good
229+
}
230+
} else if short_edge == Some(debouncr::Edge::Falling) {
231+
defmt::trace!(
232+
"Power short press out! {}",
233+
*ctx.resources.dc_power_enabled as u8
234+
);
235+
match *ctx.resources.dc_power_enabled {
236+
DcPowerState::Starting => {
237+
*ctx.resources.dc_power_enabled = DcPowerState::On;
238+
}
239+
DcPowerState::On => {
240+
// TODO: Tell host that power off was requested
241+
}
242+
DcPowerState::Off => {
243+
// Ignore
244+
}
245+
}
246+
}
247+
248+
if long_edge == Some(debouncr::Edge::Rising) {
249+
defmt::trace!(
250+
"Power long press in! {}",
251+
*ctx.resources.dc_power_enabled as u8
252+
);
253+
if *ctx.resources.dc_power_enabled == DcPowerState::On {
254+
*ctx.resources.dc_power_enabled = DcPowerState::Off;
255+
ctx.resources.led_power.set_low().unwrap();
256+
defmt::info!("Power off!");
257+
// TODO: Put system in reset here
258+
// TODO: Disable DC PSU here
259+
}
260+
}
261+
262+
// Re-schedule the timer interrupt
263+
ctx.schedule
264+
.button_poll(ctx.scheduled + DEBOUNCE_POLL_INTERVAL_MS.millis())
265+
.unwrap();
266+
}
267+
133268
// Let it use the USB interrupt as a generic software interrupt.
134269
extern "C" {
135270
fn USB();
136271
}
137272
};
138273

139-
#[exception]
140-
unsafe fn DefaultHandler(value: i16) {
141-
defmt::panic!("DefaultHandler({})", value);
142-
}
143-
274+
// TODO: Pins we haven't used yet
144275
// SPI pins
145276
// spi_clk: gpioa.pa5.into_alternate_af0(cs),
146277
// spi_cipo: gpioa.pa6.into_alternate_af0(cs),

0 commit comments

Comments
 (0)