diff --git a/examples/display_test/src/main.rs b/examples/display_test/src/main.rs index e1ab08b..cd35a13 100644 --- a/examples/display_test/src/main.rs +++ b/examples/display_test/src/main.rs @@ -5,10 +5,7 @@ use core::panic::PanicInfo; use cardputer_bsc_nostd::display::{DISPLAY_SIZE_HEIGHT, DISPLAY_SIZE_WIDTH}; use embedded_graphics::pixelcolor::Rgb565; -use esp_hal::{ - ledc::{LowSpeed, channel::ChannelIFace, timer::TimerIFace}, - main, -}; +use esp_hal::main; use esp_println::println; #[panic_handler] @@ -28,39 +25,24 @@ fn entrypoint() -> ! { fn _main() { let peripherals = esp_hal::init(esp_hal::Config::default()); - let mut display = cardputer_bsc_nostd::display::build( + + cardputer_bsc_nostd::setup_backlight! { + let (mut ledc, mut timer) = setup_backlight(peripherals.LEDC); + } + + let mut display = cardputer_bsc_nostd::display::Display::new( peripherals.SPI2, peripherals.GPIO36, peripherals.GPIO35, peripherals.GPIO37, peripherals.GPIO34, peripherals.GPIO33, + &ledc, + &timer, + peripherals.GPIO38, ) .unwrap(); - let mut ledc = esp_hal::ledc::Ledc::new(peripherals.LEDC); - ledc.set_global_slow_clock(esp_hal::ledc::LSGlobalClkSource::APBClk); - - let mut timer = ledc.timer::(esp_hal::ledc::timer::Number::Timer3); - timer.configure(esp_hal::ledc::timer::config::Config { - duty: esp_hal::ledc::timer::config::Duty::Duty8Bit, - clock_source: esp_hal::ledc::timer::LSClockSource::APBClk, - frequency: esp_hal::time::Rate::from_hz(256), - }).unwrap(); - - let mut backlight: esp_hal::ledc::channel::Channel<'_, _> = - ledc.channel(esp_hal::ledc::channel::Number::Channel7, peripherals.GPIO38); - - backlight - .configure(esp_hal::ledc::channel::config::Config { - timer: &timer, - duty_pct: 100, - pin_config: esp_hal::ledc::channel::config::PinConfig::PushPull, - }) - .unwrap(); - - - display .set_pixels( 0, @@ -86,4 +68,6 @@ fn _main() { ), ) .unwrap(); + + display.set_backlight_brightness(100).unwrap(); } diff --git a/src/display.rs b/src/display.rs index a10dfb7..8c4a44e 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,7 +1,10 @@ //! Create and initialize ST7789 display driver // use display_interface_spi::SPIInterface; +use core::ops::{Deref, DerefMut}; + use embedded_hal_bus::spi::ExclusiveDevice; +use esp_hal::ledc::{LowSpeed, channel::ChannelIFace}; use mipidsi::{ Builder, Display as MipiDisplay, interface::SpiInterface, @@ -10,7 +13,204 @@ use mipidsi::{ }; use thiserror::Error; -pub struct Display {} +/// The type for controlling the cardputer's display +/// +/// Example usage: +/// ```rust +/// cardputer_bsc_nostd::setup_backlight! { +/// let (mut ledc, mut timer) = setup_backlight(peripherals.LEDC); +/// } +/// +/// let mut display = cardputer_bsc_nostd::display::Display::build( +/// peripherals.SPI2, +/// peripherals.GPIO36, +/// peripherals.GPIO35, +/// peripherals.GPIO37, +/// peripherals.GPIO34, +/// peripherals.GPIO33, +/// &ledc, +/// &timer, +/// peripherals.GPIO38, +/// ) +/// .unwrap(); +/// +/// display.set_backlight_brightness(100).unwrap(); +/// ``` +pub struct Display<'a> { + mipi_display: Drawable<'a>, + backlight: esp_hal::ledc::channel::Channel<'a, LowSpeed>, +} + +impl<'a> Deref for Display<'a> { + type Target = Drawable<'a>; + + fn deref(&self) -> &Self::Target { + &self.mipi_display + } +} + +impl<'a> DerefMut for Display<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.mipi_display + } +} + +/// A convenience macro which simplifies setting up the LED controller and timer for the display's backlight. +/// +/// Example usage: +/// ```rust +/// cardputer_bsc_nostd::setup_backlight! { +/// let (mut ledc, mut timer) = setup_backlight(peripherals.LEDC); +/// } +/// +/// let mut display = cardputer_bsc_nostd::display::Display::build( +/// peripherals.SPI2, +/// peripherals.GPIO36, +/// peripherals.GPIO35, +/// peripherals.GPIO37, +/// peripherals.GPIO34, +/// peripherals.GPIO33, +/// &ledc, +/// &timer, +/// peripherals.GPIO38, +/// ) +/// .unwrap(); +/// ``` +/// +/// You can rename the `ledc` and `timer` bindings, and choose a different expression in place of `peripherals.LEDC`, +/// but otherwise you must leave the rest unchanged. +/// +/// Unfortunately the fictional `setup_backlight` function that the accepted syntax looks like it is invoking can't +/// be written as `timer` borrows from `ledc`. +#[macro_export] +macro_rules! setup_backlight { + (let (mut $ledc_ident:ident, mut $timer_ident:ident) = setup_backlight($ledc:expr)$(;)?) => { + let mut $ledc_ident = esp_hal::ledc::Ledc::new($ledc); + $ledc_ident.set_global_slow_clock(esp_hal::ledc::LSGlobalClkSource::APBClk); + + let mut $timer_ident = + $ledc_ident.timer::(esp_hal::ledc::timer::Number::Timer3); + + esp_hal::ledc::timer::TimerIFace::configure( + &mut $timer_ident, + esp_hal::ledc::timer::config::Config { + duty: esp_hal::ledc::timer::config::Duty::Duty8Bit, + clock_source: esp_hal::ledc::timer::LSClockSource::APBClk, + frequency: esp_hal::time::Rate::from_hz(256), + }, + ) + .unwrap(); + }; +} + +impl<'a> Display<'a> { + /// Initialises the cardputer's display. + /// + /// The backlight is left powered off as this function doesn't clear the LCD controller's display + /// buffer and it usually boots up with random noise. You can call [Display::set_backlight_brightness] + /// to switch the backlight on. + pub fn new( + spi: esp_hal::peripherals::SPI2<'a>, + sck: esp_hal::peripherals::GPIO36<'a>, + dc: esp_hal::peripherals::GPIO35<'a>, + cs: esp_hal::peripherals::GPIO37<'a>, + rs: esp_hal::peripherals::GPIO34<'a>, + rst: esp_hal::peripherals::GPIO33<'a>, + ledc: &'a esp_hal::ledc::Ledc<'a>, + timer: &'a esp_hal::ledc::timer::Timer<'a, LowSpeed>, + backlight: esp_hal::peripherals::GPIO38<'a>, + ) -> Result { + static mut DISPLAY_BUFFER: [u8; DISPLAY_BUFFER_SIZE] = [0; DISPLAY_BUFFER_SIZE]; + + let spi = esp_hal::spi::master::Spi::new( + spi, + esp_hal::spi::master::Config::default() + .with_frequency(esp_hal::time::Rate::from_mhz(80)), + )? + .with_sck(sck) + .with_mosi(dc); + + let Ok(spi) = ExclusiveDevice::new( + spi, + esp_hal::gpio::Output::new( + cs, + esp_hal::gpio::Level::Low, + esp_hal::gpio::OutputConfig::default(), + ), + esp_hal::delay::Delay::new(), + ); + + let rs = esp_hal::gpio::Output::new( + rs, + esp_hal::gpio::Level::Low, + esp_hal::gpio::OutputConfig::default(), + ); + let rst = esp_hal::gpio::Output::new( + rst, + esp_hal::gpio::Level::Low, + esp_hal::gpio::OutputConfig::default(), + ); + #[allow(static_mut_refs)] + let mut drawable = Builder::new( + ST7789, + SpiInterface::new(spi, rs, unsafe { DISPLAY_BUFFER.as_mut_slice() }), + ) //st7789(SpiInterface::new(spi, rs)) + .invert_colors(ColorInversion::Inverted) + .display_size(DISPLAY_SIZE_HEIGHT, DISPLAY_SIZE_WIDTH) // deliberately reversed order + .display_offset(40, 53) + .reset_pin(rst) + .init(&mut esp_hal::delay::Delay::new()) + .map_err(|_| DisplayInitError::MipidsiInitError)?; + // Can't capture the error since it's a public type + // in a private module and not re-exported. The master + // branch of the mipidsi repo does have a fix but it's + // not had a crates.io release yet. + + drawable + .set_orientation(Orientation::new().rotate(mipidsi::options::Rotation::Deg90)) + .map_err(|e| match e { + mipidsi::interface::SpiError::Spi(e) => match e { + embedded_hal_bus::spi::DeviceError::Spi(e) => DisplayInitError::SpiError(e), + embedded_hal_bus::spi::DeviceError::Cs(e) => match e {}, + }, + mipidsi::interface::SpiError::Dc(e) => match e {}, + })?; + drawable + .set_vertical_scroll_offset(0) + .map_err(|e| match e { + mipidsi::interface::SpiError::Spi(e) => match e { + embedded_hal_bus::spi::DeviceError::Spi(e) => DisplayInitError::SpiError(e), + embedded_hal_bus::spi::DeviceError::Cs(e) => match e {}, + }, + mipidsi::interface::SpiError::Dc(e) => match e {}, + })?; + + let mut backlight: esp_hal::ledc::channel::Channel<'_, _> = + ledc.channel(esp_hal::ledc::channel::Number::Channel7, backlight); + + backlight + .configure(esp_hal::ledc::channel::config::Config { + timer, + duty_pct: 0, + pin_config: esp_hal::ledc::channel::config::PinConfig::PushPull, + }) + .unwrap(); + + Ok(Self { + mipi_display: drawable, + backlight, + }) + } + + /// Set the brightness of the display's backlight. `brightness_percentage` should be in the closed range \[0,100]; + /// this function will return [esp_hal::ledc::channel::Error::Duty] if not. + pub fn set_backlight_brightness( + &self, + brightness_percentage: u8, + ) -> Result<(), esp_hal::ledc::channel::Error> { + self.backlight.set_duty(brightness_percentage) + } +} type Drawable<'a> = MipiDisplay< SpiInterface< @@ -35,7 +235,7 @@ pub const DISPLAY_BUFFER_SIZE: usize = DISPLAY_SIZE_WIDTH as usize * DISPLAY_SIZE_HEIGHT as usize * 2; #[derive(Debug, Error)] -pub enum BuildError { +pub enum DisplayInitError { #[error("SPI config error: {0}")] SpiConfigError(#[from] esp_hal::spi::master::ConfigError), #[error("Some error occurred when initialising the MIPI DSI display")] @@ -43,90 +243,3 @@ pub enum BuildError { #[error("SPI error: {0:?}")] SpiError(esp_hal::spi::Error), } - -pub fn build<'a>( - spi: esp_hal::peripherals::SPI2<'a>, - sck: esp_hal::peripherals::GPIO36<'a>, - dc: esp_hal::peripherals::GPIO35<'a>, - cs: esp_hal::peripherals::GPIO37<'a>, - rs: esp_hal::peripherals::GPIO34<'a>, - rst: esp_hal::peripherals::GPIO33<'a>, -) -> Result, BuildError> { - static mut DISPLAY_BUFFER: [u8; 1000] = [0; 1000]; - - // let spi_config = SpiConfig::new().baudrate(80.MHz().into()); - // let device_config = DriverConfig::new(); - // let spi = SpiDeviceDriver::new_single( - // spi, - // sck, - // dc, - // Option::::None, - // Some(cs), - // &device_config, - // &spi_config, - // )?; - - let spi = esp_hal::spi::master::Spi::new( - spi, - esp_hal::spi::master::Config::default().with_frequency(esp_hal::time::Rate::from_mhz(80)), - )? - .with_sck(sck) - .with_mosi(dc); - - let Ok(spi) = ExclusiveDevice::new( - spi, - esp_hal::gpio::Output::new( - cs, - esp_hal::gpio::Level::Low, - esp_hal::gpio::OutputConfig::default(), - ), - esp_hal::delay::Delay::new(), - ); - - let rs = esp_hal::gpio::Output::new( - rs, - esp_hal::gpio::Level::Low, - esp_hal::gpio::OutputConfig::default(), - ); - let rst = esp_hal::gpio::Output::new( - rst, - esp_hal::gpio::Level::Low, - esp_hal::gpio::OutputConfig::default(), - ); - #[allow(static_mut_refs)] - let mut drawable = Builder::new( - ST7789, - SpiInterface::new(spi, rs, unsafe { DISPLAY_BUFFER.as_mut_slice() }), - ) //st7789(SpiInterface::new(spi, rs)) - .invert_colors(ColorInversion::Inverted) - .display_size(DISPLAY_SIZE_HEIGHT, DISPLAY_SIZE_WIDTH) // deliberately reversed order - .display_offset(40, 53) - .reset_pin(rst) - .init(&mut esp_hal::delay::Delay::new()) - .map_err(|_| BuildError::MipidsiInitError)?; - // Can't capture the error since it's a public type - // in a private module and not re-exported. The master - // branch of the mipidsi repo does have a fix but it's - // not had a crates.io release yet. - - drawable - .set_orientation(Orientation::new().rotate(mipidsi::options::Rotation::Deg90)) - .map_err(|e| match e { - mipidsi::interface::SpiError::Spi(e) => match e { - embedded_hal_bus::spi::DeviceError::Spi(e) => BuildError::SpiError(e), - embedded_hal_bus::spi::DeviceError::Cs(e) => match e {}, - }, - mipidsi::interface::SpiError::Dc(e) => match e {}, - })?; - drawable - .set_vertical_scroll_offset(0) - .map_err(|e| match e { - mipidsi::interface::SpiError::Spi(e) => match e { - embedded_hal_bus::spi::DeviceError::Spi(e) => BuildError::SpiError(e), - embedded_hal_bus::spi::DeviceError::Cs(e) => match e {}, - }, - mipidsi::interface::SpiError::Dc(e) => match e {}, - })?; - - Ok(drawable) -}