diff --git a/examples/atmega328-nostd/src/bin/formatter-4bit.rs b/examples/atmega328-nostd/src/bin/formatter-4bit.rs new file mode 100644 index 0000000..77873b3 --- /dev/null +++ b/examples/atmega328-nostd/src/bin/formatter-4bit.rs @@ -0,0 +1,58 @@ +#![no_std] +#![no_main] + +use arduino_hal::Delay; +use embedded_hal::delay::DelayNs as _; +use hd44780_driver::{ + bus::{FourBitBusPins, WriteOnlyMode}, + memory_map::MemoryMap1602, + setup::DisplayOptions4Bit, + HD44780, +}; +use panic_halt as _; + +#[arduino_hal::entry] +fn main() -> ! { + let peripherals = arduino_hal::Peripherals::take().unwrap(); + let pins = arduino_hal::pins!(peripherals); + + // Setup USB Serial + let mut serial = arduino_hal::default_serial!(peripherals, pins, 115200); + + let mut delay = Delay::new(); + + ufmt::uwriteln!(serial, "Start").unwrap(); + + // Configure LCD driver with 10 pins + let options = DisplayOptions4Bit::new(MemoryMap1602::new()).with_pins(FourBitBusPins { + rs: pins.d12.into_output(), + rw: WriteOnlyMode, + en: pins.d11.into_output(), + + d4: pins.d6.into_opendrain(), + d5: pins.d5.into_opendrain(), + d6: pins.d4.into_opendrain(), + d7: pins.d3.into_opendrain(), + }); + + // Initialize LCD driver + // Note: IO Error is infallible, thus unwrapping won't panic here + let mut display = HD44780::new(options, &mut delay).unwrap_or_else(|_| unreachable!()); + + display.clear(&mut delay).unwrap(); + display.reset(&mut delay).unwrap(); + + // Writing to the display using ufmt + { + let mut writer = display.writer((0, 0), (5, 1), &mut delay).unwrap(); + const HELLO_TO: &str = "world"; + ufmt::uwrite!(writer, "Hello, {}!", HELLO_TO).unwrap(); + + writer = display.writer((7, 0), (15, 1), &mut delay).unwrap(); + ufmt::uwrite!(writer, "max={}", core::u32::MAX).unwrap(); + } + + loop { + delay.delay_ms(1000); + } +} diff --git a/src/charset.rs b/src/charset.rs index 67834ac..55fa738 100644 --- a/src/charset.rs +++ b/src/charset.rs @@ -2,6 +2,11 @@ use core::ops::{Deref, DerefMut}; pub trait Charset { fn code_from_utf8(&self, ch: char) -> Option; + + #[inline] + fn is_whitespace(&self, ch: char) -> bool { + ch.is_ascii_whitespace() + } } pub trait CharsetWithFallback { @@ -201,6 +206,11 @@ impl Charset for CharsetA00 { _ => None, } } + + #[inline] + fn is_whitespace(&self, ch: char) -> bool { + ch.is_whitespace() // unicode whitespace + } } /// European Standard Font Character Set. diff --git a/src/error.rs b/src/error.rs index 9bfa335..1c5a801 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,8 @@ pub enum Error { }, /// Invalid coordinates on the display. Position { position: (u8, u8), size: (u8, u8) }, + /// Writer has reached its end. + EOF, } impl Error { @@ -25,6 +27,7 @@ impl core::fmt::Display for Error { "coordinates out of bounds: ({};{}) not fitting in a {}x{} display", position.0, position.1, size.0, size.1 ), + Self::EOF => write!(f, "writer has reached its end"), } } } @@ -42,6 +45,7 @@ impl defmt::Format for Error { size.0, size.1 ), + Self::EOF => defmt::write!(fmt, "writer has reached its end"), } } } @@ -62,6 +66,7 @@ impl ufmt::uDisplay for Error { size.0, size.1 ), + Self::EOF => ufmt::uwrite!(f, "writer has reached its end"), } } } diff --git a/src/lib.rs b/src/lib.rs index c65606f..d5b5f9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,17 +17,19 @@ pub mod entry_mode; use entry_mode::{CursorMode, EntryMode}; pub mod setup; +use setup::blocking::DisplayOptions; pub mod charset; pub mod memory_map; +use memory_map::DisplayMemoryMap; pub mod display_mode; pub mod display_size; pub use display_mode::DisplayMode; -use memory_map::DisplayMemoryMap; -use setup::blocking::DisplayOptions; + +pub mod writer; /// Implementation of async functionality #[cfg(feature = "async")] @@ -134,6 +136,11 @@ where &self.memory_map } + /// Get the character set for this display. + pub fn charset(&self) -> &C { + &self.charset + } + /// Get the display size. pub fn display_size(&self) -> DisplaySize { self.memory_map.display_size() diff --git a/src/non_blocking/mod.rs b/src/non_blocking/mod.rs index 316a691..d7d7afd 100644 --- a/src/non_blocking/mod.rs +++ b/src/non_blocking/mod.rs @@ -111,6 +111,11 @@ where &self.memory_map } + /// Get the character set for this display. + pub fn charset(&self) -> &C { + &self.charset + } + /// Get the display size. pub fn display_size(&self) -> DisplaySize { self.memory_map.display_size() diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..7f6a8ae --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,169 @@ +use embedded_hal::delay::DelayNs; + +use crate::{ + bus::WritableDataBus, + charset::{Charset, CharsetWithFallback}, + error::Result, + memory_map::DisplayMemoryMap, + HD44780, +}; + +pub struct DisplayWriter<'display, 'delay, Display, Delay> { + display: &'display mut Display, + delay: &'delay mut Delay, + line: u8, + col: u8, + line_max: u8, + col_min: u8, + col_max: u8, + current_col_max: u8, + implicit_newline: bool, + done: bool, +} + +impl<'display, 'delay, B, M, C, Delay> DisplayWriter<'display, 'delay, HD44780, Delay> +where + B: WritableDataBus, + M: DisplayMemoryMap, + C: CharsetWithFallback, + Delay: DelayNs, +{ + fn new( + display: &'display mut HD44780, + position: (u8, u8), + max: (u8, u8), + delay: &'delay mut Delay, + ) -> Result { + display.set_cursor_xy(position, delay)?; + let this = Self { + current_col_max: display.memory_map().columns_in_line(position.1), + display, + delay, + col: position.0, + line: position.1, + col_max: max.0, + line_max: max.1, + col_min: position.0, + implicit_newline: false, + done: false, + }; + Ok(this) + } + + fn new_line(&mut self) { + self.done |= self.line == self.line_max; + if !self.done { + self.line += 1; + self.col = self.col_min; + self.done |= self.display.set_cursor_xy((self.col, self.line), self.delay).is_err(); + self.current_col_max = self.display.memory_map().columns_in_line(self.line).min(self.col_max); + } + } +} + +impl<'display, 'delay, B, M, C, Delay> core::fmt::Write for DisplayWriter<'display, 'delay, HD44780, Delay> +where + B: WritableDataBus, + M: DisplayMemoryMap, + C: CharsetWithFallback, + Delay: DelayNs, +{ + fn write_str(&mut self, s: &str) -> core::fmt::Result { + for ch in s.chars() { + self.write_char(ch)?; + } + Ok(()) + } + + fn write_char(&mut self, ch: char) -> core::fmt::Result { + if ch == '\n' { + self.done |= self.col == self.current_col_max; + self.new_line(); + return Ok(()); + } + + // Space is promoted to new line on implicit line breaks + if self.implicit_newline && self.display.charset().is_whitespace(ch) { + self.implicit_newline = false; + return Ok(()); + } + self.implicit_newline = false; + + if self.done || self.display.write_char(ch, self.delay).is_err() { + return Err(core::fmt::Error); + } + + // Continue on new line + if self.col == self.current_col_max { + self.implicit_newline = true; + self.new_line(); + } else { + self.col += 1; + } + + Ok(()) + } +} + +#[cfg(feature = "ufmt")] +impl<'display, 'delay, B, M, C, Delay> ufmt::uWrite for DisplayWriter<'display, 'delay, HD44780, Delay> +where + B: WritableDataBus, + M: DisplayMemoryMap, + C: CharsetWithFallback, + Delay: DelayNs, +{ + type Error = crate::Error; + + fn write_str(&mut self, s: &str) -> core::result::Result<(), Self::Error> { + for ch in s.chars() { + self.write_char(ch)?; + } + Ok(()) + } + + fn write_char(&mut self, ch: char) -> core::result::Result<(), Self::Error> { + if ch == '\n' { + self.done |= self.col == self.current_col_max; + self.new_line(); + return Ok(()); + } + + // Space is promoted to new line on implicit line breaks + if self.implicit_newline && self.display.charset().is_whitespace(ch) { + self.implicit_newline = false; + return Ok(()); + } + self.implicit_newline = false; + + if self.done || self.display.write_char(ch, self.delay).is_err() { + return Err(core::fmt::Error); + } + + // Continue on new line + if self.col == self.current_col_max { + self.implicit_newline = true; + self.new_line(); + } else { + self.col += 1; + } + + Ok(()) + } +} + +impl HD44780 +where + B: WritableDataBus, + M: DisplayMemoryMap, + C: CharsetWithFallback, +{ + pub fn writer<'display, 'delay, Delay: DelayNs>( + &'display mut self, + position: (u8, u8), + max: (u8, u8), + delay: &'delay mut Delay, + ) -> Result, B::Error> { + DisplayWriter::new(self, position, max, delay) + } +}