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/async_lcd.rs b/src/async_lcd.rs index ab1a4ae..b367ac6 100644 --- a/src/async_lcd.rs +++ b/src/async_lcd.rs @@ -2,17 +2,17 @@ use embedded_hal_async::{delay::DelayNs, i2c::I2c}; use crate::{ 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 +20,32 @@ 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 { + 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, 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 +174,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 +218,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..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(); //! ``` //! @@ -31,6 +32,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 +96,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..9dc43c5 100644 --- a/src/sync_lcd.rs +++ b/src/sync_lcd.rs @@ -5,17 +5,17 @@ use ufmt_write::uWrite; use crate::{ 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 +23,32 @@ 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 { + 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, 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 +170,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 +213,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 +249,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,