#![no_std]
#![no_main]
// use `defmt` for logging
use defmt_rtt as _;
use esp_backtrace as _;
use defmt::info;
use static_cell::StaticCell;
/* Embassy and HAL imports */
use embassy_executor::Spawner;
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex};
use embassy_time::{Delay, Timer, Duration};
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
use esp_hal::{
clock::ClockControl,
dma::Dma,
dma_buffers,
gpio::{Io, Level, Output},
peripherals::Peripherals,
prelude::*,
spi::{
master::{prelude::*, Spi},
SpiMode,
},
timer::SystemTimer,
Delay as HalDelay,
};
use esp_hal_embassy;
/* Display and graphics imports */
use lcd_async::{
interface,
models::ILI9341Rgb565, // Using the ILI9341 model from lcd-async
options::{ColorOrder, Orientation, Rotation},
raw_framebuf::RawFrameBuf,
Builder,
};
use embedded_graphics::{
mono_font::{profont::PROFONT_24_POINT, MonoTextStyle},
pixelcolor::Rgb565,
prelude::*,
text::Text,
};
// --- Display Configuration ---
// Physical dimensions of the ILI9341 display
const WIDTH: u16 = 240;
const HEIGHT: u16 = 320;
// Logical dimensions after applying landscape orientation
const LOGICAL_WIDTH: u16 = 320;
const LOGICAL_HEIGHT: u16 = 240;
// Framebuffer configuration
const PIXEL_SIZE: usize = 2; // Rgb565 uses 2 bytes per pixel
const FRAME_SIZE: usize = (LOGICAL_WIDTH as usize) * (LOGICAL_HEIGHT as usize) * PIXEL_SIZE;
// Static allocation for the framebuffer. This buffer will hold the image data before sending it to the display.
static FRAME_BUFFER: StaticCell<[u8; FRAME_SIZE]> = StaticCell::new();
// Static allocation for DMA descriptors
static mut TX_DESCRIPTORS: [u32; 8 * 3] = [0; 8 * 3];
static mut RX_DESCRIPTORS: [u32; 8 * 3] = [0; 8 * 3];
#[esp_hal_embassy::main]
async fn main(_spawner: Spawner) {
// Initialize defmt logging
defmt::info!("Initializing application...");
// Initialize ESP-HAL and Embassy
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::max(system.clock_control).freeze();
let timer = SystemTimer::new(peripherals.SYSTIMER).alarm0;
esp_hal_embassy::init(&clocks, timer);
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let dma = Dma::new(peripherals.DMA);
let dma_channel = dma.channel0;
// --- Pin Configuration (from your original code) ---
let sck = io.pins.gpio6;
let mosi = io.pins.gpio7;
let cs = io.pins.gpio2;
let rst = io.pins.gpio10;
let dc = io.pins.gpio3;
let backlight = io.pins.gpio4;
// MISO (gpio8) is not used for display output
// Configure backlight pin and turn it on
let mut backlight = Output::new(backlight, Level::High);
backlight.set_high().unwrap();
info!("Backlight ON");
// --- SPI Master Configuration (Async with DMA) ---
// The buffer size here is for the DMA transfer, not the entire framebuffer.
// 32000 is a good default size.
let (tx_buffer, rx_buffer) = dma_buffers!(32000);
let spi_master = Spi::new_async_dma(
peripherals.SPI2,
sck,
mosi,
esp_hal::gpio::NO_PIN, // No MISO
40.MHz(), // 40MHz is a good and stable speed for ILI9341
SpiMode::Mode0,
&clocks,
)
.with_dma(dma_channel.configure(
false,
unsafe { &mut TX_DESCRIPTORS },
unsafe { &mut RX_DESCRIPTORS },
DmaPriority::Priority0,
))
.with_tx_buffer(tx_buffer)
.with_rx_buffer(rx_buffer);
// --- Create Shared SPI Bus and Device ---
// This allows multiple devices to share the same SPI bus in the future.
static SPI_BUS: StaticCell<Mutex<NoopRawMutex, _>> = StaticCell::new();
let spi_bus = SPI_BUS.init(Mutex::new(spi_master));
let spi_device = SpiDevice::new(spi_bus, Output::new(cs, Level::High));
// --- Display Driver Initialization ---
let dc = Output::new(dc, Level::Low);
let rst = Output::new(rst, Level::Low);
let di = interface::SpiInterface::new(spi_device, dc);
let mut delay = Delay;
let mut display = Builder::new(ILI9341Rgb565, di)
.with_reset_pin(rst)
.with_display_size(WIDTH, HEIGHT)
.with_orientation(Orientation {
// This configuration matches `mipidsi`'s `Orientation::LandscapeInverted(true)`
rotation: Rotation::Deg270,
mirrored: true,
})
.with_color_order(ColorOrder::Bgr) // Match your original BGR setting
.init(&mut delay)
.await
.unwrap();
info!("Display initialized!");
// --- Drawing to the Framebuffer ---
let frame_buffer = FRAME_BUFFER.init([0; FRAME_SIZE]);
let mut raw_fb = RawFrameBuf::<Rgb565, _>::new(frame_buffer, LOGICAL_WIDTH, LOGICAL_HEIGHT);
// 1. Clear the buffer to white
raw_fb.clear(Rgb565::WHITE).unwrap();
// 2. Draw text onto the buffer
let text_style = MonoTextStyle::new(&PROFONT_24_POINT, Rgb565::BLACK);
// Position the text near the left edge and vertically centered.
// This calculation matches the logic from your original code.
let text_pos = Point::new(10, (LOGICAL_HEIGHT as i32 / 2));
Text::with_alignment(
"Display initialized",
text_pos,
text_style,
Alignment::Left,
)
.draw(&mut raw_fb)
.unwrap();
info!("Drawing complete. Sending to display...");
// --- Send Framebuffer to Display ---
// This sends the entire buffer in one go using DMA.
display
.show_raw_data(0, 0, LOGICAL_WIDTH, LOGICAL_HEIGHT, frame_buffer)
.await
.unwrap();
info!("Done!");
// --- Infinite Loop ---
loop {
Timer::after(Duration::from_secs(1)).await;
}
}