diff --git a/Cargo.toml b/Cargo.toml index 8c225b3..fc4c20b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,7 @@ esp-radio = { version = "0.16.0", features = ["smoltcp", "wifi", "esp32c3", "uns esp-rtos = { version = "0.1.1", features = ["esp32c3", "embassy"] } ili9341 = "0.6.0" static_cell = "2.1.1" + +# https://docs.espressif.com/projects/rust/esp-wifi/0.15.0/esp32c3/esp_wifi/index.html#optimization-level +[profile.dev.package.esp-wifi] +opt-level = 3 diff --git a/flake.nix b/flake.nix index fff2aea..1df0842 100644 --- a/flake.nix +++ b/flake.nix @@ -32,6 +32,7 @@ buildy pkgs.rustup pkgs.espflash + pkgs.clippy pkgs.pkg-config pkgs.stdenv.cc pkgs.libusb1 @@ -45,6 +46,7 @@ LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}"; shellHook = '' + export PATH="${lib.makeBinPath [pkgs.rust-analyzer]}:$PATH" export PROJECT_DIR="$(pwd)"; # custom bashrc stuff export PS1_PREFIX="(esp-rs)" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 98b588e..4030338 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "stable" -components = ["rust-src", "clippy", "rust-analyzer"] +components = ["rust-src", "rustc-dev"] targets = ["riscv32imc-unknown-none-elf"] diff --git a/src/main.rs b/src/main.rs index a63e400..c51de9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![feature(trait_alias)] extern crate alloc; @@ -22,7 +23,10 @@ use { alignment::HorizontalAlignment as TextHorizontalAlignment, }, embedded_layout::{ - layout::linear::LinearLayout, + view_group::ViewGroup, + layout::{ + linear::LinearLayout, + }, prelude::*, }, embedded_hal_bus::spi::{ExclusiveDevice, NoDelay}, @@ -53,34 +57,38 @@ const PASSWORD: &str = env!("WIFI_PASSWORD"); esp_bootloader_esp_idf::esp_app_desc!(); +/* +* Make it easier to follow the TFT related types. .-. +*/ + type TFTSpiDevice<'spi> = ExclusiveDevice, Output<'spi>, NoDelay>; type TFTSpiInterface<'spi> = SPIInterface, Output<'spi>, NoDelay>, Output<'spi>>; type Ili<'spi> = Ili9341, Output<'spi>>; -pub struct TFT<'spi> { - display: Ili<'spi>, -} +/* +Provide an type alias DColour (display colour) since Bgr565 is the actual colour ordering for my display. +*/ -impl<'spi> TFT<'spi> { - fn draw_target(&mut self) -> DrawFlipper<'_, 'spi> { - DrawFlipper { - display: &mut self.display, - } - } -} +type DColor = Bgr565; -fn candyflip(color: Bgr565) -> Rgb565 { - unsafe { - core::mem::transmute::(color) - } -} -fn flipcandy(color: Rgb565) -> Bgr565 { - unsafe { - core::mem::transmute::(color) - } -} +/* +* Provide an alias for something that can be put into a ViewGroup for the use of embedded-layout. +*/ + +trait Drawy = Drawable + ViewGroup; + +/* +* So, my specific ILI9341-derived display doesn't JUST have wrong colour channels (Rgb vs Gbr like +* in the actual driver implementation). +* +* It also is *mirrored* horizontally. The underlying library and the driver implementation both don't +* expose anything reasonable to handle this. +* +* The driver exposes "Orientation" of Horizontal, Portrait, HorizontalFlipped and PortraitFlipped. +* This is inadequate for the problems in the implementation I have. +*/ struct DrawFlipper<'a, 'spi> { display: &'a mut Ili<'spi>, @@ -88,7 +96,7 @@ struct DrawFlipper<'a, 'spi> { impl<'a, 'spi> DrawTarget for DrawFlipper<'a, 'spi> { type Error = as DrawTarget>::Error; - type Color = Bgr565;// as DrawTarget>::Color; + type Color = DColor;// as DrawTarget>::Color; fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, @@ -131,20 +139,40 @@ impl<'a> Dimensions for DrawFlipper<'a, '_> { } } -#[derive(Clone,Copy)] -enum StyleVariant { - Header, - Regular +/* +* Container for implementing the TFT type +*/ + +pub struct TFT<'spi> { + display: Ili<'spi>, } impl<'spi> TFT<'spi> { - const H_BG: Bgr565 = Bgr565::CSS_DARK_SLATE_GRAY; - const H_S: Bgr565 = Bgr565::CYAN; - const H_FG: Bgr565 = Bgr565::WHITE; + fn draw_target(&mut self) -> DrawFlipper<'_, 'spi> { + DrawFlipper { + display: &mut self.display, + } + } +} - const R_BG: Bgr565 = Bgr565::CSS_DARK_SLATE_GRAY; - const R_S: Bgr565 = Bgr565::BLUE; - const R_FG: Bgr565 = Bgr565::WHITE; +/* +* Provide a decent-ish way of replacing the colour channel ordering. +* +* Ask not questions about the drug terminology! +*/ + +fn candyflip(color: DColor) -> Rgb565 { + unsafe { + core::mem::transmute::(color) + } +} + +/* +* Implement rendering helpers for the weird display +*/ + +impl<'spi> TFT<'spi> { + const ROOT_BG: DColor = DColor::BLACK; pub fn new( spi2: SPI2<'spi>, @@ -185,43 +213,61 @@ impl<'spi> TFT<'spi> { .with_mode(Mode::_0) } - pub fn clear(&mut self, color: Bgr565) { - self.display.clear(candyflip(color)).unwrap(); + pub fn clear(&mut self, color: DColor) { + let _ = self.display.clear(candyflip(color)); + } + + pub fn clear_root(&mut self) { + let _ = self.display.clear(candyflip(Self::ROOT_BG)); } pub fn part_clear(&mut self, x: i32, y: i32, w: u32, h: u32) { Rectangle::new(Point::new(x, y), Size::new(w, h)) - .into_styled(PrimitiveStyle::with_fill(Bgr565::BLACK)) + .into_styled(PrimitiveStyle::with_fill(DColor::BLACK)) .draw(&mut self.draw_target()) .unwrap(); } - pub fn containing_recty(&mut self, x: i32, y: i32, w: u32, h: u32, style: StyleVariant) -> Styled> { - let bg = match style { - StyleVariant::Header => Self::H_BG, - StyleVariant::Regular => Self::R_BG, - }; - let s = match style { - StyleVariant::Header => Self::H_S, - StyleVariant::Regular => Self::R_S, - }; + pub fn contained_text<'a>(text: &'a str, margin: u32) -> impl Drawy + 'a { let style = PrimitiveStyleBuilder::new() - .fill_color(bg) - .stroke_color(s) - .stroke_width(1) + .stroke_color(DColor::RED) + .stroke_width(3) + .fill_color(DColor::CSS_DARK_SLATE_GRAY) .build(); - Rectangle::new(Point::new(x, y), Size::new(w, h)) - .clone() + let text_style = MonoTextStyle::new(&FONT_6X10, DColor::WHITE); + let text = Text::new(text, Point::new_equal((margin/2) as i32), text_style); + let margin_size = Size::new_equal(margin); + let bound = text.bounding_box(); + let size = bound.size + margin_size; + let height_offset = Point::new(0, -((margin/2) as i32)); + Chain::new( + text + ).append( + Rectangle::new(height_offset, size) .into_styled(style) + ) } - pub fn container(&mut self, margin: i32, y: i32, h: u32, style: StyleVariant) -> Styled> { - let width = self.display.bounding_box().size.width; - self.containing_recty(margin, y, width - margin as u32, h, style) + pub fn fullscreen_alert(&mut self, text: &str, clear: bool) { + if clear { + let _ = self.clear_root(); + } + let display_area = self.display.bounding_box(); + LinearLayout::vertical( + Chain::new( + LinearLayout::horizontal( + Self::contained_text("Initialized controller!", 16) + ) + ) + ).with_alignment(horizontal::Center) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center) + .draw(&mut self.draw_target()) + .unwrap(); } pub fn println(&mut self, text: &str, x: i32, y: i32) { - let style = MonoTextStyle::new(&FONT_6X10, Bgr565::WHITE); + let style = MonoTextStyle::new(&FONT_6X10, DColor::WHITE); Text::with_alignment(text, Point::new(x, y), style, Alignment::Center) .draw(&mut self.draw_target()) .unwrap(); @@ -276,49 +322,53 @@ async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) { }*/ -fn init_heap() { - const HEAP_SIZE: usize = 64 * 1024; - static mut HEAP: core::mem::MaybeUninit<[u8; HEAP_SIZE]> = core::mem::MaybeUninit::uninit(); - - unsafe { - esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new( - addr_of_mut!(HEAP) as *mut u8, - HEAP_SIZE, - esp_alloc::MemoryCapability::Internal.into(), - )); - } -} - struct Controller<'tft> { - pub tft: TFT<'tft>, + pub display: TFT<'tft>, } impl Controller<'_> { async fn init(peripherals: Peripherals) -> Self { - let tft = Self::init_screen(peripherals).await; - let controller = Self { - tft, + let mut display = Self::init_screen(peripherals).await; + let mut controller = Self { + display, }; + controller.display.fullscreen_alert("Controller initialized!", true); controller } async fn init_screen<'tft>(peripherals: Peripherals) -> TFT<'tft> { - let dc = peripherals.GPIO9; - let mosi = peripherals.GPIO6; + // Refer to https://www.espboards.dev/esp32/esp32-c3-super-mini/#esp32-c3-super-mini-pinout + let rst = peripherals.GPIO0; let sclk = peripherals.GPIO4; let miso = peripherals.GPIO5; + let mosi = peripherals.GPIO6; let cs = peripherals.GPIO7; - let rst = peripherals.GPIO0; + let dc = peripherals.GPIO9; let mut tft = TFT::new(peripherals.SPI2, sclk, miso, mosi, cs, rst, dc); - let _ = tft.draw_target().clear(Bgr565::BLACK); tft } } +/*struct WifiController { + timer: TimerGroup, +} + +impl WifiController { + async fn init_wifi(peripherals: Peripherals) -> Self { + let timer = TimerGroup::new(peripherals.TIMG1); + let rng = Rng::new(); + Self { + timer + } + } +}*/ + #[esp_rtos::main] async fn main(spawner: Spawner) { - init_heap(); + // Note that for alloc to work, `./.cargo/config.toml` should contain alloc under build-std. + esp_alloc::heap_allocator!(size: 64*1024); + #[cfg(feature = "log")] { // The default log level can be specified here. @@ -347,28 +397,6 @@ async fn main(spawner: Spawner) { let mut controller = Controller::init(peripherals).await; - let display_area = controller.tft.display.bounding_box(); - let text_style = MonoTextStyle::new(&FONT_6X10, Bgr565::WHITE); - let margin = 2; - let display_width = display_area.size.width; - let display_height = display_area.size.height; - - LinearLayout::vertical( - Chain::new( - LinearLayout::horizontal( - Chain::new( - Text::new("Initializing~!", Point::new(margin, margin), text_style) - ).append( - controller.tft.container(margin, -10, 20, StyleVariant::Header) - ) - ) - ) - ).with_alignment(horizontal::Center) - .arrange() - .align_to(&display_area, horizontal::Center, vertical::Center) - .draw(&mut controller.tft.draw_target()) - .unwrap(); - loop { // your business logic }