|
| 1 | +//! Bounce a DVD player logo around the screen |
| 2 | +//! |
| 3 | +//! Like this, but with no color changing: https://bouncingdvdlogo.com/ |
| 4 | +//! |
| 5 | +//! This example is for the STM32F103 "Blue Pill" board using I2C1. |
| 6 | +//! |
| 7 | +//! Wiring connections are as follows for a CRIUS-branded display: |
| 8 | +//! |
| 9 | +//! ``` |
| 10 | +//! Display -> Blue Pill |
| 11 | +//! (black) GND -> GND |
| 12 | +//! (red) +5V -> VCC |
| 13 | +//! (yellow) SDA -> PB9 |
| 14 | +//! (green) SCL -> PB8 |
| 15 | +//! ``` |
| 16 | +//! |
| 17 | +//! Run on a Blue Pill with `cargo run --example rtic_dvd`. |
| 18 | +
|
| 19 | +#![no_std] |
| 20 | +#![no_main] |
| 21 | + |
| 22 | +use embedded_graphics::{ |
| 23 | + geometry::Point, |
| 24 | + image::Image, |
| 25 | + pixelcolor::{BinaryColor, Rgb565}, |
| 26 | + prelude::*, |
| 27 | + primitives::{PrimitiveStyle, Rectangle}, |
| 28 | +}; |
| 29 | +use panic_semihosting as _; |
| 30 | +use rtic::app; |
| 31 | +use sh1106::{prelude::*, Builder}; |
| 32 | +use stm32f1xx_hal::{ |
| 33 | + gpio, |
| 34 | + i2c::{BlockingI2c, DutyCycle, Mode}, |
| 35 | + pac::{self, I2C1}, |
| 36 | + prelude::*, |
| 37 | + timer::{CountDownTimer, Event, Timer}, |
| 38 | +}; |
| 39 | +use tinybmp::Bmp; |
| 40 | + |
| 41 | +type Display = GraphicsMode< |
| 42 | + I2cInterface< |
| 43 | + BlockingI2c< |
| 44 | + I2C1, |
| 45 | + ( |
| 46 | + gpio::gpiob::PB8<gpio::Alternate<gpio::OpenDrain>>, |
| 47 | + gpio::gpiob::PB9<gpio::Alternate<gpio::OpenDrain>>, |
| 48 | + ), |
| 49 | + >, |
| 50 | + >, |
| 51 | +>; |
| 52 | + |
| 53 | +#[app(device = stm32f1xx_hal::pac, peripherals = true)] |
| 54 | +const APP: () = { |
| 55 | + struct Resources { |
| 56 | + display: Display, |
| 57 | + timer: CountDownTimer<pac::TIM1>, |
| 58 | + top_left: Point, |
| 59 | + velocity: Point, |
| 60 | + bmp: Bmp<Rgb565, 'static>, |
| 61 | + } |
| 62 | + |
| 63 | + #[init] |
| 64 | + fn init(cx: init::Context) -> init::LateResources { |
| 65 | + let dp = cx.device; |
| 66 | + |
| 67 | + let mut flash = dp.FLASH.constrain(); |
| 68 | + let mut rcc = dp.RCC.constrain(); |
| 69 | + |
| 70 | + let clocks = rcc |
| 71 | + .cfgr |
| 72 | + .use_hse(8.mhz()) |
| 73 | + .sysclk(72.mhz()) |
| 74 | + .pclk1(36.mhz()) |
| 75 | + .freeze(&mut flash.acr); |
| 76 | + |
| 77 | + let mut afio = dp.AFIO.constrain(&mut rcc.apb2); |
| 78 | + |
| 79 | + let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); |
| 80 | + |
| 81 | + let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh); |
| 82 | + let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh); |
| 83 | + |
| 84 | + let i2c = BlockingI2c::i2c1( |
| 85 | + dp.I2C1, |
| 86 | + (scl, sda), |
| 87 | + &mut afio.mapr, |
| 88 | + Mode::Fast { |
| 89 | + frequency: 400_000.hz(), |
| 90 | + duty_cycle: DutyCycle::Ratio2to1, |
| 91 | + }, |
| 92 | + clocks, |
| 93 | + &mut rcc.apb1, |
| 94 | + 1000, |
| 95 | + 10, |
| 96 | + 1000, |
| 97 | + 1000, |
| 98 | + ); |
| 99 | + |
| 100 | + let mut display: GraphicsMode<_> = Builder::new().connect_i2c(i2c).into(); |
| 101 | + |
| 102 | + display.init().unwrap(); |
| 103 | + display.flush().unwrap(); |
| 104 | + |
| 105 | + // Update framerate |
| 106 | + let fps = 20; |
| 107 | + |
| 108 | + let mut timer = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2).start_count_down(fps.hz()); |
| 109 | + |
| 110 | + timer.listen(Event::Update); |
| 111 | + |
| 112 | + let bmp = Bmp::from_slice(include_bytes!("dvd.bmp")).unwrap(); |
| 113 | + |
| 114 | + // Init the static resources to use them later through RTIC |
| 115 | + init::LateResources { |
| 116 | + timer, |
| 117 | + display, |
| 118 | + top_left: Point::new(5, 3), |
| 119 | + velocity: Point::new(1, 1), |
| 120 | + bmp, |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + #[idle()] |
| 125 | + fn idle(_: idle::Context) -> ! { |
| 126 | + loop { |
| 127 | + // Fix default wfi() behaviour breaking debug probe |
| 128 | + core::hint::spin_loop(); |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + #[task(binds = TIM1_UP, resources = [display, top_left, velocity, timer, bmp])] |
| 133 | + fn update(cx: update::Context) { |
| 134 | + let update::Resources { |
| 135 | + display, |
| 136 | + top_left, |
| 137 | + velocity, |
| 138 | + timer, |
| 139 | + bmp, |
| 140 | + .. |
| 141 | + } = cx.resources; |
| 142 | + |
| 143 | + let bottom_right = *top_left + bmp.bounding_box().size; |
| 144 | + |
| 145 | + // Erase previous image position with a filled black rectangle |
| 146 | + Rectangle::with_corners(*top_left, bottom_right) |
| 147 | + .into_styled(PrimitiveStyle::with_fill(BinaryColor::Off)) |
| 148 | + .draw(display) |
| 149 | + .unwrap(); |
| 150 | + |
| 151 | + // Check if the image collided with a screen edge |
| 152 | + { |
| 153 | + if bottom_right.x > display.size().width as i32 || top_left.x < 0 { |
| 154 | + velocity.x = -velocity.x; |
| 155 | + } |
| 156 | + |
| 157 | + if bottom_right.y > display.size().height as i32 || top_left.y < 0 { |
| 158 | + velocity.y = -velocity.y; |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + // Move the image |
| 163 | + *top_left += *velocity; |
| 164 | + |
| 165 | + // Draw image at new position |
| 166 | + Image::new(bmp, *top_left) |
| 167 | + .draw(&mut display.color_converted()) |
| 168 | + .unwrap(); |
| 169 | + |
| 170 | + // Write changes to the display |
| 171 | + display.flush().unwrap(); |
| 172 | + |
| 173 | + // Clears the update flag |
| 174 | + timer.clear_update_interrupt_flag(); |
| 175 | + } |
| 176 | + |
| 177 | + extern "C" { |
| 178 | + fn EXTI0(); |
| 179 | + } |
| 180 | +}; |
0 commit comments