//! //! Demonstrates use of the Flexible Static Memory Controller to interface with an ST7789 LCD //! controller //! //! Hardware required: an STM32F412G-DISCO board //! //! Procedure: Compile this example, load it onto the microcontroller, and run it. //! //! Example run command: `cargo run --release --features stm32f412,fsmc_lcd --example st7789-lcd` //! //! Expected behavior: The display shows a black background with four colored circles. Periodically, //! the color of each circle changes. //! //! Each circle takes a noticeable amount of time to draw, from top to bottom. Because //! embedded-graphics by default does not buffer anything in memory, it sends one pixel at a time //! to the LCD controller. The LCD interface can transfer rectangular blocks of pixels more quickly. //! #![no_std] #![no_main] use core::iter::{Cloned, Cycle}; use core::slice::Iter; use cortex_m_rt::entry; use panic_semihosting as _; use embedded_graphics_07::pixelcolor::Rgb565; use embedded_graphics_07::prelude::*; use embedded_graphics_07::primitives::{Circle, PrimitiveStyle}; use st7789::ST7789; use stm32f4xx_hal::fsmc_lcd::{DataPins16, FsmcLcd, LcdPins, Timing}; use stm32f4xx_hal::pac::{CorePeripherals, Peripherals}; use stm32f4xx_hal::prelude::*; #[entry] fn main() -> ! { let cp = CorePeripherals::take().unwrap(); let dp = Peripherals::take().unwrap(); let rcc = dp.RCC.constrain(); // Make HCLK faster to allow updating the display more quickly let clocks = rcc.cfgr.hclk(100.MHz()).freeze(); let mut delay = cp.SYST.delay(&clocks); let gpiod = dp.GPIOD.split(); let gpioe = dp.GPIOE.split(); let gpiof = dp.GPIOF.split(); // Pins connected to the LCD on the 32F412GDISCOVERY board use stm32f4xx_hal::gpio::alt::fsmc as alt; let lcd_pins = LcdPins::new( DataPins16::new( gpiod.pd14, gpiod.pd15, gpiod.pd0, gpiod.pd1, gpioe.pe7, gpioe.pe8, gpioe.pe9, gpioe.pe10, gpioe.pe11, gpioe.pe12, gpioe.pe13, gpioe.pe14, gpioe.pe15, gpiod.pd8, gpiod.pd9, gpiod.pd10, ), alt::Address::from(gpiof.pf0), gpiod.pd4, gpiod.pd5, alt::ChipSelect1::from(gpiod.pd7), ); let lcd_reset = gpiod.pd11.into_push_pull_output(); let backlight_control = gpiof.pf5.into_push_pull_output(); // Speed up timing settings, assuming HCLK is 100 MHz (1 cycle = 10 nanoseconds) // These read timings work to read settings, but slower timings are needed to read from the // frame memory. // Read timing: RD can go low at the same time as D/C changes and CS goes low. // RD must be low for at least 45 ns -> DATAST=8 // Also, a read cycle must take at least 160 nanoseconds, so set ADDSET=8. This means // that a whole read takes 16 HCLK cycles (160 nanoseconds). // Bus turnaround time is zero, because no particular interval is required between transactions. let read_timing = Timing::default().data(8).address_setup(8).bus_turnaround(0); // Write timing: Minimum 10 nanoseconds from when WR goes high to CS goes high, so // HCLK can't be faster than 100 MHz. // NWE must be low for at least 15 ns -> DATAST=3 // A write cycle must take at least 66 nanoseconds, so ADDSET=3. This means that a whole // write cycle takes 7 HCLK cycles (70 nanoseconds) (an extra HCLK cycle is added after NWE // goes high). // Bus turnaround time is zero, because no particular interval is required between transactions. let write_timing = Timing::default().data(3).address_setup(3).bus_turnaround(0); let (_fsmc, interface) = FsmcLcd::new(dp.FSMC, lcd_pins, &read_timing, &write_timing); // The 32F412GDISCOVERY board has an FRD154BP2902-CTP LCD. There is no easily available // datasheet, so the behavior of this code is based on the working demonstration C code: // https://github.com/STMicroelectronics/STM32CubeF4/blob/e084518f363e04344dc37822210a75e87377b200/Drivers/BSP/STM32412G-Discovery/stm32412g_discovery_lcd.c // https://github.com/STMicroelectronics/STM32CubeF4/blob/e084518f363e04344dc37822210a75e87377b200/Drivers/BSP/Components/st7789h2/st7789h2.c // Add LCD controller driver let mut lcd = ST7789::new( interface, Some(lcd_reset), Some(backlight_control), 240, 240, ); // Initialise the display and clear the screen lcd.init(&mut delay).unwrap(); lcd.clear(Rgb565::BLACK).unwrap(); // Draw some circles let test_colors = [ Rgb565::new(0x4e >> 3, 0x79 >> 2, 0xa7 >> 3), Rgb565::new(0xf2 >> 3, 0x8e >> 2, 0x2b >> 3), Rgb565::new(0xe1 >> 3, 0x57 >> 2, 0x59 >> 3), Rgb565::new(0x76 >> 3, 0xb7 >> 2, 0xb2 >> 3), Rgb565::new(0x59 >> 3, 0xa1 >> 2, 0x4f >> 3), Rgb565::new(0xed >> 3, 0xc9 >> 2, 0x48 >> 3), Rgb565::new(0xb0 >> 3, 0x7a >> 2, 0xa1 >> 3), Rgb565::new(0xff >> 3, 0x9d >> 2, 0xa7 >> 3), Rgb565::new(0x9c >> 3, 0x75 >> 2, 0x5f >> 3), Rgb565::new(0xba >> 3, 0xb0 >> 2, 0xac >> 3), ]; let center_points = [ Point::new(70, 70), Point::new(170, 70), Point::new(170, 170), Point::new(70, 170), ]; let mut drawer = ColoredCircleDrawer::new(¢er_points, &test_colors); loop { drawer.draw(&mut lcd).unwrap(); delay.delay_ms(100); } } /// Draws colored circles of various locations and colors struct ColoredCircleDrawer<'a> { /// Infinite iterator over circle center points centers: Cloned>>, /// Infinite iterator over Rgb565 colors colors: Cloned>>, } impl<'a> ColoredCircleDrawer<'a> { pub fn new(centers: &'a [Point], colors: &'a [Rgb565]) -> Self { ColoredCircleDrawer { centers: centers.iter().cycle().cloned(), colors: colors.iter().cycle().cloned(), } } /// Draws one circle onto a target pub fn draw(&mut self, target: &mut T) -> Result<(), T::Error> where T: DrawTarget, { let center = self.centers.next().unwrap(); let color = self.colors.next().unwrap(); Circle::new(center, 50) .into_styled(PrimitiveStyle::with_fill(color)) .draw(target) } }