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;
1012use rtic:: app;
11-
1213use 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.
2327static 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 ) ]
2848const 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