clerk/
display.rs

1use core::marker::PhantomData;
2
3use super::address::Address;
4use super::{DisplayControlBuilder, EntryModeBuilder, FunctionSetBuilder, Home};
5use hal::{Init, ReadMode, Receive, Send, WriteMode};
6
7const LCD_WIDTH: usize = 16;
8
9bitflags! {
10    struct Instructions: u8 {
11        const CLEAR_DISPLAY     = 0b0000_0001;
12        const RETURN_HOME       = 0b0000_0010;
13        const SHIFT             = 0b0001_0000;
14    }
15}
16
17bitflags! {
18    struct ShiftTarget: u8 {
19        const CURSOR  = 0b0000_0000;
20        const DISPLAY = 0b0000_1000;
21    }
22}
23
24bitflags! {
25    struct ShiftDirection: u8 {
26        const RIGHT = 0b0000_0100;
27        const LEFT  = 0b0000_0000;
28    }
29}
30
31enum RamType {
32    DisplayData,
33    CharacterGenerator,
34}
35
36impl From<RamType> for u8 {
37    fn from(ram_type: RamType) -> Self {
38        match ram_type {
39            RamType::DisplayData => 0b1000_0000,
40            RamType::CharacterGenerator => 0b0100_0000,
41        }
42    }
43}
44
45/// Enumeration of possible methods to shift a cursor or display.
46pub enum ShiftTo {
47    /// Shifts to the right by the given offset.
48    Right(u8),
49    /// Shifts to the left by the given offset.
50    Left(u8),
51}
52
53impl ShiftTo {
54    fn as_offset_and_raw_direction(&self) -> (u8, ShiftDirection) {
55        match *self {
56            ShiftTo::Right(offset) => (offset, ShiftDirection::RIGHT),
57            ShiftTo::Left(offset) => (offset, ShiftDirection::LEFT),
58        }
59    }
60}
61
62/// Enumeration of possible methods to seek within a `Display` object.
63pub enum SeekFrom<T: Into<Address>> {
64    /// Sets the cursor position to `Home` plus the provided number of bytes.
65    Home(u8),
66    /// Sets the cursor to the current position plus the specified number of bytes.
67    Current(u8),
68    /// Sets the cursor position to the given line plus the specified number of bytes.
69    Line { line: T, bytes: u8 },
70}
71
72/// A HD44780 compliant display.
73///
74/// It provides a high-level and hardware agnostic interface to controll a HD44780 compliant
75/// liquid crystal display (LCD).
76pub struct Display<P, U>
77where
78    U: Into<Address> + Home,
79{
80    connection: P,
81    cursor_address: Address,
82    _line_marker: PhantomData<U>,
83}
84
85impl<P, U> Display<P, U>
86where
87    P: Init + Send + Receive,
88    U: Into<Address> + Home,
89{
90    const FIRST_4BIT_INIT_INSTRUCTION: WriteMode = WriteMode::Command(0x33);
91    const SECOND_4BIT_INIT_INSTRUCTION: WriteMode = WriteMode::Command(0x32);
92
93    /// Create a new `Display` using the given connection.
94    pub fn new(connection: P) -> Self {
95        Display {
96            connection: connection,
97            cursor_address: Address::from(0),
98            _line_marker: PhantomData,
99        }
100    }
101
102    pub fn init(&self, builder: &FunctionSetBuilder) {
103        self.connection.init();
104
105        let cmd = builder.build_command();
106        let cmd = WriteMode::Command(cmd);
107
108        self.init_by_instruction(cmd);
109    }
110
111    fn init_by_instruction(&self, function_set: WriteMode) {
112        self.connection.send(Self::FIRST_4BIT_INIT_INSTRUCTION);
113        self.connection.send(Self::SECOND_4BIT_INIT_INSTRUCTION);
114
115        self.connection.send(function_set);
116
117        self.clear();
118    }
119
120    /// Sets the entry mode of the display.
121    pub fn set_entry_mode(&self, builder: &EntryModeBuilder) {
122        let cmd = WriteMode::Command(builder.build_command());
123        self.connection.send(cmd);
124    }
125
126    /// Sets the display control settings.
127    pub fn set_display_control(&self, builder: &DisplayControlBuilder) {
128        let cmd = WriteMode::Command(builder.build_command());
129        self.connection.send(cmd);
130    }
131
132    /// Shifts the cursor to the left or the right by the given offset.
133    ///
134    /// **Note:** Consider to use [seek()](struct.Display.html#method.seek) for longer distances.
135    pub fn shift_cursor(&mut self, direction: ShiftTo) {
136        let (offset, raw_direction) = direction.as_offset_and_raw_direction();
137
138        if offset == 0 {
139            return;
140        }
141
142        match direction {
143            ShiftTo::Right(offset) => self.cursor_address += offset.into(),
144            ShiftTo::Left(offset) => self.cursor_address -= offset.into(),
145        }
146
147        self.raw_shift(ShiftTarget::CURSOR, offset, raw_direction);
148    }
149
150    /// Shifts the display to the right or the left by the given offset.
151    ///
152    /// Note that the first and second line will shift at the same time.
153    ///
154    /// When the displayed data is shifted repeatedly each line moves only horizontally.
155    /// The second line display does not shift into the first line position.
156    pub fn shift(&self, direction: ShiftTo) {
157        let (offset, raw_direction) = direction.as_offset_and_raw_direction();
158
159        self.raw_shift(ShiftTarget::DISPLAY, offset, raw_direction);
160    }
161
162    fn raw_shift(&self, shift_type: ShiftTarget, offset: u8, raw_direction: ShiftDirection) {
163        let mut cmd = Instructions::SHIFT.bits();
164
165        cmd |= shift_type.bits();
166        cmd |= raw_direction.bits();
167
168        for _ in 0..offset {
169            self.connection.send(WriteMode::Command(cmd));
170        }
171    }
172
173    /// Clears the entire display, sets the cursor to the home position and undo all display
174    /// shifts.
175    ///
176    /// It also sets the cursor's move direction to `Increment`.
177    pub fn clear(&self) {
178        let cmd = Instructions::CLEAR_DISPLAY.bits();
179        self.connection.send(WriteMode::Command(cmd));
180    }
181
182    fn generic_seek(&mut self, ram_type: RamType, pos: SeekFrom<U>) {
183        let mut cmd = ram_type.into();
184
185        let (start, addr) = match pos {
186            SeekFrom::Home(bytes) => (U::FIRST_LINE_ADDRESS.into(), bytes.into()),
187            SeekFrom::Current(bytes) => (self.cursor_address, bytes.into()),
188            SeekFrom::Line { line, bytes } => (line.into(), bytes.into()),
189        };
190
191        self.cursor_address = start + addr;
192
193        cmd |= u8::from(self.cursor_address);
194
195        self.connection.send(WriteMode::Command(cmd));
196    }
197
198    /// Seeks to an offset in display data RAM.
199    pub fn seek(&mut self, pos: SeekFrom<U>) {
200        self.generic_seek(RamType::DisplayData, pos);
201    }
202
203    /// Seeks to an offset in display character generator RAM.
204    pub fn seek_cgram(&mut self, pos: SeekFrom<U>) {
205        self.generic_seek(RamType::CharacterGenerator, pos);
206    }
207
208    /// Writes the given byte to data or character generator RAM, depending on the previous
209    /// seek operation.
210    pub fn write(&mut self, c: u8) {
211        self.cursor_address += Address::from(1);
212        self.connection.send(WriteMode::Data(c));
213    }
214
215    /// Reads a single byte from data RAM.
216    pub fn read_byte(&mut self) -> u8 {
217        self.cursor_address += Address::from(1);
218        self.connection.receive(ReadMode::Data)
219    }
220
221    /// Reads busy flag and the cursor's current address.
222    pub fn read_busy_flag(&self) -> (bool, u8) {
223        let byte = self.connection.receive(ReadMode::BusyFlag);
224
225        let busy_flag = (byte & 0b1000_0000) != 0;
226
227        let address = byte & 0b0111_1111;
228
229        (busy_flag, address)
230    }
231
232    /// Writes the given message to data or character generator RAM, depending on the previous
233    /// seek operation.
234    pub fn write_message(&mut self, msg: &str) {
235        for c in msg.as_bytes().iter().take(LCD_WIDTH) {
236            self.write(*c);
237        }
238    }
239
240    pub fn get_connection(self) -> P {
241        self.connection
242    }
243}