#![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)] #![feature(destructuring_assignment)] #![allow(dead_code)] // custom panic handler #[cfg(not(test))] use core::panic::PanicInfo; use core::{cell::RefCell, ops::DerefMut}; use cortex_m::interrupt::{Mutex, free}; 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, prelude::*, rcc, rng::Rng, timer::{Timer, Event}}; mod ds3231; mod nixie; mod pca9685; mod tusb322; use nixie::*; type FaultLed = PC15>; static FAULT_LED: Mutex>> = Mutex::new(RefCell::new(None)); #[rtic::app(device = stm32l4xx_hal::pac, peripherals = true)] const APP: () = { struct Resources { #[init(Clock::default())] clock: Clock, // Late initialized resources (shared peripherals) rtc_int: PB5>, fault_int: PA3>, i2c: I2c>>,PA10>>)>, refresh_timer: Timer, cycle_timer: Timer, delay_timer: Delay, rng: Rng, } #[init(spawn = [start_cycle])] fn init(ctx: init::Context) -> init::LateResources { let mut dp = ctx.device; let cp = ctx.core; // 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) if fault_int.is_low().unwrap() { 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 // Interrupt enable is automatically handled by RTIC's #[task] 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 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 // Interrupt enable is automatically handled by RTIC's #[task] let refresh_timer = Timer::tim2(dp.TIM2, nixie::DISPLAY_REFRESH_FPS.hz(), clocks, &mut rcc.apb1r1); // Initiaize display cycle timer // Interrupt enable is automatically handled by RTIC's #[task] let cycle_timer = Timer::tim7(dp.TIM7, (1000 / nixie::CYCLE_FADE_DURATION_MS).hz(), clocks, &mut rcc.apb1r1); // 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(); // Enable the high voltage power supply last hv_enable.set_high().unwrap(); // Spawn tasks to cycle through all tubes on powerup ctx.spawn.start_cycle(0).unwrap(); ctx.spawn.start_cycle(1).unwrap(); ctx.spawn.start_cycle(2).unwrap(); ctx.spawn.start_cycle(3).unwrap(); init::LateResources { rtc_int, fault_int, i2c, refresh_timer, cycle_timer, delay_timer, rng } } #[idle(spawn = [start_cycle], resources = [delay_timer, rng])] fn idle(ctx: idle::Context) -> ! { let idle::Resources {delay_timer, rng} = ctx.resources; 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; ctx.spawn.start_cycle(tube).unwrap(); } } // Interrupt handler for 1HZ signal from offchip RTC (DS3231) #[task(binds = EXTI9_5, priority = 1, resources = [clock, rtc_int, i2c, refresh_timer])] fn rtc_interrupt(ctx: rtc_interrupt::Context) { let rtc_interrupt::Resources {mut clock, rtc_int, mut i2c, mut refresh_timer} = ctx.resources; let (mut second, mut minute, mut hour) = (0, 0, 0); let (mut weekday, mut day, mut month) = (ds3231::Weekday::Sunday, 0, 0); // Lower priority task requires a critical section to access shared peripheral i2c.lock(|i2c| { // Read new time from DS3231 (second, minute, hour) = ds3231::get_time(DS3231_ADDR, i2c); (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 }; // Lower priority task requires a critical section to access shared peripheral clock.lock(|clock| { // Trigger the processing of a new time value clock.rtc_tick(second, minute, hour); }); // Start the refresh timer to update the display // Lower priority task requires a critical section to access shared peripheral refresh_timer.lock(|refresh_timer| { 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) // Configured as priority 4 for highest priority (pre-empts all other interrupts) #[task(binds = EXTI3, priority = 4, resources = [fault_int])] fn fault_interrupt(ctx: fault_interrupt::Context) { let fault_interrupt::Resources {fault_int} = ctx.resources; if fault_int.check_interrupt() { fault_int.clear_interrupt_pending_bit(); panic!(); } } // Interrupt handler for internal timer that drives display refresh rate #[task(binds = TIM2, priority = 3, resources = [clock, i2c, refresh_timer])] fn refresh_interrupt(ctx: refresh_interrupt::Context) { let refresh_interrupt::Resources {clock, refresh_timer, i2c} = ctx.resources; // Compute updates for non-static digits let updated = clock.fps_tick(); // Write new values if values have changed, otherwise disable the refresh timer if updated { clock.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 #[task(binds = TIM7, priority = 2, resources = [clock, refresh_timer, cycle_timer])] fn cycle_interrupt(ctx: cycle_interrupt::Context) { let cycle_interrupt::Resources {mut clock, mut refresh_timer, cycle_timer} = ctx.resources; let mut cycle_ended = true; // Lower priority task requires a critical section to access shared data clock.lock(|clock| { cycle_ended = clock.cycle_tick(); }); // Trigger the next step in the cycling sequence if cycle_ended { cycle_timer.unlisten(Event::TimeOut); } else { cycle_timer.clear_interrupt(Event::TimeOut); } // Start the refresh timer to update the display // Lower priority task requires a critical section to access shared peripheral refresh_timer.lock(|refresh_timer| { refresh_timer.listen(Event::TimeOut); }); } // Trigger the start of a new cycle sequence #[task(resources = [clock, cycle_timer], priority = 2, capacity = 4)] fn start_cycle(ctx: start_cycle::Context, tube: usize) { let start_cycle::Resources {mut clock, cycle_timer} = ctx.resources; // Lower priority task requires a critical section to access shared data clock.lock(|clock| { // Trigger the start of a cycling sequence clock.cycle_start(tube); }); // Start the timer to cycle through individual digits cycle_timer.listen(Event::TimeOut); } // RTIC requires that unused interrupts are declared in an extern block when // using software tasks; these free interrupts will be used to dispatch the // software tasks. extern "C" { fn TSC(); fn LCD(); fn AES(); } }; // 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; } }