|
| 1 | +//! Example of using the SPI interface using the ESP32 WROVER DEVKIT |
| 2 | +//! |
| 3 | +//! This examples writes to the display. As this pushes many pixels it is quite slow in debug mode, |
| 4 | +//! so please run it in release mode to get an impression of the obtainable speed. |
| 5 | +
|
| 6 | +#![no_std] |
| 7 | +#![no_main] |
| 8 | + |
| 9 | +use core::{fmt::Write, panic::PanicInfo}; |
| 10 | + |
| 11 | +use esp32_hal::{ |
| 12 | + clock_control::{sleep, ClockControl, XTAL_FREQUENCY_AUTO}, |
| 13 | + dport::Split, |
| 14 | + dprintln, |
| 15 | + gpio::{InputPin, OutputPin}, |
| 16 | + prelude::*, |
| 17 | + serial::{self, Serial}, |
| 18 | + spi::{self, SPI}, |
| 19 | + target, |
| 20 | + timer::Timer, |
| 21 | +}; |
| 22 | + |
| 23 | +use embedded_hal::blocking::spi::WriteIter; |
| 24 | + |
| 25 | +use ili9341; |
| 26 | + |
| 27 | +use embedded_graphics::{ |
| 28 | + fonts::{Font12x16, Text}, |
| 29 | + pixelcolor::Rgb565, |
| 30 | + prelude::*, |
| 31 | + primitives::{Circle, Rectangle}, |
| 32 | + style::{PrimitiveStyleBuilder, TextStyle}, |
| 33 | +}; |
| 34 | + |
| 35 | +// Interface for ili9341 driver |
| 36 | +// ili9341 uses separate command/data pin, this interface set this pin to the appropriate state |
| 37 | +struct SPIInterface< |
| 38 | + CMD: embedded_hal::digital::v2::OutputPin, |
| 39 | + SCLK: OutputPin, |
| 40 | + SDO: OutputPin, |
| 41 | + SDI: InputPin + OutputPin, |
| 42 | + CS: OutputPin, |
| 43 | +> { |
| 44 | + spi: SPI<esp32::SPI2, SCLK, SDO, SDI, CS>, |
| 45 | + cmd: CMD, |
| 46 | +} |
| 47 | + |
| 48 | +impl< |
| 49 | + CMD: embedded_hal::digital::v2::OutputPin, |
| 50 | + SCLK: OutputPin, |
| 51 | + SDO: OutputPin, |
| 52 | + SDI: InputPin + OutputPin, |
| 53 | + CS: OutputPin, |
| 54 | + > ili9341::Interface for SPIInterface<CMD, SCLK, SDO, SDI, CS> |
| 55 | +{ |
| 56 | + type Error = esp32_hal::spi::Error; |
| 57 | + |
| 58 | + fn write(&mut self, command: u8, data: &[u8]) -> Result<(), Self::Error> { |
| 59 | + self.cmd |
| 60 | + .set_low() |
| 61 | + .map_err(|_| esp32_hal::spi::Error::PinError)?; |
| 62 | + self.spi.write(&[command])?; |
| 63 | + self.cmd |
| 64 | + .set_high() |
| 65 | + .map_err(|_| esp32_hal::spi::Error::PinError)?; |
| 66 | + self.spi.write(data)?; |
| 67 | + Ok(()) |
| 68 | + } |
| 69 | + |
| 70 | + fn write_iter( |
| 71 | + &mut self, |
| 72 | + command: u8, |
| 73 | + data: impl IntoIterator<Item = u16>, |
| 74 | + ) -> Result<(), Self::Error> { |
| 75 | + self.cmd |
| 76 | + .set_low() |
| 77 | + .map_err(|_| esp32_hal::spi::Error::PinError)?; |
| 78 | + self.spi.write(&[command])?; |
| 79 | + self.cmd |
| 80 | + .set_high() |
| 81 | + .map_err(|_| esp32_hal::spi::Error::PinError)?; |
| 82 | + self.spi.write_iter(data)?; |
| 83 | + Ok(()) |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +#[entry] |
| 88 | +fn main() -> ! { |
| 89 | + let dp = target::Peripherals::take().expect("Failed to obtain Peripherals"); |
| 90 | + |
| 91 | + let (mut dport, dport_clock_control) = dp.DPORT.split(); |
| 92 | + |
| 93 | + let clkcntrl = ClockControl::new( |
| 94 | + dp.RTCCNTL, |
| 95 | + dp.APB_CTRL, |
| 96 | + dport_clock_control, |
| 97 | + XTAL_FREQUENCY_AUTO, |
| 98 | + ) |
| 99 | + .unwrap(); |
| 100 | + |
| 101 | + let (clkcntrl_config, mut watchdog) = clkcntrl.freeze().unwrap(); |
| 102 | + let (_, _, _, mut watchdog0) = Timer::new(dp.TIMG0, clkcntrl_config); |
| 103 | + let (_, _, _, mut watchdog1) = Timer::new(dp.TIMG1, clkcntrl_config); |
| 104 | + |
| 105 | + watchdog.disable(); |
| 106 | + watchdog0.disable(); |
| 107 | + watchdog1.disable(); |
| 108 | + |
| 109 | + let _lock = clkcntrl_config.lock_cpu_frequency(); |
| 110 | + |
| 111 | + let pins = dp.GPIO.split(); |
| 112 | + |
| 113 | + let mut serial: Serial<_, _, _> = Serial::new( |
| 114 | + dp.UART0, |
| 115 | + serial::Pins { |
| 116 | + tx: pins.gpio1, |
| 117 | + rx: pins.gpio3, |
| 118 | + cts: None, |
| 119 | + rts: None, |
| 120 | + }, |
| 121 | + serial::config::Config { |
| 122 | + baudrate: 115200.Hz(), |
| 123 | + ..serial::config::Config::default() |
| 124 | + }, |
| 125 | + clkcntrl_config, |
| 126 | + &mut dport, |
| 127 | + ) |
| 128 | + .unwrap(); |
| 129 | + |
| 130 | + // Official ili9341 spec is 10MHz, but overdrive up to 80MHz actually works. |
| 131 | + // 26MHz chosen here: will be 26MHz when using 26MHz crystal, 20MHz when using 40MHz crystal, |
| 132 | + // due to integer clock division. |
| 133 | + // Faster is no use as the cpu is not keeping up with the embedded_graphics library. |
| 134 | + let spi: SPI<_, _, _, _, _> = SPI::<esp32::SPI2, _, _, _, _>::new( |
| 135 | + dp.SPI2, |
| 136 | + spi::Pins { |
| 137 | + sclk: pins.gpio19, |
| 138 | + sdo: pins.gpio23, |
| 139 | + sdi: Some(pins.gpio25), |
| 140 | + cs: Some(pins.gpio22), |
| 141 | + }, |
| 142 | + spi::config::Config { |
| 143 | + baudrate: 26.MHz().into(), |
| 144 | + bit_order: spi::config::BitOrder::MSBFirst, |
| 145 | + data_mode: spi::config::MODE_0, |
| 146 | + }, |
| 147 | + clkcntrl_config, |
| 148 | + &mut dport, |
| 149 | + ) |
| 150 | + .unwrap(); |
| 151 | + |
| 152 | + let mut gpio_backlight = pins.gpio5.into_push_pull_output(); |
| 153 | + let mut gpio_reset = pins.gpio18.into_push_pull_output(); |
| 154 | + let gpio_cmd = pins.gpio21.into_push_pull_output(); |
| 155 | + |
| 156 | + gpio_reset.set_low().unwrap(); |
| 157 | + sleep(100.ms()); |
| 158 | + gpio_reset.set_high().unwrap(); |
| 159 | + sleep(100.ms()); |
| 160 | + |
| 161 | + gpio_backlight.set_low().unwrap(); |
| 162 | + |
| 163 | + let spi_if = SPIInterface { spi, cmd: gpio_cmd }; |
| 164 | + |
| 165 | + let mut display = |
| 166 | + ili9341::Ili9341::new(spi_if, gpio_reset, &mut esp32_hal::delay::Delay::new()).unwrap(); |
| 167 | + |
| 168 | + display |
| 169 | + .set_orientation(ili9341::Orientation::Landscape) |
| 170 | + .unwrap(); |
| 171 | + |
| 172 | + Rectangle::new(Point::new(0, 0), Point::new(320, 240)) |
| 173 | + .into_styled( |
| 174 | + PrimitiveStyleBuilder::new() |
| 175 | + .fill_color(Rgb565::WHITE) |
| 176 | + .stroke_width(4) |
| 177 | + .stroke_color(Rgb565::BLUE) |
| 178 | + .build(), |
| 179 | + ) |
| 180 | + .draw(&mut display) |
| 181 | + .unwrap(); |
| 182 | + |
| 183 | + let rect = Rectangle::new(Point::new(10, 80), Point::new(30, 100)).into_styled( |
| 184 | + PrimitiveStyleBuilder::new() |
| 185 | + .fill_color(Rgb565::RED) |
| 186 | + .stroke_width(1) |
| 187 | + .stroke_color(Rgb565::WHITE) |
| 188 | + .build(), |
| 189 | + ); |
| 190 | + |
| 191 | + let circle = Circle::new(Point::new(20, 50), 10).into_styled( |
| 192 | + PrimitiveStyleBuilder::new() |
| 193 | + .fill_color(Rgb565::GREEN) |
| 194 | + .stroke_width(1) |
| 195 | + .stroke_color(Rgb565::WHITE) |
| 196 | + .build(), |
| 197 | + ); |
| 198 | + |
| 199 | + Text::new("Hello Rust!", Point::new(20, 16)) |
| 200 | + .into_styled(TextStyle::new(Font12x16, Rgb565::RED)) |
| 201 | + .draw(&mut display) |
| 202 | + .unwrap(); |
| 203 | + |
| 204 | + writeln!(serial, "\n\nESP32 Started\n\n").unwrap(); |
| 205 | + |
| 206 | + loop { |
| 207 | + for x in (0..280).chain((0..280).rev()) { |
| 208 | + rect.translate(Point::new(x, 0)).draw(&mut display).unwrap(); |
| 209 | + } |
| 210 | + |
| 211 | + for x in (0..280).chain((0..280).rev()) { |
| 212 | + circle |
| 213 | + .translate(Point::new(x, 0)) |
| 214 | + .draw(&mut display) |
| 215 | + .unwrap(); |
| 216 | + } |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +#[panic_handler] |
| 221 | +fn panic(info: &PanicInfo) -> ! { |
| 222 | + dprintln!("\n\n*** {:?}", info); |
| 223 | + loop {} |
| 224 | +} |
0 commit comments