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
200pub enum CharacterDisplayError<I2C>
201where
202 I2C: i2c::I2c,
203{
204 /// I2C error returned from the underlying I2C implementation
205 I2cError(I2C::Error),
206 /// Row is out of range
207 RowOutOfRange,
208 /// Column is out of range
209 ColumnOutOfRange,
210 /// Formatting error
211 FormattingError(core::fmt::Error),
212 /// The discplay type is not compatible with specific adapter.
213 UnsupportedDisplayType,
214 /// The requested operation is not supported by the adapter or controller
215 UnsupportedOperation,
216 /// Read operation is not supported by the adapter
217 ReadNotSupported,
218 /// Internal error - bad device ID
219 BadDeviceId,
220 /// Internal error - buffer too small
221 BufferTooSmall,
222}
223
224impl<I2C> From<core::fmt::Error> for CharacterDisplayError<I2C>
225where
226 I2C: i2c::I2c,
227{
228 fn from(err: core::fmt::Error) -> Self {
229 CharacterDisplayError::FormattingError(err)
230 }
231}
232
233impl<I2C> From<&CharacterDisplayError<I2C>> for &'static str
234where
235 I2C: i2c::I2c,
236{
237 fn from(err: &CharacterDisplayError<I2C>) -> Self {
238 match err {
239 CharacterDisplayError::I2cError(_) => "I2C error",
240 CharacterDisplayError::RowOutOfRange => "Row out of range",
241 CharacterDisplayError::ColumnOutOfRange => "Column out of range",
242 CharacterDisplayError::FormattingError(_) => "Formatting error",
243 CharacterDisplayError::UnsupportedDisplayType => "Unsupported display type",
244 CharacterDisplayError::UnsupportedOperation => "Unsupported operation",
245 CharacterDisplayError::ReadNotSupported => "Read operation not supported",
246 CharacterDisplayError::BadDeviceId => "Bad device ID",
247 CharacterDisplayError::BufferTooSmall => "Buffer too small",
248 }
249 }
250}
251
252#[cfg(feature = "defmt")]
253impl<I2C> defmt::Format for CharacterDisplayError<I2C>
254where
255 I2C: i2c::I2c,
256{
257 fn format(&self, fmt: defmt::Formatter) {
258 let msg: &'static str = From::from(self);
259 defmt::write!(fmt, "{}", msg);
260 }
261}
262
263#[cfg(feature = "ufmt")]
264impl<I2C> ufmt::uDisplay for CharacterDisplayError<I2C>
265where
266 I2C: i2c::I2c,
267{
268 fn fmt<W>(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
269 where
270 W: ufmt::uWrite + ?Sized,
271 {
272 let msg: &'static str = From::from(self);
273 ufmt::uwrite!(w, "{}", msg)
274 }
275}
276
277impl<I2C> Display for CharacterDisplayError<I2C>
278where
279 I2C: i2c::I2c,
280{
281 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282 let msg: &'static str = From::from(self);
283 write!(f, "{}", msg)
284 }
285}
286
287#[derive(Debug, PartialEq, Clone, Copy)]
288/// The type of LCD display. This is used to determine the number of rows and columns, and the row offsets.
289pub enum LcdDisplayType {
290 /// 20x4 display
291 Lcd20x4,
292 /// 20x2 display
293 Lcd20x2,
294 /// 16x2 display
295 Lcd16x2,
296 /// 16x4 display
297 Lcd16x4,
298 /// 8x2 display
299 Lcd8x2,
300 /// 40x2 display
301 Lcd40x2,
302 /// 40x4 display. Should be used with a DualHD44780 adapter.
303 Lcd40x4,
304}
305
306impl From<&LcdDisplayType> for &'static str {
307 fn from(display_type: &LcdDisplayType) -> Self {
308 match display_type {
309 LcdDisplayType::Lcd20x4 => "20x4",
310 LcdDisplayType::Lcd20x2 => "20x2",
311 LcdDisplayType::Lcd16x2 => "16x2",
312 LcdDisplayType::Lcd16x4 => "16x4",
313 LcdDisplayType::Lcd8x2 => "8x2",
314 LcdDisplayType::Lcd40x2 => "40x2",
315 LcdDisplayType::Lcd40x4 => "40x4",
316 }
317 }
318}
319
320#[cfg(feature = "defmt")]
321impl defmt::Format for LcdDisplayType {
322 fn format(&self, fmt: defmt::Formatter) {
323 let msg: &'static str = From::from(self);
324 defmt::write!(fmt, "{}", msg);
325 }
326}
327
328#[cfg(feature = "ufmt")]
329impl ufmt::uDisplay for LcdDisplayType {
330 fn fmt<W>(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
331 where
332 W: ufmt::uWrite + ?Sized,
333 {
334 let msg: &'static str = From::from(self);
335 ufmt::uwrite!(w, "{}", msg)
336 }
337}
338
339impl Display for LcdDisplayType {
340 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341 let msg: &'static str = From::from(self);
342 write!(f, "{}", msg)
343 }
344}
345
346impl LcdDisplayType {
347 /// Get the number of rows for the display type
348 const fn rows(&self) -> u8 {
349 match self {
350 LcdDisplayType::Lcd20x4 => 4,
351 LcdDisplayType::Lcd20x2 => 2,
352 LcdDisplayType::Lcd16x2 => 2,
353 LcdDisplayType::Lcd16x4 => 4,
354 LcdDisplayType::Lcd8x2 => 2,
355 LcdDisplayType::Lcd40x2 => 2,
356 LcdDisplayType::Lcd40x4 => 4,
357 }
358 }
359
360 /// Get the number of columns for the display type
361 const fn cols(&self) -> u8 {
362 match self {
363 LcdDisplayType::Lcd20x4 => 20,
364 LcdDisplayType::Lcd20x2 => 20,
365 LcdDisplayType::Lcd16x2 => 16,
366 LcdDisplayType::Lcd16x4 => 16,
367 LcdDisplayType::Lcd8x2 => 8,
368 LcdDisplayType::Lcd40x2 => 40,
369 LcdDisplayType::Lcd40x4 => 40,
370 }
371 }
372
373 /// Get the row offsets for the display type. This always returns an array of length 4.
374 /// For displays with less than 4 rows, the unused rows will be set to offsets offscreen.
375 const fn row_offsets(&self) -> [u8; 4] {
376 match self {
377 LcdDisplayType::Lcd20x4 => [0x00, 0x40, 0x14, 0x54],
378 LcdDisplayType::Lcd20x2 => [0x00, 0x40, 0x00, 0x40],
379 LcdDisplayType::Lcd16x2 => [0x00, 0x40, 0x10, 0x50],
380 LcdDisplayType::Lcd16x4 => [0x00, 0x40, 0x10, 0x50],
381 LcdDisplayType::Lcd8x2 => [0x00, 0x40, 0x00, 0x40],
382 LcdDisplayType::Lcd40x2 => [0x00, 0x40, 0x00, 0x40],
383 LcdDisplayType::Lcd40x4 => [0x00, 0x40, 0x00, 0x40],
384 }
385 }
386}
387
388pub struct DeviceSetupConfig<I2C, DELAY>
389where
390 I2C: i2c::I2c,
391 DELAY: DelayNs,
392{
393 lcd_type: LcdDisplayType,
394 i2c: I2C,
395 address: u8,
396 delay: DELAY,
397}
398
399pub struct BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
400where
401 I2C: i2c::I2c,
402 DELAY: DelayNs,
403 DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
404 ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
405{
406 device: DEVICE,
407 actions: ACTIONS,
408 _phantom_i2c: PhantomData<I2C>,
409 _phantom_delay: PhantomData<DELAY>,
410}
411
412impl<I2C, DELAY, DEVICE, ACTIONS> BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
413where
414 I2C: i2c::I2c,
415 DELAY: DelayNs,
416 DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
417 ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
418{
419 /// Create a new character display object with the default I2C address for the adapter.
420 pub fn new(i2c: I2C, lcd_type: LcdDisplayType, delay: DELAY) -> Self {
421 Self::new_with_address(i2c, DEVICE::default_i2c_address(), lcd_type, delay)
422 }
423
424 /// Create a new character display object with a specific I2C address for the adapter.
425 pub fn new_with_address(i2c: I2C, address: u8, lcd_type: LcdDisplayType, delay: DELAY) -> Self {
426 let config = DeviceSetupConfig {
427 lcd_type,
428 i2c,
429 address,
430 delay,
431 };
432 Self {
433 device: DEVICE::new(config),
434 actions: ACTIONS::default(),
435 _phantom_i2c: PhantomData,
436 _phantom_delay: PhantomData,
437 }
438 }
439
440 /// Initialize the display. This must be called before using the display.
441 pub fn init(&mut self) -> Result<(), CharacterDisplayError<I2C>> {
442 let (display_function, display_control, display_mode) = self.device.init()?;
443 self.actions
444 .init_display_state(display_function, display_control, display_mode)?;
445 Ok(())
446 }
447
448 /// returns a reference to the I2C peripheral. mostly needed for testing
449 fn i2c(&mut self) -> &mut I2C {
450 self.device.i2c()
451 }
452
453 /// returns the `LcdDisplayType` used to create the display
454 pub fn display_type(&self) -> LcdDisplayType {
455 self.device.lcd_type()
456 }
457
458 /// Supports the ability to read from the display.
459 pub fn supports_reads() -> bool {
460 DEVICE::supports_reads()
461 }
462
463 // /// Writes a data byte to the display. Normally users do not need to call this directly.
464 // /// For multiple devices, this writes the data to the currently active contoller device.
465 // fn write_data(&mut self, data: u8) -> Result<&mut Self, CharacterDisplayError<I2C>> {
466 // self.device.write_data(&mut self.config, data)?;
467 // Ok(self)
468 // }
469
470 /// Reads into the buffer data from the display device either the CGRAM or DDRAM at the current cursor position.
471 /// For multiple devices, this reads from the currently active device as set by the cursor position.
472 /// The amount of data read is determined by the length of the buffer.
473 /// Not all adapters support reads from the device. This will return an error if the adapter
474 /// does not support reads.
475 pub fn read_device_data(
476 &mut self,
477 buffer: &mut [u8],
478 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
479 self.actions.read_device_data(&mut self.device, buffer)?;
480
481 Ok(self)
482 }
483
484 /// Reads the address counter from the display device. The ready bit is masked off.
485 /// Not all adapters support reads from the device. This will return an error if the adapter
486 /// does not support reads.
487 pub fn read_address_counter(&mut self) -> Result<u8, CharacterDisplayError<I2C>> {
488 self.actions.read_address_counter(&mut self.device)
489 }
490
491 //--------------------------------------------------------------------------------------------------
492 // high level commands, for the user!
493 //--------------------------------------------------------------------------------------------------
494
495 /// Clear the display
496 pub fn clear(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
497 self.actions.clear(&mut self.device)?;
498 Ok(self)
499 }
500
501 /// Set the cursor to the home position.
502 pub fn home(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
503 self.actions.home(&mut self.device)?;
504 Ok(self)
505 }
506
507 /// Set the cursor position at specified column and row. Columns and rows are zero-indexed.
508 pub fn set_cursor(
509 &mut self,
510 col: u8,
511 row: u8,
512 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
513 self.actions.set_cursor(&mut self.device, col, row)?;
514 Ok(self)
515 }
516
517 /// Set the cursor visibility.
518 pub fn show_cursor(
519 &mut self,
520 show_cursor: bool,
521 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
522 self.actions.show_cursor(&mut self.device, show_cursor)?;
523 Ok(self)
524 }
525
526 /// Set the cursor blinking.
527 pub fn blink_cursor(
528 &mut self,
529 blink_cursor: bool,
530 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
531 self.actions.blink_cursor(&mut self.device, blink_cursor)?;
532 Ok(self)
533 }
534
535 /// Set the display visibility.
536 pub fn show_display(
537 &mut self,
538 show_display: bool,
539 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
540 self.actions.show_display(&mut self.device, show_display)?;
541 Ok(self)
542 }
543
544 /// Scroll the display to the left.
545 pub fn scroll_display_left(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
546 self.actions.scroll_left(&mut self.device)?;
547 Ok(self)
548 }
549
550 /// Scroll the display to the right.
551 pub fn scroll_display_right(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
552 self.actions.scroll_right(&mut self.device)?;
553 Ok(self)
554 }
555
556 /// Set the text flow direction to left to right.
557 pub fn left_to_right(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
558 self.actions.left_to_right(&mut self.device)?;
559 Ok(self)
560 }
561
562 /// Set the text flow direction to right to left.
563 pub fn right_to_left(&mut self) -> Result<&mut Self, CharacterDisplayError<I2C>> {
564 self.actions.right_to_left(&mut self.device)?;
565 Ok(self)
566 }
567
568 /// Set the auto scroll mode.
569 pub fn autoscroll(
570 &mut self,
571 autoscroll: bool,
572 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
573 self.actions.autoscroll(&mut self.device, autoscroll)?;
574 Ok(self)
575 }
576
577 /// Create a new custom character.
578 pub fn create_char(
579 &mut self,
580 location: u8,
581 charmap: [u8; 8],
582 ) -> Result<&mut Self, CharacterDisplayError<I2C>> {
583 self.actions
584 .create_char(&mut self.device, location, charmap)?;
585 Ok(self)
586 }
587
588 /// Prints a string to the LCD at the current cursor position of the active device.
589 pub fn print(&mut self, text: &str) -> Result<&mut Self, CharacterDisplayError<I2C>> {
590 self.actions.print(&mut self.device, text)?;
591 Ok(self)
592 }
593
594 /// Turn the backlight on or off.
595 /// Note that the AIP31068 controller does not support backlight control.
596 pub fn backlight(&mut self, on: bool) -> Result<&mut Self, CharacterDisplayError<I2C>> {
597 self.actions.backlight(&mut self.device, on)?;
598 Ok(self)
599 }
600
601 /// Set the contrast level of the display. This is only supported by the ST7032i controller.
602 pub fn set_contrast(&mut self, contrast: u8) -> Result<&mut Self, CharacterDisplayError<I2C>> {
603 self.actions.set_contrast(&mut self.device, contrast)?;
604 Ok(self)
605 }
606}
607
608/// Implement the `core::fmt::Write` trait, allowing it to be used with the `write!` macro.
609/// This is a convenience method for printing to the display. For multi-device, this will print to the active device as set by
610/// `set_cursor`.
611impl<I2C, DELAY, DEVICE, ACTIONS> core::fmt::Write
612 for BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
613where
614 I2C: i2c::I2c,
615 DELAY: DelayNs,
616 DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
617 ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
618{
619 fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
620 if let Err(_e) = self.print(s) {
621 return Err(core::fmt::Error);
622 }
623 Ok(())
624 }
625}
626
627#[cfg(feature = "ufmt")]
628/// Implement the `ufmt::uWrite` trait, allowing it to be used with the `uwriteln!` and `uwrite!` macros.
629/// This is a convenience method for printing to the display. For multi-device, this will print to the active device as set by
630/// `set_cursor`.
631impl<I2C, DELAY, DEVICE, ACTIONS> ufmt::uWrite for BaseCharacterDisplay<I2C, DELAY, DEVICE, ACTIONS>
632where
633 I2C: i2c::I2c,
634 DELAY: DelayNs,
635 DEVICE: driver::DeviceHardwareTrait<I2C, DELAY>,
636 ACTIONS: driver::DisplayActionsTrait<I2C, DELAY, DEVICE>,
637{
638 fn write_str(&mut self, s: &str) -> Result<(), CharacterDisplayError<I2C>> {
639 if let Err(e) = self.print(s) {
640 return Err(e);
641 }
642 Ok(())
643 }
644
645 type Error = CharacterDisplayError<I2C>;
646}
647
648#[cfg(test)]
649mod lib_tests {
650 extern crate std;
651 use super::*;
652 use embedded_hal_mock::eh1::{
653 delay::NoopDelay,
654 i2c::{Mock as I2cMock, Transaction as I2cTransaction},
655 };
656
657 #[test]
658 fn test_character_display_pcf8574t_init() {
659 let i2c_address = 0x27_u8;
660 let expected_i2c_transactions = std::vec![
661 // the PCF8574T has no adapter init sequence, so nothing to prepend
662 // the LCD init sequence
663 // write low nibble of 0x03 3 times
664 I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
665 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
666 I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
667 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
668 I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
669 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
670 // write high nibble of 0x02 one time
671 I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
672 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
673 // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // backlight on
674 // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
675 // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
676 I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
677 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
678 I2cTransaction::write(i2c_address, std::vec![0b1000_0100]), // low nibble, rw=0, enable=1
679 I2cTransaction::write(i2c_address, std::vec![0b1000_0000]), // low nibble, rw=0, enable=0
680 // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
681 // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
682 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
683 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
684 I2cTransaction::write(i2c_address, std::vec![0b1100_0100]), // low nibble, rw=0, enable=1
685 I2cTransaction::write(i2c_address, std::vec![0b1100_0000]), // low nibble, rw=0, enable=0
686 // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
687 // = 0x04 | 0x02 | 0x00 = 0x06
688 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
689 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
690 I2cTransaction::write(i2c_address, std::vec![0b0110_0100]), // low nibble, rw=0, enable=1
691 I2cTransaction::write(i2c_address, std::vec![0b0110_0000]), // low nibble, rw=0, enable=0
692 // LCD_CMD_CLEARDISPLAY
693 // = 0x01
694 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
695 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
696 I2cTransaction::write(i2c_address, std::vec![0b0001_0100]), // low nibble, rw=0, enable=1
697 I2cTransaction::write(i2c_address, std::vec![0b0001_0000]), // low nibble, rw=0, enable=0
698 // LCD_CMD_RETURNHOME
699 // = 0x02
700 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
701 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
702 I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // low nibble, rw=0, enable=1
703 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // low nibble, rw=0, enable=0
704 // Set Backlight
705 I2cTransaction::write(i2c_address, std::vec![0b0010_1000]), // backlight on
706 ];
707
708 let i2c = I2cMock::new(&expected_i2c_transactions);
709 let mut lcd = CharacterDisplayPCF8574T::new(i2c, LcdDisplayType::Lcd16x2, NoopDelay::new());
710 let result = lcd.init();
711 assert!(result.is_ok());
712
713 // finish the i2c mock
714 lcd.i2c().done();
715 }
716
717 #[test]
718 fn test_adafruit_lcd_backpack_init() {
719 let i2c_address = 0x20_u8;
720 let expected_i2c_transactions = std::vec![
721 // the Adafruit Backpack need to init the adapter IC first
722 // write 0x00 to the MCP23008 IODIR register to set all pins as outputs
723 I2cTransaction::write(i2c_address, std::vec![0x00, 0x00]),
724 // the LCD init sequence
725 // write low nibble of 0x03 3 times
726 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
727 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
728 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
729 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
730 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
731 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
732 // write high nibble of 0x02 one time
733 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // high nibble, rw=0, enable=1
734 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // high nibble, rw=0, enable=0
735 // turn on the backlight
736 // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // backlight on
737 // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
738 // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
739 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // high nibble, rw=0, enable=1
740 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // high nibble, rw=0, enable=0
741 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1000_100]), // low nibble, rw=0, enable=1
742 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1000_000]), // low nibble, rw=0, enable=0
743 // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
744 // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
745 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
746 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
747 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1100_100]), // low nibble, rw=0, enable=1
748 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_1100_000]), // low nibble, rw=0, enable=0
749 // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
750 // = 0x04 | 0x02 | 0x00 = 0x06
751 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
752 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
753 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0110_100]), // low nibble, rw=0, enable=1
754 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0110_000]), // low nibble, rw=0, enable=0
755 // LCD_CMD_CLEARDISPLAY
756 // = 0x01
757 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
758 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
759 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0001_100]), // low nibble, rw=0, enable=1
760 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0001_000]), // low nibble, rw=0, enable=0
761 // LCD_CMD_RETURNHOME
762 // = 0x02
763 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_100]), // high nibble, rw=0, enable=1
764 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0000_000]), // high nibble, rw=0, enable=0
765 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // low nibble, rw=0, enable=1
766 I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // low nibble, rw=0, enable=0
767 // Set Backlight
768 I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0010_000]), // backlight on
769 ];
770
771 let i2c = I2cMock::new(&expected_i2c_transactions);
772 let mut lcd = AdafruitLCDBackpack::new(i2c, LcdDisplayType::Lcd16x2, NoopDelay::new());
773
774 let result = lcd.init();
775 assert!(result.is_ok());
776 assert!(lcd.display_type() == LcdDisplayType::Lcd16x2);
777
778 // finish the i2c mock
779 lcd.i2c().done();
780 }
781
782 #[test]
783 fn test_character_display_dual_hd44780_init() {
784 let i2c_address = 0x27_u8;
785 let expected_i2c_transactions = std::vec![
786 // the PCF8574T has no adapter init sequence, so nothing to prepend
787 // *** Device 0 ***
788 // the LCD init sequence for device 0
789 // write low nibble of 0x03 3 times
790 I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
791 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
792 I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
793 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
794 I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
795 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
796 // write high nibble of 0x02 one time
797 I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
798 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
799 // turn on the backlight
800 // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // backlight on
801 // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
802 // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
803 I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
804 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
805 I2cTransaction::write(i2c_address, std::vec![0b1000_0100]), // low nibble, rw=0, enable=1
806 I2cTransaction::write(i2c_address, std::vec![0b1000_0000]), // low nibble, rw=0, enable=0
807 // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
808 // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
809 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
810 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
811 I2cTransaction::write(i2c_address, std::vec![0b1100_0100]), // low nibble, rw=0, enable=1
812 I2cTransaction::write(i2c_address, std::vec![0b1100_0000]), // low nibble, rw=0, enable=0
813 // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
814 // = 0x04 | 0x02 | 0x00 = 0x06
815 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
816 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
817 I2cTransaction::write(i2c_address, std::vec![0b0110_0100]), // low nibble, rw=0, enable=1
818 I2cTransaction::write(i2c_address, std::vec![0b0110_0000]), // low nibble, rw=0, enable=0
819 // LCD_CMD_CLEARDISPLAY
820 // = 0x01
821 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
822 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
823 I2cTransaction::write(i2c_address, std::vec![0b0001_0100]), // low nibble, rw=0, enable=1
824 I2cTransaction::write(i2c_address, std::vec![0b0001_0000]), // low nibble, rw=0, enable=0
825 // LCD_CMD_RETURNHOME
826 // = 0x02
827 I2cTransaction::write(i2c_address, std::vec![0b0000_0100]), // high nibble, rw=0, enable=1
828 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
829 I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // low nibble, rw=0, enable=1
830 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // low nibble, rw=0, enable=0
831 // *** Device 1 ***
832 // the LCD init sequence for device 0
833 // write low nibble of 0x03 3 times
834 I2cTransaction::write(i2c_address, std::vec![0b0011_0010]), // low nibble, rw=0, enable=1
835 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
836 I2cTransaction::write(i2c_address, std::vec![0b0011_0010]), // low nibble, rw=0, enable=1
837 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
838 I2cTransaction::write(i2c_address, std::vec![0b0011_0010]), // low nibble, rw=0, enable=1
839 I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
840 // write high nibble of 0x02 one time
841 I2cTransaction::write(i2c_address, std::vec![0b0010_0010]), // high nibble, rw=0, enable=1
842 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
843 // turn on the backlight
844 // I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // backlight on
845 // LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
846 // = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
847 I2cTransaction::write(i2c_address, std::vec![0b0010_0010]), // high nibble, rw=0, enable=1
848 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
849 I2cTransaction::write(i2c_address, std::vec![0b1000_0010]), // low nibble, rw=0, enable=1
850 I2cTransaction::write(i2c_address, std::vec![0b1000_0000]), // low nibble, rw=0, enable=0
851 // LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
852 // = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
853 I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
854 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
855 I2cTransaction::write(i2c_address, std::vec![0b1100_0010]), // low nibble, rw=0, enable=1
856 I2cTransaction::write(i2c_address, std::vec![0b1100_0000]), // low nibble, rw=0, enable=0
857 // LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
858 // = 0x04 | 0x02 | 0x00 = 0x06
859 I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
860 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
861 I2cTransaction::write(i2c_address, std::vec![0b0110_0010]), // low nibble, rw=0, enable=1
862 I2cTransaction::write(i2c_address, std::vec![0b0110_0000]), // low nibble, rw=0, enable=0
863 // LCD_CMD_CLEARDISPLAY
864 // = 0x01
865 I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
866 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
867 I2cTransaction::write(i2c_address, std::vec![0b0001_0010]), // low nibble, rw=0, enable=1
868 I2cTransaction::write(i2c_address, std::vec![0b0001_0000]), // low nibble, rw=0, enable=0
869 // LCD_CMD_RETURNHOME
870 // = 0x02
871 I2cTransaction::write(i2c_address, std::vec![0b0000_0010]), // high nibble, rw=0, enable=1
872 I2cTransaction::write(i2c_address, std::vec![0b0000_0000]), // high nibble, rw=0, enable=0
873 I2cTransaction::write(i2c_address, std::vec![0b0010_0010]), // low nibble, rw=0, enable=1
874 I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // low nibble, rw=0, enable=0
875 // Set Backlight
876 I2cTransaction::write(i2c_address, std::vec![0b0010_1000]), // backlight on
877 ];
878
879 let i2c = I2cMock::new(&expected_i2c_transactions);
880 let mut lcd =
881 CharacterDisplayDualHD44780::new(i2c, LcdDisplayType::Lcd40x4, NoopDelay::new());
882 let result = lcd.init();
883 assert!(result.is_ok());
884 assert!(lcd.display_type() == LcdDisplayType::Lcd40x4);
885
886 // finish the i2c mock
887 lcd.i2c().done();
888 }
889}