i2c_character_display/
lib.rs

1//! This Rust `embedded-hal`-based library is a simple way to control a character display that has either a [HD44780](https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller)
2//! or [AiP31068](https://support.newhavendisplay.com/hc/en-us/articles/4414486901783--AiP31068) controller with an I2C interface
3//! in an embedded, `no_std` environment. A number of I2C interfaces are supported:
4//!
5//! - **[Adafruit I2C/SPI LCD Backpack](https://www.adafruit.com/product/292)** - This is a simple I2C adapter for HD44780 character displays that can be used with either I2C
6//!   or SPI. It is available from Adafruit and other retailers. This library only supports the I2C interface of this adapter.
7//! - **PCF8574-based I2C adapter** - These adapters are ubiquitous on eBay and AliExpress and have no clear branding. Furthermore, some HD44780-based character
8//!   display makers, such as [Surenoo](https://www.surenoo.com), integrate a PCF8574T directly on the display board enabling I2C connections without a seperate adapter.
9//!   The most common pin wiring uses 4 data pins and 3 control pins. Most models have the display's 4-bit mode data pins connected to P4-P7 of the PCF8574.
10//!   This library supports that configuration, though it would be straightforward to add support for other pin configurations.
11//! - **AiP31068** - This is a character display controller with a built-in I2C support. The command set is similar to the HD44780, but the controller
12//!   operates in 8-bit mode and is initialized differently.  Examples of displays that use this controller include the [Surenoo SLC1602O](https://www.surenoo.com/products/8109143).
13//! - **ST7032i** - This is an I2C character display controller used with LCD displays. It is similar to the HD44780, but with some differences in the command set.
14//!   Examples of displays that use this controller include the [Surenoo SLC1602K3](https://www.surenoo.com/collections/81733622/products/8131705).
15//!
16//! Key features include:
17//! - Convenient high-level API for controlling many types of character display
18//! - Support for custom characters
19//! - Backlight control on hardwarware that supports it
20//! - `core::fmt::Write` implementation for easy use with the `write!` macro
21//! - Compatible with the `embedded-hal` traits v1.0 and later
22//! - Support for character displays that uses multiple HD44780 drivers, such as the 40x4 display
23//! - Optional support for the `defmt` and `ufmt` logging frameworks
24//! - Optional support for reading from the display on controllers and adapters that support it
25//!
26//! ## Usage
27//! Add this to your `Cargo.toml`:
28//! ```toml
29//! [dependencies]
30//! i2c-character-display = { version = "0.4", features = ["defmt"] }
31//! ```
32//! The `features = ["defmt"]` line is optional and enables the `defmt` feature, which allows the library's errors to be used with the `defmt` logging
33//! framework. Another optional feature is `features = ["ufmt"]`, which enables the `ufmt` feature, allowing the `uwriteln!` and `uwrite!` macros to be used.
34//!
35//! Then select the appropriate adapter for your display:
36//! ```rust
37//! use i2c_character_display::{AdafruitLCDBackpack, CharacterDisplayPCF8574T, LcdDisplayType};
38//! use embedded_hal::delay::DelayMs;
39//! use embedded_hal::i2c::I2c;
40//!
41//! // board setup
42//! let i2c = ...; // I2C peripheral
43//! let delay = ...; // DelayMs implementation
44//!
45//! // It is recommended that the `i2c` object be wrapped in an `embedded_hal_bus::i2c::CriticalSectionDevice` so that it can be shared between
46//! // multiple peripherals.
47//!
48//! // Adafruit backpack for a single HD44780 controller
49//! let mut lcd = AdafruitLCDBackpack::new(i2c, LcdDisplayType::Lcd16x2, delay);
50//! // PCF8574T adapter for a single HD44780 controller
51//! let mut lcd = CharacterDisplayPCF8574T::new(i2c, LcdDisplayType::Lcd16x2, delay);
52//! // Character display with dual HD44780 controllers using a single PCF8574T I2C adapter
53//! let mut lcd = CharacterDisplayDualHD44780::new(i2c, LcdDisplayType::Lcd40x4, delay);
54//! // Character display with the AiP31068 controller
55//! let mut lcd = CharacterDisplayAIP31068::new(i2c, LcdDisplayType::Lcd16x2, delay);
56//! // Character display with the ST7032i controller
57//! let mut lcd = CharacterDisplayST7032i::new(i2c, LcdDisplayType::Lcd16x2, delay);
58//! ```
59//! When creating the display object, you can choose the display type from the `LcdDisplayType` enum. The display type should match the physical
60//! display you are using. This display type configures the number of rows and columns, and the internal row offsets for the display.
61//!
62//! Initialize the display:
63//! ```rust
64//! if let Err(e) = lcd.init() {
65//!    panic!("Error initializing LCD: {}", e);
66//! }
67//! ```
68//! Use the display:
69//! ```rust
70//! // set up the display
71//! lcd.backlight(true)?.clear()?.home()?;
72//! // print a message
73//! lcd.print("Hello, world!")?;
74//! // can also use the `core::fmt::write!` macro
75//! use core::fmt::Write;
76//!
77//! write!(lcd, "Hello, world!")?;
78//! ```
79//! The optional `ufmt` feature enables the `ufmt` crate, which allows the `uwriteln!` and `uwrite!` macros to be used with the display:
80//! ```rust
81//! use ufmt::uwriteln;
82//!
83//! uwriteln!(lcd, "Hello, world!")?;
84//! ```
85//!
86//! The various methods for controlling the LCD are also available. Each returns a `Result` that wraps the display object in `Ok()`, allowing for easy chaining
87//! of commands. For example:
88//! ```rust
89//! lcd.backlight(true)?.clear()?.home()?.print("Hello, world!")?;
90//! ```
91//! ### Reading from the display
92//! Some I2C adapters support reading data from the HD44780 controller. For the I2C adapters that support it, the `read_device_data` method can be used to read
93//! from either the CGRAM or DDRAM at the current cursor position. The `read_address_counter` method can be used to read the address counter from the HD44780 controller.
94//! In both cases, the specific meaning of the data depends on the prior commands sent to the display. See the HD44780 datasheet for more information.
95//!
96//! ### Backlight control
97//! All HD44780 controllers support backlight control. The `backlight` method can be used to turn the backlight on or off. The AiP31068 controller does not support
98//! backlight control, and calling the `backlight` method with a AiP31068 controller will return an error.
99//!
100//! ### Multiple HD44780 controller character displays
101//! Some character displays, such as the 40x4 display, use two HD44780 controllers to drive the display. This library supports these displays by
102//! treating them as one logical display with multiple HD44780 controllers. The `CharacterDisplayDualHD44780` type is used to control these displays.
103//! Use the various methods to control the display as you would with a single HD44780 controller display. The `set_cursor` method sets the active HD44780
104//! controller device based on the row number you select.
105//!
106#![no_std]
107#![allow(dead_code, non_camel_case_types, non_upper_case_globals)]
108use core::{fmt::Display, marker::PhantomData};
109
110use embedded_hal::{delay::DelayNs, i2c};
111
112/// HD44780 based character display using a generic PCF8574T I2C adapter.
113pub type CharacterDisplayPCF8574T<I2C, DELAY> = BaseCharacterDisplay<
114    I2C,
115    DELAY,
116    crate::driver::hd44780::adapter::generic_pcf8574t::GenericPCF8574TAdapter<I2C, DELAY>,
117    crate::driver::hd44780::GenericHD44780PCF8574T<I2C, DELAY>,
118>;
119
120/// HD44780 based character display using an Adafruit I2C/SPI LCD backpack adapter.
121pub type AdafruitLCDBackpack<I2C, DELAY> = BaseCharacterDisplay<
122    I2C,
123    DELAY,
124    crate::driver::hd44780::adapter::adafruit_lcd_backpack::AdafruitLCDBackpackAdapter<I2C, DELAY>,
125    crate::driver::hd44780::AdafruitLCDBackpack<I2C, DELAY>,
126>;
127
128/// Character display using dual HD44780 I2C drivers connected using a generic PCF8574T I2C adapter with a pinout that
129/// has two enable pins, one for each HD44780 driver. Typically used for 40x4 character displays.
130pub type CharacterDisplayDualHD44780<I2C, DELAY> = BaseCharacterDisplay<
131    I2C,
132    DELAY,
133    crate::driver::hd44780::adapter::dual_controller_pcf8574t::DualHD44780_PCF8574TAdapter<
134        I2C,
135        DELAY,
136    >,
137    crate::driver::hd44780::DualHD44780PCF8574T<I2C, DELAY>,
138>;
139
140/// Character display using the AIP31068 controller with built-in I2C adapter.
141pub type CharacterDisplayAIP31068<I2C, DELAY> = BaseCharacterDisplay<
142    I2C,
143    DELAY,
144    crate::driver::aip31068::AIP31068<I2C, DELAY>,
145    crate::driver::standard::StandardCharacterDisplayHandler,
146>;
147
148/// Character display using the ST7032i controller with built-in I2C adapter.
149pub type CharacterDisplayST7032i<I2C, DELAY> = BaseCharacterDisplay<
150    I2C,
151    DELAY,
152    crate::driver::st7032i::ST7032i<I2C, DELAY>,
153    crate::driver::st7032i::ST7032iDisplayActions<I2C, DELAY>,
154>;
155
156// commands
157const LCD_CMD_CLEARDISPLAY: u8 = 0x01; //  Clear display, set cursor position to zero
158const LCD_CMD_RETURNHOME: u8 = 0x02; //  Set cursor position to zero
159const LCD_CMD_ENTRYMODESET: u8 = 0x04; //  Sets the entry mode
160const LCD_CMD_DISPLAYCONTROL: u8 = 0x08; //  Controls the display; does stuff like turning it off and on
161const LCD_CMD_CURSORSHIFT: u8 = 0x10; //  Lets you move the cursor
162const LCD_CMD_FUNCTIONSET: u8 = 0x20; //  Used to send the function to set to the display
163const LCD_CMD_SETCGRAMADDR: u8 = 0x40; //  Used to set the CGRAM (character generator RAM) with characters
164const LCD_CMD_SETDDRAMADDR: u8 = 0x80; //  Used to set the DDRAM (Display Data RAM)
165
166// flags for display entry mode
167const LCD_FLAG_ENTRYRIGHT: u8 = 0x00; //  Used to set text to flow from right to left
168const LCD_FLAG_ENTRYLEFT: u8 = 0x02; //  Uset to set text to flow from left to right
169const LCD_FLAG_ENTRYSHIFTINCREMENT: u8 = 0x01; //  Used to 'right justify' text from the cursor
170const LCD_FLAG_ENTRYSHIFTDECREMENT: u8 = 0x00; //  Used to 'left justify' text from the cursor
171
172// flags for display on/off control
173const LCD_FLAG_DISPLAYON: u8 = 0x04; //  Turns the display on
174const LCD_FLAG_DISPLAYOFF: u8 = 0x00; //  Turns the display off
175const LCD_FLAG_CURSORON: u8 = 0x02; //  Turns the cursor on
176const LCD_FLAG_CURSOROFF: u8 = 0x00; //  Turns the cursor off
177const LCD_FLAG_BLINKON: u8 = 0x01; //  Turns on the blinking cursor
178const LCD_FLAG_BLINKOFF: u8 = 0x00; //  Turns off the blinking cursor
179
180// flags for display/cursor shift
181const LCD_FLAG_DISPLAYMOVE: u8 = 0x08; //  Flag for moving the display
182const LCD_FLAG_CURSORMOVE: u8 = 0x00; //  Flag for moving the cursor
183const LCD_FLAG_MOVERIGHT: u8 = 0x04; //  Flag for moving right
184const LCD_FLAG_MOVELEFT: u8 = 0x00; //  Flag for moving left
185
186// flags for function set
187const LCD_FLAG_8BITMODE: u8 = 0x10; //  LCD 8 bit mode
188const LCD_FLAG_4BITMODE: u8 = 0x00; //  LCD 4 bit mode
189const LCD_FLAG_2LINE: u8 = 0x08; //  LCD 2 line mode
190const LCD_FLAG_1LINE: u8 = 0x00; //  LCD 1 line mode
191const LCD_FLAG_5x10_DOTS: u8 = 0x04; //  10 pixel high font mode
192const LCD_FLAG_5x8_DOTS: u8 = 0x00; //  8 pixel high font mode
193
194mod driver;
195
196const MAX_DEVICE_COUNT: usize = 2;
197
198#[derive(Debug, PartialEq, Copy, Clone)]
199/// Errors that can occur when using the LCD backpack
200#[non_exhaustive]
201pub enum CharacterDisplayError<I2C>
202where
203    I2C: i2c::I2c,
204{
205    /// I2C error returned from the underlying I2C implementation
206    I2cError(I2C::Error),
207    /// Row is out of range
208    RowOutOfRange,
209    /// Column is out of range
210    ColumnOutOfRange,
211    /// Formatting error
212    FormattingError(core::fmt::Error),
213    /// The discplay type is not compatible with specific adapter.
214    UnsupportedDisplayType,
215    /// The requested operation is not supported by the adapter or controller
216    #[deprecated(
217        since = "0.5.0",
218        note = "Use `UnsupportedOperationWithMessage` instead"
219    )]
220    UnsupportedOperation,
221    /// The requested operation is not supported by the adapter or controller.
222    /// The string provides the name of the unsupported operation.
223    UnsupportedOperationWithMessage(&'static str),
224    /// Read operation is not supported by the adapter
225    ReadNotSupported,
226    /// Internal error - bad device ID
227    BadDeviceId,
228    /// Internal error - buffer too small
229    BufferTooSmall,
230}
231
232impl<I2C> From<core::fmt::Error> for CharacterDisplayError<I2C>
233where
234    I2C: i2c::I2c,
235{
236    fn from(err: core::fmt::Error) -> Self {
237        CharacterDisplayError::FormattingError(err)
238    }
239}
240
241impl<I2C> From<&CharacterDisplayError<I2C>> for &'static str
242where
243    I2C: i2c::I2c,
244{
245    fn from(err: &CharacterDisplayError<I2C>) -> Self {
246        match err {
247            CharacterDisplayError::I2cError(_) => "I2C error",
248            CharacterDisplayError::RowOutOfRange => "Row out of range",
249            CharacterDisplayError::ColumnOutOfRange => "Column out of range",
250            CharacterDisplayError::FormattingError(_) => "Formatting error",
251            CharacterDisplayError::UnsupportedDisplayType => "Unsupported display type",
252            #[allow(deprecated)]
253            CharacterDisplayError::UnsupportedOperation => "Unsupported operation",
254            CharacterDisplayError::UnsupportedOperationWithMessage(_) => "Unsupported operation",
255            CharacterDisplayError::ReadNotSupported => "Read operation not supported",
256            CharacterDisplayError::BadDeviceId => "Bad device ID",
257            CharacterDisplayError::BufferTooSmall => "Buffer too small",
258        }
259    }
260}
261
262#[cfg(feature = "defmt")]
263impl<I2C> defmt::Format for CharacterDisplayError<I2C>
264where
265    I2C: i2c::I2c,
266{
267    fn format(&self, fmt: defmt::Formatter) {
268        let msg: &'static str = From::from(self);
269        defmt::write!(fmt, "{}", msg);
270    }
271}
272
273#[cfg(feature = "ufmt")]
274impl<I2C> ufmt::uDisplay for CharacterDisplayError<I2C>
275where
276    I2C: i2c::I2c,
277{
278    fn fmt<W>(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
279    where
280        W: ufmt::uWrite + ?Sized,
281    {
282        let msg: &'static str = From::from(self);
283        ufmt::uwrite!(w, "{}", msg)
284    }
285}
286
287impl<I2C> Display for CharacterDisplayError<I2C>
288where
289    I2C: i2c::I2c,
290{
291    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
292        let msg: &'static str = From::from(self);
293        write!(f, "{}", msg)
294    }
295}
296
297#[derive(Debug, PartialEq, Clone, Copy)]
298/// The type of LCD display. This is used to determine the number of rows and columns, and the row offsets.
299pub enum LcdDisplayType {
300    /// 20x4 display
301    Lcd20x4,
302    /// 20x2 display
303    Lcd20x2,
304    /// 16x2 display
305    Lcd16x2,
306    /// 16x4 display
307    Lcd16x4,
308    /// 8x2 display
309    Lcd8x2,
310    /// 40x2 display
311    Lcd40x2,
312    /// 40x4 display. Should be used with a DualHD44780 adapter.
313    Lcd40x4,
314}
315
316impl From<&LcdDisplayType> for &'static str {
317    fn from(display_type: &LcdDisplayType) -> Self {
318        match display_type {
319            LcdDisplayType::Lcd20x4 => "20x4",
320            LcdDisplayType::Lcd20x2 => "20x2",
321            LcdDisplayType::Lcd16x2 => "16x2",
322            LcdDisplayType::Lcd16x4 => "16x4",
323            LcdDisplayType::Lcd8x2 => "8x2",
324            LcdDisplayType::Lcd40x2 => "40x2",
325            LcdDisplayType::Lcd40x4 => "40x4",
326        }
327    }
328}
329
330#[cfg(feature = "defmt")]
331impl defmt::Format for LcdDisplayType {
332    fn format(&self, fmt: defmt::Formatter) {
333        let msg: &'static str = From::from(self);
334        defmt::write!(fmt, "{}", msg);
335    }
336}
337
338#[cfg(feature = "ufmt")]
339impl ufmt::uDisplay for LcdDisplayType {
340    fn fmt<W>(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
341    where
342        W: ufmt::uWrite + ?Sized,
343    {
344        let msg: &'static str = From::from(self);
345        ufmt::uwrite!(w, "{}", msg)
346    }
347}
348
349impl Display for LcdDisplayType {
350    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351        let msg: &'static str = From::from(self);
352        write!(f, "{}", msg)
353    }
354}
355
356impl LcdDisplayType {
357    /// Get the number of rows for the display type
358    const fn rows(&self) -> u8 {
359        match self {
360            LcdDisplayType::Lcd20x4 => 4,
361            LcdDisplayType::Lcd20x2 => 2,
362            LcdDisplayType::Lcd16x2 => 2,
363            LcdDisplayType::Lcd16x4 => 4,
364            LcdDisplayType::Lcd8x2 => 2,
365            LcdDisplayType::Lcd40x2 => 2,
366            LcdDisplayType::Lcd40x4 => 4,
367        }
368    }
369
370    /// Get the number of columns for the display type
371    const fn cols(&self) -> u8 {
372        match self {
373            LcdDisplayType::Lcd20x4 => 20,
374            LcdDisplayType::Lcd20x2 => 20,
375            LcdDisplayType::Lcd16x2 => 16,
376            LcdDisplayType::Lcd16x4 => 16,
377            LcdDisplayType::Lcd8x2 => 8,
378            LcdDisplayType::Lcd40x2 => 40,
379            LcdDisplayType::Lcd40x4 => 40,
380        }
381    }
382
383    /// Get the row offsets for the display type. This always returns an array of length 4.
384    /// For displays with less than 4 rows, the unused rows will be set to offsets offscreen.
385    const fn row_offsets(&self) -> [u8; 4] {
386        match self {
387            LcdDisplayType::Lcd20x4 => [0x00, 0x40, 0x14, 0x54],
388            LcdDisplayType::Lcd20x2 => [0x00, 0x40, 0x00, 0x40],
389            LcdDisplayType::Lcd16x2 => [0x00, 0x40, 0x10, 0x50],
390            LcdDisplayType::Lcd16x4 => [0x00, 0x40, 0x10, 0x50],
391            LcdDisplayType::Lcd8x2 => [0x00, 0x40, 0x00, 0x40],
392            LcdDisplayType::Lcd40x2 => [0x00, 0x40, 0x00, 0x40],
393            LcdDisplayType::Lcd40x4 => [0x00, 0x40, 0x00, 0x40],
394        }
395    }
396}
397
398pub struct DeviceSetupConfig<I2C, DELAY>
399where
400    I2C: i2c::I2c,
401    DELAY: DelayNs,
402{
403    lcd_type: LcdDisplayType,
404    i2c: I2C,
405    address: u8,
406    delay: DELAY,
407}
408
409pub struct BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
410where
411    I2C: i2c::I2c,
412    DELAY: DelayNs,
413    DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
414    ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
415{
416    device: DEVICE,
417    actions: ACTIONS,
418    _phantom_i2c: PhantomData<I2C>,
419    _phantom_delay: PhantomData<DELAY>,
420}
421
422impl<I2C, DELAY, DEVICE, ACTIONS> BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
423where
424    I2C: i2c::I2c,
425    DELAY: DelayNs,
426    DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
427    ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
428{
429    /// Create a new character display object with the default I2C address for the adapter.
430    pub fn new(i2c: I2C, lcd_type: LcdDisplayType, delay: DELAY) -> Self {
431        Self::new_with_address(i2c, DEVICE::default_i2c_address(), lcd_type, delay)
432    }
433
434    /// Create a new character display object with a specific I2C address for the adapter.
435    pub fn new_with_address(i2c: I2C, address: u8, lcd_type: LcdDisplayType, delay: DELAY) -> Self {
436        let config = DeviceSetupConfig {
437            lcd_type,
438            i2c,
439            address,
440            delay,
441        };
442        Self {
443            device: DEVICE::new(config),
444            actions: ACTIONS::default(),
445            _phantom_i2c: PhantomData,
446            _phantom_delay: PhantomData,
447        }
448    }
449
450    /// Initialize the display. This must be called before using the display.
451    pub fn init(&mut self) -> Result<(), CharacterDisplayError<I2C>> {
452        let (display_function, display_control, display_mode) = self.device.init()?;
453        self.actions
454            .init_display_state(display_function, display_control, display_mode)?;
455        Ok(())
456    }
457
458    /// returns a reference to the I2C peripheral. mostly needed for testing
459    fn i2c(&mut self) -> &mut I2C {
460        self.device.i2c()
461    }
462
463    /// returns the `LcdDisplayType` used to create the display
464    pub fn display_type(&self) -> LcdDisplayType {
465        self.device.lcd_type()
466    }
467
468    /// Supports the ability to read from the display.
469    pub fn supports_reads() -> bool {
470        DEVICE::supports_reads()
471    }
472
473    // /// Writes a data byte to the display. Normally users do not need to call this directly.
474    // /// For multiple devices, this writes the data to the currently active contoller device.
475    // fn write_data(&mut self, data: u8) -> Result<&mut Self, CharacterDisplayError<I2C>> {
476    //     self.device.write_data(&mut self.config, data)?;
477    //     Ok(self)
478    // }
479
480    /// Reads into the buffer data from the display device either the CGRAM or DDRAM at the current cursor position.
481    /// For multiple devices, this reads from the currently active device as set by the cursor position.
482    /// The amount of data read is determined by the length of the buffer.
483    /// Not all adapters support reads from the device. This will return an error if the adapter
484    /// does not support reads.
485    pub fn read_device_data(
486        &mut self,
487        buffer: &mut [u8],
488    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
489        self.actions.read_device_data(&mut self.device, buffer)?;
490
491        Ok(self)
492    }
493
494    /// Reads the address counter from the display device. The ready bit is masked off.
495    /// Not all adapters support reads from the device. This will return an error if the adapter
496    /// does not support reads.
497    pub fn read_address_counter(&mut self) -> Result<u8, CharacterDisplayError<I2C>> {
498        self.actions.read_address_counter(&mut self.device)
499    }
500
501    //--------------------------------------------------------------------------------------------------
502    // high level commands, for the user!
503    //--------------------------------------------------------------------------------------------------
504
505    /// Clear the display
506    pub fn clear(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
507        self.actions.clear(&mut self.device)?;
508        Ok(self)
509    }
510
511    /// Set the cursor to the home position.
512    pub fn home(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
513        self.actions.home(&mut self.device)?;
514        Ok(self)
515    }
516
517    /// Set the cursor position at specified column and row. Columns and rows are zero-indexed.
518    pub fn set_cursor(
519        &mut self,
520        col: u8,
521        row: u8,
522    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
523        self.actions.set_cursor(&mut self.device, col, row)?;
524        Ok(self)
525    }
526
527    /// Set the cursor visibility.
528    pub fn show_cursor(
529        &mut self,
530        show_cursor: bool,
531    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
532        self.actions.show_cursor(&mut self.device, show_cursor)?;
533        Ok(self)
534    }
535
536    /// Set the cursor blinking.
537    pub fn blink_cursor(
538        &mut self,
539        blink_cursor: bool,
540    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
541        self.actions.blink_cursor(&mut self.device, blink_cursor)?;
542        Ok(self)
543    }
544
545    /// Set the display visibility.
546    pub fn show_display(
547        &mut self,
548        show_display: bool,
549    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
550        self.actions.show_display(&mut self.device, show_display)?;
551        Ok(self)
552    }
553
554    /// Scroll the display to the left.
555    pub fn scroll_display_left(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
556        self.actions.scroll_left(&mut self.device)?;
557        Ok(self)
558    }
559
560    /// Scroll the display to the right.
561    pub fn scroll_display_right(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
562        self.actions.scroll_right(&mut self.device)?;
563        Ok(self)
564    }
565
566    /// Set the text flow direction to left to right.
567    pub fn left_to_right(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
568        self.actions.left_to_right(&mut self.device)?;
569        Ok(self)
570    }
571
572    /// Set the text flow direction to right to left.
573    pub fn right_to_left(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
574        self.actions.right_to_left(&mut self.device)?;
575        Ok(self)
576    }
577
578    /// Set the auto scroll mode.
579    pub fn autoscroll(
580        &mut self,
581        autoscroll: bool,
582    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
583        self.actions.autoscroll(&mut self.device, autoscroll)?;
584        Ok(self)
585    }
586
587    /// Create a new custom character.
588    pub fn create_char(
589        &mut self,
590        location: u8,
591        charmap: [u8; 8],
592    ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
593        self.actions
594            .create_char(&mut self.device, location, charmap)?;
595        Ok(self)
596    }
597
598    /// Prints a string to the LCD at the current cursor position of the active device.
599    pub fn print(&mut self, text: &str) -> Result<&mut Self, CharacterDisplayError<I2C>> {
600        self.actions.print(&mut self.device, text)?;
601        Ok(self)
602    }
603
604    /// Turn the backlight on or off.
605    /// Note that the AIP31068 controller does not support backlight control.
606    pub fn backlight(&mut self, on: bool) -> Result<&mut Self, CharacterDisplayError<I2C>> {
607        self.actions.backlight(&mut self.device, on)?;
608        Ok(self)
609    }
610
611    /// Set the contrast level of the display. This is only supported by the ST7032i controller.
612    pub fn set_contrast(&mut self, contrast: u8) -> Result<&mut Self, CharacterDisplayError<I2C>> {
613        self.actions.set_contrast(&mut self.device, contrast)?;
614        Ok(self)
615    }
616}
617
618/// Implement the `core::fmt::Write` trait, allowing it to be used with the `write!` macro.
619/// This is a convenience method for printing to the display. For multi-device, this will print to the active device as set by
620/// `set_cursor`.
621impl<I2C, DELAY, DEVICE, ACTIONS> core::fmt::Write
622    for BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
623where
624    I2C: i2c::I2c,
625    DELAY: DelayNs,
626    DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
627    ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
628{
629    fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
630        if let Err(_e) = self.print(s) {
631            return Err(core::fmt::Error);
632        }
633        Ok(())
634    }
635}
636
637#[cfg(feature = "ufmt")]
638/// Implement the `ufmt::uWrite` trait, allowing it to be used with the `uwriteln!` and `uwrite!` macros.
639/// This is a convenience method for printing to the display. For multi-device, this will print to the active device as set by
640/// `set_cursor`.
641impl<I2C, DELAY, DEVICE, ACTIONS> ufmt::uWrite for BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
642where
643    I2C: i2c::I2c,
644    DELAY: DelayNs,
645    DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
646    ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
647{
648    fn write_str(&mut self, s: &str) -> Result<(), CharacterDisplayError<I2C>> {
649        if let Err(e) = self.print(s) {
650            return Err(e);
651        }
652        Ok(())
653    }
654
655    type Error = CharacterDisplayError<I2C>;
656}
657
658#[cfg(test)]
659mod lib_tests {
660    extern crate std;
661    use super::*;
662    use embedded_hal_mock::eh1::{
663        delay::NoopDelay,
664        i2c::{Mock as I2cMock, Transaction as I2cTransaction},
665    };
666
667    #[test]
668    fn test_character_display_pcf8574t_init() {
669        let i2c_address = 0x27_u8;
670        let expected_i2c_transactions = std::vec![
671            // the PCF8574T has no adapter init sequence, so nothing to prepend
672            // the LCD init sequence
673            // write low nibble of 0x03 3 times
674            I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
675            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
676            I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
677            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
678            I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
679            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
680            // write high nibble of 0x02 one time
681            I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
682            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
683            // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]),    // backlight on
684            // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
685            // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
686            I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
687            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
688            I2cTransaction::write(i2c_address, std::vec![0b1000_0100]), // low nibble, rw=0, enable=1
689            I2cTransaction::write(i2c_address, std::vec![0b1000_0000]), // low nibble, rw=0, enable=0
690            // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
691            // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
692            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
693            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
694            I2cTransaction::write(i2c_address, std::vec![0b1100_0100]), // low nibble, rw=0, enable=1
695            I2cTransaction::write(i2c_address, std::vec![0b1100_0000]), // low nibble, rw=0, enable=0
696            // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
697            // = 0x04 | 0x02 | 0x00 = 0x06
698            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
699            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
700            I2cTransaction::write(i2c_address, std::vec![0b0110_0100]), // low nibble, rw=0, enable=1
701            I2cTransaction::write(i2c_address, std::vec![0b0110_0000]), // low nibble, rw=0, enable=0
702            // LCD_CMD_CLEARDISPLAY
703            // = 0x01
704            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
705            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
706            I2cTransaction::write(i2c_address, std::vec![0b0001_0100]), // low nibble, rw=0, enable=1
707            I2cTransaction::write(i2c_address, std::vec![0b0001_0000]), // low nibble, rw=0, enable=0
708            // LCD_CMD_RETURNHOME
709            // = 0x02
710            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
711            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
712            I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // low nibble, rw=0, enable=1
713            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // low nibble, rw=0, enable=0
714            // Set Backlight
715            I2cTransaction::write(i2c_address, std::vec![0b0010_1000]), // backlight on
716        ];
717
718        let i2c = I2cMock::new(&expected_i2c_transactions);
719        let mut lcd = CharacterDisplayPCF8574T::new(i2c, LcdDisplayType::Lcd16x2, NoopDelay::new());
720        let result = lcd.init();
721        assert!(result.is_ok());
722
723        // finish the i2c mock
724        lcd.i2c().done();
725    }
726
727    #[test]
728    fn test_adafruit_lcd_backpack_init() {
729        let i2c_address = 0x20_u8;
730        let expected_i2c_transactions = std::vec![
731            // the Adafruit Backpack need to init the adapter IC first
732            // write 0x00 to the MCP23008 IODIR register to set all pins as outputs
733            I2cTransaction::write(i2c_address, std::vec![0x00, 0x00]),
734            // the LCD init sequence
735            // write low nibble of 0x03 3 times
736            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
737            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
738            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
739            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
740            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
741            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
742            // write high nibble of 0x02 one time
743            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // high nibble, rw=0, enable=1
744            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // high nibble, rw=0, enable=0
745            // turn on the backlight
746            // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]),    // backlight on
747            // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
748            // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
749            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // high nibble, rw=0, enable=1
750            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // high nibble, rw=0, enable=0
751            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1000_100]), // low nibble, rw=0, enable=1
752            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1000_000]), // low nibble, rw=0, enable=0
753            // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
754            // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
755            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
756            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
757            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1100_100]), // low nibble, rw=0, enable=1
758            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1100_000]), // low nibble, rw=0, enable=0
759            // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
760            // = 0x04 | 0x02 | 0x00 = 0x06
761            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
762            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
763            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0110_100]), // low nibble, rw=0, enable=1
764            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0110_000]), // low nibble, rw=0, enable=0
765            // LCD_CMD_CLEARDISPLAY
766            // = 0x01
767            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
768            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
769            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0001_100]), // low nibble, rw=0, enable=1
770            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0001_000]), // low nibble, rw=0, enable=0
771            // LCD_CMD_RETURNHOME
772            // = 0x02
773            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
774            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
775            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // low nibble, rw=0, enable=1
776            I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // low nibble, rw=0, enable=0
777            // Set Backlight
778            I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0010_000]), // backlight on
779        ];
780
781        let i2c = I2cMock::new(&expected_i2c_transactions);
782        let mut lcd = AdafruitLCDBackpack::new(i2c, LcdDisplayType::Lcd16x2, NoopDelay::new());
783
784        let result = lcd.init();
785        assert!(result.is_ok());
786        assert!(lcd.display_type() == LcdDisplayType::Lcd16x2);
787
788        // finish the i2c mock
789        lcd.i2c().done();
790    }
791
792    #[test]
793    fn test_character_display_dual_hd44780_init() {
794        let i2c_address = 0x27_u8;
795        let expected_i2c_transactions = std::vec![
796            // the PCF8574T has no adapter init sequence, so nothing to prepend
797            // *** Device 0 ***
798            // the LCD init sequence for device 0
799            // write low nibble of 0x03 3 times
800            I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
801            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
802            I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
803            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
804            I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
805            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
806            // write high nibble of 0x02 one time
807            I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
808            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
809            // turn on the backlight
810            // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]),    // backlight on
811            // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
812            // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
813            I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
814            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
815            I2cTransaction::write(i2c_address, std::vec![0b1000_0100]), // low nibble, rw=0, enable=1
816            I2cTransaction::write(i2c_address, std::vec![0b1000_0000]), // low nibble, rw=0, enable=0
817            // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
818            // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
819            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
820            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
821            I2cTransaction::write(i2c_address, std::vec![0b1100_0100]), // low nibble, rw=0, enable=1
822            I2cTransaction::write(i2c_address, std::vec![0b1100_0000]), // low nibble, rw=0, enable=0
823            // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
824            // = 0x04 | 0x02 | 0x00 = 0x06
825            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
826            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
827            I2cTransaction::write(i2c_address, std::vec![0b0110_0100]), // low nibble, rw=0, enable=1
828            I2cTransaction::write(i2c_address, std::vec![0b0110_0000]), // low nibble, rw=0, enable=0
829            // LCD_CMD_CLEARDISPLAY
830            // = 0x01
831            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
832            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
833            I2cTransaction::write(i2c_address, std::vec![0b0001_0100]), // low nibble, rw=0, enable=1
834            I2cTransaction::write(i2c_address, std::vec![0b0001_0000]), // low nibble, rw=0, enable=0
835            // LCD_CMD_RETURNHOME
836            // = 0x02
837            I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
838            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
839            I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // low nibble, rw=0, enable=1
840            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // low nibble, rw=0, enable=0
841            // *** Device 1 ***
842            // the LCD init sequence for device 0
843            // write low nibble of 0x03 3 times
844            I2cTransaction::write(i2c_address, std::vec![0b0011_0010]), // low nibble, rw=0, enable=1
845            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
846            I2cTransaction::write(i2c_address, std::vec![0b0011_0010]), // low nibble, rw=0, enable=1
847            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
848            I2cTransaction::write(i2c_address, std::vec![0b0011_0010]), // low nibble, rw=0, enable=1
849            I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
850            // write high nibble of 0x02 one time
851            I2cTransaction::write(i2c_address, std::vec![0b0010_0010]), // high nibble, rw=0, enable=1
852            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
853            // turn on the backlight
854            // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]),    // backlight on
855            // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
856            // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
857            I2cTransaction::write(i2c_address, std::vec![0b0010_0010]), // high nibble, rw=0, enable=1
858            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
859            I2cTransaction::write(i2c_address, std::vec![0b1000_0010]), // low nibble, rw=0, enable=1
860            I2cTransaction::write(i2c_address, std::vec![0b1000_0000]), // low nibble, rw=0, enable=0
861            // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
862            // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
863            I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
864            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
865            I2cTransaction::write(i2c_address, std::vec![0b1100_0010]), // low nibble, rw=0, enable=1
866            I2cTransaction::write(i2c_address, std::vec![0b1100_0000]), // low nibble, rw=0, enable=0
867            // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
868            // = 0x04 | 0x02 | 0x00 = 0x06
869            I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
870            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
871            I2cTransaction::write(i2c_address, std::vec![0b0110_0010]), // low nibble, rw=0, enable=1
872            I2cTransaction::write(i2c_address, std::vec![0b0110_0000]), // low nibble, rw=0, enable=0
873            // LCD_CMD_CLEARDISPLAY
874            // = 0x01
875            I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
876            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
877            I2cTransaction::write(i2c_address, std::vec![0b0001_0010]), // low nibble, rw=0, enable=1
878            I2cTransaction::write(i2c_address, std::vec![0b0001_0000]), // low nibble, rw=0, enable=0
879            // LCD_CMD_RETURNHOME
880            // = 0x02
881            I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
882            I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
883            I2cTransaction::write(i2c_address, std::vec![0b0010_0010]), // low nibble, rw=0, enable=1
884            I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // low nibble, rw=0, enable=0
885            // Set Backlight
886            I2cTransaction::write(i2c_address, std::vec![0b0010_1000]), // backlight on
887        ];
888
889        let i2c = I2cMock::new(&expected_i2c_transactions);
890        let mut lcd =
891            CharacterDisplayDualHD44780::new(i2c, LcdDisplayType::Lcd40x4, NoopDelay::new());
892        let result = lcd.init();
893        assert!(result.is_ok());
894        assert!(lcd.display_type() == LcdDisplayType::Lcd40x4);
895
896        // finish the i2c mock
897        lcd.i2c().done();
898    }
899}