Skip to main content

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//}