#![no_std] #![no_main] use core::{ cell::RefCell, fmt::Write, }; use panic_halt as _; use cortex_m_rt::entry; use cortex_m::{ interrupt::Mutex, }; use stm32f1xx_hal::{ afio, device::{EXTI, NVIC}, delay::Delay, i2c::{BlockingI2c, DutyCycle, Mode}, gpio::{ Input, Floating, ExtiPin, Edge, gpioa::{PA8, PA9}, }, pac::{ CorePeripherals, Peripherals, Interrupt, interrupt, }, prelude::*, }; use switch_hal::{IntoSwitch, InputSwitch, OutputSwitch, ToggleableOutputSwitch}; use ssd1306::{ prelude::*, Builder, }; use embedded_graphics::{ fonts::{Font6x8, Text}, pixelcolor::BinaryColor, prelude::*, primitives::Circle, style::{PrimitiveStyle, TextStyle}, }; use arrayvec::ArrayString; mod rotary; use rotary::{Direction, Rotary}; type CLKPIN = PA8>; type DTPIN = PA9>; static CLK_PIN: Mutex>> = Mutex::new(RefCell::new(None)); static DT_PIN: Mutex>> = Mutex::new(RefCell::new(None)); static COUNT: Mutex> = Mutex::new(RefCell::new(0)); #[entry] fn main() -> ! { // Get access to the core peripherals from the cortex-m crate let mut core_periph = CorePeripherals::take().unwrap(); // Get access to the device specific peripherals from the peripheral access crate let dev_periph = Peripherals::take().unwrap(); // RCC is the primary clock control peripheral, but FLASH is also involved in setting // up clock speed because the wait states must be increased at higher clock rates let mut rcc = dev_periph.RCC.constrain(); let mut flash = dev_periph.FLASH.constrain(); let mut afio = dev_periph.AFIO.constrain(&mut rcc.apb2); // Peripherals often need to know the clock settings to be properly configured, so // we configure the clock and "freeze" the configuration so we can pass it let clocks = rcc .cfgr .use_hse(8.mhz()) // Use High Speed External 8Mhz crystal oscillator .sysclk(72.mhz()) // Use the PLL to multiply SYSCLK to 72MHz .hclk(72.mhz()) // Leave AHB prescaler at /1 .pclk1(36.mhz()) // Use the APB1 prescaler to divide the clock to 36MHz (max supported) .pclk2(72.mhz()) // Leave the APB2 prescaler at /1 .adcclk(12.mhz()) // ADC prescaler of /6 (max speed of 14MHz, but /4 gives 18MHz) .freeze(&mut flash.acr); // In order to have precisely-timed delays, we can use the core SysTick clock as a // delay provider let mut delay = Delay::new(core_periph.SYST, clocks); // Acquire the necessary gpio peripherals let mut gpioa = dev_periph.GPIOA.split(&mut rcc.apb2); let mut gpiob = dev_periph.GPIOB.split(&mut rcc.apb2); let mut gpioc = dev_periph.GPIOC.split(&mut rcc.apb2); // Configure the rotary encoder pins A8, A9 and switch pin A10 let clk = gpioa.pa8 .into_floating_input(&mut gpioa.crh); let dt = gpioa.pa9 .into_floating_input(&mut gpioa.crh); let sw = gpioa.pa10 .into_floating_input(&mut gpioa.crh) .into_active_low_switch(); // Set up the rotary encoder pins for use with the interrupt init_encoder_pins(clk, dt, &mut afio, &dev_periph.EXTI); // Configure pin C13 to drive the "PC13" LED as an active-low switch let mut led = gpioc.pc13 .into_push_pull_output(&mut gpioc.crh) .into_active_low_switch(); // Configure the I2C pins we are using for the display to the correct mode let scl = gpiob.pb10.into_alternate_open_drain(&mut gpiob.crh); let sda = gpiob.pb11.into_alternate_open_drain(&mut gpiob.crh); // Very brief delay before starting up i2c; otherwise the startup process // could hang. delay.delay_us(10_u8); // Configure the I2C peripheral itself let i2c = BlockingI2c::i2c2( dev_periph.I2C2, (scl, sda), Mode::Fast { frequency: 400_000.hz(), duty_cycle: DutyCycle::Ratio2to1, }, clocks, &mut rcc.apb1, 1000, 10, 1000, 1000, ); // Initialize the display let mut display: GraphicsMode<_> = Builder::new() .with_i2c_addr(0x3c) .connect_i2c(i2c) .into(); display.init().unwrap(); // Every set of commands to the display is buffered until we flush it display.flush().unwrap(); // Enable interrupts unsafe { core_periph.NVIC.set_priority(Interrupt::EXTI9_5, 1); NVIC::unmask(Interrupt::EXTI9_5); } NVIC::unpend(Interrupt::EXTI9_5); // We will draw a fixed message on the screen and also a moving circle const C_RADIUS: i32 = 8; const DISPLAY_W: i32 = 128; const DISPLAY_H: i32 = 64; let mut cx = 20; let mut cy = 20; let t = Text::new("Hello Rust!", Point::new(20, 16)) .into_styled(TextStyle::new(Font6x8, BinaryColor::On)); // Turn the LED on via the OutputPin trait led.on().unwrap(); let mut button_last = false; // Microcontroller programs never exit main, so we must loop! loop { // Check our inputs let button = sw.is_active().unwrap(); let counter = cortex_m::interrupt::free(|cs| *COUNT.borrow(cs).borrow()); if button_last && !button { led.toggle().unwrap(); } button_last = button; let c = Circle::new(Point::new(cx, cy), C_RADIUS as u32) .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)); // Show the position of the knob and thus the ball let mut textbuf = ArrayString::<[u8; 15]>::new(); write!(&mut textbuf, "count: {}", counter).unwrap(); let count = Text::new(&textbuf, Point::new(20, 36)) .into_styled(TextStyle::new(Font6x8, BinaryColor::On)); display.clear(); c.draw(&mut display).unwrap(); t.draw(&mut display).unwrap(); count.draw(&mut display).unwrap(); display.flush().unwrap(); // Control the horizontal position with the knob cx = counter.max(C_RADIUS/2).min(DISPLAY_W - C_RADIUS/2); // Wrap the ball back to the top when it falls off the bottom cy += 1; if cy > (DISPLAY_H + C_RADIUS) { cy = -C_RADIUS }; } } fn init_encoder_pins( mut clk: PA8>, dt: PA9>, afio: &mut afio::Parts, exti: &EXTI, ) { cortex_m::interrupt::free(|cs| { clk.make_interrupt_source(afio); clk.trigger_on_edge(exti, Edge::RISING_FALLING); clk.enable_interrupt(exti); CLK_PIN.borrow(cs).replace(Some(clk)); DT_PIN.borrow(cs).replace(Some(dt)); }); } #[interrupt] fn EXTI9_5() { static mut ENC: Option> = None; let enc = ENC.get_or_insert_with(|| { cortex_m::interrupt::free(|cs| { let clk = CLK_PIN.borrow(cs).replace(None).unwrap(); let dt = DT_PIN.borrow(cs).replace(None).unwrap(); Rotary::new(clk, dt) }) }); if enc.pin_a.check_interrupt() { match enc.update().unwrap() { Direction::Clockwise => cortex_m::interrupt::free(|cs| { COUNT .borrow(cs) .replace_with(|count| count.wrapping_add(1)); }), Direction::CounterClockwise => cortex_m::interrupt::free(|cs| { COUNT .borrow(cs) .replace_with(|count| count.wrapping_add(-1)); }), Direction::None => {} }; enc.pin_a.clear_interrupt_pending_bit(); } }