lcd_1602_i2c/
lib.rs

1#![deny(missing_docs)]
2
3/*!
4# Platform-agnostic driver for I2C 16x2 character displays
5
6Provides a driver for common 16x2 LCD displays that use the AiP31068L chip to
7drive the display, and a PCA9633 chip to drive the RGB backlight.
8
9This is a basic implementation, and doesn't currently support custom characters.
10
11This has been tested with the [Waveshare LCD1602 module](https://www.waveshare.com/wiki/LCD1602_RGB_Module).
12It may also work with other RGB displays like the [Groove 16X2 LDC RGB](https://www.seeedstudio.com/Grove-LCD-RGB-Backlight-p-1643.html)
13*/
14
15#![no_std]
16use embedded_hal::blocking::{i2c, delay::DelayMs};
17
18mod display_control;
19use display_control::{DisplayControl};
20
21pub use display_control::{Cursor, LcdDisplay, Blink};
22
23/**
24Handles all the logic related to working with the character LCD via I2C. You'll
25need to create an instance of this with the `new()` method.
26
27The `I` generic type needs to implement the `embedded_hal::blocking::Write` trait.
28*/
29pub struct Lcd<I>
30where
31    I: i2c::Write,
32{
33    i2c: I,
34    control: DisplayControl,
35    address: u8,
36    rgb_address: u8,
37}
38
39impl<I> Lcd<I>
40where
41    I: i2c::Write
42    {
43    /**
44    Creates a new instance of the display object.
45
46    # Example
47
48    ```rust
49    let lcd = Lcd::new(i2c_bus, address, rgb_address, &mut delay);
50    ```
51
52    `i2c` needs to implement the `embedded_hal::blocking::Write` trait.
53
54    `delay` needs to implement the `embedded_hal::blocking::delay::DelayMs` trait.
55
56    # Errors
57
58    The I2C library will return an error if it's not able to write to the device.
59    This is always a trait of type `embedded_hal::blocking::Write::Error` that
60    is implemented by the I2C instance.
61    */
62    pub fn new<D>(i2c: I, address: u8, rgb_address: u8, delay: &mut D) -> Result<Self, <I as i2c::Write>::Error>
63    where
64        D: DelayMs<u16>
65    {
66        let mut display = Lcd {
67            i2c,
68            control: DisplayControl::new(),
69            address,
70            rgb_address,
71        };
72        display.init(delay)?;
73        Ok(display)
74    }
75
76    // Initialize the display for the first time after power up
77    fn init<D>(&mut self, delay: &mut D) -> Result<(), <I as i2c::Write>::Error>
78    where D: DelayMs<u16> {
79        delay.delay_ms(80); // Need to wait at least 40ms before sending commands
80
81        // Send the initial command sequence according to the HD44780 datasheet
82        self.write_function_set()?;
83        delay.delay_ms(5);
84
85        self.write_function_set()?;
86        delay.delay_ms(5);
87
88        self.write_function_set()?;
89
90        self.set_display(LcdDisplay::On)?;
91
92        self.clear(delay)?;
93
94        // Display entry mode
95        const LCD_ENTRYLEFT: u8 = 0x02;
96        const LCD_ENTRYSHIFTDECREMENT: u8 = 0x00;
97        const LCD_ENTRYMODESET: u8 = 0x04;
98
99        self.write_command(LCD_ENTRYMODESET | LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT)?;
100
101        // Initialize the backlight
102        const REG_MODE1: u8     = 0x00;
103        const REG_MODE2: u8     = 0x01;
104        const REG_OUTPUT: u8    = 0x08;
105    
106        self.write_reg(REG_MODE1, 0)?;
107
108        // Set the LEDs controllable by both PWM and GRPPWM registers
109        self.write_reg(REG_OUTPUT, 0xFF)?;
110        self.write_reg(REG_MODE2, 0x20)
111    }
112
113    /**
114    Clear the display. The LCD display driver requires a 2ms delay after clearing, which
115    is why this method requires a `delay` object.
116
117    # Errors
118
119    Returns a `Result` that will report I2C errors, if any.
120    */
121    pub fn clear(&mut self, delay: &mut dyn DelayMs<u16>) -> Result<(), <I as i2c::Write>::Error> {
122        const LCD_CLEARDISPLAY: u8 = 0x01;
123
124        let result = self.write_command(LCD_CLEARDISPLAY);
125        delay.delay_ms(2);
126        result
127    }
128
129    /**
130    Set the position of the cursor
131
132    # Errors
133
134    Returns a `Result` that will report I2C errors, if any.
135    */
136    pub fn set_cursor_position(&mut self, x: u8, y: u8) -> Result<(), <I as i2c::Write>::Error> {
137        let col = if y == 0_u8 { x | 0x80 } else { x | 0xC0 };
138        self.write_command(col)
139    }
140
141    /**
142    Control whether the display is on or off
143
144    # Errors
145
146    Returns a `Result` that will report I2C errors, if any.
147    */
148    pub fn set_display(&mut self, display: LcdDisplay) -> Result<(), <I as i2c::Write>::Error> {
149        self.control.display = display;
150        self.write_display_control()
151    }
152
153    /**
154    Sets the visiblity of the cursor, which is a non-blinking _
155
156    # Errors
157
158    Returns a `Result` that will report I2C errors, if any.
159    */
160    pub fn set_cursor(&mut self, cursor: Cursor) -> Result<(), <I as i2c::Write>::Error> {
161        self.control.cursor = cursor;
162        self.write_display_control()
163    }
164
165    /**
166    Turns on the blinking block cursor
167
168    # Errors
169
170    Returns a `Result` that will report I2C errors, if any.
171    */
172    pub fn set_blink(&mut self, blink: Blink) -> Result<(), <I as i2c::Write>::Error> {
173        self.control.blink = blink;
174        self.write_display_control()
175    }
176
177    /**
178    Adds a single character to the current position. The cursor will advance
179    after this call to the next column
180
181    # Errors
182
183    Returns a `Result` that will report I2C errors, if any.
184    */
185    pub fn write_char(&mut self, char: char) -> Result<(), <I as i2c::Write>::Error> {
186        self.write_two(0x40, char as u8)
187    }
188
189    /**
190    Adds a string to the current position. The cursor will advance
191    after this call to the next column
192
193    # Errors
194
195    Returns a `Result` that will report I2C errors, if any.
196    */
197    pub fn write_str(&mut self, s: &str) -> Result<(), <I as i2c::Write>::Error> {
198        for c in s.chars() {
199            self.write_char(c)?;
200        }
201
202        Ok(())
203    }
204
205    /**
206    Set the color of the backlight for displays that have an RGB backlight.
207
208    # Errors
209
210    Returns a `Result` that will report I2C errors, if any.
211    */
212    pub fn set_rgb(&mut self, r: u8, g: u8, b: u8) -> Result<(), <I as i2c::Write>::Error> {
213        const REG_RED: u8       = 0x04;        // pwm2
214        const REG_GREEN: u8     = 0x03;        // pwm1
215        const REG_BLUE: u8      = 0x02;        // pwm0
216    
217        self.write_reg(REG_RED, r)?;
218        self.write_reg(REG_GREEN, g)?;
219        self.write_reg(REG_BLUE, b)
220    }
221
222    fn write_reg(&mut self, addr: u8, data: u8) -> Result<(), <I as i2c::Write>::Error> {
223        self.i2c.write(self.rgb_address, &[addr, data])
224    }
225
226    fn write_function_set(&mut self) -> Result<(), <I as i2c::Write>::Error> {
227        const LCD_4BITMODE: u8 = 0x00;
228        const LCD_2LINE: u8 = 0x08;
229        const LCD_5X8_DOTS: u8 = 0x00;
230        const LCD_FUNCTIONSET: u8 = 0x20;
231
232        self.write_command(LCD_FUNCTIONSET | LCD_4BITMODE | LCD_2LINE | LCD_5X8_DOTS)
233    }
234
235    // Set one of the display's control options and then send the updated set of options to the display
236    fn write_display_control(&mut self) -> Result<(), <I as i2c::Write>::Error> {
237        self.write_command(self.control.value())
238    }
239
240    // Send a command to the LCD display
241    fn write_command(&mut self, value: u8) -> Result<(), <I as i2c::Write>::Error> {
242        self.write_two(0x80, value)
243    }
244
245    // Send two bytes to the display
246    fn write_two(&mut self, byte1: u8, byte2: u8) -> Result<(), <I as i2c::Write>::Error> {
247        self.i2c.write(self.address, &[byte1, byte2])
248    }
249}