#![cfg_attr(test, allow(unused_imports))] #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![feature(half_open_range_patterns)] #![feature(exclusive_range_pattern)] #![allow(dead_code)] // custom panic handler #[cfg(not(test))] use core::panic::PanicInfo; use core::{cell::RefCell, ops::DerefMut}; use cortex_m::{interrupt::free, interrupt::Mutex, peripheral::NVIC}; use cortex_m_rt::entry; use stm32l4xx_hal::{delay::Delay, device::{I2C1, TIM2, TIM7}, gpio::{ Alternate, Edge, Floating, Input, OpenDrain, Output, PullUp, PushPull, AF4, PA3, PB5, PC15, }, gpio::{State, PA10, PA9}, i2c::I2c, interrupt, pac, prelude::*, rcc, stm32::Interrupt, timer::{Timer, Event}, }; mod ds3231; mod nixie; mod pca9685; mod tusb322; use nixie::*; // Local peripheral mappings type RtcInt = PB5>; type FaultInt = PA3>; type FaultLed = PC15>; type I2c1 = I2c>>,PA10>>)>; type FpsTimer = Timer; type CycleTimer = Timer; // Global peripheral singletons static RTC_INT: Mutex>> = Mutex::new(RefCell::new(None)); static FAULT_INT: Mutex>> = Mutex::new(RefCell::new(None)); static FAULT_LED: Mutex>> = Mutex::new(RefCell::new(None)); static I2C: Mutex>> = Mutex::new(RefCell::new(None)); static REFRESH_TIMER: Mutex>> = Mutex::new(RefCell::new(None)); static CYCLE_TIMER: Mutex>> = Mutex::new(RefCell::new(None)); static CLOCK: Mutex> = Mutex::new(RefCell::new(Clock::default())); #[cfg(not(test))] #[entry] fn main() -> ! { // Acquire a singleton instance for the chip's peripherals let mut dp = pac::Peripherals::take().unwrap(); let cp = pac::CorePeripherals::take().unwrap(); // Consume the raw peripheral and return a new object that implements a higher level API let mut flash = dp.FLASH.constrain(); let mut rcc = dp.RCC.constrain(); let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); // Configure clocks to run at maximum frequency off internal oscillator let clocks = rcc .cfgr .pll_source(rcc::PllSource::HSI16) .sysclk(64.mhz()) .hclk(64.mhz()) .pclk1(64.mhz()) .pclk2(64.mhz()) .hsi48(true) .freeze(&mut flash.acr, &mut pwr); // Configure delay timer that operates off systick timer let mut delay_timer = Delay::new(cp.SYST, clocks); // Split GPIO peripheral into independent pins and registers let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); let mut gpioc = dp.GPIOC.split(&mut rcc.ahb2); // Configure high voltage PSU enable pin on PA2 let mut hv_enable = gpioa.pa2.into_push_pull_output_with_state(&mut gpioa.moder, &mut gpioa.otyper, State::Low); // Configure serial port // let tx = gpiob.pb6.into_af7(&mut gpiob.moder, &mut gpiob.afrl); // let rx = gpiob.pb7.into_af7(&mut gpiob.moder, &mut gpiob.afrl); // let _serial = Serial::usart1( // dp.USART1, // (tx, rx), // Config::default().baudrate(115_200.bps()), // clocks, // &mut rcc.apb2, // ); // Configure fault LED output on PC15 let fault_led = gpioc.pc15.into_push_pull_output_with_state(&mut gpioc.moder, &mut gpioc.otyper, State::Low); // Store fault LED in static singleton so that it's accessible from anywhere free(|cs| { FAULT_LED.borrow(cs).replace(Some(fault_led)); }); // Configure fault input interrupt on PA3 let mut fault_int = gpioa.pa3.into_pull_up_input(&mut gpioa.moder, &mut gpioa.pupdr); fault_int.make_interrupt_source(&mut dp.SYSCFG, &mut rcc.apb2); fault_int.enable_interrupt(&mut dp.EXTI); fault_int.trigger_on_edge(&mut dp.EXTI, Edge::FALLING); // Sanity check that fault pin isn't already set (active low) before enabling interrupt if fault_int.is_high().unwrap() { // Configure NVIC mask to enable interrupt source unsafe { NVIC::unmask(Interrupt::EXTI3); } // Store fault interrupt in static singleton so that interrupt has access to it free(|cs| { FAULT_INT.borrow(cs).replace(Some(fault_int)); }); } else { panic!(); } // Enable RNG peripheral let rng = dp.RNG.enable(&mut rcc.ahb2, clocks); // Configure I2C SCL pin let scl = gpioa.pa9.into_open_drain_output(&mut gpioa.moder, &mut gpioa.otyper); let scl = scl.into_af4(&mut gpioa.moder, &mut gpioa.afrh); // Configure I2C SDA pin let sda = gpioa.pa10.into_open_drain_output(&mut gpioa.moder, &mut gpioa.otyper); let sda = sda.into_af4(&mut gpioa.moder, &mut gpioa.afrh); // Initialize I2C (configured for 1Mhz, but actually runs at 600kHz) let mut i2c = I2c::i2c1(dp.I2C1, (scl, sda), 1.mhz(), clocks, &mut rcc.apb1r1); // Initialize TUSB322 (USB Type-C configuration chip) tusb322::init(TUSB322_ADDR, &mut i2c); // Initialize DS3231 (RTC) ds3231::init(DS3231_ADDR, &mut i2c); // ds3231::set_date(DS3231_ADDR, &mut i2c, ds3231::Weekday::Wednesday, 15, 9, 21, 20); // ds3231::set_time(DS3231_ADDR, &mut i2c, 00, 37, 12); // Configure input interrupt pin from DS3231 on PB5 // Interrupt is pulled high, with open drain on DS3231 let mut rtc_int = gpiob.pb5.into_floating_input(&mut gpiob.moder, &mut gpiob.pupdr); rtc_int.make_interrupt_source(&mut dp.SYSCFG, &mut rcc.apb2); rtc_int.enable_interrupt(&mut dp.EXTI); rtc_int.trigger_on_edge(&mut dp.EXTI, Edge::FALLING); // Configure NVIC mask to enable interrupt from DS3231 unsafe { NVIC::unmask(Interrupt::EXTI9_5); } // Store RTC interrupt in static singleton so that interrupt has access to it free(|cs| { RTC_INT.borrow(cs).replace(Some(rtc_int)); }); // Configure DAC AMP enable pin for AD8591 on PB1 let mut _dac_enable = gpiob.pb1.into_push_pull_output_with_state(&mut gpiob.moder, &mut gpiob.otyper, State::High); // Configure DAC VIN for AD8591 on PA5 // Note that this pin should actually be configured as analog output (for DAC) // but stm32l4xx_hal doesn't have support for the DAC as of now. We also currently // set the output to only the highest possible voltage, so the same functionality // can be achieved by configuring the pin as a digital output set to high. let mut _dac_output = gpioa.pa5.into_push_pull_output_with_state(&mut gpioa.moder, &mut gpioa.otyper, State::High); // Configure PWM enable pin (active low) for PCA9685 on PA7 let mut pwm_enable = gpioa.pa7.into_push_pull_output_with_state(&mut gpioa.moder, &mut gpioa.otyper, State::High); // Initialize the PCA9685 display refresh timer let refresh_timer = Timer::tim2(dp.TIM2, nixie::DISPLAY_REFRESH_FPS.hz(), clocks, &mut rcc.apb1r1); // Configure NVIC mask to enable interrupt for the display refresh timer unsafe { NVIC::unmask(Interrupt::TIM2) }; // Save display refresh timer in static singleton so that interrupt has access to it free(|cs| { REFRESH_TIMER.borrow(cs).replace(Some(refresh_timer)); }); // Initiaize display cycle timer let cycle_timer = Timer::tim7(dp.TIM7, (1000 / nixie::CYCLE_FADE_DURATION_MS).hz(), clocks, &mut rcc.apb1r1); // Configure NVIC mask to enable interrupt for display cycle timer unsafe { NVIC::unmask(Interrupt::TIM7) }; // Save display cycle timer in static singleton so that interrupt has access to it free(|cs| { CYCLE_TIMER.borrow(cs).replace(Some(cycle_timer)); }); // Small delay to ensure that PCA9685 is fully powered on before writing to it delay_timer.delay_us(10_u32); // Initialize PCA9685 (PWM driver) pca9685::init(PCA9685_ALL_CALL, &mut i2c); // Enable PWM output after PCA9685 has been initialized pwm_enable.set_low().unwrap(); // Store I2C peripheral in global static variable as it is used in interrupt free(|cs| { I2C.borrow(cs).replace(Some(i2c)); }); // Enable the high voltage power supply last hv_enable.set_high().unwrap(); // Cycle through all tubes on powerup start_cycle(0); start_cycle(1); start_cycle(2); start_cycle(3); loop { // Delay before cycling digits to prevent cathode poisoning delay_timer.delay_ms(CYCLE_REFRESH_INTERVAL * 1000); // Choose a random tube to cycle let tube = (rng.get_random_data() % 4) as usize; start_cycle(tube); } } // Trigger the start of a new cycle sequence fn start_cycle(tube: usize) { free(|cs| { let mut cycle_timer_ref = CYCLE_TIMER.borrow(cs).borrow_mut(); let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); if let Some(ref mut cycle_timer) = cycle_timer_ref.deref_mut() { // Trigger the start of a cycling sequence clock_ref.deref_mut().cycle_start(tube); // Start the timer to cycle through individual digits cycle_timer.listen(Event::TimeOut); } }); } // Interrupt handler for 1HZ signal from offchip RTC (DS3231) #[interrupt] fn EXTI9_5() { free(|cs| { let mut rtc_int_ref = RTC_INT.borrow(cs).borrow_mut(); let mut i2c_int_ref = I2C.borrow(cs).borrow_mut(); let mut refresh_timer_ref = REFRESH_TIMER.borrow(cs).borrow_mut(); let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); if let Some(ref mut rtc_int) = rtc_int_ref.deref_mut() { if let Some(ref mut i2c) = i2c_int_ref.deref_mut() { if let Some(ref mut refresh_timer) = refresh_timer_ref.deref_mut() { // Read new time from DS3231 let (second, minute, hour) = ds3231::get_time(DS3231_ADDR, i2c); let (weekday, day, month, _, _) = ds3231::get_date(DS3231_ADDR, i2c); // Calculate new values and account for DST let hour = if ds3231::in_dst(weekday, day, month, hour) { (hour + 1) % 12 } else { hour % 12 }; let hour = if hour == 0 { 12 } else { hour }; // Trigger the processing of a new time value clock_ref.deref_mut().rtc_tick(second, minute, hour); // Start the refresh timer to update the display refresh_timer.listen(Event::TimeOut); // Clear the interrupt flag for the timer rtc_int.clear_interrupt_pending_bit(); } } } }); } // Interrupt handler for fault interrupt from USB monitor (TUSB322) #[interrupt] fn EXTI3() { free(|cs| { let mut nfault_ref = FAULT_INT.borrow(cs).borrow_mut(); if let Some(ref mut nfault) = nfault_ref.deref_mut() { if nfault.check_interrupt() { nfault.clear_interrupt_pending_bit(); panic!(); } } }); } // Interrupt handler for internal timer that drives display refresh rate #[interrupt] fn TIM2() { free(|cs| { let mut i2c_int_ref = I2C.borrow(cs).borrow_mut(); let mut refresh_timer_ref = REFRESH_TIMER.borrow(cs).borrow_mut(); let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); if let Some(ref mut i2c) = i2c_int_ref.deref_mut() { if let Some(ref mut refresh_timer) = refresh_timer_ref.deref_mut() { // Write new values if values have changed, otherwise disable the refresh timer if clock_ref.deref_mut().fps_tick() { clock_ref.deref_mut().write_i2c(i2c); refresh_timer.clear_interrupt(Event::TimeOut); } else { refresh_timer.unlisten(Event::TimeOut); } } } }); } // Interrupt handler for internal timer that drives individual digits within a cycle sequence #[interrupt] fn TIM7() { free(|cs| { let mut cycle_timer_ref = CYCLE_TIMER.borrow(cs).borrow_mut(); let mut refresh_timer_ref = REFRESH_TIMER.borrow(cs).borrow_mut(); let mut clock_ref = CLOCK.borrow(cs).borrow_mut(); if let Some(ref mut cycle_timer) = cycle_timer_ref.deref_mut() { if let Some(ref mut refresh_timer) = refresh_timer_ref.deref_mut() { // Trigger the next step in the cycling sequence if clock_ref.deref_mut().cycle_tick() { cycle_timer.unlisten(Event::TimeOut); } else { cycle_timer.clear_interrupt(Event::TimeOut); } // Start the refresh timer to update the display refresh_timer.listen(Event::TimeOut); } } }); } // Helper function to set onboard LED state fn set_fault_led(state: State) { free(|cs| { let mut led_ref = FAULT_LED.borrow(cs).borrow_mut(); if let Some(ref mut led) = led_ref.deref_mut() { match state { State::High => led.set_high().unwrap(), State::Low => led.set_low().unwrap(), }; } }); } // Custom panic handler #[panic_handler] #[cfg(not(test))] fn panic(_info: &PanicInfo) -> ! { set_fault_led(State::High); loop { continue; } }