From e79e1ab222a55ded0dd17a06886f5289da9a9ff8 Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Wed, 18 Sep 2024 23:51:22 +0200 Subject: [PATCH 1/4] Use generics for rows and columns Provide some default types --- src/async_lcd.rs | 33 ++++++++++++++++++++------------- src/lib.rs | 17 +++++++++++++++++ src/sync_lcd.rs | 35 +++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/async_lcd.rs b/src/async_lcd.rs index ab1a4ae..47f7d31 100644 --- a/src/async_lcd.rs +++ b/src/async_lcd.rs @@ -1,18 +1,17 @@ use embedded_hal_async::{delay::DelayNs, i2c::I2c}; use crate::{ - Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, + Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, OFFSETS_16X4, OFFSETS_NORMAL, }; /// API to write to the LCD. -pub struct Lcd<'a, I, D> +pub struct Lcd<'a, const ROWS: u8, const COLUMNS: u8, I, D> where I: I2c, D: DelayNs, { i2c: &'a mut I, address: u8, - rows: u8, delay: &'a mut D, backlight_state: Backlight, cursor_on: bool, @@ -20,31 +19,27 @@ where font_mode: Font, } -impl<'a, I, D> Lcd<'a, I, D> +impl<'a, const ROWS: u8, const COLUMNS: u8, I, D> Lcd<'a, ROWS, COLUMNS, I, D> where I: I2c, D: DelayNs, { /// Create new instance with only the I2C and delay instance. pub fn new(i2c: &'a mut I, delay: &'a mut D) -> Self { + assert!(ROWS > 0, "ROWS needs to be larger than zero!"); + assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); + assert!(ROWS < 5, "This library only supports LCDs with up to four rows!"); // Because we don't have offets for more than four rows Self { i2c, delay, backlight_state: Backlight::On, address: 0, - rows: 0, cursor_blink: false, cursor_on: false, font_mode: Font::Font5x8, } } - /// Zero based number of rows. - pub fn with_rows(mut self, rows: u8) -> Self { - self.rows = rows; - self - } - /// Set I2C address, see [lcd address]. /// /// [lcd address]: https://badboi.dev/rust,/microcontrollers/2020/11/09/i2c-hello-world.html @@ -173,7 +168,16 @@ where /// Set the cursor to (rows, col). Coordinates are zero-based. pub async fn set_cursor(&mut self, row: u8, col: u8) -> Result<(), I::Error> { - let shift: u8 = row * 0x40 + col; + assert!(row < ROWS, "Row needs to be smaller than ROWS"); + assert!(col < COLUMNS, "col needs to be smaller than COLUMNS"); + + let offset = if ROWS == 4 && COLUMNS == 16 { + OFFSETS_16X4[row as usize] + } else { + OFFSETS_NORMAL[row as usize] + }; + + let shift: u8 = col + offset; self.command(Mode::DDRAMAddr as u8 | shift).await } @@ -208,7 +212,10 @@ where /// Recomputes function set and updates the lcd async fn update_function_set(&mut self) -> Result<(), I::Error> { // Function set command - let lines = if self.rows == 0 { 0x00 } else { 0x08 }; + let lines = match ROWS { + 1 => 0x00, + _ => 0x08 + }; self.command( Mode::FunctionSet as u8 | self.font_mode as u8 | lines, // Two line display ) diff --git a/src/lib.rs b/src/lib.rs index 98b8522..755797c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,8 @@ //! [this one]: https://funduinoshop.com/elektronische-module/displays/lcd/16x02-i2c-lcd-modul-hintergrundbeleuchtung-blau //! [lcd address]: https://www.ardumotive.com/i2clcden.html +use sync_lcd::Lcd; + #[cfg(feature = "async")] pub mod async_lcd; pub mod sync_lcd; @@ -93,3 +95,18 @@ pub enum DisplayShift { Decrement = 0x00, Increment = 0x01, } + +// offsets taken from the NewLiquidCrystal library +const OFFSETS_NORMAL: [u8; 4] = [ 0x00, 0x40, 0x14, 0x54 ]; // For regular LCDs +const OFFSETS_16X4: [u8; 4] = [ 0x00, 0x40, 0x10, 0x50 ]; // For 16x4 LCDs + +pub type LCD16x2<'a, I, D> = Lcd<'a, 2, 16, I, D>; +pub type LCD16x4<'a, I, D> = Lcd<'a, 4, 16, I, D>; +pub type LCD20x4<'a, I, D> = Lcd<'a, 4, 20, I, D>; + +#[cfg(feature = "async")] +pub type AsyncLCD16x2<'a, I, D> = async_lcd::Lcd<'a, 2, 16, I, D>; +#[cfg(feature = "async")] +pub type AsyncLCD16x4<'a, I, D> = async_lcd::Lcd<'a, 4, 16, I, D>; +#[cfg(feature = "async")] +pub type AsyncLCD20x4<'a, I, D> = async_lcd::Lcd<'a, 4, 20, I, D>; diff --git a/src/sync_lcd.rs b/src/sync_lcd.rs index 24c968e..ad425c1 100644 --- a/src/sync_lcd.rs +++ b/src/sync_lcd.rs @@ -4,18 +4,17 @@ use embedded_hal::i2c::I2c; use ufmt_write::uWrite; use crate::{ - Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, + Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, OFFSETS_16X4, OFFSETS_NORMAL, }; /// API to write to the LCD. -pub struct Lcd<'a, I, D> +pub struct Lcd<'a, const ROWS: u8, const COLUMNS: u8, I, D> where I: I2c, D: DelayNs, { i2c: &'a mut I, address: u8, - rows: u8, delay: &'a mut D, backlight_state: Backlight, cursor_on: bool, @@ -23,31 +22,27 @@ where font_mode: Font, } -impl<'a, I, D> Lcd<'a, I, D> +impl<'a, const ROWS: u8, const COLUMNS: u8, I, D> Lcd<'a, ROWS, COLUMNS, I, D> where I: I2c, D: DelayNs, { /// Create new instance with only the I2C and delay instance. pub fn new(i2c: &'a mut I, delay: &'a mut D) -> Self { + assert!(ROWS > 0, "ROWS needs to be larger than zero!"); + assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); + assert!(ROWS < 5, "This library only supports LCDs with up to four rows!"); // Because we don't have offets for more than four rows Self { i2c, delay, backlight_state: Backlight::On, address: 0, - rows: 0, cursor_blink: false, cursor_on: false, font_mode: Font::Font5x8, } } - /// Zero based number of rows. - pub fn with_rows(mut self, rows: u8) -> Self { - self.rows = rows; - self - } - /// Set I2C address, see [lcd address]. /// /// [lcd address]: https://badboi.dev/rust,/microcontrollers/2020/11/09/i2c-hello-world.html @@ -169,7 +164,16 @@ where /// Set the cursor to (rows, col). Coordinates are zero-based. pub fn set_cursor(&mut self, row: u8, col: u8) -> Result<(), I::Error> { - let shift: u8 = row * 0x40 + col; + assert!(row < ROWS, "Row needs to be smaller than ROWS"); + assert!(col < COLUMNS, "col needs to be smaller than COLUMNS"); + + let offset = if ROWS == 4 && COLUMNS == 16 { + OFFSETS_16X4[row as usize] + } else { + OFFSETS_NORMAL[row as usize] + }; + + let shift: u8 = col + offset; self.command(Mode::DDRAMAddr as u8 | shift) } @@ -203,7 +207,10 @@ where /// Recomputes function set and updates the lcd fn update_function_set(&mut self) -> Result<(), I::Error> { // Function set command - let lines = if self.rows == 0 { 0x00 } else { 0x08 }; + let lines = match ROWS { + 1 => 0x00, + _ => 0x08 + }; self.command( Mode::FunctionSet as u8 | self.font_mode as u8 | lines, // Two line display ) @@ -236,7 +243,7 @@ where } } -impl<'a, I, D> uWrite for Lcd<'a, I, D> +impl<'a, const ROWS: u8, const COLUMNS: u8, I, D> uWrite for Lcd<'a, ROWS, COLUMNS, I, D> where I: I2c, D: DelayNs, From 77eb1034518df5ee6d30a91060dee406e31edf0f Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Sat, 5 Oct 2024 00:25:37 +0200 Subject: [PATCH 2/4] Fix formatting --- src/async_lcd.rs | 10 +++++++--- src/lib.rs | 4 ++-- src/sync_lcd.rs | 12 ++++++++---- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/async_lcd.rs b/src/async_lcd.rs index 47f7d31..006ebce 100644 --- a/src/async_lcd.rs +++ b/src/async_lcd.rs @@ -1,7 +1,8 @@ use embedded_hal_async::{delay::DelayNs, i2c::I2c}; use crate::{ - Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, OFFSETS_16X4, OFFSETS_NORMAL, + Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, + OFFSETS_16X4, OFFSETS_NORMAL, }; /// API to write to the LCD. @@ -28,7 +29,10 @@ where pub fn new(i2c: &'a mut I, delay: &'a mut D) -> Self { assert!(ROWS > 0, "ROWS needs to be larger than zero!"); assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); - assert!(ROWS < 5, "This library only supports LCDs with up to four rows!"); // Because we don't have offets for more than four rows + assert!( + ROWS < 5, + "This library only supports LCDs with up to four rows!" + ); // Because we don't have offsets for more than four rows Self { i2c, delay, @@ -214,7 +218,7 @@ where // Function set command let lines = match ROWS { 1 => 0x00, - _ => 0x08 + _ => 0x08, }; self.command( Mode::FunctionSet as u8 | self.font_mode as u8 | lines, // Two line display diff --git a/src/lib.rs b/src/lib.rs index 755797c..28e2ff2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,8 +97,8 @@ pub enum DisplayShift { } // offsets taken from the NewLiquidCrystal library -const OFFSETS_NORMAL: [u8; 4] = [ 0x00, 0x40, 0x14, 0x54 ]; // For regular LCDs -const OFFSETS_16X4: [u8; 4] = [ 0x00, 0x40, 0x10, 0x50 ]; // For 16x4 LCDs +const OFFSETS_NORMAL: [u8; 4] = [0x00, 0x40, 0x14, 0x54]; // For regular LCDs +const OFFSETS_16X4: [u8; 4] = [0x00, 0x40, 0x10, 0x50]; // For 16x4 LCDs pub type LCD16x2<'a, I, D> = Lcd<'a, 2, 16, I, D>; pub type LCD16x4<'a, I, D> = Lcd<'a, 4, 16, I, D>; diff --git a/src/sync_lcd.rs b/src/sync_lcd.rs index ad425c1..fc4af29 100644 --- a/src/sync_lcd.rs +++ b/src/sync_lcd.rs @@ -4,7 +4,8 @@ use embedded_hal::i2c::I2c; use ufmt_write::uWrite; use crate::{ - Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, OFFSETS_16X4, OFFSETS_NORMAL, + Backlight, BitMode, Commands, CursorMoveDir, DisplayControl, DisplayShift, Font, Mode, + OFFSETS_16X4, OFFSETS_NORMAL, }; /// API to write to the LCD. @@ -31,7 +32,10 @@ where pub fn new(i2c: &'a mut I, delay: &'a mut D) -> Self { assert!(ROWS > 0, "ROWS needs to be larger than zero!"); assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); - assert!(ROWS < 5, "This library only supports LCDs with up to four rows!"); // Because we don't have offets for more than four rows + assert!( + ROWS < 5, + "This library only supports LCDs with up to four rows!" + ); // Because we don't have offsets for more than four rows Self { i2c, delay, @@ -166,7 +170,7 @@ where pub fn set_cursor(&mut self, row: u8, col: u8) -> Result<(), I::Error> { assert!(row < ROWS, "Row needs to be smaller than ROWS"); assert!(col < COLUMNS, "col needs to be smaller than COLUMNS"); - + let offset = if ROWS == 4 && COLUMNS == 16 { OFFSETS_16X4[row as usize] } else { @@ -209,7 +213,7 @@ where // Function set command let lines = match ROWS { 1 => 0x00, - _ => 0x08 + _ => 0x08, }; self.command( Mode::FunctionSet as u8 | self.font_mode as u8 | lines, // Two line display From b351f0d8465931cdda3fcf2800b7b6312b76bf8a Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Sat, 5 Oct 2024 00:32:34 +0200 Subject: [PATCH 3/4] Update readme and lib docs --- README.md | 10 ++++++---- src/lib.rs | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5a5b376..2205022 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Driver to write characters to LCD displays with a LM1602 connected via i2c like [this one] with 16x2 characters. It requires a I2C instance implementing [`embedded_hal::blocking::i2c::Write`] and a instance to delay execution with [`embedded_hal::blocking::delay::DelayMs`]. +Other LCD sizes are supported, up to displays with 20x4 characters. Everything that uses a +HD44780U or comparable controller and is connected via i2c should work. +Like [this] Usage: ``` @@ -20,10 +23,9 @@ let mut i2c = arduino_hal::I2c::new( ); let mut delay = arduino_hal::Delay::new(); -let mut lcd = lcd_lcm1602_i2c::Lcd::new(&mut i2c, &mut delay) +let mut lcd = lcd_lcm1602_i2c::LCD16x2::new(&mut i2c, &mut delay) .address(LCD_ADDRESS) - .cursor_on(false) // no visible cursos - .rows(2) // two rows + .cursor_on(false) // no visible cursor .init().unwrap(); ``` @@ -34,4 +36,4 @@ There is a similar crate [lcd_1602_i2c] but that did not work with [this display [this one]: https://funduinoshop.com/elektronische-module/displays/lcd/16x02-i2c-lcd-modul-hintergrundbeleuchtung-blau [lcd address]: https://www.ardumotive.com/i2clcden.html [lcd_1602_i2c]: https://crates.io/crates/lcd_1602_i2c - +[this]: https://www.az-delivery.de/en/products/hd44780-2004-lcd-display-bundle-4x20-zeichen-mit-i2c-schnittstelle diff --git a/src/lib.rs b/src/lib.rs index 28e2ff2..bfb3036 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ //! Driver to write characters to LCD displays with a LM1602 connected via i2c like [this one] with //! 16x2 characters. It requires a I2C instance implementing [`embedded_hal::blocking::i2c::Write`] //! and a instance to delay execution with [`embedded_hal::blocking::delay::DelayMs`]. +//! Other LCD sizes are supported, up to displays with 20x4 characters. Everything that uses a +//! HD44780U or comparable controller and is connected via i2c should work //! //! Usage: //! ``` @@ -19,10 +21,9 @@ //! ); //! let mut delay = arduino_hal::Delay::new(); //! -//! let mut lcd = lcd_lcm1602_i2c::Lcd::new(&mut i2c, &mut delay) +//! let mut lcd = lcd_lcm1602_i2c::LCD16x2::new(&mut i2c, &mut delay) //! .with_address(LCD_ADDRESS) -//! .with_cursor_on(false) // no visible cursos -//! .with_rows(2) // two rows +//! .with_cursor_on(false) // no visible cursor //! .init().unwrap(); //! ``` //! From 0a8c51dd0ad2a9e935bdf97eecc05351c939ee4b Mon Sep 17 00:00:00 2001 From: "Jomer.Dev" Date: Wed, 9 Oct 2024 18:13:31 +0200 Subject: [PATCH 4/4] Use const asserts where possible --- src/async_lcd.rs | 14 ++++++++------ src/sync_lcd.rs | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/async_lcd.rs b/src/async_lcd.rs index 006ebce..b367ac6 100644 --- a/src/async_lcd.rs +++ b/src/async_lcd.rs @@ -27,12 +27,14 @@ where { /// Create new instance with only the I2C and delay instance. pub fn new(i2c: &'a mut I, delay: &'a mut D) -> Self { - assert!(ROWS > 0, "ROWS needs to be larger than zero!"); - assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); - assert!( - ROWS < 5, - "This library only supports LCDs with up to four rows!" - ); // Because we don't have offsets for more than four rows + const { + assert!(ROWS > 0, "ROWS needs to be larger than zero!"); + assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); + assert!( + ROWS < 5, + "This library only supports LCDs with up to four rows!" + ); // Because we don't have offsets for more than four rows + }; Self { i2c, delay, diff --git a/src/sync_lcd.rs b/src/sync_lcd.rs index fc4af29..9dc43c5 100644 --- a/src/sync_lcd.rs +++ b/src/sync_lcd.rs @@ -30,12 +30,14 @@ where { /// Create new instance with only the I2C and delay instance. pub fn new(i2c: &'a mut I, delay: &'a mut D) -> Self { - assert!(ROWS > 0, "ROWS needs to be larger than zero!"); - assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); - assert!( - ROWS < 5, - "This library only supports LCDs with up to four rows!" - ); // Because we don't have offsets for more than four rows + const { + assert!(ROWS > 0, "ROWS needs to be larger than zero!"); + assert!(COLUMNS > 0, "COLUMNS needs to be larger than zero!"); + assert!( + ROWS < 5, + "This library only supports LCDs with up to four rows!" + ); // Because we don't have offsets for more than four rows + }; Self { i2c, delay,