diff --git a/boards/components/src/graphic_display.rs b/boards/components/src/graphic_display.rs new file mode 100644 index 0000000000..28f6e23f5e --- /dev/null +++ b/boards/components/src/graphic_display.rs @@ -0,0 +1,101 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for the Screen. +//! +//! Buffer Size +//! ----------- +//! +//! Displays can receive a large amount of data and having larger transfer buffers +//! optimizes the number of bus writes. +//! +//! As memory is limited on some MCUs, the `components::screen_buffer_size`` +//! macro allows users to define the size of the screen buffer. +//! +//! Usage +//! ----- +//! +//! // Screen +//! ```rust +//! let screen = +//! components::screen::GraphicDisplayComponent::new(board_kernel, tft, None) +//! .finalize(components::graphic_display_component_static!(40960)); +//! ``` +//! +//! // Screen with Setup +//! ```rust +//! let screen = +//! components::screen::GraphicDisplayComponent::new(board_kernel, tft, Some(tft)) +//! .finalize(components::graphic_display_component_static!(40960)); +//! ``` + +use capsules_extra::graphic_display::GraphicDisplay; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::display; + +#[macro_export] +macro_rules! graphic_display_component_static { + ($s:literal $(,)?) => {{ + let buffer = kernel::static_buf!([u8; $s]); + let screen = kernel::static_buf!(capsules_extra::graphic_display::GraphicDisplay); + + (buffer, screen) + };}; +} + +pub struct GraphicDisplayComponent { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + display: &'static dyn display::GraphicDisplay<'static>, + display_setup: Option<&'static dyn display::FrameBufferSetup<'static>>, +} + +impl GraphicDisplayComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + display: &'static dyn display::GraphicDisplay<'static>, + display_setup: Option<&'static dyn display::FrameBufferSetup<'static>>, + ) -> GraphicDisplayComponent { + GraphicDisplayComponent { + board_kernel, + driver_num, + display, + display_setup, + } + } +} + +impl Component for GraphicDisplayComponent { + type StaticInput = ( + &'static mut MaybeUninit<[u8; SCREEN_BUF_LEN]>, + &'static mut MaybeUninit>, + ); + type Output = &'static GraphicDisplay<'static>; + + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant_screen = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let buffer = static_input.0.write([0; SCREEN_BUF_LEN]); + + let display = static_input.1.write(GraphicDisplay::new( + self.display, + self.display_setup, + buffer, + grant_screen, + )); + + display::Screen::set_client(self.display, Some(display)); + display::FrameBuffer::set_client(self.display, Some(display)); + if let Some(display_setup) = self.display_setup { + display::FrameBuffer::set_client(display_setup, Some(display)); + } + + display + } +} diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs index 28f27f9585..54ba69802e 100644 --- a/boards/components/src/lib.rs +++ b/boards/components/src/lib.rs @@ -33,6 +33,7 @@ pub mod fm25cl; pub mod ft6x06; pub mod fxos8700; pub mod gpio; +pub mod graphic_display; pub mod hd44780; pub mod hmac; pub mod hs3003; @@ -77,6 +78,7 @@ pub mod si7021; pub mod siphash; pub mod sound_pressure; pub mod spi; +pub mod ssd1306; pub mod st77xx; pub mod temperature; pub mod temperature_rp2040; diff --git a/boards/components/src/ssd1306.rs b/boards/components/src/ssd1306.rs new file mode 100644 index 0000000000..810abb84cb --- /dev/null +++ b/boards/components/src/ssd1306.rs @@ -0,0 +1,109 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use capsules_extra::bus; +use capsules_extra::ssd1306; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::deferred_call::DeferredCall; +use kernel::deferred_call::DeferredCallClient; + +#[macro_export] +macro_rules! ssd1306_component_static { + ($B: ty, $(,)?) => {{ + let buffer = kernel::static_buf!([u8; capsules_extra::ssd1306::BUFFER_SIZE]); + let app_write_buffer = kernel::static_buf!( + [u8; capsules_extra::ssd1306::WIDTH * capsules_extra::ssd1306::HEIGHT / 8 + + capsules_extra::ssd1306::BUFFER_PADDING] + ); + let bus_write_buffer = kernel::static_buf!( + [u8; capsules_extra::ssd1306::WIDTH * capsules_extra::ssd1306::HEIGHT / 8 + + capsules_extra::ssd1306::BUFFER_PADDING] + ); + let aux_write_buffer = kernel::static_buf!( + [u8; capsules_extra::ssd1306::WIDTH * capsules_extra::ssd1306::HEIGHT / 8 + + capsules_extra::ssd1306::BUFFER_PADDING] + ); + let command_sequence = kernel::static_buf!( + [capsules_extra::ssd1306::ScreenCommand; capsules_extra::ssd1306::SEQUENCE_BUFFER_SIZE] + ); + let ssd1306 = kernel::static_buf!(capsules_extra::ssd1306::SSD1306<'static, $B>); + ( + ssd1306, + command_sequence, + buffer, + app_write_buffer, + bus_write_buffer, + aux_write_buffer, + ) + };}; +} + +pub struct SSD1306Component> { + bus: &'static B, + deferred_caller: DeferredCall, +} + +impl> SSD1306Component { + pub fn new(bus: &'static B) -> SSD1306Component { + SSD1306Component { + bus, + deferred_caller: DeferredCall::new(), + } + } +} + +impl> Component for SSD1306Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[ssd1306::ScreenCommand; ssd1306::SEQUENCE_BUFFER_SIZE]>, + &'static mut MaybeUninit<[u8; ssd1306::BUFFER_SIZE]>, + &'static mut MaybeUninit< + [u8; ssd1306::HEIGHT * ssd1306::WIDTH / 8 + ssd1306::BUFFER_PADDING], + >, + &'static mut MaybeUninit< + [u8; ssd1306::HEIGHT * ssd1306::WIDTH / 8 + ssd1306::BUFFER_PADDING], + >, + &'static mut MaybeUninit< + [u8; ssd1306::HEIGHT * ssd1306::WIDTH / 8 + ssd1306::BUFFER_PADDING], + >, + ); + + type Output = &'static ssd1306::SSD1306<'static, B>; + + fn finalize(self, static_memory: Self::StaticInput) -> Self::Output { + let command_sequence = static_memory.1.write( + [ssd1306::ScreenCommand { + id: ssd1306::CommandId::Nop, + parameters: None, + }; ssd1306::SEQUENCE_BUFFER_SIZE], + ); + let command_arguments = static_memory.2.write([0; ssd1306::BUFFER_SIZE]); + let app_write_buffer = static_memory + .3 + .write([0; ssd1306::HEIGHT * ssd1306::WIDTH / 8 + ssd1306::BUFFER_PADDING]); + let bus_write_buffer = static_memory + .4 + .write([0; ssd1306::HEIGHT * ssd1306::WIDTH / 8 + ssd1306::BUFFER_PADDING]); + let aux_write_buffer = static_memory + .5 + .write([0; ssd1306::HEIGHT * ssd1306::WIDTH / 8 + ssd1306::BUFFER_PADDING]); + + let ssd1306 = static_memory.0.write(ssd1306::SSD1306::new( + self.bus, + command_sequence, + command_arguments, + app_write_buffer, + bus_write_buffer, + aux_write_buffer, + self.deferred_caller, + )); + self.bus.set_client(ssd1306); + + // todo remove ssd1306.initialize_callback_handle(self.deferred_caller.register(ssd1306).unwrap()); + + ssd1306.register(); + ssd1306 + } +} diff --git a/boards/nucleo_f429zi/Cargo.toml b/boards/nucleo_f429zi/Cargo.toml index 8c202fd811..12c03a6502 100644 --- a/boards/nucleo_f429zi/Cargo.toml +++ b/boards/nucleo_f429zi/Cargo.toml @@ -14,6 +14,7 @@ components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } stm32f429zi = { path = "../../chips/stm32f429zi" } +stm32f4xx = { path = "../../chips/stm32f4xx" } capsules-core = { path = "../../capsules/core" } capsules-extra = { path = "../../capsules/extra" } diff --git a/boards/nucleo_f429zi/src/main.rs b/boards/nucleo_f429zi/src/main.rs index af7d0023fd..8afa7c6b1a 100644 --- a/boards/nucleo_f429zi/src/main.rs +++ b/boards/nucleo_f429zi/src/main.rs @@ -86,6 +86,7 @@ struct NucleoF429ZI { 'static, stm32f429zi::rtc::Rtc<'static>, >, + //todo uncomment: display: &'static capsules_extra::graphic_display::GraphicDisplay<'static>, } /// Mapping of integer syscalls to objects that implement syscalls. @@ -636,6 +637,41 @@ pub unsafe fn main() { stm32f429zi::rtc::Rtc<'static> )); + // I2C SSD1306 screen + let i2c_mux = components::i2c::I2CMuxComponent::new(&peripherals.stm32f4.i2c1, None).finalize( + components::i2c_mux_component_static!(stm32f4xx::i2c::I2C<'static>), + ); + kernel::deferred_call::DeferredCallClient::register(i2c_mux); + + //todo: bus initialization + uncomment + // let bus = components::bus::I2CMasterBusComponent::new( + // i2c_mux, + // capsules_extra::ssd1306::SLAVE_ADDRESS_WRITE, + // ) + // .finalize(components::i2c_master_bus_component_static!()); + + // let ssd1306_screen = components::ssd1306::SSD1306Component::new(bus).finalize( + // components::ssd1306_component_static!( + // capsules_extra::bus::I2CMasterBus< + // 'static, + // capsules_core::virtualizers::virtual_i2c::I2CDevice< + // 'static, + // stm32f4xx::i2c::I2C<'static>, + // >, + // >, + // ), + // ); + + // let _ = ssd1306_screen.init(); + + // let display = components::graphic_display::GraphicDisplayComponent::new( + // board_kernel, + // capsules_extra::graphic_display::DRIVER_NUM, + // ssd1306_screen, + // Some(ssd1306_screen), + // ) + // .finalize(components::graphic_display_component_static!(1025)); + // PROCESS CONSOLE let process_console = components::process_console::ProcessConsoleComponent::new( board_kernel, @@ -672,6 +708,7 @@ pub unsafe fn main() { systick: cortexm4::systick::SysTick::new(), can: can, date_time, + //todo uncomment: display, }; // // Optional kernel tests diff --git a/capsules/extra/src/graphic_display.rs b/capsules/extra/src/graphic_display.rs new file mode 100644 index 0000000000..deef606aa0 --- /dev/null +++ b/capsules/extra/src/graphic_display.rs @@ -0,0 +1,581 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace with access to the screen. +//! +//! Usage +//! ----- +//! +//! You need a screen that provides the `hil::screen::Screen` trait. +//! +//! ```rust +//! let screen = +//! components::screen::ScreenComponent::new(board_kernel, tft).finalize(); +//! ``` + +use core::cell::Cell; +use core::convert::From; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::processbuffer::ReadableProcessBuffer; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::{ErrorCode, ProcessId}; + +use kernel::hil::display::{GraphicsFrame, GraphicsMode, PixelFormat, Point, Rotation}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Screen as usize; + +/// Ids for read-only allow buffers +mod ro_allow { + pub const SHARED: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +fn screen_rotation_from(screen_rotation: usize) -> Option { + match screen_rotation { + 0 => Some(Rotation::Normal), + 1 => Some(Rotation::Rotated90), + 2 => Some(Rotation::Rotated180), + 3 => Some(Rotation::Rotated270), + _ => None, + } +} + +fn screen_pixel_format_from(screen_pixel_format: usize) -> Option { + match screen_pixel_format { + 0 => Some(PixelFormat::Mono), + 1 => Some(PixelFormat::RGB_233), + 2 => Some(PixelFormat::RGB_565), + 3 => Some(PixelFormat::RGB_888), + 4 => Some(PixelFormat::ARGB_8888), + _ => None, + } +} + +#[derive(Clone, Copy, PartialEq)] +enum ScreenCommand { + Nop, + SetBrightness(u16), + SetPower(bool), + SetInvert(bool), + SetRotation(Rotation), + SetMode(GraphicsMode), + // SetPixelFormat(PixelFormat), + SetWriteFrame { origin: Point, size: GraphicsFrame }, + Write(usize), + Fill, +} + +fn pixels_in_bytes(pixels: usize, bits_per_pixel: usize) -> usize { + let bytes = pixels * bits_per_pixel / 8; + if pixels * bits_per_pixel % 8 != 0 { + bytes + 1 + } else { + bytes + } +} + +pub struct App { + pending_command: bool, + write_position: usize, + write_len: usize, + command: ScreenCommand, + width: u16, + height: u16, +} + +impl Default for App { + fn default() -> App { + App { + pending_command: false, + command: ScreenCommand::Nop, + width: 0, + height: 0, + write_len: 0, + write_position: 0, + } + } +} + +pub struct GraphicDisplay<'a> { + display: &'a dyn kernel::hil::display::GraphicDisplay<'a>, + display_setup: Option<&'a dyn kernel::hil::display::FrameBufferSetup<'a>>, + apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + current_process: OptionalCell, + display_format: Cell, + buffer: TakeCell<'static, [u8]>, +} + +impl<'a> GraphicDisplay<'a> { + pub fn new( + display: &'a dyn kernel::hil::display::GraphicDisplay<'a>, + display_setup: Option<&'a dyn kernel::hil::display::FrameBufferSetup<'a>>, + buffer: &'static mut [u8], + grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + ) -> GraphicDisplay<'a> { + GraphicDisplay { + display, + display_setup, + apps: grant, + current_process: OptionalCell::empty(), + display_format: Cell::new(display.get_mode()), + buffer: TakeCell::new(buffer), + } + } + + // Check to see if we are doing something. If not, + // go ahead and do this command. If so, this is queued + // and will be run when the pending command completes. + fn enqueue_command(&self, command: ScreenCommand, process_id: ProcessId) -> CommandReturn { + match self + .apps + .enter(process_id, |app, _| { + if app.pending_command { + CommandReturn::failure(ErrorCode::BUSY) + } else { + app.pending_command = true; + app.command = command; + app.write_position = 0; + CommandReturn::success() + } + }) + .map_err(ErrorCode::from) + { + Err(e) => CommandReturn::failure(e), + Ok(r) => { + if self.current_process.is_none() { + self.current_process.set(process_id); + let r = self.call_screen(command, process_id); + if r != Ok(()) { + self.current_process.clear(); + } + CommandReturn::from(r) + } else { + r + } + } + } + } + + fn is_len_multiple_color_depth(&self, len: usize) -> bool { + let depth = pixels_in_bytes(1, self.display.get_mode().pixel_format.get_bits_per_pixel()); + (len % depth) == 0 + } + + fn call_screen(&self, command: ScreenCommand, process_id: ProcessId) -> Result<(), ErrorCode> { + match command { + ScreenCommand::SetBrightness(brighness) => self.display.set_brightness(brighness), + ScreenCommand::SetPower(enabled) => self.display.set_power(enabled), + ScreenCommand::SetInvert(enabled) => self.display.set_invert(enabled), + ScreenCommand::SetRotation(rotation) => self.display.set_rotation(rotation), + ScreenCommand::SetMode(graphics_mode) => { + if let Some(display) = self.display_setup { + display.set_mode(graphics_mode) + } else { + Err(ErrorCode::NOSUPPORT) + } + } + ScreenCommand::Fill => match self + .apps + .enter(process_id, |app, kernel_data| { + let len = kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .map_or(0, |shared| shared.len()); + // Ensure we have a buffer that is the correct size + if len == 0 { + Err(ErrorCode::NOMEM) + } else if !self.is_len_multiple_color_depth(len) { + Err(ErrorCode::INVAL) + } else { + app.write_position = 0; + app.write_len = pixels_in_bytes( + app.width as usize * app.height as usize, + self.display_format.get().pixel_format.get_bits_per_pixel(), + ); + Ok(()) + } + }) + .unwrap_or_else(|err| err.into()) + { + Err(e) => Err(e), + Ok(()) => self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let len = self.fill_next_buffer_for_write(buffer); + if len > 0 { + self.display.write(buffer, len, false) + } else { + self.buffer.replace(buffer); + self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); + Ok(()) + } + }), + }, + + ScreenCommand::Write(data_len) => match self + .apps + .enter(process_id, |app, kernel_data| { + let len = kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .map_or(0, |shared| shared.len()) + .min(data_len); + // Ensure we have a buffer that is the correct size + if len == 0 { + Err(ErrorCode::NOMEM) + } else if !self.is_len_multiple_color_depth(len) { + Err(ErrorCode::INVAL) + } else { + app.write_position = 0; + app.write_len = len; + Ok(()) + } + }) + .unwrap_or_else(|err| err.into()) + { + Ok(()) => self.buffer.take().map_or(Err(ErrorCode::FAIL), |buffer| { + let len = self.fill_next_buffer_for_write(buffer); + if len > 0 { + self.display.write(buffer, len, false) + } else { + self.buffer.replace(buffer); + self.display.flush() + } + }), + Err(e) => Err(e), + }, + + ScreenCommand::SetWriteFrame { + origin: Point { x, y }, + size: GraphicsFrame { width, height }, + } => self + .apps + .enter(process_id, |app, _| { + app.write_position = 0; + app.width = width; + app.height = height; + + self.display + .set_write_frame(Point { x, y }, GraphicsFrame { width, height }) + }) + .unwrap_or_else(|err| err.into()), + _ => Err(ErrorCode::NOSUPPORT), + } + } + + fn schedule_callback(&self, data1: usize, data2: usize, data3: usize) { + if let Some(process_id) = self.current_process.take() { + let _ = self.apps.enter(process_id, |app, upcalls| { + app.pending_command = false; + upcalls.schedule_upcall(0, (data1, data2, data3)).ok(); + }); + } + } + + fn run_next_command(&self, data1: usize, data2: usize, data3: usize) { + self.schedule_callback(data1, data2, data3); + + let mut command = ScreenCommand::Nop; + + // Check if there are any pending events. + for app in self.apps.iter() { + let process_id = app.processid(); + let start_command = app.enter(|app, _| { + if app.pending_command { + app.pending_command = false; + command = app.command; + self.current_process.set(process_id); + true + } else { + false + } + }); + if start_command { + match self.call_screen(command, process_id) { + Err(err) => { + self.current_process.clear(); + self.schedule_callback(kernel::errorcode::into_statuscode(Err(err)), 0, 0); + } + Ok(()) => { + break; + } + } + } + } + } + + fn fill_next_buffer_for_write(&self, buffer: &mut [u8]) -> usize { + let (before, after) = self.display.get_buffer_padding(); + self.current_process.map_or_else( + || 0, + |process_id| { + self.apps + .enter(process_id, |app, kernel_data| { + let position = app.write_position; + let mut len = app.write_len; + // debug!("position is {} and len is {}, (before, after) - ({}, {})", position, len, before, after); + if position < len { + let buffer_size = buffer.len(); + let chunk_number = position / buffer_size; + let initial_pos = chunk_number * buffer_size; + let mut pos = initial_pos; + match app.command { + ScreenCommand::Write(_) => { + let res = kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .and_then(|shared| { + shared.enter(|s| { + let mut chunks = + s.chunks(buffer_size - before - after); + if let Some(chunk) = chunks.nth(chunk_number) { + for item in buffer.iter_mut().take(before) { + *item = 0x00; + } + for (i, byte) in chunk.iter().enumerate() { + if pos + after < len { + buffer[i + before] = byte.get(); + pos += 1 + } else { + break; + } + } + for item in buffer + .iter_mut() + .take(buffer_size) + .skip(before + chunk.len()) + { + *item = 0x00; + } + app.write_len - initial_pos + } else { + // stop writing + 0 + } + }) + }) + .unwrap_or(0); + // debug!("in fill buffer {}", res); + if res > 0 { + app.write_position = pos; + } + res + } + ScreenCommand::Fill => { + // TODO bytes per pixel + len -= position; + let bytes_per_pixel = pixels_in_bytes( + 1, + self.display_format.get().pixel_format.get_bits_per_pixel(), + ); + let mut write_len = + (buffer_size - before - after) / bytes_per_pixel; + if write_len > len { + write_len = len + }; + app.write_position += write_len * bytes_per_pixel; + kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .and_then(|shared| { + shared.enter(|data| { + let mut bytes = data.iter(); + // bytes per pixel + + for item in buffer.iter_mut().take(bytes_per_pixel) + { + if let Some(byte) = bytes.next() { + *item = byte.get(); + } + } + for i in 1..write_len { + // bytes per pixel + for j in 0..bytes_per_pixel { + buffer[bytes_per_pixel * i + j] = buffer[j] + } + } + write_len * bytes_per_pixel + }) + }) + .unwrap_or(0) + } + _ => 0, + } + } else { + 0 + } + }) + .unwrap_or(0) + }, + ) + } +} + +impl<'a> kernel::hil::display::ScreenClient for GraphicDisplay<'a> { + fn command_complete(&self, r: Result<(), ErrorCode>) { + // debug!("[display capsule] command complete received from screen client cu {:?}", r); + self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); + } +} + +impl<'a> kernel::hil::display::FrameBufferClient for GraphicDisplay<'a> { + fn write_complete(&self, buffer: &'static mut [u8], r: Result<(), ErrorCode>) { + // debug!("[display capsule] write complete received from client"); + let len = self.fill_next_buffer_for_write(buffer); + + if r == Ok(()) && len > 0 { + let _ = self.display.write(buffer, len, false); + } else { + self.buffer.replace(buffer); + let _ = self.display.flush(); + // self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); + } + } + + fn command_complete(&self, r: Result<(), ErrorCode>) { + // debug!("[display capsule] command complete received from frame buffer client"); + self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); + } +} + +impl<'a> SyscallDriver for GraphicDisplay<'a> { + fn command( + &self, + command_num: usize, + data1: usize, + data2: usize, + process_id: ProcessId, + ) -> CommandReturn { + match command_num { + 0 => + // This driver exists. + { + CommandReturn::success() + } + // Does it have the screen setup + 1 => CommandReturn::success_u32(self.display_setup.is_some() as u32), + // Set power + 2 => self.enqueue_command(ScreenCommand::SetPower(data1 != 0), process_id), + // Set Brightness + 3 => self.enqueue_command(ScreenCommand::SetBrightness(data1 as u16), process_id), + // Invert on (deprecated) + 4 => self.enqueue_command(ScreenCommand::SetInvert(true), process_id), + // Invert off (deprecated) + 5 => self.enqueue_command(ScreenCommand::SetInvert(false), process_id), + // Set Invert + 6 => self.enqueue_command(ScreenCommand::SetInvert(data1 != 0), process_id), + + // Get Graphics Modes count + 11 => { + if let Some(display) = self.display_setup { + CommandReturn::success_u32(display.get_num_supported_modes() as u32) + } else { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + } + // Get Graphics Mode + 12 => { + if let Some(display) = self.display_setup { + match display.get_supported_mode(data1) { + Some(GraphicsMode { + frame: GraphicsFrame { width, height }, + pixel_format, + }) if width > 0 && height > 0 => CommandReturn::success_u32_u32( + (width as u32) << 16 | (height as u32), + pixel_format as u32, + ), + _ => CommandReturn::failure(ErrorCode::INVAL), + } + } else { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + } + + // Get Rotation + 21 => CommandReturn::success_u32(self.display.get_rotation() as u32), + // Set Rotation + 22 => self.enqueue_command( + ScreenCommand::SetRotation(screen_rotation_from(data1).unwrap_or(Rotation::Normal)), + process_id, + ), + + // Get Resolution + 23 => { + let GraphicsMode { + frame: GraphicsFrame { width, height }, + pixel_format: _, + } = self.display.get_mode(); + CommandReturn::success_u32_u32(width as u32, height as u32) + } + // Set Resolution + 24 => { + let GraphicsMode { + frame: _, + pixel_format, + } = self.display.get_mode(); + self.enqueue_command( + ScreenCommand::SetMode(GraphicsMode { + frame: GraphicsFrame { + width: data1 as u16, + height: data2 as u16, + }, + pixel_format, + }), + process_id, + ) + } + + // Get pixel format + 25 => { + let GraphicsMode { + frame: _, + pixel_format, + } = self.display.get_mode(); + CommandReturn::success_u32(pixel_format as u32) + } + // Set pixel format + 26 => { + if let Some(new_pixel_format) = screen_pixel_format_from(data1) { + let GraphicsMode { + frame, + pixel_format: _, + } = self.display.get_mode(); + self.enqueue_command( + ScreenCommand::SetMode(GraphicsMode { + frame, + pixel_format: new_pixel_format, + }), + process_id, + ) + } else { + CommandReturn::failure(ErrorCode::INVAL) + } + } + + // Set Write Frame + 100 => self.enqueue_command( + ScreenCommand::SetWriteFrame { + origin: Point { + x: ((data1 >> 16) & 0xFFFF) as u16, + y: (data1 & 0xFFFF) as u16, + }, + size: GraphicsFrame { + width: ((data2 >> 16) & 0xFFFF) as u16, + height: (data2 & 0xFFFF) as u16, + }, + }, + process_id, + ), + // Write + 200 => self.enqueue_command(ScreenCommand::Write(data1), process_id), + // Fill + 300 => self.enqueue_command(ScreenCommand::Fill, process_id), + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/lib.rs b/capsules/extra/src/lib.rs index 240db15598..1eab0b122b 100644 --- a/capsules/extra/src/lib.rs +++ b/capsules/extra/src/lib.rs @@ -36,6 +36,7 @@ pub mod fm25cl; pub mod ft6x06; pub mod fxos8700cq; pub mod gpio_async; +pub mod graphic_display; pub mod hd44780; pub mod hmac; pub mod hmac_sha256; @@ -85,6 +86,7 @@ pub mod sht4x; pub mod si7021; pub mod sip_hash; pub mod sound_pressure; +pub mod ssd1306; pub mod st77xx; pub mod symmetric_encryption; pub mod temperature; diff --git a/capsules/extra/src/ssd1306.rs b/capsules/extra/src/ssd1306.rs new file mode 100644 index 0000000000..2578e5d2d1 --- /dev/null +++ b/capsules/extra/src/ssd1306.rs @@ -0,0 +1,872 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::{ + utilities::cells::{OptionalCell, TakeCell}, + ErrorCode, +}; + +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; + +use crate::bus::{self, Bus, BusWidth}; +use core::cell::Cell; +use kernel::hil::display::{ + Align, FrameBuffer, FrameBufferClient, FrameBufferSetup, GraphicsFrame, GraphicsMode, + PixelFormat, Point, Rotation, Screen, ScreenClient, Tile, +}; + +pub const SLAVE_ADDRESS_WRITE: u8 = 0b0111100; +pub const SLAVE_ADDRESS_READ: u8 = 0b0111101; +pub const WIDTH: usize = 128; +pub const HEIGHT: usize = 64; +pub const BUFFER_PADDING: usize = 1; +pub const TILE_SIZE: u16 = 8; +pub const I2C_ADDR: usize = 0x3D; +pub const WRITE_COMMAND: u8 = 0x40; +pub const CONTROL_COMMAND: u8 = 0x00; + +pub const BUFFER_SIZE: usize = 64; +pub const SEQUENCE_BUFFER_SIZE: usize = 32; +#[derive(PartialEq, Copy, Clone)] +pub struct ScreenCommand { + pub id: CommandId, + pub parameters: Option<&'static [u8]>, +} + +#[repr(u8)] +pub enum MemoryAddressing { + Page = 0x10, + Horizontal = 0x00, + Vertical = 0x01, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +enum ScreenStatus { + Idle, + Init, + Error(ErrorCode), + SendCommand, + SendCommandId(bool, usize), + SendCommandArguments(usize), + SendCommandArgumentsDone, + WriteData, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +enum ScreenAsyncCommand { + Idle, + AsyncFrameBufferCommand(Result<(), kernel::ErrorCode>), + AsyncScreenCommand(Result<(), kernel::ErrorCode>), + Write(Result<(), kernel::ErrorCode>), +} + +#[derive(PartialEq, Copy, Clone)] +#[repr(u8)] +pub enum CommandId { + /* Fundamental Commands */ + // Contrast Control - 2 bytes command + // 2nd byte: contrast step - 0 <-> 255 + SetContrastControl = 0x81, + + // Entire Display ON + // 0xA4 - output follows RAM content + // 0xA5 - output ignores RAM content + EntireDisplay = 0xA4, + + // Set Normal Display - 0xA6 + // 0/1 in RAM - OFF/ON in display panel + // Set Inverse Display - 0xA7 + // 0/1 in RAM - ON?OFF in display panel + SetNormalDisplayOn = 0xA6, + SetNormalDisplayOff = 0xA7, + + // Set Display Off - 0xAE + // Set Display On - 0xAF + SetDisplayOff = 0xAE, + SetDisplayOn = 0xAF, + + /* Addressing Settings */ + // Set Lower Column - 0x00 <-> 0x0F + SetLowerColumn = 0x00, + + // Set Higher Column - 0x10 <-> 0x1F + SetHigherColumn = 0x10, + + // Set Memory Addressing Mode - 2 bytes command + // 2nd byte: MemoryAddressing enum + SetMemoryMode = 0x20, + + // Set Column Address - 3 bytes command + // 2nd byte: column start address (0-127) + // 3rd byte: column end address (0-127) + SetColumnAddr = 0x21, + + // Set Page Address - 3 bytes command + // 2nd byte: page start address (0-7) + // 3rd byte: page end address (0-7) + SetPageAddr = 0x22, + + // Set Page Start Address for MemoryAddressing::Page - 0xB0 <-> 0xB7 + SetPageStart = 0xB0, + + /* Hardware Configuration */ + // Set Display Start Line - 0x40 <-> 0x7F + SetDisplayStart = 0x40, + + // Set Segment Re-map + // column address 0 -> SEG0 - 0xA0 + // column address 127 -> SEG0 - 0xA1 + SetSegmentRemap0 = 0xA0, + SetSegmentRemap127 = 0xA1, + + // Set Multiplex Radio - 2 bytes command + // 2nd byte: mux - 16 <-> 64 + SetMultiplexRadio = 0xA8, + + // Set COM Output Scan Direction + // from COM0 -> COM[multiplex radio - 1] - 0xC0 + // from COM[multiplex radio - 1] -> COM0 - 0xC8 + SetCOMOutputScanAsc = 0xC0, + SetCOMOutputScanDes = 0xC8, + + // Set Display Offset - 2 bytes command + // 2nd byte: the vertical shift - 0 <->63 + SetDisplayOffset = 0xD3, + + // Set COM Pins - 2 bytes command + // 2nd byte: - bit 4: 0 -> seq COM pin configuration + // 1 -> reset + alternative pin configuration + // - bit 5: 0 -> reset + disable COM left/right remap + // 1 -> enable COM left/right remap + SetCOMPins = 0xDA, + + /* Timing & Driving Scheme Setting */ + // Set Display Clock Divide Ratio + Oscillator Freq - 2 bytes command + // 2nd byte: - bits 3:0 -> divide ratio (D) of the display clocks (DCLK) + // D = bits 3:0 + 1 + // - bits 7:4 -> adds to the oscillator frequency + SetDisplayClockDivideRatio = 0xD5, + + // Set Pre-charge period - 2 bytes command + // 2nd byte: - bits 3:0 -> phase 1 period: 1 <-> 15 + // - bits 7:4 -> phase 2 period: 1 <-> 15 + SetPreChargePeriod = 0xD9, + + // Set Vcomh Deselect Level - 2 bytes command + // 2nd byte: bits 6:4 - 000b (0,65), 010b (0,77), 020b (0,83) + SetVcomhDeselect = 0xDB, + + // Nop + Nop = 0xE3, + Write = 0xE4, + + /* Scrolling Commands */ + // Continous Horizontal Scroll Setup - 7 bytes commands + // 2nd, 6th and 7th bytes: dummy bytes + // 3rd byte: start page address - 0 <-> 7 + // 4th byte: set time interval between each scroll step in frame freq + // 000b -> 5 | 001b -> 64 | 010b -> 128 | 011b -> 256 + // 100b -> 3 | 101b -> 4 | 110b -> 25 | 111b -> 2 + // 5th byte: end page address - 0 <-> 7 (>= 3rd byte) + ContHorizontalScrollRight = 0x26, + ContHorizontalScrollLeft = 0x27, + + // Continous Horizontal & Vertical Scroll Setup - 6 bytes commands + // 2nd byte: dummy byte + // 3rd byte: start page address - 0 <-> 7 + // 4th byte: set time interval between each scroll step in frame freq + // 000b -> 5 | 001b -> 64 | 010b -> 128 | 011b -> 256 + // 100b -> 3 | 101b -> 4 | 110b -> 25 | 111b -> 2 + // 5th byte: end page address - 0 <-> 7 (>= 3rd byte) + // 6th byte: vertical scroll offset - 0 <-> 63 + ContVertHorizontalScrollRight = 0x29, + ContVertHorizontalScrollLeft = 0x2A, + + // Deactivate Scrolling that is configured by one of the last 4 commands + DeactivateScrolling = 0x2E, + + // Activate Scrolling that is configured by one of the last 4 commands + // Overwrites the previously configured setup + ActivateScrolling = 0x2F, + + // Set Vertical Scroll Area - 3 bytes command + // 2nd byte: number of rows in top fixed area + // 3rd byte: number of rows in scroll area + SetVerticalScroll = 0xA3, + + /* Charge Pump Settings */ + // Charge Pump Command - 2 bytes command + // 2nd byte: - 0x14 - enable (followed by 0xAF - display on) + // - 0x10 - disable + ChargePump = 0x8D, +} + +const SSD1306_INIT_SEQ: [ScreenCommand; 20] = [ + ScreenCommand { + id: CommandId::SetDisplayOff, + parameters: None, + }, + ScreenCommand { + id: CommandId::SetDisplayClockDivideRatio, + parameters: Some(&[CONTROL_COMMAND, 0x80]), + }, + ScreenCommand { + id: CommandId::SetMultiplexRadio, + parameters: Some(&[CONTROL_COMMAND, HEIGHT as u8 - 1]), + }, + ScreenCommand { + id: CommandId::SetDisplayOffset, + parameters: Some(&[CONTROL_COMMAND, 0x00]), + }, + ScreenCommand { + id: CommandId::SetDisplayStart, + parameters: None, + }, + ScreenCommand { + id: CommandId::ChargePump, + parameters: Some(&[CONTROL_COMMAND, 0x14]), + }, + ScreenCommand { + id: CommandId::SetMemoryMode, + parameters: Some(&[CONTROL_COMMAND, MemoryAddressing::Horizontal as u8]), + }, + ScreenCommand { + id: CommandId::SetSegmentRemap127, + parameters: None, + }, + ScreenCommand { + id: CommandId::SetCOMOutputScanDes, + parameters: None, + }, + ScreenCommand { + id: CommandId::SetCOMPins, + parameters: Some(&[CONTROL_COMMAND, 0x12]), + }, + ScreenCommand { + id: CommandId::SetContrastControl, + parameters: Some(&[CONTROL_COMMAND, 0xCF]), + }, + ScreenCommand { + id: CommandId::SetPreChargePeriod, + parameters: Some(&[CONTROL_COMMAND, 0xF1]), + }, + ScreenCommand { + id: CommandId::SetVcomhDeselect, + parameters: Some(&[CONTROL_COMMAND, 0x40]), + }, + ScreenCommand { + id: CommandId::EntireDisplay, + parameters: None, + }, + ScreenCommand { + id: CommandId::SetNormalDisplayOn, + parameters: None, + }, + ScreenCommand { + id: CommandId::DeactivateScrolling, + parameters: None, + }, + ScreenCommand { + id: CommandId::SetDisplayOn, + parameters: None, + }, + ScreenCommand { + id: CommandId::SetPageAddr, + parameters: Some(&[CONTROL_COMMAND, 0x00, 0xFF]), + }, + ScreenCommand { + id: CommandId::SetColumnAddr, + parameters: Some(&[CONTROL_COMMAND, 0x00, WIDTH as u8 - 1]), + }, + ScreenCommand { + id: CommandId::Write, + parameters: None, + }, + // ScreenCommand { + // id: CommandId::SetPageAddr, + // parameters: Some(&[CONTROL_COMMAND, 0x02, 0x5]), + // }, + // ScreenCommand { + // id: CommandId::SetColumnAddr, + // parameters: Some(&[CONTROL_COMMAND, 0xA, WIDTH as u8 - 0x10]), + // }, + // ScreenCommand { + // id: CommandId::Write, + // parameters: None, + // }, + // ScreenCommand { + // id: CommandId::Write, + // parameters: None, + // } +]; + +pub struct SSD1306<'a, B: Bus<'a>> { + bus: &'a B, + status: Cell, + async_status: Cell, + + rotation: Cell, + graphics_mode: Cell, + origin: Cell, + tile: Cell, + + app_write_buffer: TakeCell<'static, [u8]>, + bus_write_buffer: TakeCell<'static, [u8]>, + aux_write_buffer: TakeCell<'static, [u8]>, + write_buffer_len: Cell, + write_buffer_position: Cell, + + command_sequence: TakeCell<'static, [ScreenCommand]>, + command_sequence_length: Cell, + command_sequence_position: Cell, + command_arguments: TakeCell<'static, [u8]>, + + initialization_complete: Cell, + screen_client: OptionalCell<&'static dyn ScreenClient>, + frame_buffer_client: OptionalCell<&'static dyn FrameBufferClient>, + + deferred_caller: DeferredCall, + + initial_write: Cell, + invert: Cell, +} + +impl<'a, B: Bus<'a>> SSD1306<'a, B> { + pub fn new( + bus: &'a B, + command_sequence: &'static mut [ScreenCommand], + command_arguments: &'static mut [u8], + app_write_buffer: &'static mut [u8], + bus_write_buffer: &'static mut [u8], + aux_write_buffer: &'static mut [u8], + deferred_caller: DeferredCall, + ) -> SSD1306<'a, B> { + SSD1306 { + bus, + status: Cell::new(ScreenStatus::Idle), + async_status: Cell::new(ScreenAsyncCommand::Idle), + rotation: Cell::new(Rotation::Normal), + origin: Cell::new(Point { x: 0, y: 0 }), + tile: Cell::new(Tile { + align: Align { + horizontal: 0, + vertical: 0, + }, + size: GraphicsFrame { + width: TILE_SIZE, + height: TILE_SIZE, + }, + }), + graphics_mode: Cell::new(GraphicsMode { + frame: GraphicsFrame { + width: WIDTH as u16, + height: HEIGHT as u16, + }, + pixel_format: PixelFormat::Mono, + }), + app_write_buffer: TakeCell::new(app_write_buffer), + bus_write_buffer: TakeCell::new(bus_write_buffer), + aux_write_buffer: TakeCell::new(aux_write_buffer), + write_buffer_len: Cell::new(0), + write_buffer_position: Cell::new(0), + command_sequence: TakeCell::new(command_sequence), + command_sequence_length: Cell::new(0), + command_sequence_position: Cell::new(0), + command_arguments: TakeCell::new(command_arguments), + initialization_complete: Cell::new(false), + screen_client: OptionalCell::empty(), + frame_buffer_client: OptionalCell::empty(), + initial_write: Cell::new(false), + deferred_caller: deferred_caller, + invert: Cell::new(false), + } + } + + pub fn init(&self) -> Result<(), ErrorCode> { + if self.status.get() == ScreenStatus::Idle { + self.status.set(ScreenStatus::Init); + self.do_next_op(); + Ok(()) + } else { + Err(ErrorCode::BUSY) + } + } + + // todo remove pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { + // self.handle.replace(handle); + // } + + pub fn do_next_op(&self) { + match self.status.get() { + ScreenStatus::Init => { + self.status.set(ScreenStatus::Idle); + if let Err(err) = self.prepare_init_sequence() { + self.status.set(ScreenStatus::Error(err)); + } else { + self.command_sequence_position.set(0); + self.status.set(ScreenStatus::SendCommand); + } + self.do_next_op(); + } + ScreenStatus::Idle => {} + ScreenStatus::Error(err) => { + panic!("{:?}", err); + } + ScreenStatus::SendCommand => { + let position = self.command_sequence_position.get(); + if position < self.command_sequence_length.get() { + self.command_sequence.map_or_else( + || panic!("ssd1306: do next op has no command sequence buffer"), + |command_sequence| { + self.send_command(command_sequence[position]); + }, + ) + } else { + // todo commands done + self.status.set(ScreenStatus::Idle); + if !self.initialization_complete.get() { + self.initialization_complete.set(true); + self.command_sequence_position.set(0); + self.command_sequence_length.set(0); + } else { + self.do_next_op(); + } + } + } + ScreenStatus::SendCommandId(arguments, len) => { + if arguments { + self.status.set(ScreenStatus::SendCommandArguments(len)); + self.do_next_op(); + } else { + self.command_sequence_position + .set(self.command_sequence_position.get() + 1); + self.status.set(ScreenStatus::SendCommand); + self.do_next_op(); + } + } + ScreenStatus::SendCommandArguments(len) => { + self.send_arguments(len); + } + ScreenStatus::SendCommandArgumentsDone => { + self.command_sequence_position + .set(self.command_sequence_position.get() + 1); + self.status.set(ScreenStatus::SendCommand); + self.do_next_op(); + } + ScreenStatus::WriteData => { + self.prepare_write_buffer(); + self.status.set(ScreenStatus::SendCommand); + self.do_next_op(); + } + } + } + + fn send_arguments(&self, len: usize) { + self.command_arguments.take().map_or_else( + || panic!("ssd1306: send argument has no command arguments buffer"), + |arguments| { + self.status.set(ScreenStatus::SendCommandArgumentsDone); + let _ = self.bus.write(BusWidth::Bits8, arguments, len); + }, + ); + } + + fn prepare_write_buffer(&self) { + self.bus_write_buffer.map_or_else( + || panic!("write function has no write buffer"), + |bus_write_buffer| { + bus_write_buffer[0] = WRITE_COMMAND; + + self.aux_write_buffer.map_or_else( + || panic!("write function has no app write buffer"), + |app_write_buffer| { + let mut app_buf_index = 0; + let GraphicsMode { + frame: GraphicsFrame { width, height }, + pixel_format: _, + } = self.graphics_mode.get(); + let Point { x, y } = self.origin.get(); + for h in y..y + height { + for _l in x..x + width { + for index in 0..8 { + let bit = (app_write_buffer[app_buf_index] + & (1 << (7 - index))) + >> (7 - index); + let buffer_index = (app_buf_index % (width as usize / 8)) * 8 + + h as usize * WIDTH + + index + + x as usize; + let bit_index = ((app_buf_index % width as usize) + / (width as usize / 8)) + as u8; + + if bit == 0 { + bus_write_buffer[buffer_index] &= !(1 << bit_index); + } else if bit == 1 { + bus_write_buffer[buffer_index] |= 1 << bit_index; + } + } + app_buf_index += 1; + } + } + }, + ); + }, + ); + } + + fn send_command(&self, cmd: ScreenCommand) { + if cmd.id == CommandId::Write { + self.bus_write_buffer.take().map_or_else( + || panic!("ssd1306: send_command has no write buffer"), + |buffer| { + buffer[0] = WRITE_COMMAND; + if !self.initial_write.get() { + self.initial_write.set(true); + let GraphicsMode { + frame: GraphicsFrame { width, height }, + pixel_format: _, + } = self.graphics_mode.get(); + self.write_buffer_len.set((height * width) as usize / 8); + for i in 0..self.write_buffer_len.get() { + buffer[i + 1] = 0x00; + } + } + for i in 1..buffer.len() { + buffer[i] = !buffer[i]; + } + self.status.set(ScreenStatus::SendCommandId(false, 0)); + let _ = self.bus.write(BusWidth::Bits8, buffer, buffer.len()); + }, + ) + } else { + let _ = self.bus.set_addr(BusWidth::Bits16LE, cmd.id as usize); + let new_state = if let Some(params) = cmd.parameters { + self.populate_arguments_buffer(cmd); + ScreenStatus::SendCommandId(true, params.len()) + } else { + ScreenStatus::SendCommandId(false, 0) + }; + self.status.set(new_state); + } + } + + fn populate_arguments_buffer(&self, cmd: ScreenCommand) { + self.command_arguments.map_or_else( + || panic!("ssd1306 populate arguments has no command arguments buffer"), + |command_buffer| { + if let Some(parameters) = cmd.parameters { + for (i, param) in parameters.iter().enumerate() { + command_buffer[i] = *param; + } + } + }, + ) + } + + fn prepare_init_sequence(&self) -> Result<(), ErrorCode> { + if self.status.get() == ScreenStatus::Idle { + self.command_sequence.map_or_else( + || panic!("ssd1306: init sequence has no command sequence buffer"), + |command_sequence| { + if SSD1306_INIT_SEQ.len() <= command_sequence.len() { + self.command_sequence_length.set(SSD1306_INIT_SEQ.len()); + for (i, cmd) in SSD1306_INIT_SEQ.iter().enumerate() { + command_sequence[i] = *cmd; + } + Ok(()) + } else { + Err(ErrorCode::NOMEM) + } + }, + ) + } else { + Err(ErrorCode::BUSY) + } + } + + fn prepare_write_sequence(&self) -> Result<(), ErrorCode> { + if self.status.get() == ScreenStatus::Idle { + self.command_sequence.map_or_else( + || panic!("ssd1306: write sequence has no command sequence buffer"), + |command_sequence| { + command_sequence[0] = ScreenCommand { + id: CommandId::SetPageAddr, + parameters: Some(&[CONTROL_COMMAND, 0x00, 0xFF]), + }; + command_sequence[1] = ScreenCommand { + id: CommandId::SetColumnAddr, + parameters: Some(&[CONTROL_COMMAND, 0x00, WIDTH as u8 - 1]), + }; + command_sequence[2] = ScreenCommand { + id: CommandId::Write, + parameters: None, + }; + self.command_sequence_length.set(3); + self.command_sequence_position.set(0); + Ok(()) + }, + ) + } else { + Err(ErrorCode::BUSY) + } + } +} + +impl<'a, B: Bus<'a>> bus::Client for SSD1306<'a, B> { + fn command_complete( + &self, + buffer: Option<&'static mut [u8]>, + _len: usize, + status: Result<(), kernel::ErrorCode>, + ) { + if let Some(buf) = buffer { + if self.status.get() == ScreenStatus::SendCommandArgumentsDone { + self.command_arguments.replace(buf); + } else { + // write command complete + self.bus_write_buffer.replace(buf); + self.frame_buffer_client.map_or_else( + || panic!("ssd1306: do next op has no screen client"), + |frame_buffer_client| { + // callback + self.write_buffer_len.replace(0); + self.write_buffer_position.replace(0); + frame_buffer_client.command_complete(status); + }, + ); + } + } + + if let Err(err) = status { + self.status.set(ScreenStatus::Error(err)); + } + + self.do_next_op(); + } +} + +impl<'a, B: Bus<'a>> FrameBuffer<'static> for SSD1306<'a, B> { + fn get_mode(&self) -> GraphicsMode { + self.graphics_mode.get() + } + + fn get_tile_format(&self) -> kernel::hil::display::Tile { + self.tile.get() + } + + fn set_write_frame(&self, origin: Point, size: GraphicsFrame) -> Result<(), ErrorCode> { + if !self.initialization_complete.get() { + Err(ErrorCode::OFF) + } else if self.status.get() == ScreenStatus::Idle { + let mut current_mode = self.graphics_mode.get(); + if (origin.x + size.width > WIDTH as u16) || (origin.y + size.height > HEIGHT as u16) { + return Err(ErrorCode::INVAL); + } + current_mode.frame.height = size.height / 8; + current_mode.frame.width = size.width; + self.graphics_mode.replace(current_mode); + self.origin.replace(Point { + x: origin.x, + y: origin.y / 8, + }); + self.async_status + .set(ScreenAsyncCommand::AsyncFrameBufferCommand(Ok(()))); + // todo remove self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_caller.set(); + Ok(()) + } else { + Err(ErrorCode::BUSY) + } + } + + fn get_buffer_padding(&self) -> (usize, usize) { + (BUFFER_PADDING, 0) + } + + fn write( + &self, + buffer: &'static mut [u8], + len: usize, + reset_position: bool, + ) -> Result<(), ErrorCode> { + let ret = if !self.initialization_complete.get() { + Err(ErrorCode::OFF) + } else if self.status.get() == ScreenStatus::Idle { + if reset_position { + self.write_buffer_position.set(0); + self.write_buffer_len.set(0); + } + self.app_write_buffer.replace(buffer); + let mut status = Ok(()); + self.app_write_buffer.map_or_else( + || panic!("write has no app buffer"), + |app_buffer| { + self.aux_write_buffer.map_or_else( + || panic!("write has no aux buffer"), + |aux_buffer| { + let current_position = self.write_buffer_position.get(); + if len + current_position > aux_buffer.len() { + status = Err(ErrorCode::INVAL); + } else { + aux_buffer[current_position..(len + current_position)] + .copy_from_slice( + &app_buffer[BUFFER_PADDING..(len + BUFFER_PADDING)], + ); + self.write_buffer_position.replace(len + current_position); + } + self.write_buffer_len.set(len + current_position); + }, + ); + }, + ); + status + } else { + Err(ErrorCode::BUSY) + }; + self.async_status.set(ScreenAsyncCommand::Write(ret)); + // todo removeself.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_caller.set(); + ret + } + + fn flush(&self) -> Result<(), ErrorCode> { + if !self.initialization_complete.get() { + Err(ErrorCode::OFF) + } else if self.status.get() == ScreenStatus::Idle { + if self.bus_write_buffer.is_none() { + Err(ErrorCode::NOSUPPORT) + } else { + if let Err(err) = self.prepare_write_sequence() { + self.status.set(ScreenStatus::Error(err)); + } else { + self.status.set(ScreenStatus::WriteData); + } + self.do_next_op(); + Ok(()) + } + } else { + Err(ErrorCode::BUSY) + } + } + + fn set_client(&self, client: Option<&'static dyn FrameBufferClient>) { + if let Some(client) = client { + self.frame_buffer_client.set(client); + } else { + self.frame_buffer_client.clear(); + } + } +} + +impl<'a, B: Bus<'a>> Screen<'static> for SSD1306<'a, B> { + fn get_rotation(&self) -> Rotation { + self.rotation.get() + } + + fn set_client(&self, client: Option<&'static dyn ScreenClient>) { + if let Some(client) = client { + self.screen_client.set(client); + } else { + self.screen_client.clear(); + } + } + + fn set_brightness(&self, _brightness: u16) -> Result<(), kernel::ErrorCode> { + self.async_status + .set(ScreenAsyncCommand::AsyncScreenCommand(Ok(()))); + // todo remove self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_caller.set(); + Ok(()) + } + + fn set_power(&self, _enabled: bool) -> Result<(), kernel::ErrorCode> { + self.async_status + .set(ScreenAsyncCommand::AsyncScreenCommand(Ok(()))); + // todo remove self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_caller.set(); + Ok(()) + } + + fn set_invert(&self, enabled: bool) -> Result<(), kernel::ErrorCode> { + self.invert.replace(enabled); + self.async_status + .set(ScreenAsyncCommand::AsyncScreenCommand(Ok(()))); + //todo remove self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_caller.set(); + Ok(()) + } + + fn set_rotation(&self, rotation: Rotation) -> Result<(), ErrorCode> { + self.rotation.set(rotation); + // todo update origin and graphics mode + self.async_status + .set(ScreenAsyncCommand::AsyncScreenCommand(Ok(()))); + //todo remove self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_caller.set(); + Ok(()) + } +} + +impl<'a, B: Bus<'a>> FrameBufferSetup<'static> for SSD1306<'a, B> { + fn set_mode(&self, mode: GraphicsMode) -> Result<(), ErrorCode> { + self.graphics_mode.replace(mode); + Ok(()) + } + + fn get_num_supported_modes(&self) -> usize { + 1 + } + + fn get_supported_mode(&self, index: usize) -> Option { + match index { + 0 => Some(self.graphics_mode.get()), + _ => None, + } + } +} + +impl<'a, B: Bus<'a>> DeferredCallClient for SSD1306<'a, B> { + fn handle_deferred_call(&self) { + match self.async_status.get() { + ScreenAsyncCommand::Idle => panic!("Received dynamic call without a caller"), + ScreenAsyncCommand::AsyncScreenCommand(res) => { + self.screen_client.map_or_else( + || panic!("ssd1306: dynamic deferred call client has no screen setup client"), + |screen_client| { + self.async_status.set(ScreenAsyncCommand::Idle); + screen_client.command_complete(res); + }, + ); + } + ScreenAsyncCommand::AsyncFrameBufferCommand(res) => { + self.frame_buffer_client.map_or_else( + || panic!("ssd1306: dynamic deferred call client has no frame buffer client"), + |frame_buffer_client| { + self.async_status.set(ScreenAsyncCommand::Idle); + frame_buffer_client.command_complete(res); + }, + ); + } + ScreenAsyncCommand::Write(res) => { + self.frame_buffer_client.map_or_else( + || panic!("ssd1306: dynamic deferred call client has no frame buffer client"), + |frame_buffer_client| { + self.app_write_buffer.take().map_or_else( + || panic!("ssd1306: dynamic deferred call has no app write buffer"), + |buffer| { + self.async_status.set(ScreenAsyncCommand::Idle); + frame_buffer_client.write_complete(buffer, res); + }, + ); + }, + ); + } + } + } + + fn register(&'static self) { + self.deferred_caller.register(self); + } +} diff --git a/kernel/src/hil/display.rs b/kernel/src/hil/display.rs new file mode 100644 index 0000000000..62b218ed8d --- /dev/null +++ b/kernel/src/hil/display.rs @@ -0,0 +1,418 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Interface for screens and displays. +use crate::ErrorCode; +use core::ops::Add; +use core::ops::Sub; + +pub const MAX_BRIGHTNESS: u16 = 65535; + +#[derive(Copy, Clone, PartialEq)] +pub enum Rotation { + Normal, + Rotated90, + Rotated180, + Rotated270, +} + +impl Add for Rotation { + type Output = Self; + + fn add(self, other: Self) -> Self { + match (self, other) { + (Rotation::Normal, _) => other, + (_, Rotation::Normal) => self, + (Rotation::Rotated90, Rotation::Rotated90) => Rotation::Rotated180, + (Rotation::Rotated90, Rotation::Rotated180) => Rotation::Rotated270, + (Rotation::Rotated90, Rotation::Rotated270) => Rotation::Normal, + + (Rotation::Rotated180, Rotation::Rotated90) => Rotation::Rotated270, + (Rotation::Rotated180, Rotation::Rotated180) => Rotation::Normal, + (Rotation::Rotated180, Rotation::Rotated270) => Rotation::Rotated90, + + (Rotation::Rotated270, Rotation::Rotated90) => Rotation::Normal, + (Rotation::Rotated270, Rotation::Rotated180) => Rotation::Rotated90, + (Rotation::Rotated270, Rotation::Rotated270) => Rotation::Rotated180, + } + } +} + +impl Sub for Rotation { + type Output = Self; + + fn sub(self, other: Self) -> Self { + match (self, other) { + (_, Rotation::Normal) => self, + + (Rotation::Normal, Rotation::Rotated90) => Rotation::Rotated270, + (Rotation::Normal, Rotation::Rotated180) => Rotation::Rotated180, + (Rotation::Normal, Rotation::Rotated270) => Rotation::Rotated90, + + (Rotation::Rotated90, Rotation::Rotated90) => Rotation::Normal, + (Rotation::Rotated90, Rotation::Rotated180) => Rotation::Rotated270, + (Rotation::Rotated90, Rotation::Rotated270) => Rotation::Rotated180, + + (Rotation::Rotated180, Rotation::Rotated90) => Rotation::Rotated90, + (Rotation::Rotated180, Rotation::Rotated180) => Rotation::Normal, + (Rotation::Rotated180, Rotation::Rotated270) => Rotation::Rotated270, + + (Rotation::Rotated270, Rotation::Rotated90) => Rotation::Rotated180, + (Rotation::Rotated270, Rotation::Rotated180) => Rotation::Rotated90, + (Rotation::Rotated270, Rotation::Rotated270) => Rotation::Normal, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +#[allow(non_camel_case_types)] +#[non_exhaustive] +pub enum PixelFormat { + /// Pixels encoded as 1-bit, used for monochromatic displays + Mono, + /// Pixels encoded as 2-bit red channel, 3-bit green channel, 3-bit blue channel. + RGB_233, + /// Pixels encoded as 5-bit red channel, 6-bit green channel, 5-bit blue channel. + RGB_565, + /// Pixels encoded as 8-bit red channel, 8-bit green channel, 8-bit blue channel. + RGB_888, + /// Pixels encoded as 8-bit alpha channel, 8-bit red channel, 8-bit green channel, 8-bit blue channel. + ARGB_8888, + // other pixel formats may be defined. +} + +impl PixelFormat { + pub fn get_bits_per_pixel(&self) -> usize { + match self { + Self::Mono => 1, + Self::RGB_233 => 8, + Self::RGB_565 => 16, + Self::RGB_888 => 24, + Self::ARGB_8888 => 32, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +#[allow(non_camel_case_types)] +#[non_exhaustive] +pub enum CharacterFormat { + /// Characters are encoded using 8 bits ASCII, used for monochromatic displays + ASCII, + /// Characters are encoded using UTF8, used for monochromatic displays + UTF8, + /// Characters are encoded using 16 bits, as in the VGA text mode + /// https://en.wikipedia.org/wiki/VGA_text_mode + /// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + /// | B |Background | Foreground | Code Point | + /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + /// B - blink + VGA, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct GraphicsMode { + pub frame: GraphicsFrame, + pub pixel_format: PixelFormat, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct TextMode { + pub frame: TextFrame, + pub character_format: CharacterFormat, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Align { + pub horizontal: usize, + pub vertical: usize, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct GraphicsFrame { + pub width: u16, + pub height: u16, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct TextFrame { + columns: u16, + lines: u16, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Tile { + pub align: Align, + pub size: GraphicsFrame, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct Point { + pub x: u16, + pub y: u16, +} + +/* TextBuffer */ +pub trait TextBuffer<'a> { + fn set_client(&self, client: Option<&'a dyn TextBufferClient>); + + /// Returns a tuple (width, height) with the resolution of the + /// screen that is being used. This function is synchronous as the + /// resolution is known by the driver at any moment. + /// + /// The resolution is constant. + fn get_mode(&self) -> TextMode; + + /// Sends a write command to the driver, and the buffer to write from + /// and the len are sent as arguments. When the `write` operation is + /// finished, the driver will call the `write_complete()` callback. + /// + /// Return values: + /// - `Ok(())`: The write command is valid and will be sent to the driver. + /// - `BUSY`: The driver is busy with another command. + fn print( + &self, + buffer: &'static mut [u8], + len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8])>; + + /// Sends to the driver a command to set the cursor at a given position + /// (x_position, y_position). When finished, the driver will call the + /// `command_complete()` callback. + /// + /// Return values: + /// - `Ok(())`: The command is valid and will be sent to the driver. + /// - `BUSY`: Another command is in progress. + fn set_cursor_position(&self, position: Point) -> Result<(), ErrorCode>; + + /// Sends to the driver a command to show the cursor. When finished, + /// the driver will call the `command_complete()` callback. + /// + /// Return values: + /// - `Ok(())`: The command is valid and will be sent to the driver. + /// - `BUSY`: Another command is in progress. + fn set_show_cursor(&self, show: bool) -> Result<(), ErrorCode>; + + /// Sends to the driver a command to turn on the blinking cursor. When finished, + /// the driver will call the `command_complete()` callback. + /// + /// Return values: + /// - `Ok(())`: The command is valid and will be sent to the driver. + /// - `BUSY`: Another command is in progress. + fn set_blink_cursor(&self, blink: bool) -> Result<(), ErrorCode>; + + /// Sends to the driver a command to clear the display of the screen. + /// When finished, the driver will call the `command_complete()` callback. + /// + /// Return values: + /// - `Ok(())`: The command is valid and will be sent to the driver. + /// - `BUSY`: Another command is in progress. + fn clear(&self) -> Result<(), ErrorCode>; +} + +pub trait TextBufferClient { + /// The driver calls this function when any command (but a write one) + /// finishes executing. + fn command_complete(&self, r: Result<(), ErrorCode>); + + /// The driver calls this function when a write command finishes executing. + fn write_complete(&self, buffer: &'static mut [u8], len: usize, r: Result<(), ErrorCode>); +} + +/* FrameBuffer */ + +pub trait FrameBuffer<'a> { + /// Returns a GraphicsMode struct with the current resolution (in pixels) and + /// the pixel format of the display. This function is synchronous as the + /// driver should know this value without requesting it from the screen. + /// + /// Note that width and height may change due to rotation + fn get_mode(&self) -> GraphicsMode; + + /// Returns the format of minimum tile (in bytes) that can be written to the framebuffer. + /// + /// Due to hardware constraints, framebuffer writes have to be rounded up to a tile size. + /// This means that the size of the write buffer has to be a multiple of the tile. An + /// example use case is a framebuffer that has the minimum write unit a full line on + /// the display. This means that clients can only write entire lines to the framebuffer + /// as opossed to single pixels. + fn get_tile_format(&self) -> Tile; + + /// Sets the video memory frame. + /// This function has to be called before the first call to the write function. + /// This will generate a `command_complete()` callback when finished. + /// + /// Return values: + /// - `Ok(())`: The write frame is valid. + /// - `INVAL`: The parameters of the write frame are not valid. + /// - `BUSY`: Unable to set the write frame on the device. + fn set_write_frame(&self, origin: Point, size: GraphicsFrame) -> Result<(), ErrorCode>; + + /// Returns the required buffer padding in the format of + /// a tuble (free bytes before, free bytes after). + /// + /// The supplied buffer has to be + /// +----------------------+------------+---------------------+ + /// | before padding bytes | frame data | after padding bytes | + /// +----------------------+------------+---------------------+ + /// + /// Some displays,like the SSD1306, require some command bytes placed before + /// and after the actual framebuffer data. Without this padding, the display + /// driver would have to keep another buffer and additional data copy. + /// + /// The HIL's user has to fill in data only in between the padding + /// bytes. Any data written to the padding bytes might be overwritten + /// by the underlying display driver. + fn get_buffer_padding(&self) -> (usize, usize); + + /// Sends a write command to write data in the selected video memory frame. + /// When finished, the driver will call the `write_complete()` callback. + /// + /// Return values: + /// - `Ok(())`: Write is valid and will be sent to the screen. + /// - `INVAL`: Write is invalid or length is wrong. + /// - `BUSY`: Another write is in progress. + fn write( + &self, + buffer: &'static mut [u8], + len: usize, + reset_position: bool, + ) -> Result<(), ErrorCode>; + + /// Flush the framebuffer changes to the hardware device. + /// + /// Some framebuffers keep in a temporary memory the changes and require a flush command + /// to send the changes to the hardware. + /// + /// Return values: + /// - `Ok(())`: Flush is in progress and the client will receive + /// a call to `command_complete`. + /// - `ENOSUPPORT`: Flush has been done synchronous or there is no + /// no need to flush the framebuffer. + /// - `BUSY`: Another write or flush is in progress. + fn flush(&self) -> Result<(), ErrorCode>; + + /// Set the object to receive the asynchronous command callbacks. + fn set_client(&self, client: Option<&'a dyn FrameBufferClient>); +} + +pub trait FrameBufferClient { + /// The framebuffer will call this function to notify that the write command has finished. + /// This is different from `command_complete` as it has to pass back the write buffer + fn write_complete(&self, buffer: &'static mut [u8], r: Result<(), ErrorCode>); + + /// The framebuffer will call this function to notify that a command (except `write` and + /// `write_continue`) has finished. + fn command_complete(&self, r: Result<(), ErrorCode>); +} + +pub trait FrameBufferSetup<'a>: FrameBuffer<'a> { + /// Sets the graphics mode of the display. Returns ENOSUPPORT if the resolution is + /// not supported. The function should return Ok(()) if the request is registered + /// and will be sent to the screen. + /// Upon Ok(()), the caller has to wait for the `command_complete()` callback function + /// that will return the actual Result<(), ErrorCode> after setting the resolution. + fn set_mode(&self, mode: GraphicsMode) -> Result<(), ErrorCode>; + + /// Returns the number of the graphic modes supported. Should return at least + /// one (the current resolution). This function is synchronous as the driver + /// should know this value without requesting it from the screen (most + /// screens do not support such a request, resolutions and pixel formats + /// are described in the data sheet). + /// + /// If the screen supports such a feature, the driver should request this information + /// from the screen upfront. + fn get_num_supported_modes(&self) -> usize; + + /// Can be called with an index from 0 .. count-1 and will return a + /// GraphicsMode struct with the current resolution and pixel format. + /// + /// Note that width and height may change due to rotation + /// + /// This function is synchronous as the driver should know this value without + /// requesting it from the screen. + fn get_supported_mode(&self, index: usize) -> Option; +} + +/* Screen */ +pub trait Screen<'a> { + /// Returns the current rotation. + /// This function is synchronous as the driver should know this value without + /// requesting it from the screen. + fn get_rotation(&self) -> Rotation; + + /// Sets the rotation of the display. + /// The function should return Ok(()) if the request is registered + /// and will be sent to the screen. + /// Upon Ok(()), the caller has to wait for the `command_complete` callback function + /// that will return the actual Result<(), ErrorCode> after setting the rotation. + /// + /// Note that in the case of `Rotated90` or `Rotated270`, this will swap the width and height. + fn set_rotation(&self, rotation: Rotation) -> Result<(), ErrorCode>; + + /// Controls the screen power supply. + /// + /// Use it to initialize the display device. + /// + /// For screens where display needs nonzero brightness (e.g. LED), + /// this shall set brightness to a default value + /// if `set_brightness` was not called first. + /// + /// The device may implement power independently from brightness, + /// so call `set_brightness` to turn on/off the module completely. + /// + /// When finished, calls `ScreenClient::screen_is_ready`, + /// both when power was enabled and disabled. + fn set_power(&self, enabled: bool) -> Result<(), ErrorCode>; + + /// Set on or off the inversion of colors. + fn set_invert(&self, inverted: bool) -> Result<(), ErrorCode>; + + /// Sets the display brightness value + /// + /// Depending on the display, this may not cause any actual changes + /// until and unless power is enabled (see `set_power`). + /// + /// The following values must be supported: + /// - 0 - completely no light emitted + /// - 1..MAX_BRIGHTNESS - on, set brightness to the given level + /// + /// The display should interpret the brightness value as *lightness* + /// (each increment should change preceived brightness the same). + /// 1 shall be the minimum supported brightness, + /// `MAX_BRIGHTNESS` and greater represent the maximum. + /// Values in between should approximate the intermediate values; + /// minimum and maximum included (e.g. when there is only 1 level). + fn set_brightness(&self, brightness: u16) -> Result<(), ErrorCode>; + + /// Set the object to receive the asynchronous command callbacks. + fn set_client(&self, client: Option<&'a dyn ScreenClient>); +} + +pub trait ScreenClient { + /// The screen will call this function to notify that a command (except write) has finished. + fn command_complete(&self, r: Result<(), ErrorCode>); +} + +/* Text Display */ + +pub trait TextDisplay<'a>: TextBuffer<'a> + Screen<'a> {} + +// Provide blanket implementation for trait group +impl<'a, T: Screen<'a> + TextBuffer<'a>> TextDisplay<'a> for T {} + +/* Graphic Display */ + +pub trait GraphicDisplay<'a>: Screen<'a> + FrameBuffer<'a> {} + +// Provide blanket implementations for trait group +impl<'a, T: Screen<'a> + FrameBuffer<'a>> GraphicDisplay<'a> for T {} + +/* Display Advanced */ + +pub trait FullGraphicDisplay<'a>: GraphicDisplay<'a> + FrameBufferSetup<'a> {} + +// Provide blanket implementations for trait group +impl<'a, T: GraphicDisplay<'a> + FrameBufferSetup<'a>> FullGraphicDisplay<'a> for T {} diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs index 0eb367d21a..989dec7c7a 100644 --- a/kernel/src/hil/mod.rs +++ b/kernel/src/hil/mod.rs @@ -14,6 +14,7 @@ pub mod crc; pub mod dac; pub mod date_time; pub mod digest; +pub mod display; pub mod eic; pub mod entropy; pub mod flash;