capsules_extra/
hd44780.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! SyscallDriver for the HD44780 LCD screen.
6//!
7//! The LCD must be connected as shown here, because the pins of the LCD are
8//! already defined in the kernel, and modifying them means re-compiling the
9//! kernel with the modifications.
10//!
11//! This capsule takes an alarm, an array of pins and one buffer initialized
12//! to 0.
13//!
14//! This capsule uses the TextScreen capsule and implements the TextScreen trait,
15//! through which it can receive commands (specific driver commands or write
16//! commands) and call specific callbacks (write_complete() or command_complete()).
17//!
18//! According to the HD44780 datasheet, there must be a delay between certain
19//! operations on the device. Since there cannot be a delay while running on
20//! kernel mode, the alarm is the best way to implement those delays. To
21//! remember the state before and after each delay, the program will be a big
22//! state-machine that goes through the possible states defined in the
23//! LCDStatus enum. Also, after every command completed, a callback will be called
24//! to the text_screen capsule, in order for this capsule to be able to receive new
25//! commands. If a command is sent while this capsule is busy, it will return a
26//! "BUSY" code.
27
28//! Usage
29//! -----
30//! ```rust,ignore
31//! let lcd = components::hd44780::HD44780Component::new(mux_alarm).finalize(
32//!     components::hd44780_component_helper!(
33//!         stm32f429zi::tim2::Tim2,
34//!         // rs pin
35//!         gpio_ports.pins[5][13].as_ref().unwrap(),
36//!         // en pin
37//!         gpio_ports.pins[4][11].as_ref().unwrap(),
38//!         // data 4 pin
39//!         gpio_ports.pins[5][14].as_ref().unwrap(),
40//!         // data 5 pin
41//!         gpio_ports.pins[4][13].as_ref().unwrap(),
42//!         // data 6 pin
43//!         gpio_ports.pins[5][15].as_ref().unwrap(),
44//!         // data 7 pin
45//!         gpio_ports.pins[6][14].as_ref().unwrap()
46//!     )
47//! );
48//!
49//! let text_screen = components::text_screen::TextScreenComponent::new(board_kernel, lcd)
50//!                 .finalize(components::screen_buffer_size!(64));
51//! ```
52//!
53//! Author: Teona Severin <teona.severin9@gmail.com>
54
55use core::cell::Cell;
56use kernel::hil::gpio;
57use kernel::hil::text_screen::{TextScreen, TextScreenClient};
58use kernel::hil::time::{self, Alarm, Frequency};
59use kernel::utilities::cells::{OptionalCell, TakeCell};
60use kernel::ErrorCode;
61
62/// commands
63static LCD_CLEARDISPLAY: u8 = 0x01;
64static LCD_ENTRYMODESET: u8 = 0x04;
65static LCD_DISPLAYCONTROL: u8 = 0x08;
66static LCD_FUNCTIONSET: u8 = 0x20;
67static LCD_SETDDRAMADDR: u8 = 0x80;
68
69/// flags for display entry mode
70static LCD_ENTRYLEFT: u8 = 0x02;
71static LCD_ENTRYSHIFTDECREMENT: u8 = 0x00;
72
73/// flags for display on/off control
74static LCD_DISPLAYON: u8 = 0x04;
75static LCD_CURSORON: u8 = 0x02;
76static LCD_BLINKON: u8 = 0x01;
77static LCD_BLINKOFF: u8 = 0x00;
78
79/// flags for function set
80static LCD_8BITMODE: u8 = 0x10;
81static LCD_4BITMODE: u8 = 0x00;
82static LCD_2LINE: u8 = 0x08;
83static LCD_1LINE: u8 = 0x00;
84static LCD_5X8DOTS: u8 = 0x00;
85
86pub const BUF_LEN: usize = 4;
87
88/// The states the program can be in.
89#[derive(Copy, Clone, PartialEq)]
90enum LCDStatus {
91    Idle,
92    Begin0,
93    Begin0_1,
94    Begin1,
95    Begin1_2,
96    Begin2,
97    Begin2_3,
98    Begin3,
99    Begin4,
100    Begin5,
101    Begin6,
102    Begin7,
103    Begin8,
104    Begin9,
105    Begin10,
106    Begin11,
107    Begin12,
108    Printing,
109    PulseLow,
110    PulseHigh,
111    Command,
112    Clear,
113}
114
115pub struct HD44780<'a, A: Alarm<'a>> {
116    rs_pin: &'a dyn gpio::Pin,
117    en_pin: &'a dyn gpio::Pin,
118    data_4_pin: &'a dyn gpio::Pin,
119    data_5_pin: &'a dyn gpio::Pin,
120    data_6_pin: &'a dyn gpio::Pin,
121    data_7_pin: &'a dyn gpio::Pin,
122
123    width: Cell<u8>,
124    height: Cell<u8>,
125
126    display_function: Cell<u8>,
127    display_control: Cell<u8>,
128    display_mode: Cell<u8>,
129    num_lines: Cell<u8>,
130    row_offsets: TakeCell<'static, [u8]>,
131
132    alarm: &'a A,
133
134    lcd_status: Cell<LCDStatus>,
135    lcd_after_pulse_status: Cell<LCDStatus>,
136    lcd_after_command_status: Cell<LCDStatus>,
137    lcd_after_delay_status: Cell<LCDStatus>,
138    command_to_finish: Cell<u8>,
139
140    begin_done: Cell<bool>,
141    initialized: Cell<bool>,
142
143    text_screen_client: OptionalCell<&'a dyn TextScreenClient>,
144
145    done_printing: Cell<bool>,
146
147    write_buffer: TakeCell<'static, [u8]>,
148    write_len: Cell<u8>,
149    write_buffer_len: Cell<u8>,
150    write_offset: Cell<u8>,
151}
152
153impl<'a, A: Alarm<'a>> HD44780<'a, A> {
154    pub fn new(
155        rs_pin: &'a dyn gpio::Pin,
156        en_pin: &'a dyn gpio::Pin,
157        data_4_pin: &'a dyn gpio::Pin,
158        data_5_pin: &'a dyn gpio::Pin,
159        data_6_pin: &'a dyn gpio::Pin,
160        data_7_pin: &'a dyn gpio::Pin,
161        row_offsets: &'static mut [u8],
162        alarm: &'a A,
163        width: u8,
164        height: u8,
165    ) -> HD44780<'a, A> {
166        rs_pin.make_output();
167        en_pin.make_output();
168        data_4_pin.make_output();
169        data_5_pin.make_output();
170        data_6_pin.make_output();
171        data_7_pin.make_output();
172        let hd44780 = HD44780 {
173            rs_pin,
174            en_pin,
175            data_4_pin,
176            data_5_pin,
177            data_6_pin,
178            data_7_pin,
179            width: Cell::new(width),
180            height: Cell::new(height),
181            display_function: Cell::new(LCD_4BITMODE | LCD_1LINE | LCD_5X8DOTS),
182            display_control: Cell::new(0),
183            display_mode: Cell::new(0),
184            num_lines: Cell::new(0),
185            row_offsets: TakeCell::new(row_offsets),
186            alarm,
187            lcd_status: Cell::new(LCDStatus::Idle),
188            lcd_after_pulse_status: Cell::new(LCDStatus::Idle),
189            lcd_after_command_status: Cell::new(LCDStatus::Idle),
190            lcd_after_delay_status: Cell::new(LCDStatus::Idle),
191            command_to_finish: Cell::new(0),
192            begin_done: Cell::new(false),
193            initialized: Cell::new(false),
194            text_screen_client: OptionalCell::empty(),
195            done_printing: Cell::new(false),
196            write_buffer: TakeCell::empty(),
197            write_len: Cell::new(0),
198            write_buffer_len: Cell::new(0),
199            write_offset: Cell::new(0),
200        };
201        hd44780.init(width, height);
202
203        hd44780
204    }
205
206    /// `init()` initializes the functioning parameters and communication
207    /// parameters of the LCD, according to its datasheet (HD44780).
208    ///
209    /// When the init is done, the screen capsule will receive a "screen_is_ready()"
210    /// callback, in order to be able to receive other commands.
211    ///
212    /// `init()` is called after the capsule is instantiated:
213    /// - hd44780.init(16,2);
214    ///
215    fn init(&self, col: u8, row: u8) {
216        self.begin_done.set(false);
217        self.width.set(col);
218        self.height.set(row);
219
220        if row > 1 {
221            self.display_function
222                .replace(self.display_function.get() | LCD_2LINE);
223        }
224
225        self.num_lines.replace(row);
226        let _ = self.set_rows(0x00, 0x40, 0x00 + col, 0x40 + col);
227    }
228
229    pub fn screen_command(&self, command: usize, op: usize, value: u8) -> Result<(), ErrorCode> {
230        if self.lcd_status.get() == LCDStatus::Idle {
231            match command {
232                1 => {
233                    if op == 0 {
234                        self.display_control.set(self.display_control.get() | value);
235                    } else {
236                        self.display_control
237                            .set(self.display_control.get() & !value);
238                    }
239                    self.command_to_finish
240                        .replace(LCD_DISPLAYCONTROL | self.display_control.get());
241                    self.lcd_command(self.command_to_finish.get(), LCDStatus::Idle);
242                    Ok(())
243                }
244
245                2 => {
246                    self.lcd_clear(LCDStatus::Idle);
247                    Ok(())
248                }
249
250                _ => Err(ErrorCode::INVAL),
251            }
252        } else {
253            Err(ErrorCode::BUSY)
254        }
255    }
256
257    /// `set_rows()` sets initializing parameters for the communication.
258    ///
259    /// Example:
260    ///  self.set_rows(0x00, 0x40, 0x00+col, 0x40+col);
261    ///
262    fn set_rows(&self, row0: u8, row1: u8, row2: u8, row3: u8) -> Result<(), ErrorCode> {
263        self.row_offsets.map(|buffer| {
264            buffer[0] = row0;
265            buffer[1] = row1;
266            buffer[2] = row2;
267            buffer[3] = row3;
268        });
269        Ok(())
270    }
271
272    /// `pulse()` function starts executing the toggle needed by the device after
273    /// each write operation, according to the HD44780 datasheet, figure 26,
274    /// toggle that will be continued in the fired() function.
275    ///
276    /// As argument, there is:
277    ///  - the status of the program after the process of pulse is done
278    ///
279    /// Example:
280    ///  self.pulse(LCDStatus::Idle);
281    ///
282    fn pulse(&self, after_pulse_status: LCDStatus) {
283        self.lcd_after_pulse_status.set(after_pulse_status);
284        self.en_pin.clear();
285        self.set_delay(500, LCDStatus::PulseLow);
286    }
287
288    /// `write_4_bits()` will either set or clear each data_pin according to the
289    /// value to be written on the device.
290    ///
291    /// As arguments, there are:
292    ///  - the value to be written
293    ///  - the next status of the program after writing the value
294    ///
295    /// Example:
296    ///  self.write_4_bits(27, LCDStatus::Idle);
297    ///
298    fn write_4_bits(&self, value: u8, next_status: LCDStatus) {
299        if (value >> 0) & 0x01 != 0 {
300            self.data_4_pin.set();
301        } else {
302            self.data_4_pin.clear();
303        }
304
305        if (value >> 1) & 0x01 != 0 {
306            self.data_5_pin.set();
307        } else {
308            self.data_5_pin.clear();
309        }
310
311        if (value >> 2) & 0x01 != 0 {
312            self.data_6_pin.set();
313        } else {
314            self.data_6_pin.clear();
315        }
316
317        if (value >> 3) & 0x01 != 0 {
318            self.data_7_pin.set();
319        } else {
320            self.data_7_pin.clear();
321        }
322
323        self.pulse(next_status);
324    }
325
326    /// `continue_ops()` is called after an alarm is fired and continues to
327    /// execute the command from the state it was left in before the alarm
328    fn continue_ops(&self) {
329        let state = self.lcd_status.get();
330
331        match state {
332            // the execution of a command was just finished and a callback to the
333            // screen capsule will be sent (according to the command type)
334            LCDStatus::Idle => {
335                self.text_screen_client.map(|client| {
336                    if self.begin_done.get() {
337                        self.begin_done.set(false);
338                        self.initialized.set(true);
339                        client.command_complete(Ok(()));
340                    } else if self.write_len.get() > 0 {
341                        self.write_character();
342                    } else if self.done_printing.get() {
343                        self.done_printing.set(false);
344                        if self.write_buffer.is_some() {
345                            self.write_buffer.take().map(|buffer| {
346                                client.write_complete(
347                                    buffer,
348                                    self.write_buffer_len.get() as usize,
349                                    Ok(()),
350                                )
351                            });
352                        }
353                    } else {
354                        client.command_complete(Ok(()));
355                    }
356                });
357            }
358
359            LCDStatus::Begin0 => {
360                self.rs_pin.clear();
361                self.en_pin.clear();
362
363                if (self.display_function.get() & LCD_8BITMODE) == 0 {
364                    self.write_4_bits(0x03, LCDStatus::Begin0_1);
365                } else {
366                    self.rs_pin.clear();
367                    self.lcd_command(
368                        (LCD_FUNCTIONSET | self.display_function.get()) >> 4,
369                        LCDStatus::Begin4,
370                    );
371                }
372            }
373
374            LCDStatus::Begin0_1 => {
375                self.set_delay(200, LCDStatus::Begin1);
376            }
377
378            LCDStatus::Begin1 => {
379                self.write_4_bits(0x03, LCDStatus::Begin1_2);
380            }
381
382            LCDStatus::Begin1_2 => {
383                self.set_delay(200, LCDStatus::Begin2);
384            }
385
386            LCDStatus::Begin2 => {
387                self.write_4_bits(0x03, LCDStatus::Begin2_3);
388            }
389
390            LCDStatus::Begin2_3 => {
391                self.set_delay(500, LCDStatus::Begin3);
392            }
393
394            LCDStatus::Begin3 => {
395                self.write_4_bits(0x02, LCDStatus::Begin9);
396            }
397
398            LCDStatus::Begin4 => {
399                self.command_to_finish
400                    .set(LCD_FUNCTIONSET | self.display_function.get());
401                self.lcd_command(
402                    LCD_FUNCTIONSET | self.display_function.get(),
403                    LCDStatus::Begin5,
404                );
405            }
406
407            LCDStatus::Begin5 => self.set_delay(200, LCDStatus::Begin6),
408
409            LCDStatus::Begin6 => {
410                self.lcd_command(
411                    LCD_FUNCTIONSET | self.display_function.get(),
412                    LCDStatus::Begin7,
413                );
414            }
415
416            LCDStatus::Begin7 => {
417                self.set_delay(500, LCDStatus::Begin8);
418            }
419
420            LCDStatus::Begin8 => {
421                self.lcd_command(
422                    LCD_FUNCTIONSET | self.display_function.get(),
423                    LCDStatus::Begin9,
424                );
425            }
426
427            LCDStatus::Begin9 => {
428                self.command_to_finish
429                    .set(LCD_FUNCTIONSET | self.display_function.get());
430                self.lcd_command(
431                    LCD_FUNCTIONSET | self.display_function.get(),
432                    LCDStatus::Begin10,
433                );
434            }
435
436            LCDStatus::Begin10 => {
437                self.display_control
438                    .set(LCD_DISPLAYON | LCD_CURSORON | LCD_BLINKOFF);
439                self.lcd_display(LCDStatus::Begin11);
440            }
441
442            LCDStatus::Begin11 => {
443                self.lcd_clear(LCDStatus::Begin12);
444            }
445
446            LCDStatus::Begin12 => {
447                self.display_mode
448                    .set(LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT);
449                self.command_to_finish
450                    .set(LCD_ENTRYMODESET | self.display_mode.get());
451                self.begin_done.set(true);
452                self.lcd_command(self.command_to_finish.get(), LCDStatus::Idle);
453            }
454
455            LCDStatus::Clear => {
456                self.set_delay(500, self.lcd_after_delay_status.get());
457            }
458
459            LCDStatus::Printing => {
460                self.write_4_bits(self.command_to_finish.get(), LCDStatus::Idle);
461            }
462
463            LCDStatus::PulseLow => {
464                self.en_pin.set();
465                self.set_delay(500, LCDStatus::PulseHigh);
466            }
467
468            LCDStatus::Command => {
469                self.write_4_bits(
470                    self.command_to_finish.get(),
471                    self.lcd_after_command_status.get(),
472                );
473            }
474
475            LCDStatus::PulseHigh => {
476                self.en_pin.clear();
477                self.set_delay(500, self.lcd_after_pulse_status.get());
478            }
479        }
480    }
481
482    /// `lcd_display()` will call lcd_command with certain arguments for the display
483    /// initialization.
484    ///
485    /// As argument, there is:
486    ///  - the status of the program after setting the display
487    ///
488    /// Example:
489    ///  self.lcd_display(LCDStatus::Idle);
490    ///
491    fn lcd_display(&self, next_state: LCDStatus) {
492        self.command_to_finish
493            .set(LCD_DISPLAYCONTROL | self.display_control.get());
494        self.lcd_command(LCD_DISPLAYCONTROL | self.display_control.get(), next_state);
495    }
496
497    /// `lcd_command()` is the main function that communicates with the device, and
498    /// sends certain values received as arguments to the device (through
499    /// write_4_bits function). Due to the delays, the function is continued in
500    /// the fired() function.
501    ///
502    /// As arguments, there are:
503    ///  - the value to be sent to the device
504    ///  - the next status of the program after sending the value
505    ///
506    /// Example:
507    ///  self.lcd_command(LCD_CLEARDISPLAY, LCDStatus::Clear);
508    ///
509    fn lcd_command(&self, value: u8, next_state: LCDStatus) {
510        self.lcd_after_command_status.set(next_state);
511        self.command_to_finish.set(value);
512        self.rs_pin.clear();
513        self.write_4_bits(value >> 4, LCDStatus::Command);
514    }
515
516    /// `lcd_clear()` clears the lcd and brings the cursor at position (0,0).
517    ///
518    /// As argument, there is:
519    ///  - the status of the program after clearing the display
520    ///
521    /// Example:
522    ///  self.clear(LCDStatus::Idle);
523    ///
524    fn lcd_clear(&self, next_state: LCDStatus) {
525        self.lcd_after_delay_status.set(next_state);
526        self.lcd_command(LCD_CLEARDISPLAY, LCDStatus::Clear);
527    }
528
529    /// `set_delay()` sets an alarm and saved the next state after that.
530    ///
531    /// As argument, there are:
532    ///  - the duration of the alarm:
533    ///      - 10 means 100 ms
534    ///      - 100 means 10 ms
535    ///      - 500 means 2 ms
536    ///  - the status of the program after the alarm fires
537    ///
538    /// Example:
539    ///  self.set_delay(10, LCDStatus::Idle);
540    ///
541    fn set_delay(&self, timer: u32, next_status: LCDStatus) {
542        self.lcd_status.set(next_status);
543        self.alarm.set_alarm(
544            self.alarm.now(),
545            A::Ticks::from(<A::Frequency>::frequency() / timer),
546        );
547    }
548
549    /// `write_character()` will send the next character to be written on the
550    /// LCD display. The character is saved in the "write_buffer" buffer.
551    ///
552    /// Example:
553    /// - self.write_character();
554    ///
555    fn write_character(&self) {
556        let offset = self.write_offset.get() as usize;
557        let mut value = 0;
558        self.write_buffer.map(|buffer| {
559            value = buffer[offset];
560        });
561        self.done_printing.set(false);
562        self.write_offset.set(self.write_offset.get() + 1);
563        self.write_len.set(self.write_len.get() - 1);
564        if self.write_len.get() == 0 {
565            self.done_printing.set(true);
566        }
567        self.rs_pin.set();
568        self.command_to_finish.set(value);
569        self.write_4_bits(value >> 4, LCDStatus::Printing);
570    }
571
572    /// `set_cursor()` sends a command to the LCD display about the position for
573    /// the cursor to be set to.
574    ///
575    /// As argument, there are:
576    /// - the column for the position
577    /// - the row for the position
578    ///
579    /// Example:
580    /// - self.set_cursor(16,2);
581    ///
582    fn set_cursor(&self, col: u8, row: u8) {
583        let mut value: u8 = 0;
584        self.row_offsets.map(|buffer| {
585            value = buffer[row as usize];
586        });
587        self.command_to_finish
588            .replace(LCD_SETDDRAMADDR | (col + value));
589        self.lcd_command(self.command_to_finish.get(), LCDStatus::Idle);
590    }
591}
592
593impl<'a, A: Alarm<'a>> time::AlarmClient for HD44780<'a, A> {
594    /// `alarm()` is called after each alarm finished, and depending on the
595    /// current state of the program, the next step in being decided.
596    fn alarm(&self) {
597        self.continue_ops();
598    }
599}
600
601impl<'a, A: Alarm<'a>> TextScreen<'a> for HD44780<'a, A> {
602    fn get_size(&self) -> (usize, usize) {
603        (16, 2)
604    }
605
606    fn print(
607        &self,
608        buffer: &'static mut [u8],
609        len: usize,
610    ) -> Result<(), (ErrorCode, &'static mut [u8])> {
611        if self.lcd_status.get() == LCDStatus::Idle {
612            self.write_buffer.replace(buffer);
613            self.write_len.replace(len as u8);
614            self.write_buffer_len.replace(len as u8);
615            self.write_offset.set(0);
616            self.write_character();
617            Ok(())
618        } else {
619            Err((ErrorCode::BUSY, buffer))
620        }
621    }
622
623    fn set_cursor(&self, x_position: usize, y_position: usize) -> Result<(), ErrorCode> {
624        if self.lcd_status.get() == LCDStatus::Idle {
625            let mut line_number: u8 = y_position as u8;
626            if line_number >= 4 {
627                line_number = 3;
628            }
629
630            if line_number >= self.num_lines.get() {
631                line_number = self.num_lines.get() - 1;
632            }
633
634            self.set_cursor(x_position as u8, line_number);
635            Ok(())
636        } else {
637            Err(ErrorCode::BUSY)
638        }
639    }
640
641    fn hide_cursor(&self) -> Result<(), ErrorCode> {
642        self.screen_command(1, 1, LCD_CURSORON)
643    }
644
645    fn show_cursor(&self) -> Result<(), ErrorCode> {
646        self.screen_command(1, 0, LCD_CURSORON)
647    }
648
649    fn blink_cursor_on(&self) -> Result<(), ErrorCode> {
650        self.screen_command(1, 0, LCD_BLINKON)
651    }
652
653    fn blink_cursor_off(&self) -> Result<(), ErrorCode> {
654        self.screen_command(1, 1, LCD_BLINKON)
655    }
656
657    fn display_on(&self) -> Result<(), ErrorCode> {
658        if !self.initialized.get() {
659            if self.lcd_status.get() == LCDStatus::Idle {
660                self.set_delay(10, LCDStatus::Begin0);
661                Ok(())
662            } else {
663                Err(ErrorCode::BUSY)
664            }
665        } else {
666            self.screen_command(1, 0, LCD_DISPLAYON)
667        }
668    }
669
670    fn display_off(&self) -> Result<(), ErrorCode> {
671        self.screen_command(1, 1, LCD_DISPLAYON)
672    }
673
674    fn clear(&self) -> Result<(), ErrorCode> {
675        self.screen_command(2, 0, 0)
676    }
677
678    fn set_client(&self, client: Option<&'a dyn TextScreenClient>) {
679        if let Some(client) = client {
680            self.text_screen_client.set(client);
681        } else {
682            self.text_screen_client.clear();
683        }
684    }
685}