From 9abda6c6654ccef2ae739e29da979ef031f0f8a7 Mon Sep 17 00:00:00 2001 From: Claas Date: Sun, 25 Jan 2026 21:06:23 +0100 Subject: [PATCH] Fix expecting error instead of success and clean up --- Cargo.lock | 9 +- Cargo.toml | 1 + src/eink_display.rs | 206 ++++++++++++++++++++++++++++---------------- src/main.rs | 18 +--- 4 files changed, 138 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 505ab02..46fcbb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,7 @@ dependencies = [ "esp-rtos", "smoltcp", "static_cell", + "thiserror", "trouble-host", ] @@ -1662,18 +1663,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index cbbb43f..f69930a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ trouble-host = { version = "0.5.0", features = ["gatt"] } critical-section = "1.2.0" static_cell = "2.1.1" embedded-hal = "1.0.0" +thiserror = { version = "2.0.18", default-features = false } [profile.dev] diff --git a/src/eink_display.rs b/src/eink_display.rs index 3496acd..0d3e2af 100644 --- a/src/eink_display.rs +++ b/src/eink_display.rs @@ -1,15 +1,17 @@ -use bt_hci::cmd::info; -use defmt::{error, info}; +use defmt::info; use embassy_time::{Duration, TimeoutError, Timer, with_timeout}; use esp_hal::{ Async, - dma::{DmaChannelFor, DmaRxBuf, DmaTxBuf}, + dma::{DmaBufError, DmaChannelFor, DmaRxBuf, DmaTxBuf}, dma_buffers, gpio::{ Input, InputConfig, InputPin, Level, Output, OutputConfig, OutputPin, interconnect::PeripheralOutput, }, - spi::master::{AnySpi, Config, Instance, Spi, SpiDmaBus}, + spi::{ + self, + master::{AnySpi, Config, Instance, Spi, SpiDmaBus}, + }, time::Rate, }; @@ -46,6 +48,56 @@ enum Command { AutoWriteBwRam = 0x46, } +#[derive(Debug, thiserror::Error, defmt::Format)] +pub(super) enum CreateError { + #[error("Failed to create direct memory access (DMA) receive channel buffer")] + DmaReceiveBuffer(DmaBufError), + #[error("Failed to create direct memory access (DMA) transmit channel buffer")] + DmaTransmitBuffer(DmaBufError), + #[error("Failed to create SPI bus")] + SpiBus(#[from] spi::master::ConfigError), +} + +#[derive(Debug, thiserror::Error)] +#[error("Failed to send command")] +pub(super) struct SendCommandError(spi::Error); + +#[derive(Debug, thiserror::Error)] +#[error("Failed to send data")] +pub(super) struct SendDataError(spi::Error); + +#[derive(Debug, thiserror::Error)] +pub(super) enum SetRamAreaError { + #[error("Failed to send command")] + SendCommand(#[from] SendCommandError), + #[error("Failed to send data")] + SendData(#[from] SendDataError), +} + +#[derive(Debug, thiserror::Error)] +pub(super) enum InitializeControllerError { + #[error("Failed to send command")] + SendCommand(#[from] SendCommandError), + #[error("Failed to send data")] + SendData(#[from] SendDataError), + #[error("Timed out waiting for busy")] + WaitForBusy(#[from] WaitForBusyTimeoutError), + #[error("Failed to set RAM area")] + SetRamArea(#[from] SetRamAreaError), +} + +#[derive(Debug, thiserror::Error, defmt::Format)] +#[error("Timeout waiting for busy")] +pub(super) struct WaitForBusyTimeoutError(TimeoutError); + +#[derive(Debug, thiserror::Error)] +pub(super) enum InitializationError { + #[error("Failed to create e-ink display driver instance")] + Create(#[from] CreateError), + #[error("Failed to initialize e-ink display controller")] + InitializeController(#[from] InitializeControllerError), +} + pub(super) struct EinkDisplay<'d> { spi: SpiDmaBus<'d, Async>, reset: Output<'d>, @@ -63,16 +115,15 @@ impl<'d> EinkDisplay<'d> { reset: impl OutputPin + 'd, data_command: impl OutputPin + 'd, busy: impl InputPin + 'd, - ) -> Self { + ) -> Result { // DMA = Direct Memory Access let (receive_buffer, receive_descriptor, transmit_buffer, transmit_descriptors) = dma_buffers!(BUFFER_SIZE); let direct_memory_access_receive_buffer = DmaRxBuf::new(receive_descriptor, receive_buffer) - .expect("Expected direct memory access (DMA) receive channel buffer to be created"); + .map_err(CreateError::DmaReceiveBuffer)?; let direct_memory_access_transmit_buffer = - DmaTxBuf::new(transmit_descriptors, transmit_buffer).expect( - "Expected direct memory access (DMA) transmit channel buffer to be created", - ); + DmaTxBuf::new(transmit_descriptors, transmit_buffer) + .map_err(CreateError::DmaTransmitBuffer)?; // Initialize SPI with custom pins let spi = Spi::new( @@ -81,8 +132,7 @@ impl<'d> EinkDisplay<'d> { .with_frequency(Rate::from_mhz(40)) .with_mode(esp_hal::spi::Mode::_0) .with_read_bit_order(esp_hal::spi::BitOrder::MsbFirst), // .with_write_bit_order(esp_hal::spi::BitOrder::MsbFirst) - ) - .expect("Failed to create SPI bus") + )? .with_sck(serial_clock) .with_mosi(master_in_slave_out) // .with_miso(todo!("Not defined in XteinkX4 screen spec")) @@ -99,12 +149,12 @@ impl<'d> EinkDisplay<'d> { let data_command = Output::new(data_command, Level::High, OutputConfig::default()); let busy = Input::new(busy, InputConfig::default()); - Self { + Ok(Self { spi, reset, data_command, busy, - } + }) } async fn reset(&mut self) { @@ -119,29 +169,40 @@ impl<'d> EinkDisplay<'d> { info!("Display reset completed"); } - async fn send_command(&mut self, command: Command) { + async fn send_command(&mut self, command: Command) -> Result<(), SendCommandError> { info!("Sending command: {:?}", command); // Set into command mode self.data_command.set_low(); self.spi .write_async(&[command as u8]) .await - .expect_err("Expected to write command"); + .map_err(SendCommandError)?; info!("Command sent"); + Ok(()) } - async fn send_data(&mut self, data: &[u8]) { + async fn send_data(&mut self, data: &[u8]) -> Result<(), SendDataError> { info!("Sending data: {:?}", data); // Set into data mode self.data_command.set_high(); - self.spi - .write_async(data) - .await - .expect("Expected to write data"); + self.spi.write_async(data).await.map_err(SendDataError)?; info!("Data sent"); + Ok(()) } - async fn set_ram_area(&mut self, x: u16, y: u16, width: u16, height: u16) { + async fn wait_for_busy(&mut self) -> Result<(), WaitForBusyTimeoutError> { + with_timeout(Duration::from_millis(10_000), self.busy.wait_for_low()) + .await + .map_err(WaitForBusyTimeoutError) + } + + async fn set_ram_area( + &mut self, + x: u16, + y: u16, + width: u16, + height: u16, + ) -> Result<(), SetRamAreaError> { // Data entry x increment y decrement??? const DATA_ENTRY_X_INC_Y_DEC: u8 = 0x01; @@ -149,99 +210,94 @@ impl<'d> EinkDisplay<'d> { // Reverse Y coordinate (gates are reversed on this display) let y = DISPLAY_HEIGHT - y - height; - self.send_command(Command::DataEntryMode).await; - self.send_data(&[DATA_ENTRY_X_INC_Y_DEC]).await; + self.send_command(Command::DataEntryMode).await?; + self.send_data(&[DATA_ENTRY_X_INC_Y_DEC]).await?; // Set RAM X address range (start, end) - X is in PIXELS - self.send_command(Command::SetRamXRange).await; + self.send_command(Command::SetRamXRange).await?; //TODO safe arithmetic and casting // Start low byte - self.send_data(&[(x % 256) as u8]).await; + self.send_data(&[(x % 256) as u8]).await?; // Start high byte - self.send_data(&[(x / 256) as u8]).await; + self.send_data(&[(x / 256) as u8]).await?; // End low byte - self.send_data(&[((x + width - 1) % 256) as u8]).await; + self.send_data(&[((x + width - 1) % 256) as u8]).await?; // End high byte - self.send_data(&[((x + width - 1) / 256) as u8]).await; + self.send_data(&[((x + width - 1) / 256) as u8]).await?; // Set RAM Y address range (start, end) - Y is in PIXELS - self.send_command(Command::SetRamYRange).await; + self.send_command(Command::SetRamYRange).await?; // Start low byte - self.send_data(&[((y + height - 1) % 256) as u8]).await; + self.send_data(&[((y + height - 1) % 256) as u8]).await?; // Start high byte - self.send_data(&[((y + height - 1) / 256) as u8]).await; + self.send_data(&[((y + height - 1) / 256) as u8]).await?; // End low byte - self.send_data(&[(y % 256) as u8]).await; + self.send_data(&[(y % 256) as u8]).await?; // End high byte - self.send_data(&[(y / 256) as u8]).await; + self.send_data(&[(y / 256) as u8]).await?; // Set RAM X address counter - X is in PIXELS - self.send_command(Command::SetRamXCounter).await; + self.send_command(Command::SetRamXCounter).await?; // Low byte - self.send_data(&[(x % 256) as u8]).await; + self.send_data(&[(x % 256) as u8]).await?; // High byte - self.send_data(&[(x / 256) as u8]).await; + self.send_data(&[(x / 256) as u8]).await?; // Set RAM Y address counter - Y is in PIXELS - self.send_command(Command::SetRamYCounter).await; + self.send_command(Command::SetRamYCounter).await?; // Low byte - self.send_data(&[((y + height - 1) % 256) as u8]).await; + self.send_data(&[((y + height - 1) % 256) as u8]).await?; // High byte - self.send_data(&[((y + height - 1) / 256) as u8]).await; + self.send_data(&[((y + height - 1) / 256) as u8]).await?; + Ok(()) } - pub(super) async fn initialize_controller(&mut self) { + pub(super) async fn initialize_controller(&mut self) -> Result<(), InitializeControllerError> { info!("Initializing SSD1677 controller"); // Soft reset - self.send_command(Command::SoftReset).await; - let result = with_timeout(Duration::from_millis(10_000), self.busy.wait_for_low()).await; - if let Err(TimeoutError) = result { - error!("Timeout waiting for busy"); - return; - } - - info!("Busy wait completed"); + self.send_command(Command::SoftReset).await?; + self.wait_for_busy().await?; // Temperature sensor control (internal) const TEMPERATURE_SENSOR_INTERNAL: u8 = 0x80; - self.send_command(Command::TemperatureSensorControl).await; - self.send_data(&[TEMPERATURE_SENSOR_INTERNAL]).await; + self.send_command(Command::TemperatureSensorControl).await?; + self.send_data(&[TEMPERATURE_SENSOR_INTERNAL]).await?; // Booster soft-start control (GDEQ0426T82 specific values) - self.send_command(Command::BoosterSoftStart).await; - self.send_data(&[0xAE]).await; - self.send_data(&[0xC7]).await; - self.send_data(&[0xC3]).await; - self.send_data(&[0xC0]).await; - self.send_data(&[0xC0]).await; - self.send_data(&[0x40]).await; + self.send_command(Command::BoosterSoftStart).await?; + self.send_data(&[0xAE]).await?; + self.send_data(&[0xC7]).await?; + self.send_data(&[0xC3]).await?; + self.send_data(&[0xC0]).await?; + self.send_data(&[0xC0]).await?; + self.send_data(&[0x40]).await?; // Driver output control: set display height (480) and scan direction - self.send_command(Command::DriverOutputControl).await; + self.send_command(Command::DriverOutputControl).await?; //TODO safer casting - self.send_data(&[((DISPLAY_HEIGHT - 1) % 256) as u8]).await; - self.send_data(&[((DISPLAY_HEIGHT - 1) / 256) as u8]).await; - self.send_data(&[0x02]).await; + self.send_data(&[((DISPLAY_HEIGHT - 1) % 256) as u8]) + .await?; + self.send_data(&[((DISPLAY_HEIGHT - 1) / 256) as u8]) + .await?; + self.send_data(&[0x02]).await?; // Border waveform control - self.send_command(Command::BorderWaveformControl).await; - self.send_data(&[0x01]).await; + self.send_command(Command::BorderWaveformControl).await?; + self.send_data(&[0x01]).await?; // Set up full screen RAM area - self.set_ram_area(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT).await; + self.set_ram_area(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT) + .await?; info!("Clearing RAM buffers"); // Auto write BW RAM - self.send_command(Command::AutoWriteBwRam).await; - self.send_data(&[0xF7]).await; - let result = with_timeout(Duration::from_millis(10_000), self.busy.wait_for_low()).await; - if let Err(TimeoutError) = result { - error!("Timeout waiting for busy"); - return; - } + self.send_command(Command::AutoWriteBwRam).await?; + self.send_data(&[0xF7]).await?; + self.wait_for_busy().await?; info!("SSD1677 controller initialized"); + Ok(()) } pub(super) async fn initialize( @@ -253,7 +309,7 @@ impl<'d> EinkDisplay<'d> { reset: impl OutputPin + 'd, data_command: impl OutputPin + 'd, busy: impl InputPin + 'd, - ) -> Self { + ) -> Result { info!("Initializing e-ink display driver"); let mut this = Self::new( spi, @@ -264,14 +320,14 @@ impl<'d> EinkDisplay<'d> { reset, data_command, busy, - ); + )?; this.reset().await; - this.initialize_controller().await; + this.initialize_controller().await?; info!("E-ink display driver initialized"); - this + Ok(this) } } diff --git a/src/main.rs b/src/main.rs index bf013f3..1df3e83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use defmt::info; use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use esp_hal::clock::CpuClock; -use esp_hal::gpio::{self, InputConfig, Level, OutputConfig}; use esp_hal::timer::timg::TimerGroup; use {esp_backtrace as _, esp_println as _}; @@ -21,9 +20,6 @@ use crate::eink_display::EinkDisplay; extern crate alloc; -const CONNECTIONS_MAX: usize = 1; -const L2CAP_CHANNELS_MAX: usize = 1; - // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: esp_bootloader_esp_idf::esp_app_desc!(); @@ -58,18 +54,6 @@ async fn main(spawner: Spawner) { info!("Embassy initialized!"); - // Radio setup - // let radio_init = esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller"); - // let (mut _wifi_controller, _interfaces) = - // esp_radio::wifi::new(&radio_init, peripherals.WIFI, Default::default()) - // .expect("Failed to initialize Wi-Fi controller"); - // // find more examples https://github.com/embassy-rs/trouble/tree/main/examples/esp32 - // let transport = BleConnector::new(&radio_init, peripherals.BT, Default::default()).unwrap(); - // let ble_controller = ExternalController::<_, 1>::new(transport); - // let mut resources: HostResources = - // HostResources::new(); - // let _stack = trouble_host::new(ble_controller, &mut resources); - // Set up epaper display // Custom pins for XteinkX4, not hardware SPI defaults // SPI Clock (SCLK = serial clock) @@ -86,7 +70,7 @@ async fn main(spawner: Spawner) { let busy = peripherals.GPIO6; let direct_memory_access_channel = peripherals.DMA_CH0; - let mut display = EinkDisplay::initialize( + let _display = EinkDisplay::initialize( peripherals.SPI2, serial_clock, master_in_slave_out, -- 2.51.2