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}