#![no_std]
#![no_main]
use panic_semihosting as _;
use stm32f4xx_hal as hal;
use crate::hal::{
gpio::{gpioa::PA0, Edge, Input, PullDown},
interrupt, pac,
prelude::*,
rcc::{Clocks, Rcc},
spi::Spi,
timer::{CounterUs, Event, Timer},
};
use core::cell::{Cell, RefCell};
use core::fmt::Write;
use core::ops::DerefMut;
use cortex_m::interrupt::{free, CriticalSection, Mutex};
use heapless::String;
use hal::spi::{Mode, Phase, Polarity};
use core::f32::consts::{FRAC_PI_2, PI};
use cortex_m_rt::{entry, exception, ExceptionFrame};
use embedded_graphics::{
mono_font::{ascii::FONT_5X8, MonoTextStyleBuilder},
pixelcolor::BinaryColor,
prelude::*,
primitives::{Circle, Line, PrimitiveStyle, PrimitiveStyleBuilder},
text::Text,
};
use micromath::F32Ext;
use ssd1306::{prelude::*, Ssd1306};
static ELAPSED_MS: Mutex<Cell<u32>> = Mutex::new(Cell::new(0u32));
static ELAPSED_RESET_MS: Mutex<Cell<u32>> = Mutex::new(Cell::new(0u32));
static TIMER_TIM2: Mutex<RefCell<Option<CounterUs<pac::TIM2>>>> = Mutex::new(RefCell::new(None));
static STATE: Mutex<Cell<StopwatchState>> = Mutex::new(Cell::new(StopwatchState::Ready));
static BUTTON: Mutex<RefCell<Option<PA0<Input<PullDown>>>>> = Mutex::new(RefCell::new(None));
const CENTER: Point = Point::new(64, 40);
const SIZE: u32 = 23;
const START: f32 = -FRAC_PI_2;
#[derive(Clone, Copy)]
enum StopwatchState {
Ready,
Running,
Stopped,
}
#[entry]
fn main() -> ! {
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::peripheral::Peripherals::take().unwrap();
dp.RCC.apb2enr.write(|w| w.syscfgen().enabled());
let rcc = dp.RCC.constrain();
let clocks = setup_clocks(rcc);
let mut syscfg = dp.SYSCFG.constrain();
let gpioa = dp.GPIOA.split();
let gpioe = dp.GPIOE.split();
let mut board_btn = gpioa.pa0.into_pull_down_input();
board_btn.make_interrupt_source(&mut syscfg);
board_btn.enable_interrupt(&mut dp.EXTI);
board_btn.trigger_on_edge(&mut dp.EXTI, Edge::Falling);
let sck = gpioe.pe2.into_alternate();
let miso = gpioe.pe5.into_alternate();
let mosi = gpioe.pe6.into_alternate();
let spi = Spi::new(
dp.SPI4,
(sck, miso, mosi),
Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnFirstTransition,
},
2000.khz(),
&clocks,
);
let gpiog = dp.GPIOG.split();
let mut led3 = gpiog.pg13.into_push_pull_output();
let mut led4 = gpiog.pg14.into_push_pull_output();
let dc = gpioe.pe3.into_push_pull_output();
let mut ss = gpioe.pe4.into_push_pull_output();
let mut delay = hal::delay::Delay::new(cp.SYST, &clocks);
ss.set_high();
delay.delay_ms(100_u32);
ss.set_low();
let interface = SPIInterfaceNoCS::new(spi, dc);
let mut disp = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
disp.init().unwrap();
disp.flush().unwrap();
let mut timer = Timer::new(dp.TIM2, &clocks).counter();
timer.start(1.secs()).unwrap();
timer.listen(Event::TimeOut);
free(|cs| {
TIMER_TIM2.borrow(cs).replace(Some(timer));
BUTTON.borrow(cs).replace(Some(board_btn));
});
pac::NVIC::unpend(hal::pac::Interrupt::TIM2);
pac::NVIC::unpend(hal::pac::Interrupt::EXTI0);
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::EXTI0);
};
let mut state_led = false;
loop {
let elapsed = free(|cs| ELAPSED_MS.borrow(cs).get());
let mut format_buf = String::<10>::new();
format_elapsed(&mut format_buf, elapsed);
disp.clear();
let state = free(|cs| STATE.borrow(cs).get());
let state_msg = match state {
StopwatchState::Ready => "Ready",
StopwatchState::Running => "Running",
StopwatchState::Stopped => "Stopped",
};
state_led = !state_led;
match state {
StopwatchState::Ready => {
led3.set_high();
led4.set_low();
}
StopwatchState::Running => {
led4.set_low();
if state_led {
led3.set_high();
} else {
led3.set_low();
}
}
StopwatchState::Stopped => {
led3.set_low();
led4.set_high();
}
};
let text_style = MonoTextStyleBuilder::new()
.font(&FONT_5X8)
.text_color(BinaryColor::On)
.build();
Text::new(state_msg, Point::new(0, 0), text_style)
.draw(&mut disp)
.unwrap();
Text::new(
format_buf.as_str(),
Point::new((128 / 2) - 1, 0),
text_style,
)
.draw(&mut disp)
.unwrap();
draw_face().draw(&mut disp).unwrap();
draw_seconds_hand(elapsed_to_s(elapsed))
.draw(&mut disp)
.unwrap();
disp.flush().unwrap();
delay.delay_ms(100u32);
}
}
fn setup_clocks(rcc: Rcc) -> Clocks {
rcc.cfgr
.hclk(180.mhz())
.sysclk(180.mhz())
.pclk1(45.mhz())
.pclk2(90.mhz())
.freeze()
}
#[interrupt]
fn EXTI0() {
free(|cs| {
let mut btn_ref = BUTTON.borrow(cs).borrow_mut();
if let Some(ref mut btn) = btn_ref.deref_mut() {
btn.clear_interrupt_pending_bit();
let state = STATE.borrow(cs).get();
match state {
StopwatchState::Ready => {
ELAPSED_RESET_MS.borrow(cs).replace(0);
stopwatch_start(cs);
STATE.borrow(cs).replace(StopwatchState::Running);
}
StopwatchState::Running => {
ELAPSED_RESET_MS.borrow(cs).replace(0);
stopwatch_stop(cs);
stopwatch_reset_start(cs);
STATE.borrow(cs).replace(StopwatchState::Stopped);
}
StopwatchState::Stopped => {
let cell_reset = ELAPSED_RESET_MS.borrow(cs);
let val_reset = cell_reset.get();
if val_reset > 500_u32 {
ELAPSED_MS.borrow(cs).replace(0);
stopwatch_reset_stop(cs);
STATE.borrow(cs).replace(StopwatchState::Ready);
} else {
stopwatch_reset_stop(cs);
stopwatch_continue(cs);
STATE.borrow(cs).replace(StopwatchState::Running);
}
}
}
}
});
}
#[interrupt]
fn TIM2() {
free(|cs| {
if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() {
tim2.clear_interrupt(Event::TimeOut);
}
let cell = ELAPSED_MS.borrow(cs);
let cell_reset = ELAPSED_RESET_MS.borrow(cs);
let val = cell.get();
let val_reset = cell_reset.get();
match STATE.borrow(cs).get() {
StopwatchState::Ready => {
cell.replace(val + 1);
}
StopwatchState::Running => {
cell.replace(val + 1);
}
StopwatchState::Stopped => {
let mut btn_ref = BUTTON.borrow(cs).borrow_mut();
if let Some(ref mut btn) = btn_ref.deref_mut() {
if btn.is_high() {
cell_reset.replace(val_reset + 1);
}
}
}
}
});
}
fn stopwatch_start(cs: &CriticalSection) {
ELAPSED_MS.borrow(cs).replace(0);
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::TIM2);
}
}
fn stopwatch_continue(_cs: &CriticalSection) {
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::TIM2);
}
}
fn stopwatch_stop(_cs: &CriticalSection) {
pac::NVIC::mask(hal::pac::Interrupt::TIM2);
}
fn stopwatch_reset_start(cs: &CriticalSection) {
ELAPSED_RESET_MS.borrow(cs).replace(0);
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::TIM2);
}
}
fn stopwatch_reset_stop(_cs: &CriticalSection) {
pac::NVIC::mask(hal::pac::Interrupt::TIM2);
}
fn format_elapsed(buf: &mut String<10>, elapsed: u32) {
let minutes = elapsed_to_m(elapsed);
let seconds = elapsed_to_s(elapsed);
let millis = elapsed_to_ms(elapsed);
write!(buf, "{}:{:02}.{:03}", minutes, seconds, millis).unwrap();
}
fn elapsed_to_ms(elapsed: u32) -> u32 {
elapsed % 1000
}
fn elapsed_to_s(elapsed: u32) -> u32 {
(elapsed - elapsed_to_ms(elapsed)) % 60000 / 1000
}
fn elapsed_to_m(elapsed: u32) -> u32 {
elapsed / 60000
}
fn polar(angle: f32, radius: f32) -> Point {
CENTER + Point::new((angle.cos() * radius) as i32, (angle.sin() * radius) as i32)
}
fn draw_face() -> impl Iterator<Item = Pixel<BinaryColor>> {
let tic_len = 3.0;
let face =
Circle::new(CENTER, SIZE * 2).into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
let tics = (0..12).into_iter().map(move |index| {
let angle = START + (PI * 2.0 / 12.0) * index as f32;
let start = polar(angle, SIZE as f32);
let end = polar(angle, SIZE as f32 - tic_len);
Line::new(start, end)
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.pixels()
});
face.pixels().into_iter().chain(tics.flatten())
}
fn draw_seconds_hand(seconds: u32) -> impl Iterator<Item = Pixel<BinaryColor>> {
let seconds_radians = ((seconds as f32 / 60.0) * 2.0 * PI) + START;
let end = polar(seconds_radians, SIZE as f32);
let hand = Line::new(CENTER, end).into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
let decoration_position = polar(seconds_radians, SIZE as f32 - 23.0);
let decoration_style = PrimitiveStyleBuilder::new()
.fill_color(BinaryColor::Off)
.stroke_color(BinaryColor::On)
.stroke_width(1)
.build();
let decoration = Circle::new(decoration_position, 3).into_styled(decoration_style);
hand.pixels()
.into_iter()
.chain(decoration.pixels().into_iter())
}
#[exception]
unsafe fn HardFault(ef: &ExceptionFrame) -> ! {
panic!("{:#?}", ef);
}