297 lines
8.9 KiB
Rust
297 lines
8.9 KiB
Rust
//! 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, CounterHz};
|
|
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<Input<Floating>, 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: CounterHz<TIM2>,
|
|
pub blink_timer: CounterHz<TIM3>,
|
|
}
|
|
|
|
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::new(periphs.TIM2, &clocks).counter_hz();
|
|
// Use TIM3 for the LED blinker task
|
|
let blink_timer = Timer::new(periphs.TIM3, &clocks).counter_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<Output<PushPull>, 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<Input<Floating>, CRH, 'C', 13>, cr: &mut hal::gpio::Cr<CRH, 'C'>) -> 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<Input<Floating>, CRL, 'B', 6>,
|
|
Pin<Input<Floating>, 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<Pin<Input<Floating>, 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<Input<Floating>, CRL, 'B', 6>,
|
|
dt_pin: Pin<Input<Floating>, CRL, 'B', 7>,
|
|
button_pin: Pin<Input<Floating>, CRH, 'B', 8>,
|
|
mapr: &mut hal::afio::MAPR,
|
|
&clocks: &hal::rcc::Clocks,
|
|
) -> Self {
|
|
let qei = Timer::new(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<Alternate<OpenDrain>, CRH, 'B', 10>,
|
|
Pin<Alternate<OpenDrain>, CRH, 'B', 11>)
|
|
>;
|
|
type DisplayController = Ssd1306<
|
|
I2CInterface<DisplayI2c>,
|
|
DisplaySize128x64,
|
|
BufferedGraphicsMode<DisplaySize128x64>
|
|
>;
|
|
/// 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
|
|
}
|
|
} |