|
| 1 | +//! Cycle the screen brightness through 5 predefined brightness levels when the "DVD" logo hits one |
| 2 | +//! of the sides of the display. |
| 3 | +//! |
| 4 | +//! For best results, run with the `--release` flag. |
| 5 | +
|
| 6 | +#![no_std] |
| 7 | +#![no_main] |
| 8 | + |
| 9 | +use core::convert::TryFrom; |
| 10 | +use display_interface_spi::SPIInterfaceNoCS; |
| 11 | +use embedded_graphics::{ |
| 12 | + geometry::Point, image::Image, pixelcolor::BinaryColor, prelude::*, primitives::Rectangle, |
| 13 | +}; |
| 14 | +use panic_halt as _; |
| 15 | +use rtfm::app; |
| 16 | +use ssd1306::{prelude::*, Builder}; |
| 17 | +use stm32f1xx_hal::{ |
| 18 | + delay::Delay, |
| 19 | + gpio, |
| 20 | + pac::{self, SPI1}, |
| 21 | + prelude::*, |
| 22 | + spi::{self, Mode, Phase, Polarity, Spi}, |
| 23 | + timer::{CountDownTimer, Event, Timer}, |
| 24 | +}; |
| 25 | +use tinybmp::Bmp; |
| 26 | + |
| 27 | +type Display = ssd1306::mode::graphics::GraphicsMode< |
| 28 | + SPIInterfaceNoCS< |
| 29 | + spi::Spi< |
| 30 | + SPI1, |
| 31 | + spi::Spi1NoRemap, |
| 32 | + ( |
| 33 | + gpio::gpioa::PA5<gpio::Alternate<gpio::PushPull>>, |
| 34 | + gpio::gpioa::PA6<gpio::Input<gpio::Floating>>, |
| 35 | + gpio::gpioa::PA7<gpio::Alternate<gpio::PushPull>>, |
| 36 | + ), |
| 37 | + >, |
| 38 | + gpio::gpiob::PB1<gpio::Output<gpio::PushPull>>, |
| 39 | + >, |
| 40 | +>; |
| 41 | + |
| 42 | +#[app(device = stm32f1xx_hal::pac, peripherals = true)] |
| 43 | +const APP: () = { |
| 44 | + struct Resources { |
| 45 | + display: Display, |
| 46 | + timer: CountDownTimer<pac::TIM1>, |
| 47 | + top_left: Point, |
| 48 | + velocity: Point, |
| 49 | + bmp: Bmp<'static>, |
| 50 | + brightness: Brightness, |
| 51 | + } |
| 52 | + |
| 53 | + #[init] |
| 54 | + fn init(cx: init::Context) -> init::LateResources { |
| 55 | + let dp = cx.device; |
| 56 | + let core = cx.core; |
| 57 | + |
| 58 | + let mut flash = dp.FLASH.constrain(); |
| 59 | + let mut rcc = dp.RCC.constrain(); |
| 60 | + |
| 61 | + let clocks = rcc |
| 62 | + .cfgr |
| 63 | + .use_hse(8.mhz()) |
| 64 | + .sysclk(72.mhz()) |
| 65 | + .pclk1(36.mhz()) |
| 66 | + .freeze(&mut flash.acr); |
| 67 | + |
| 68 | + let mut afio = dp.AFIO.constrain(&mut rcc.apb2); |
| 69 | + |
| 70 | + let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); |
| 71 | + let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); |
| 72 | + |
| 73 | + // SPI1 |
| 74 | + let sck = gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl); |
| 75 | + let miso = gpioa.pa6; |
| 76 | + let mosi = gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl); |
| 77 | + |
| 78 | + let mut delay = Delay::new(core.SYST, clocks); |
| 79 | + |
| 80 | + let mut rst = gpiob.pb0.into_push_pull_output(&mut gpiob.crl); |
| 81 | + let dc = gpiob.pb1.into_push_pull_output(&mut gpiob.crl); |
| 82 | + |
| 83 | + let spi = Spi::spi1( |
| 84 | + dp.SPI1, |
| 85 | + (sck, miso, mosi), |
| 86 | + &mut afio.mapr, |
| 87 | + Mode { |
| 88 | + polarity: Polarity::IdleLow, |
| 89 | + phase: Phase::CaptureOnFirstTransition, |
| 90 | + }, |
| 91 | + 8.mhz(), |
| 92 | + clocks, |
| 93 | + &mut rcc.apb2, |
| 94 | + ); |
| 95 | + |
| 96 | + let interface = display_interface_spi::SPIInterfaceNoCS::new(spi, dc); |
| 97 | + let mut display: GraphicsMode<_> = Builder::new() |
| 98 | + .with_rotation(DisplayRotation::Rotate180) |
| 99 | + .connect(interface) |
| 100 | + .into(); |
| 101 | + |
| 102 | + display.reset(&mut rst, &mut delay).unwrap(); |
| 103 | + display.init().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 RTFM |
| 115 | + init::LateResources { |
| 116 | + timer, |
| 117 | + display, |
| 118 | + top_left: Point::new(5, 3), |
| 119 | + velocity: Point::new(1, 1), |
| 120 | + bmp, |
| 121 | + brightness: Brightness::default(), |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + #[task(binds = TIM1_UP, resources = [display, top_left, velocity, timer, bmp, brightness])] |
| 126 | + fn update(cx: update::Context) { |
| 127 | + let update::Resources { |
| 128 | + display, |
| 129 | + top_left, |
| 130 | + velocity, |
| 131 | + timer, |
| 132 | + bmp, |
| 133 | + brightness, |
| 134 | + .. |
| 135 | + } = cx.resources; |
| 136 | + |
| 137 | + let bottom_right = *top_left + Point::try_from(bmp.dimensions()).unwrap(); |
| 138 | + |
| 139 | + // Erase previous image position with a filled black rectangle |
| 140 | + Rectangle::new(*top_left, bottom_right) |
| 141 | + .into_styled(embedded_graphics::primitive_style!( |
| 142 | + fill_color = BinaryColor::Off |
| 143 | + )) |
| 144 | + .draw(display) |
| 145 | + .unwrap(); |
| 146 | + |
| 147 | + // Check if the image collided with a screen edge |
| 148 | + { |
| 149 | + let mut collision = false; |
| 150 | + if bottom_right.x > display.size().width as i32 || top_left.x < 0 { |
| 151 | + velocity.x = -velocity.x; |
| 152 | + collision = true; |
| 153 | + } |
| 154 | + |
| 155 | + if bottom_right.y > display.size().height as i32 || top_left.y < 0 { |
| 156 | + velocity.y = -velocity.y; |
| 157 | + collision = true; |
| 158 | + } |
| 159 | + |
| 160 | + if collision { |
| 161 | + // Change the brightness |
| 162 | + *brightness = match *brightness { |
| 163 | + Brightness::DIMMEST => Brightness::DIM, |
| 164 | + Brightness::DIM => Brightness::NORMAL, |
| 165 | + Brightness::NORMAL => Brightness::BRIGHT, |
| 166 | + Brightness::BRIGHT => Brightness::BRIGHTEST, |
| 167 | + Brightness::BRIGHTEST => Brightness::DIMMEST, // restart cycle |
| 168 | + _ => Brightness::NORMAL, // Brightness is not an enum, cover rest of patterns |
| 169 | + }; |
| 170 | + |
| 171 | + // Send the new brightness value to the display |
| 172 | + display.set_brightness(*brightness).unwrap(); |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + // Move the image |
| 177 | + *top_left += *velocity; |
| 178 | + |
| 179 | + // Draw image at new position |
| 180 | + Image::new(bmp, *top_left).draw(display).unwrap(); |
| 181 | + |
| 182 | + // Write changes to the display |
| 183 | + display.flush().unwrap(); |
| 184 | + |
| 185 | + // Clears the update flag |
| 186 | + timer.clear_update_interrupt_flag(); |
| 187 | + } |
| 188 | + |
| 189 | + extern "C" { |
| 190 | + fn EXTI0(); |
| 191 | + } |
| 192 | +}; |
0 commit comments