hd44780_driver/lib.rs
1#![no_std]
2
3//use core::fmt::Result;
4//use core::fmt::Write;
5
6use embedded_hal::blocking::delay::{DelayMs, DelayUs};
7use embedded_hal::blocking::i2c;
8use embedded_hal::digital::v2::OutputPin;
9
10pub mod bus;
11use bus::{DataBus, EightBitBus, FourBitBus, I2CBus};
12
13pub mod error;
14use error::Result;
15
16pub mod entry_mode;
17
18use entry_mode::{CursorMode, EntryMode};
19
20pub mod display_mode;
21
22pub use display_mode::DisplayMode;
23
24pub struct HD44780<B: DataBus> {
25 bus: B,
26 entry_mode: EntryMode,
27 display_mode: DisplayMode,
28}
29
30/// Used in the direction argument for shifting the cursor and the display
31pub enum Direction {
32 Left,
33 Right,
34}
35
36/// Used in set_display_mode to make the parameters more clear
37pub enum Display {
38 On,
39 Off,
40}
41
42pub enum Cursor {
43 Visible,
44 Invisible,
45}
46
47pub enum CursorBlink {
48 On,
49 Off,
50}
51
52impl<
53 RS: OutputPin,
54 EN: OutputPin,
55 D0: OutputPin,
56 D1: OutputPin,
57 D2: OutputPin,
58 D3: OutputPin,
59 D4: OutputPin,
60 D5: OutputPin,
61 D6: OutputPin,
62 D7: OutputPin,
63 > HD44780<EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7>>
64{
65 /// Create an instance of a `HD44780` from 8 data pins, a register select
66 /// pin, an enable pin and a struct implementing the delay trait.
67 /// - The delay instance is used to sleep between commands to
68 /// ensure the `HD44780` has enough time to process commands.
69 /// - The eight db0..db7 pins are used to send and recieve with
70 /// the `HD44780`.
71 /// - The register select pin is used to tell the `HD44780`
72 /// if incoming data is a command or data.
73 /// - The enable pin is used to tell the `HD44780` that there
74 /// is data on the 8 data pins and that it should read them in.
75 ///
76 pub fn new_8bit<D: DelayUs<u16> + DelayMs<u8>>(
77 rs: RS,
78 en: EN,
79 d0: D0,
80 d1: D1,
81 d2: D2,
82 d3: D3,
83 d4: D4,
84 d5: D5,
85 d6: D6,
86 d7: D7,
87 delay: &mut D,
88 ) -> Result<HD44780<EightBitBus<RS, EN, D0, D1, D2, D3, D4, D5, D6, D7>>> {
89 let mut hd = HD44780 {
90 bus: EightBitBus::from_pins(rs, en, d0, d1, d2, d3, d4, d5, d6, d7),
91 entry_mode: EntryMode::default(),
92 display_mode: DisplayMode::default(),
93 };
94
95 hd.init_8bit(delay)?;
96
97 return Ok(hd);
98 }
99}
100
101impl<RS: OutputPin, EN: OutputPin, D4: OutputPin, D5: OutputPin, D6: OutputPin, D7: OutputPin>
102 HD44780<FourBitBus<RS, EN, D4, D5, D6, D7>>
103{
104 /// Create an instance of a `HD44780` from 4 data pins, a register select
105 /// pin, an enable pin and a struct implementing the delay trait.
106 /// - The delay instance is used to sleep between commands to
107 /// ensure the `HD44780` has enough time to process commands.
108 /// - The four db0..db3 pins are used to send and recieve with
109 /// the `HD44780`.
110 /// - The register select pin is used to tell the `HD44780`
111 /// if incoming data is a command or data.
112 /// - The enable pin is used to tell the `HD44780` that there
113 /// is data on the 4 data pins and that it should read them in.
114 ///
115 /// This mode operates differently than 8 bit mode by using 4 less
116 /// pins for data, which is nice on devices with less I/O although
117 /// the I/O takes a 'bit' longer
118 ///
119 /// Instead of commands being sent byte by byte each command is
120 /// broken up into it's upper and lower nibbles (4 bits) before
121 /// being sent over the data bus
122 ///
123 pub fn new_4bit<D: DelayUs<u16> + DelayMs<u8>>(
124 rs: RS,
125 en: EN,
126 d4: D4,
127 d5: D5,
128 d6: D6,
129 d7: D7,
130 delay: &mut D,
131 ) -> Result<HD44780<FourBitBus<RS, EN, D4, D5, D6, D7>>> {
132 let mut hd = HD44780 {
133 bus: FourBitBus::from_pins(rs, en, d4, d5, d6, d7),
134 entry_mode: EntryMode::default(),
135 display_mode: DisplayMode::default(),
136 };
137
138 hd.init_4bit(delay)?;
139
140 return Ok(hd);
141 }
142}
143
144impl<I2C: i2c::Write> HD44780<I2CBus<I2C>> {
145 /// Create an instance of a `HD44780` from an i2c write peripheral,
146 /// the `HD44780` I2C address and a struct implementing the delay trait.
147 /// - The delay instance is used to sleep between commands to
148 /// ensure the `HD44780` has enough time to process commands.
149 /// - The i2c peripheral is used to send data to the `HD44780` and to set
150 /// its register select and enable pins.
151 ///
152 /// This mode operates on an I2C bus, using an I2C to parallel port expander
153 ///
154 pub fn new_i2c<D: DelayUs<u16> + DelayMs<u8>>(
155 i2c_bus: I2C,
156 address: u8,
157 delay: &mut D,
158 ) -> Result<HD44780<I2CBus<I2C>>> {
159 let mut hd = HD44780 {
160 bus: I2CBus::new(i2c_bus, address),
161 entry_mode: EntryMode::default(),
162 display_mode: DisplayMode::default(),
163 };
164
165 hd.init_4bit(delay)?;
166
167 return Ok(hd);
168 }
169}
170
171impl<B> HD44780<B>
172where
173 B: DataBus,
174{
175 /// Unshifts the display and sets the cursor position to 0
176 ///
177 /// ```rust,ignore
178 /// lcd.reset();
179 /// ```
180 pub fn reset<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> {
181 self.write_command(0b0000_0010, delay)?;
182
183 Ok(())
184 }
185
186 /// Set if the display should be on, if the cursor should be
187 /// visible, and if the cursor should blink
188 ///
189 /// Note: This is equivilent to calling all of the other relavent
190 /// methods however this operation does it all in one go to the `HD44780`
191 pub fn set_display_mode<D: DelayUs<u16> + DelayMs<u8>>(
192 &mut self,
193 display_mode: DisplayMode,
194 delay: &mut D,
195 ) -> Result<()> {
196 self.display_mode = display_mode;
197
198 let cmd_byte = self.display_mode.as_byte();
199
200 self.write_command(cmd_byte, delay)?;
201
202 Ok(())
203 }
204
205 /// Clear the entire display
206 ///
207 /// ```rust,ignore
208 /// lcd.clear();
209 /// ```
210 pub fn clear<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> {
211 self.write_command(0b0000_0001, delay)?;
212
213 Ok(())
214 }
215
216 /// If enabled, automatically scroll the display when a new
217 /// character is written to the display
218 ///
219 /// ```rust,ignore
220 /// lcd.set_autoscroll(true);
221 /// ```
222 pub fn set_autoscroll<D: DelayUs<u16> + DelayMs<u8>>(
223 &mut self,
224 enabled: bool,
225 delay: &mut D,
226 ) -> Result<()> {
227 self.entry_mode.shift_mode = enabled.into();
228
229 let cmd = self.entry_mode.as_byte();
230
231 self.write_command(cmd, delay)?;
232
233 Ok(())
234 }
235
236 /// Set if the cursor should be visible
237 pub fn set_cursor_visibility<D: DelayUs<u16> + DelayMs<u8>>(
238 &mut self,
239 visibility: Cursor,
240 delay: &mut D,
241 ) -> Result<()> {
242 self.display_mode.cursor_visibility = visibility;
243
244 let cmd = self.display_mode.as_byte();
245
246 self.write_command(cmd, delay)?;
247
248 Ok(())
249 }
250
251 /// Set if the characters on the display should be visible
252 pub fn set_display<D: DelayUs<u16> + DelayMs<u8>>(
253 &mut self,
254 display: Display,
255 delay: &mut D,
256 ) -> Result<()> {
257 self.display_mode.display = display;
258
259 let cmd = self.display_mode.as_byte();
260
261 self.write_command(cmd, delay)?;
262
263 Ok(())
264 }
265
266 /// Set if the cursor should blink
267 pub fn set_cursor_blink<D: DelayUs<u16> + DelayMs<u8>>(
268 &mut self,
269 blink: CursorBlink,
270 delay: &mut D,
271 ) -> Result<()> {
272 self.display_mode.cursor_blink = blink;
273
274 let cmd = self.display_mode.as_byte();
275
276 self.write_command(cmd, delay)?;
277
278 Ok(())
279 }
280
281 /// Set which way the cursor will move when a new character is written
282 ///
283 /// ```rust,ignore
284 /// // Move right (Default) when a new character is written
285 /// lcd.set_cursor_mode(CursorMode::Right)
286 ///
287 /// // Move left when a new character is written
288 /// lcd.set_cursor_mode(CursorMode::Left)
289 /// ```
290 pub fn set_cursor_mode<D: DelayUs<u16> + DelayMs<u8>>(
291 &mut self,
292 mode: CursorMode,
293 delay: &mut D,
294 ) -> Result<()> {
295 self.entry_mode.cursor_mode = mode;
296
297 let cmd = self.entry_mode.as_byte();
298
299 self.write_command(cmd, delay)?;
300
301 Ok(())
302 }
303
304 /// Set the cursor position
305 ///
306 /// ```rust,ignore
307 /// // Move to line 2
308 /// lcd.set_cursor_pos(40)
309 /// ```
310 pub fn set_cursor_pos<D: DelayUs<u16> + DelayMs<u8>>(
311 &mut self,
312 position: u8,
313 delay: &mut D,
314 ) -> Result<()> {
315 let lower_7_bits = 0b0111_1111 & position;
316
317 self.write_command(0b1000_0000 | lower_7_bits, delay)?;
318
319 Ok(())
320 }
321
322 /// Shift just the cursor to the left or the right
323 ///
324 /// ```rust,ignore
325 /// lcd.shift_cursor(Direction::Left);
326 /// lcd.shift_cursor(Direction::Right);
327 /// ```
328 pub fn shift_cursor<D: DelayUs<u16> + DelayMs<u8>>(
329 &mut self,
330 dir: Direction,
331 delay: &mut D,
332 ) -> Result<()> {
333 let bits = match dir {
334 Direction::Left => 0b0000_0000,
335 Direction::Right => 0b0000_0100,
336 };
337
338 self.write_command(0b0001_0000 | bits | bits, delay)?;
339
340 Ok(())
341 }
342
343 /// Shift the entire display to the left or the right
344 ///
345 /// ```rust,ignore
346 /// lcd.shift_display(Direction::Left);
347 /// lcd.shift_display(Direction::Right);
348 /// ```
349 pub fn shift_display<D: DelayUs<u16> + DelayMs<u8>>(
350 &mut self,
351 dir: Direction,
352 delay: &mut D,
353 ) -> Result<()> {
354 let bits = match dir {
355 Direction::Left => 0b0000_0000,
356 Direction::Right => 0b0000_0100,
357 };
358
359 self.write_command(0b0001_1000 | bits, delay)?;
360
361 Ok(())
362 }
363
364 /// Write a single character to the `HD44780`. This `char` just gets downcast to a `u8`
365 /// internally, so make sure that whatever character you're printing fits inside that range, or
366 /// you can just use [write_byte](#method.write_byte) to have the compiler check for you.
367 /// See the documentation on that function for more details about compatibility.
368 ///
369 /// ```rust,ignore
370 /// lcd.write_char('A', &mut delay)?; // prints 'A'
371 /// ```
372 pub fn write_char<D: DelayUs<u16> + DelayMs<u8>>(
373 &mut self,
374 data: char,
375 delay: &mut D,
376 ) -> Result<()> {
377 self.write_byte(data as u8, delay)
378 }
379
380 fn write_command<D: DelayUs<u16> + DelayMs<u8>>(
381 &mut self,
382 cmd: u8,
383 delay: &mut D,
384 ) -> Result<()> {
385 self.bus.write(cmd, false, delay)?;
386
387 // Wait for the command to be processed
388 delay.delay_us(100);
389 Ok(())
390 }
391
392 fn init_4bit<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> {
393 // Wait for the LCD to wakeup if it was off
394 delay.delay_ms(15u8);
395
396 // Initialize Lcd in 4-bit mode
397 self.bus.write(0x33, false, delay)?;
398
399 // Wait for the command to be processed
400 delay.delay_ms(5u8);
401
402 // Sets 4-bit operation and enables 5x7 mode for chars
403 self.bus.write(0x32, false, delay)?;
404
405 // Wait for the command to be processed
406 delay.delay_us(100);
407
408 self.bus.write(0x28, false, delay)?;
409
410 // Wait for the command to be processed
411 delay.delay_us(100);
412
413 // Clear Display
414 self.bus.write(0x0E, false, delay)?;
415
416 // Wait for the command to be processed
417 delay.delay_us(100);
418
419 // Move the cursor to beginning of first line
420 self.bus.write(0x01, false, delay)?;
421
422 // Wait for the command to be processed
423 delay.delay_us(100);
424
425 // Set entry mode
426 self.bus.write(self.entry_mode.as_byte(), false, delay)?;
427
428 // Wait for the command to be processed
429 delay.delay_us(100);
430
431 self.bus.write(0x80, false, delay)?;
432
433 // Wait for the command to be processed
434 delay.delay_us(100);
435
436 Ok(())
437 }
438
439 // Follow the 8-bit setup procedure as specified in the HD44780 datasheet
440 fn init_8bit<D: DelayUs<u16> + DelayMs<u8>>(&mut self, delay: &mut D) -> Result<()> {
441 // Wait for the LCD to wakeup if it was off
442 delay.delay_ms(15u8);
443
444 // Initialize Lcd in 8-bit mode
445 self.bus.write(0b0011_0000, false, delay)?;
446
447 // Wait for the command to be processed
448 delay.delay_ms(5u8);
449
450 // Sets 8-bit operation and enables 5x7 mode for chars
451 self.bus.write(0b0011_1000, false, delay)?;
452
453 // Wait for the command to be processed
454 delay.delay_us(100);
455
456 self.bus.write(0b0000_1110, false, delay)?;
457
458 // Wait for the command to be processed
459 delay.delay_us(100);
460
461 // Clear Display
462 self.bus.write(0b0000_0001, false, delay)?;
463
464 // Wait for the command to be processed
465 delay.delay_us(100);
466
467 // Move the cursor to beginning of first line
468 self.bus.write(0b000_0111, false, delay)?;
469
470 // Wait for the command to be processed
471 delay.delay_us(100);
472
473 // Set entry mode
474 self.bus.write(self.entry_mode.as_byte(), false, delay)?;
475
476 // Wait for the command to be processed
477 delay.delay_us(100);
478
479 Ok(())
480 }
481
482 /// Writes a string to the HD44780. Internally, this just prints the string byte-by-byte, so
483 /// make sure the characters in the string fit in a normal `u8`. See the documentation on
484 /// [write_byte](#method.write_byte) for more details on compatibility.
485 ///
486 /// ```rust,ignore
487 /// lcd.write_str("Hello, World!", &mut delay)?;
488 /// ```
489 pub fn write_str<D: DelayUs<u16> + DelayMs<u8>>(
490 &mut self,
491 string: &str,
492 delay: &mut D,
493 ) -> Result<()> {
494 self.write_bytes(string.as_bytes(), delay)
495 }
496
497 /// Writes a sequence of bytes to the HD44780. See the documentation on the
498 /// [write_byte](#method.write_byte) function for more details about compatibility.
499 ///
500 /// ```rust,ignore
501 /// lcd.write_bytes(b"Hello, World!", &mut delay)?;
502 /// ```
503 pub fn write_bytes<D: DelayUs<u16> + DelayMs<u8>>(
504 &mut self,
505 string: &[u8],
506 delay: &mut D,
507 ) -> Result<()> {
508 for &b in string {
509 self.write_byte(b, delay)?;
510 }
511 Ok(())
512 }
513
514 /// Writes a single byte to the HD44780. These usually map to ASCII characters when printed on the
515 /// screen, but not always. While it varies depending on the ROM of the LCD, `0x20u8..=0x5b`
516 /// and `0x5d..=0x7d` should map to their standard ASCII characters. That is, all the printable
517 /// ASCII characters work, excluding `\` and `~`, which are usually displayed as `¥` and `🡢`
518 /// respectively.
519 ///
520 /// More information can be found in the Hitachi datasheets for the HD44780.
521 ///
522 /// ```rust,ignore
523 /// lcd.write_byte(b'A', &mut delay)?; // prints 'A'
524 /// lcd.write_byte(b'\\', &mut delay)?; // usually prints ¥
525 /// lcd.write_byte(b'~', &mut delay)?; // usually prints 🡢
526 /// lcd.write_byte(b'\x7f', &mut delay)?; // usually prints 🡠
527 /// ```
528 pub fn write_byte<D: DelayUs<u16> + DelayMs<u8>>(
529 &mut self,
530 data: u8,
531 delay: &mut D
532 ) -> Result<()> {
533 self.bus.write(data, true, delay)?;
534
535 // Wait for the command to be processed
536 delay.delay_us(100);
537
538 Ok(())
539 }
540
541 // Pulse the enable pin telling the HD44780 that we something for it
542 /*fn pulse_enable(&mut self) {
543 self.en.set_high();
544 self.delay.delay_ms(15u8);
545 self.en.set_low();
546 }*/
547}
548
549//impl<B> Write for HD44780<B>
550//where
551// B: DataBus,
552//{
553// fn write_str(&mut self, string: &str) -> Result {
554// for c in string.chars() {
555// self.write_char(c, delay);
556// }
557// Ok(())
558// }
559//}