diff --git a/Cargo.toml b/Cargo.toml index faec0c6..2619f66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,34 +6,17 @@ name = "fan_monitor" version = "0.1.0" [dependencies] +embedded-hal = "0.2.6" +nb = "0.1.2" cortex-m = "0.6.0" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" panic-halt = "0.2.0" -embedded-hal = "0.2.3" hd44780-driver = "0.3.0" -nb = "0.1.1" - -[dependencies.stm32f1] -version = "0.8.0" -features = ["stm32f103", "rt"] [dependencies.stm32f1xx-hal] -version = "0.4.0" -features = ["stm32f103", "rt"] - -# Uncomment for the panic example. -# panic-itm = "0.4.1" - -# Uncomment for the allocator example. -# alloc-cortex-m = "0.3.5" - -# Uncomment for the device example. -# Update `memory.x`, set target to `thumbv7em-none-eabihf` in `.cargo/config`, -# and then use `cargo build --examples device` to build it. -# [dependencies.stm32f3] -# features = ["stm32f303", "rt"] -# version = "0.7.1" +version = "0.7.0" +features = ["stm32f103", "rt", "medium"] # this lets you use `cargo fix`! [[bin]] @@ -45,3 +28,4 @@ bench = false codegen-units = 1 # better optimizations debug = true # symbols are nice and they don't increase the size on Flash lto = true # better optimizations + diff --git a/openocd.gdb b/openocd.gdb new file mode 100644 index 0000000..7795319 --- /dev/null +++ b/openocd.gdb @@ -0,0 +1,40 @@ +target extended-remote :3333 + +# print demangled symbols +set print asm-demangle on + +# set backtrace limit to not have infinite backtrace loops +set backtrace limit 32 + +# detect unhandled exceptions, hard faults and panics +break DefaultHandler +break HardFault +break rust_begin_unwind +# # run the next few lines so the panic message is printed immediately +# # the number needs to be adjusted for your panic handler +# commands $bpnum +# next 4 +# end + +# *try* to stop at the user entry point (it might be gone due to inlining) +break main + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.txt uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 8000000 must match the core clock frequency +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load + +# start the process but immediately halt the processor +stepi diff --git a/src/lcd_gui.rs b/src/lcd_gui.rs new file mode 100644 index 0000000..b185180 --- /dev/null +++ b/src/lcd_gui.rs @@ -0,0 +1,110 @@ +use core::{sync::atomic::{AtomicBool, Ordering}}; + +use embedded_hal::{ + //digital::v1_compat::OldOutputPin, + digital::v2::OutputPin, + Qei, + blocking::delay::{DelayUs, DelayMs}, +}; + +use hd44780_driver::{ + HD44780, + display_mode::DisplayMode, + bus::DataBus, +}; + +pub struct LCDGui +where + L: LCDScreen, + Q: Qei, + B: OutputPin, +{ + lcd: L, + qei: Q, + button: &'static AtomicBool, + backlight: Option, + count: u16, +} + +impl LCDGui +where + L: LCDScreen, + Q: Qei, + B: OutputPin, +{ + + pub fn new(lcd: L, qei: Q, button: &'static AtomicBool, backlight: Option) + -> LCDGui { + use hd44780_driver::{Cursor, CursorBlink, Display}; + + let count = qei.count(); + let mut gui = LCDGui {lcd, qei, button, backlight, count}; + + if let Some(bl) = &mut gui.backlight { let _ = bl.set_high(); }; + + gui.lcd.reset(); + gui.lcd.clear(); + gui.lcd.set_display_mode( + DisplayMode { + display: Display::On, + cursor_visibility: Cursor::Visible, + cursor_blink: CursorBlink::On, + } + ); + let _ = gui.lcd.write_str("Hello world!"); + + gui + } + + pub fn run(&mut self) -> ! { + loop { + //TODO deduplicate button detection + if self.button.swap(false, Ordering::AcqRel) { + self.lcd.write_str("paf").unwrap(); + } + + if self.count != self.qei.count() { + self.count = self.qei.count(); + self.lcd.set_cursor_pos(0x40); + self.lcd.write_str(" ").unwrap(); + self.lcd.set_cursor_pos(0x40); + write!(self.lcd, "{}", self.qei.count()/2).unwrap(); + } + + // put device in sleep mode until next interrupt (button or timer) + cortex_m::asm::wfi(); + } + } +} + +pub trait LCDScreen: core::fmt::Write { + + fn reset(&mut self); + fn clear(&mut self); + fn set_display_mode(&mut self, mode: DisplayMode); + fn set_cursor_pos(&mut self, positions: u8); +} + +impl LCDScreen for HD44780 +where + D: DelayUs + DelayMs, + DB: DataBus, +{ + + fn reset(&mut self) { + self.reset() + } + + fn clear(&mut self) { + self.clear() + } + + fn set_display_mode(&mut self, mode: DisplayMode) { + self.set_display_mode(mode) + } + + fn set_cursor_pos(&mut self, position: u8) { + self.set_cursor_pos(position); + } +} + diff --git a/src/main.rs b/src/main.rs index 4b587be..fab3563 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,15 @@ #![no_std] #![no_main] + +mod lcd_gui; + +use lcd_gui::LCDGui; extern crate panic_halt; use core::{ cell::RefCell, - fmt::Write, + sync::atomic::{AtomicBool, Ordering}, }; use cortex_m::interrupt::Mutex; @@ -17,63 +21,108 @@ use embedded_hal::digital::{ }; use stm32f1xx_hal::{ - delay::Delay, - gpio::{gpioc, gpiob, Output, PushPull}, + gpio::*, pac, - pac::{interrupt, Interrupt}, + pac::{interrupt, Interrupt, EXTI}, prelude::*, - timer::{Event, Timer}, - qei::Qei, + timer::{Timer, CountDownTimer, Event}, + qei::{QeiOptions, SlaveMode}, + rtc::Rtc, }; -use hd44780_driver::{Cursor, CursorBlink, Display, DisplayMode, HD44780}; +//-------------------------------------------------------------------------------------------------- +/* system config */ +const GUI_TICK_SEC: f32 = 0.2; +const HEARTBEAT_SEC: f32 = 1.0; + +const TEMPS_TICK_SEC: u32 = 5; + +//-------------------------------------------------------------------------------------------------- +/* interrupt variables */ + +// tick/heartbeat timer static PCB_LED: Mutex>>>> = Mutex::new(RefCell::new(None)); +static TICK_TIMER: Mutex>>> + = Mutex::new(RefCell::new(None)); + +// button interrupt +static BUTTON_PIN: Mutex>>>> + = Mutex::new(RefCell::new(None)); +static BUTTON_FLAG: AtomicBool = AtomicBool::new(false); + +// temps interrupt static RED_LED: Mutex>>>> = Mutex::new(RefCell::new(None)); -static TIMER: Mutex>>> = Mutex::new(RefCell::new(None)); -static TIMER2: Mutex>>> = Mutex::new(RefCell::new(None)); +static RTC: Mutex>> = Mutex::new(RefCell::new(None)); +static G_EXTI: Mutex>> = Mutex::new(RefCell::new(None)); +//-------------------------------------------------------------------------------------------------- +/* interrupt service routines */ #[interrupt] fn TIM2() { - cortex_m::interrupt::free(|cs| { - let _ = PCB_LED.borrow(cs).borrow_mut().as_mut().unwrap().toggle(); - }); + static mut TICK_DIVIDER: u32 = 0; cortex_m::interrupt::free(|cs| { - let _ = TIMER.borrow(cs).borrow_mut().as_mut().unwrap().clear_update_interrupt_flag(); + let _ = TICK_TIMER.borrow(cs).borrow_mut().as_mut().unwrap().clear_update_interrupt_flag(); }); + + *TICK_DIVIDER += 1; + if *TICK_DIVIDER >= (HEARTBEAT_SEC / GUI_TICK_SEC) as u32 { + *TICK_DIVIDER = 0; + cortex_m::interrupt::free(|cs| { + let _ = PCB_LED.borrow(cs).borrow_mut().as_mut().unwrap().toggle(); + }); + } } #[interrupt] -fn TIM3() { +fn EXTI9_5() { cortex_m::interrupt::free(|cs| { - let _ = RED_LED.borrow(cs).borrow_mut().as_mut().unwrap().toggle(); + let _ = BUTTON_PIN.borrow(cs).borrow_mut().as_mut().unwrap().clear_interrupt_pending_bit(); + }); + + BUTTON_FLAG.store(true, Ordering::Release); +} + +#[interrupt] +fn RTCALARM() { + + cortex_m::interrupt::free(|cs| { + let _ = G_EXTI.borrow(cs).borrow_mut().as_mut().unwrap().pr.write(|w| w.pr17().set_bit()); }); cortex_m::interrupt::free(|cs| { - let _ = TIMER2.borrow(cs).borrow_mut().as_mut().unwrap().clear_update_interrupt_flag(); + let mut rtc = RTC.borrow(cs).borrow_mut(); + let time = rtc.as_mut().unwrap().current_time(); + rtc.as_mut().unwrap().set_alarm(time + TEMPS_TICK_SEC); //also clears the flag + }); + + cortex_m::interrupt::free(|cs| { + let _ = RED_LED.borrow(cs).borrow_mut().as_mut().unwrap().toggle(); }); } +//-------------------------------------------------------------------------------------------------- +/* main function */ #[entry] fn main() -> ! { - // Get access to the core peripherals from the cortex-m crate - let cp = cortex_m::Peripherals::take().unwrap(); - // Get access to the device specific peripherals from the peripheral access crate - let dp = pac::Peripherals::take().unwrap(); + + /* setup */ + + // basic setup + let mut cp = cortex_m::Peripherals::take().unwrap(); + let mut dp = pac::Peripherals::take().unwrap(); - // Take ownership over the raw flash and rcc devices and convert them into the corresponding - // HAL structs - let mut flash = dp.FLASH.constrain(); + // clocks config let mut rcc = dp.RCC.constrain(); - - // Freeze the configuration of all the clocks in the system and store the frozen frequencies in - // `clocks` - let clocks = rcc.cfgr.freeze(&mut flash.acr); + let mut flash = dp.FLASH.constrain(); + let clocks = rcc.cfgr + .use_hse(8.mhz()) + .freeze(&mut flash.acr); // GPIOs let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); @@ -83,45 +132,49 @@ fn main() -> ! { // setup LED let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); let _ = led.set_high(); + cortex_m::interrupt::free(|cs| { PCB_LED.borrow(cs).borrow_mut().replace(led) }); - // Configure the syst timer to trigger an update every second - let mut timer = Timer::tim2(dp.TIM2, 1.hz(), clocks, &mut rcc.apb1); + // Configure the timer 2 as tick timer + let mut timer = Timer::tim2(dp.TIM2, &clocks, &mut rcc.apb1) + .start_count_down(((1.0/GUI_TICK_SEC) as u32).hz()); timer.listen(Event::Update); + cortex_m::interrupt::free(|cs| { - TIMER.borrow(cs).borrow_mut().replace(timer) + TICK_TIMER.borrow(cs).borrow_mut().replace(timer) }); // setup LED - let mut led = gpiob.pb11.into_push_pull_output(&mut gpiob.crh); - let _ = led.set_high(); + let mut red_led = gpiob.pb11.into_push_pull_output(&mut gpiob.crh); + let _ = red_led.set_high(); + cortex_m::interrupt::free(|cs| { - RED_LED.borrow(cs).borrow_mut().replace(led) + RED_LED.borrow(cs).borrow_mut().replace(red_led) + }); + + // set EXTI 17 (see note in 18.4.2, in short : needed for rtc ISR to trigger) + dp.EXTI.ftsr.write(|w| w.tr17().set_bit()); + dp.EXTI.imr.write(|w| w.mr17().set_bit()); + + // setup RTC + let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut rcc.apb1, &mut dp.PWR); + let mut rtc = Rtc::rtc(dp.RTC, &mut backup_domain); + rtc.set_alarm(rtc.current_time() + TEMPS_TICK_SEC); + rtc.listen_alarm(); + + cortex_m::interrupt::free(|cs| { + RTC.borrow(cs).borrow_mut().replace(rtc) }); - // Configure the syst timer to trigger an update every second - let mut timer = Timer::tim3(dp.TIM3, 5.hz(), clocks, &mut rcc.apb1); - timer.listen(Event::Update); - cortex_m::interrupt::free(|cs| { - TIMER2.borrow(cs).borrow_mut().replace(timer) - }); - - // Enable timer interrupts - unsafe { - cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2); - cortex_m::peripheral::NVIC::unmask(Interrupt::TIM3); - } - let mut afio = dp.AFIO.constrain(&mut rcc.apb2); // Setup display - let mut lcd = { - - // Set backlight - let mut backlight_pin = gpiob.pb9.into_push_pull_output(&mut gpiob.crh); - let _ = backlight_pin.set_high(); + let mut gui = { + use stm32f1xx_hal::{gpio::*, delay::Delay}; + + use hd44780_driver::HD44780; // Disable JTAG to free pins let (d3_pin, d5_pin, d7_pin) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4); @@ -140,9 +193,9 @@ fn main() -> ! { // Force write mode let mut rw_pin = gpioa.pa9.into_push_pull_output(&mut gpioa.crh); - rw_pin.set_low(); + rw_pin.set_low().unwrap(); - HD44780::new_8bit( + let lcd = HD44780::new_8bit( OldOutputPin::new(rs_pin), OldOutputPin::new(en_pin), OldOutputPin::new(d0_pin), @@ -154,39 +207,56 @@ fn main() -> ! { OldOutputPin::new(d6_pin), OldOutputPin::new(d7_pin), Delay::new(cp.SYST, clocks) - )}; + ); - // setup encoder - let enc = Qei::tim4(dp.TIM4, (gpiob.pb6, gpiob.pb7), &mut afio.mapr, &mut rcc.apb1); + // setup button (interrupt) + let mut but_pin = gpiob.pb8.into_pull_up_input(&mut gpiob.crh); + but_pin.make_interrupt_source(&mut afio); + but_pin.trigger_on_edge(&dp.EXTI, Edge::RISING); + but_pin.enable_interrupt(&dp.EXTI); - lcd.reset(); - lcd.clear(); - lcd.set_display_mode( - DisplayMode { - display: Display::On, - cursor_visibility: Cursor::Visible, - cursor_blink: CursorBlink::On, - } - ); - let _ = lcd.write_str("Hello world!"); + cortex_m::interrupt::free(|cs| { + BUTTON_PIN.borrow(cs).borrow_mut().replace(but_pin); + }); - lcd.set_cursor_pos(0x40); - write!(lcd, "{}", enc.count()/4).unwrap(); - let mut old_count = enc.count(); + // Set backlight + let backlight_pin = gpiob.pb9.into_push_pull_output(&mut gpiob.crh); - loop { - if enc.count() != old_count { - old_count = enc.count(); - lcd.set_cursor_pos(0x40); - lcd.write_str(" "); - lcd.set_cursor_pos(0x40); - write!(lcd, "{}", enc.count()/4).unwrap(); - } - for i in 0..10000 { - cortex_m::asm::nop; - } + // setup encoder + let enc = Timer::tim4(dp.TIM4, &clocks, &mut rcc.apb1) + .qei((gpiob.pb6, gpiob.pb7), &mut afio.mapr, + QeiOptions { + slave_mode:SlaveMode::EncoderMode2, + auto_reload_value: 200 + }); + + LCDGui::new(lcd, enc, &BUTTON_FLAG, Some(backlight_pin)) + }; + + let exti = dp.EXTI; + cortex_m::interrupt::free(|cs| { + G_EXTI.borrow(cs).borrow_mut().replace(exti); + }); + + // Enable interrupts + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2); + cortex_m::peripheral::NVIC::unmask(Interrupt::EXTI9_5); + cortex_m::peripheral::NVIC::unmask(Interrupt::RTCALARM); } + + // allow the debugging to continue in sleep and stop modes + #[cfg(debug_assertions)] + dp.DBGMCU.cr.write(|w| w.dbg_sleep().set_bit()); + #[cfg(debug_assertions)] + dp.DBGMCU.cr.write(|w| w.dbg_stop().set_bit()); + + // configure stop mode (not working yet, probably due to fake chip) + //dp.PWR.cr.write(|w| w.pdds().stop_mode()); + //dp.PWR.cr.write(|w| w.lpds().set_bit()); + cp.SCB.set_sleepdeep(); + + /* run */ + gui.run(); } - -