yuri91 / ili9341-rs

A WIP, no_std, generic driver for the ILI9341 (and ILI9340C) TFT LCD display
Apache License 2.0
57 stars 50 forks source link

Add some examples #13

Closed pepijndevos closed 2 years ago

pepijndevos commented 4 years ago

It took me two days to get Rust and this library working on my STM32F4DISCOVERY, so it would be really amazing to have some examples. I'll share the code I ended up with, but I'm not sure about the correct setup to add it as a proper example. My project is based on https://github.com/stm32-rs/stm32f407g-disc with the following main file

#![no_main]
#![no_std]

use panic_halt as _;

use stm32f407g_disc as board;

use crate::board::{
    hal::stm32,
    hal::spi::{Mode, Phase, Polarity, Spi},
    hal::{delay::Delay, prelude::*},
};

use cortex_m::peripheral::Peripherals;

use cortex_m_rt::entry;

use embedded_graphics::{
    fonts::{Font8x16, Text},
    pixelcolor::Rgb565,
    prelude::*,
    primitives::{Circle, Rectangle},
    style::{PrimitiveStyle, TextStyle},
};

use ili9341::{Ili9341, Orientation};

#[entry]
fn main() -> ! {
    if let (Some(p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) {

        let rcc = p.RCC.constrain();
        let gpioa = p.GPIOA.split();

        let clocks = rcc
            .cfgr
            .use_hse(8.mhz())
            .sysclk(48.mhz())
            .pclk1(24.mhz())
            .freeze();

        // Configure pins for SPI
        let sck = gpioa.pa5.into_alternate_af5();
        let miso = gpioa.pa6.into_alternate_af5();
        let mosi = gpioa.pa7.into_alternate_af5();

        let spi_mode = Mode {
            polarity: Polarity::IdleLow,
            phase: Phase::CaptureOnFirstTransition,
        };

        let spi = Spi::spi1(
            p.SPI1,
            (sck, miso, mosi),
            spi_mode,
            16.mhz().into(),
            clocks,
        );
        // Get delay provider
        let mut delay = Delay::new(cp.SYST, clocks);

        let mut display = Ili9341::new_spi(
            spi,
            gpioa.pa4.into_push_pull_output(),
            gpioa.pa3.into_push_pull_output(),
            gpioa.pa2.into_push_pull_output(),
            &mut delay
        ).expect("display init failed");

        display.set_orientation(Orientation::LandscapeFlipped);

        let c = Circle::new(Point::new(20, 20), 50)
            .into_styled(PrimitiveStyle::with_fill(Rgb565::RED));
        let t = Text::new("Hello Rust!", Point::new(20, 16))
            .into_styled(TextStyle::new(Font8x16, Rgb565::GREEN));
        let r = Rectangle::new(Point::new(0, 0), Point::new(320, 240))
            .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK));

        r.draw(&mut display).expect("draw failed");
        c.draw(&mut display).expect("draw failed");
        t.draw(&mut display).expect("draw failed");
    }

    loop {
        continue;
    }
}
yuri91 commented 4 years ago

Hi!

The issue with examples is that I have to assume a particular board for the example itself, and most of the code would be setting up the specific board and its peripherals, more that using the library.

That said, having at least one (blue pill? raspberry pi?) is probably warranted

pepijndevos commented 4 years ago

An alternative could be to add ILI9341 examples to upstream HAL crates. The fact that the STM32F4 HAL crate did not have an SPI example was pretty much day two of my struggles. I had to adapt it from the F3 crate.

But yea, assuming the HAL crate has an SPI example, I'd just add an example for whichever board you have at home that is reasonably popular.

maxdignan commented 2 years ago

I think I'd really benefit from more examples. Thanks @pepijndevos for sharing your's! I'm working on an F3-Discovery with a 3.5" TFT Touchscreen Breakout adafru.it/2050 . Running into issues getting it to do anything more than light up. Any suggestions @yuri91 ?

#![feature(alloc_error_handler)]
#![no_main]
#![no_std]

extern crate alloc;

use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use cortex_m::Peripherals;
use cortex_m::delay::Delay;
use cortex_m::peripheral::SYST;
use cortex_m::peripheral::syst::SystClkSource;
use cortex_m::prelude::{_embedded_hal_blocking_delay_DelayMs, _embedded_hal_blocking_spi_Write};
use f3::hal::rcc::Rcc;
use core::borrow::Borrow;
use core::marker::PhantomData;
use core::ops::Deref;
use core::{alloc::Layout, pin::Pin};
use core::fmt::Write;
use core::panic::PanicInfo;
use cortex_m::{asm::nop, iprintln};
use cortex_m_rt::{entry, exception, ExceptionFrame};

use embedded_hal::{digital::v2::OutputPin, spi::{Mode, Phase, Polarity}, blocking::delay::DelayMs, };
use embedded_sdmmc::{DirEntry, TimeSource, Timestamp};
use f3::hal::{gpio::{gpioa::{PA0, PA1, PA4, PA5, PA6, PA7}, gpioe, GpioExt, Output, PushPull, AF5},  i2c::SdaPin, prelude::_stm32f30x_hal_flash_FlashExt,  rcc::{Clocks, RccExt, APB2}, spi::{MisoPin, MosiPin, SckPin, Spi}, stm32f30x::{self, FLASH, GPIOA, RCC, SPI1 }, time::Hertz};
use heapless::String;
use panic_itm; // panic handler

use embedded_graphics::{
  fonts::{Font8x16, Text},
  pixelcolor::Rgb565,
  prelude::*,
  primitives::{Circle, Rectangle},
  style::{PrimitiveStyle, TextStyle},
};

// use embedded_graphics_core::{primitives::rectangle::Rectangle, draw_target::DrawTarget, pixelcolor::Rgb565, geometry::{Size, Point}};

use ili9341::{Ili9341, Orientation};
use display_interface;

// use state_mgmt;

use alloc_cortex_m::CortexMHeap;
// this is the allocator the application will use
#[global_allocator]
static ALLOCATOR: CortexMHeap = CortexMHeap::empty();

const HEAP_SIZE: usize = 1024 * 40; // 40 KB .. the RAM size on f3discovery

// struct my_delay;

// impl DelayMs<u16> for my_delay {
//   fn delay_ms(&mut self, ms: u16) {
//     delay(ms.into())
//   }
// }

struct SPIWrapper {
  spi: Box<Spi<SPI1, (PA5<AF5>, PA6<AF5>, PA7<AF5>)>>,
}

impl display_interface::WriteOnlyDataCommand for SPIWrapper {
  fn send_commands(&mut self, cmd: display_interface::DataFormat<'_>) -> Result<(), display_interface::DisplayError> {
    match cmd {
      display_interface::DataFormat::U8(d) => {
        self.spi.write(d);

      },
      display_interface::DataFormat::U16(d) => {
        for item in d.iter() {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16BE(d) => {
        for item in d.iter() {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16LE(d) => {
        for item in d.iter() {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U8Iter(d) => {
        for item in d {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16BEIter(d) => {
        for item in d {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16LEIter(d) => {
        for item in d {
          self.spi.write(&item.to_be_bytes());
        }
      },
      _ => {},
    }
    Ok(())
  }
  fn send_data(&mut self, buf: display_interface::DataFormat<'_>) -> Result<(), display_interface::DisplayError> {
    match buf {
      display_interface::DataFormat::U8(d) => {
        self.spi.write(d);

      },
      display_interface::DataFormat::U16(d) => {
        for item in d.iter() {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16BE(d) => {
        for item in d.iter() {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16LE(d) => {
        for item in d.iter() {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U8Iter(d) => {
        for item in d {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16BEIter(d) => {
        for item in d {
          self.spi.write(&item.to_be_bytes());
        }
      },
      display_interface::DataFormat::U16LEIter(d) => {
        for item in d {
          self.spi.write(&item.to_be_bytes());
        }
      },
      _ => {},
    }
    Ok(())
  }
}

#[entry]
fn main() -> ! {
    let mut cp = cortex_m::Peripherals::take().unwrap();
    let mut dp = stm32f30x::Peripherals::take().unwrap();

    let mut rcc_2 = dp.RCC.constrain();
    let mut flash_2 = dp.FLASH.constrain();

    // let clocks = rcc_2.cfgr.hclk(Hertz(8_000_000)).sysclk(Hertz(48_000_000)).pclk1(Hertz(24_000_000)).freeze(&mut flash_2.acr);
    let clocks = rcc_2.cfgr.freeze(&mut flash_2.acr);

    // dp.GPIOA.moder.write(|w| {
    //     w.moder0().output();
    //     w.moder1().output();
    //     w.moder4().output();
    //     w.moder5().output();
    //     w.moder6().output();
    //     w.moder7().output()
    // });

    let mut gpioa = dp.GPIOA.split(&mut rcc_2.ahb);

    let slave_select = gpioa
        .pa4
        .into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
    let s_clock = gpioa.pa5.into_af5(&mut gpioa.moder, &mut gpioa.afrl);
    let s_miso = gpioa.pa6.into_af5(&mut gpioa.moder, &mut gpioa.afrl);
    let s_mosi = gpioa.pa7.into_af5(&mut gpioa.moder, &mut gpioa.afrl);
    let s_dc = gpioa.pa0.into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
    let s_reset = gpioa.pa1.into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);

    let spi: Spi<SPI1, (PA5<AF5>, PA6<AF5>, PA7<AF5>)> = Spi::spi1(
      dp.SPI1,
      (s_clock, s_miso, s_mosi),
      Mode {
          polarity: Polarity::IdleLow,
          phase: Phase::CaptureOnFirstTransition,
      },
      Hertz(8_000_000),
      clocks,
      &mut rcc_2.apb2,
    );

    // let screen_card_spi = SPIWrapper {
    //   spi: Box::new(spi),
    // };

    let mut delayy = Delay::new(cp.SYST, clocks.sysclk().0);

    match Ili9341::new_spi(spi, slave_select, s_dc, s_reset, &mut delayy) {
      Ok(mut display) => {
          // screen.fill_solid(&Rectangle {size: Size {height: 100, width: 100}, top_left: Point {x: 0, y: 0}}, Rgb565::new(120,120,120));
          display.set_orientation(Orientation::LandscapeFlipped);

          let c = Circle::new(Point::new(20, 20), 50)
              .into_styled(PrimitiveStyle::with_fill(Rgb565::RED));
          let t = Text::new("Hello Rust!", Point::new(20, 16))
              .into_styled(TextStyle::new(Font8x16, Rgb565::GREEN));
          let r = Rectangle::new(Point::new(0, 0), Point::new(320, 240))
              .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK));

          r.draw(&mut display).expect("draw failed");
          c.draw(&mut display).expect("draw failed");
          t.draw(&mut display).expect("draw failed");

              cortex_m::asm::nop();
      }
      Err(err) => {
        cortex_m::asm::nop();
      }
    }

    loop {
        cortex_m::asm::nop();
    }
}

// define what happens in an Out Of Memory (OOM) condition
#[alloc_error_handler]
fn alloc_error(_layout: Layout) -> ! {
    loop {
        cortex_m::asm::bkpt();
        cortex_m::asm::nop();
    }
}

#[exception]
fn HardFault(ef: &ExceptionFrame) -> ! {
    loop {
        cortex_m::asm::bkpt();
        cortex_m::asm::nop();
    }
}
plaes commented 2 years ago

@maxdignan You are using an old and already removed API. Here's a barebones skeleton example:

use display_interface_spi::SPIInterface;
use embedded_time::rate::Extensions;
use ili9341::{Ili9341, Orientation};

use embedded_graphics::{
    pixelcolor::Rgb565,
    prelude::*,
    primitives::{PrimitiveStyle, Rectangle},
};

fn main() {
    /// ... most of the peripheral setup is dropped...

    let cs = pins.gpio5.into_push_pull_output();
    let dc = pins.gpio11.into_push_pull_output();
    let reset = pins.gpio10.into_push_pull_output();

    let spi = hal::spi::Spi::<_, _, 8>::new(pac.SPI0);

    let spi = spi.init(
        &mut pac.RESETS,
        clocks.peripheral_clock.freq(),
        16_000_000u32.Hz(),
        &embedded_hal::spi::MODE_0,
    );

    let spi_iface = SPIInterface::new(spi, dc, cs);

    let mut display = Ili9341::new(
        spi_iface,
        reset,
        &mut delay,
        Orientation::Portrait,
        ili9341::DisplaySize240x320,
    ).unwrap();

    display.clear(Rgb565::BLACK).unwrap();
    let on = PrimitiveStyle::with_fill(Rgb565::RED);

    Rectangle::new(Point::new(10, 20), Size::new(10, 10))
        .into_styled(on)
        .draw(&mut display).unwrap();

    loop {}
}
maxdignan commented 2 years ago

@plaes Thank you so much! This was super helpful to me!

yuri91 commented 2 years ago

A basic example was added in https://github.com/yuri91/ili9341-rs/pull/32, so I am closing this