//! Hardware initialization and control wrappers use stm32f1xx_hal as hal; use hal::{ prelude::*, stm32::{self, Peripherals}, timer::{Timer, Tim4NoRemap}, i2c::{BlockingI2c, DutyCycle, Mode}, qei::{Qei, QeiOptions}, }; // re-export some of the types from the HAL we're not wrapping pub use hal::timer::{Event, CountDownTimer}; pub use stm32::{TIM2, TIM3}; use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306, mode::BufferedGraphicsMode}; // Imports for specifying the types of pins // The Pin type takes 4 parameters; config, low/high register, bank name, and number // E.g. Pin, CRL, 'A', 0> for PA0 in input mode with no pull resistor. use hal::gpio::{Pin, Input, Output, Floating, PushPull, CRL, CRH, Alternate, OpenDrain}; // // The Board peripheral collection // /// The managed peripherals for this Blue Pill project board pub struct Board { pub encoder: Encoder, pub display: Display, pub led: UserLed, pub poll_timer: CountDownTimer, pub blink_timer: CountDownTimer, } impl Board { /// Set up clocks and pin mappings for peripherals on the board /// /// This returns a plain struct containing all the managed /// peripherals so that it can be destructured. pub fn init(periphs: Peripherals) -> Self { // Set up board clocks let mut flash = periphs.FLASH.constrain(); let rcc = periphs.RCC.constrain(); let clocks = rcc .cfgr .use_hse(8.mhz()) .sysclk(72.mhz()) .hclk(72.mhz()) .pclk1(36.mhz()) .pclk2(72.mhz()) .adcclk(12.mhz()) .freeze(&mut flash.acr); // LED is on pin C13, configure it for output let mut gpioc = periphs.GPIOC.split(); let led_pin = gpioc.pc13; let led = UserLed::new(led_pin, &mut gpioc.crh); let mut gpiob = periphs.GPIOB.split(); // Rotary encoder uses TIM4 on B6 and B7, and B8 as the encoder button. let enc_clk = gpiob.pb6; let enc_dt = gpiob.pb7; let enc_button = gpiob.pb8; let mut afio = periphs.AFIO.constrain(); let encoder = Encoder::new( periphs.TIM4, enc_clk, enc_dt, enc_button, &mut afio.mapr, &clocks, ); // Use TIM2 for the control polling task let poll_timer = Timer::tim2(periphs.TIM2, &clocks).start_count_down(1.hz()); // Use TIM3 for the LED blinker task let blink_timer = Timer::tim3(periphs.TIM3, &clocks).start_count_down(2.hz()); // I2C for display is on gpiob B10 and B11 let scl = gpiob.pb10.into_alternate_open_drain(&mut gpiob.crh); let sda = gpiob.pb11.into_alternate_open_drain(&mut gpiob.crh); // Configure the I2C peripheral itself let i2c = BlockingI2c::i2c2( periphs.I2C2, (scl, sda), Mode::Fast { frequency: 400_000.hz(), duty_cycle: DutyCycle::Ratio2to1, }, clocks, 1000, 10, 1000, 1000, ); let display = Display::new(i2c); Self { encoder, display, led, poll_timer, blink_timer, } } } /// The user-controllable green LED pub struct UserLed(Pin, CRH, 'C', 13>); impl UserLed { /// Create the LED controller from the pin it is attached to /// /// Requires both the pin and a mutable reference to its control register pub fn new(pin: Pin, CRH, 'C', 13>, cr: &mut hal::gpio::Cr) -> Self { let led = pin.into_push_pull_output(cr); Self(led) } /// Toggle the state of the LED /// /// The state is managed in the hardware; the HAL api this calls will read, /// modify, and write the control register to change the state. pub fn toggle(&mut self) { self.0.toggle(); } } // // Rotary Encoder // use switch_hal::{Switch, ActiveLow, InputSwitch, IntoSwitch}; type EncoderQei = Qei< stm32::TIM4, Tim4NoRemap, (Pin, CRL, 'B', 6>, Pin, CRL, 'B', 7>) >; /// A rotary encoder peripheral with a push-button shaft /// /// This can return the current readings of the shaft count and buttons and /// also statefully poll for rotation deltas and "button up" events. /// /// Monitoring the position is handled by the TIM4 timer in quadrature encoder /// mode, which provides clean and accurate positioning with no software /// overhead. pub struct Encoder { qei: EncoderQei, button: Switch, CRH, 'B', 8>, ActiveLow>, last_count: u16, last_active: bool, } impl Encoder { /// Create a new encoder manager /// /// You must supply the following: /// + The timer control register /// + 2 pins attached to the timer for monitoring rotation /// + 1 gpio pin for monitoring the button (active low) /// + Mutable reference to the `MAPR` register /// + Shared reference to `Clocks` pub fn new( timer: stm32::TIM4, clk_pin: Pin, CRL, 'B', 6>, dt_pin: Pin, CRL, 'B', 7>, button_pin: Pin, CRH, 'B', 8>, mapr: &mut hal::afio::MAPR, &clocks: &hal::rcc::Clocks, ) -> Self { let qei = Timer::tim4(timer, &clocks) .qei((clk_pin, dt_pin), mapr, QeiOptions::default()); let button = button_pin.into_active_low_switch(); Self { qei, button, last_count: 0, last_active: false, } } /// Get the current count from the encoder /// /// Each tick of the counter represents one of the tactile detents, which /// occur once every 4 of the raw counts. pub fn count(&self) -> u16 { self.qei.count() / 4 } /// Get the current depressed state of the shaft button of the encoder /// /// This will be `true` while the shaft is being pressed down and `false` /// when it is not being depressed. pub fn is_pressed(&self) -> bool { self.button.is_active().unwrap() } /// Statefully get the number of ticks the shaft has turned since last poll /// /// Each poll stores the current position and returns the delta between the /// current position and the last one. pub fn poll_count_delta(&mut self) -> i32 { let prev = self.last_count; let current = self.count(); self.last_count = current; i32::from(current) - i32::from(prev) } /// Statefully poll for "button up" events /// /// Each poll stores the current button state and returns whether the /// button returned to the inactive state from the active state since /// the previous poll. pub fn poll_button_up(&mut self) -> bool { let prev = self.last_active; let current = self.button.is_active().unwrap(); self.last_active = current; !current && prev } } // // OLED Display // type DisplayI2c = BlockingI2c< stm32::I2C2, (Pin, CRH, 'B', 10>, Pin, CRH, 'B', 11>) >; type DisplayController = Ssd1306< I2CInterface, DisplaySize128x64, BufferedGraphicsMode >; /// An I2C-attached SSD1306 128x64 OLED display /// /// This manages the I2C communication with the controller, providing `draw` /// and `flush` methods for interacting with the `embedded-graphics` crate. pub struct Display { controller: DisplayController, } impl Display { /// Create the OLED display manager /// /// This requires a pre-configred `BlockingI2c` bus manager. It initializes /// the controller upon construction but doesn't flush it. /// /// The underlying `Ssd1306` controller is configured for buffered graphics /// mode. pub fn new(i2c: DisplayI2c) -> Self { let interface = I2CDisplayInterface::new(i2c); let mut controller = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) .into_buffered_graphics_mode(); controller.init().unwrap(); Self { controller, } } /// Flush the buffered graphics operations to the display pub fn flush(&mut self) { self.controller.flush().ok(); } /// Draw the current state of the UI model to the display /// /// This includes a flush operation. pub fn draw(&mut self, model: &mut ui::HelloDisplay<128,64>) { model.draw(&mut self.controller).ok(); self.controller.flush().ok(); } } // Implement Deref and DerefMut so we can treat Display as a DisplayController use core::ops::{Deref, DerefMut}; impl Deref for Display { type Target = DisplayController; fn deref(&self) -> &Self::Target { &self.controller } } impl DerefMut for Display { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.controller } }