Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core::fmt and ufmt support for writing to LCDs #59

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions examples/atmega328-nostd/src/bin/formatter-4bit.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
10 changes: 10 additions & 0 deletions src/charset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use core::ops::{Deref, DerefMut};

pub trait Charset {
fn code_from_utf8(&self, ch: char) -> Option<u8>;

#[inline]
fn is_whitespace(&self, ch: char) -> bool {
ch.is_ascii_whitespace()
}
}

pub trait CharsetWithFallback {
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub enum Error<IoE> {
},
/// Invalid coordinates on the display.
Position { position: (u8, u8), size: (u8, u8) },
/// Writer has reached its end.
EOF,
}

impl<E> Error<E> {
Expand All @@ -25,6 +27,7 @@ impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
"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"),
}
}
}
Expand All @@ -42,6 +45,7 @@ impl<E: defmt::Format> defmt::Format for Error<E> {
size.0,
size.1
),
Self::EOF => defmt::write!(fmt, "writer has reached its end"),
}
}
}
Expand All @@ -62,6 +66,7 @@ impl<E: ufmt::uDebug> ufmt::uDisplay for Error<E> {
size.0,
size.1
),
Self::EOF => ufmt::uwrite!(f, "writer has reached its end"),
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/non_blocking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
169 changes: 169 additions & 0 deletions src/writer.rs
Original file line number Diff line number Diff line change
@@ -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<B, M, C>, Delay>
where
B: WritableDataBus,
M: DisplayMemoryMap,
C: CharsetWithFallback,
Delay: DelayNs,
{
fn new(
display: &'display mut HD44780<B, M, C>,
position: (u8, u8),
max: (u8, u8),
delay: &'delay mut Delay,
) -> Result<Self, B::Error> {
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<B, M, C>, 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<B, M, C>, Delay>
where
B: WritableDataBus,
M: DisplayMemoryMap,
C: CharsetWithFallback,
Delay: DelayNs,
{
type Error = crate::Error<B::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<B, M, C> HD44780<B, M, C>
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<DisplayWriter<'display, 'delay, Self, Delay>, B::Error> {
DisplayWriter::new(self, position, max, delay)
}
}