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}