stm32f4xx-hal 0.14.0

Peripheral access API for STM32F4 series microcontrollers
Documentation
//! Draw Ferris the Rust mascot on an SSD1306 display
//!
//! For example, to run on an STM32F411 Nucleo
//! dev board, run the following:
//!
//! ```bash
//! cargo run --features stm32f411--release --example ssd1306-image
//! ```
//!
//! Note that `--release` is required to fix link errors for smaller devices.

#![allow(clippy::empty_loop)]
#![no_std]
#![no_main]

use panic_semihosting as _;
use stm32f4xx_hal as hal;

use cortex_m_rt::ExceptionFrame;
use cortex_m_rt::{entry, exception};
use embedded_graphics::{image::Image, image::ImageRaw, pixelcolor::BinaryColor, prelude::*};
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};

use crate::hal::{pac, prelude::*};

#[entry]
fn main() -> ! {
    if let (Some(dp), Some(_cp)) = (
        pac::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {
        // Set up the system clock. We want to run at 48MHz for this one.
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.MHz()).freeze();

        // Set up I2C - SCL is PB8 and SDA is PB9; they are set to Alternate Function 4
        // as per the STM32F446xC/E datasheet page 60. Pin assignment as per the Nucleo-F446 board.
        let gpiob = dp.GPIOB.split();
        let scl = gpiob
            .pb8
            .into_alternate()
            .internal_pull_up(true)
            .set_open_drain();
        let sda = gpiob
            .pb9
            .into_alternate()
            .internal_pull_up(true)
            .set_open_drain();
        // let i2c = I2c::new(dp.I2C1, (scl, sda), 400.kHz(), &clocks);
        // or
        let i2c = dp.I2C1.i2c((scl, sda), 400.kHz(), &clocks);

        // There's a button on PC13. On the Nucleo board, it's pulled up by a 4.7kOhm resistor
        // and therefore is active LOW. There's even a 100nF capacitor for debouncing - nice for us
        // since otherwise we'd have to debounce in software.
        let gpioc = dp.GPIOC.split();
        let btn = gpioc.pc13.into_pull_down_input();

        // Set up the display
        let interface = I2CDisplayInterface::new(i2c);
        let mut disp = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
            .into_buffered_graphics_mode();
        disp.init().unwrap();
        disp.flush().unwrap();

        // Display the rustacean
        let raw_image: ImageRaw<BinaryColor> =
            ImageRaw::new(include_bytes!("./ssd1306-image.data"), 128);
        let image = Image::new(&raw_image, Point::zero());
        image.draw(&mut disp).unwrap();
        disp.flush().unwrap();

        // Set up state for the loop
        let mut orientation = DisplayRotation::Rotate0;
        let mut was_pressed = btn.is_low();

        // This runs continuously, as fast as possible
        loop {
            // Check if the button has just been pressed.
            // Remember, active low.
            let is_pressed = btn.is_low();
            if !was_pressed && is_pressed {
                // Since the button was pressed, flip the screen upside down
                orientation = get_next_rotation(orientation);
                disp.set_rotation(orientation).unwrap();
                // Now that we've flipped the screen, store the fact that the button is pressed.
                was_pressed = true;
            } else if !is_pressed {
                // If the button is released, confirm this so that next time it's pressed we'll
                // know it's time to flip the screen.
                was_pressed = false;
            }
        }
    }

    loop {}
}

/// Helper function - what rotation flips the screen upside down from
/// the rotation we're in now?
fn get_next_rotation(rotation: DisplayRotation) -> DisplayRotation {
    match rotation {
        DisplayRotation::Rotate0 => DisplayRotation::Rotate180,
        DisplayRotation::Rotate180 => DisplayRotation::Rotate0,

        // Default branch - if for some reason we end up in one of the portrait modes,
        // reset to 0 degrees landscape. On most SSD1306 displays, this means down is towards
        // the flat flex coming out of the display (and up is towards the breakout board pins).
        _ => DisplayRotation::Rotate0,
    }
}

#[exception]
unsafe fn HardFault(ef: &ExceptionFrame) -> ! {
    panic!("{:#?}", ef);
}