st7032i/
lib.rs

1//! A platform agnostic Rust driver for the ST7032i, based on the
2//! [`embedded-hal`](https://github.com/japaric/embedded-hal) traits.
3//!
4//! ## The Device
5//!
6//! The Sitronix ST7032i is a dot matrix LCD controller with I²C interface.
7//!
8//! - [Details and datasheet](http://www.newhavendisplay.com/app_notes/ST7032.pdf)
9//!
10//! ## Usage
11//!
12//! ### Instantiating
13//!
14//! Import this crate and an `embedded_hal` implementation:
15//!
16//! ```
17//! extern crate linux_embedded_hal as hal;
18//! extern crate st7032i;
19//! ```
20//!
21//! Then instantiate the device:
22//!
23//! ```no_run
24//! use core::fmt::Write;
25//! use linux_embedded_hal::{Delay, I2cdev};
26//! use st7032i::ST7032i;
27//!
28//! # fn main() {
29//! let dev = I2cdev::new("/dev/i2c-1")?;
30//! let mut display = ST7032i::new(dev, Delay, 2);
31//! display.init()?;
32//! write!(display, "Hello")?;
33//! display.move_cursor(1, 0)?;
34//! write!(display, "Rust")?;
35//! # }
36//! ```
37
38#![no_std]
39
40extern crate embedded_hal as hal;
41
42use core::fmt;
43use hal::blocking::delay::DelayMs;
44use hal::blocking::i2c::{Read, Write, WriteRead};
45
46pub const I2C_ADRESS: u8 = 0x3e;
47
48/// ST7032i instruction set
49#[derive(Debug, PartialEq)]
50enum InstructionSet {
51    Normal,
52    Extented,
53}
54
55/// Moving direction
56#[derive(Debug, PartialEq, Clone, Copy)]
57pub enum Direction {
58    LeftToRigh,
59    RightToLeft,
60}
61
62/// Driver for the ST7032i
63#[derive(Debug)]
64pub struct ST7032i<I2C, D> {
65    i2c: I2C,
66    delay: D,
67    entry: Direction,
68    lines: u8,
69    scroll: bool,
70    display: bool,
71    cursor: bool,
72    blink: bool,
73}
74
75impl<I2C, E, D> ST7032i<I2C, D>
76where
77    I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
78    D: DelayMs<u8>,
79{
80    /// Initialize the ST7032i driver.
81    pub fn new(i2c: I2C, delay: D, lines: u8) -> Self {
82        ST7032i {
83            i2c,
84            delay,
85            lines,
86            entry: Direction::RightToLeft,
87            scroll: false,
88            display: false,
89            cursor: false,
90            blink: false,
91        }
92    }
93
94    /// Initialize the display.
95    pub fn init(&mut self) -> Result<(), E> {
96        match self.send_function(InstructionSet::Normal, 1, false) {
97            Ok(_) => self.delay.delay_ms(1),
98            Err(_) => self.delay.delay_ms(20),
99        };
100
101        self.send_function(InstructionSet::Extented, 1, false)?;
102        self.delay.delay_ms(5);
103
104        self.send_function(InstructionSet::Extented, 1, false)?;
105        self.delay.delay_ms(5);
106
107        self.send_function(InstructionSet::Extented, self.lines, false)?;
108        self.delay.delay_ms(5);
109
110        self.off()?;
111
112        self.send_osc_config(true, 0)?;
113        self.send_contrast(0)?;
114        self.send_booster_config(true, false, 0)?;
115        self.send_follower_config(true, 0)?;
116
117        self.send_entry_mode()?;
118        self.delay.delay_ms(20);
119
120        self.on()?;
121
122        self.clear()
123    }
124
125    /// Switch display on
126    pub fn on(&mut self) -> Result<(), E> {
127        self.display = true;
128        self.send_display_mode()
129    }
130
131    /// Switch display off
132    pub fn off(&mut self) -> Result<(), E> {
133        self.display = false;
134        self.send_display_mode()
135    }
136
137    /// Clear all the display data by writing "20H" (space code)
138    /// to all DDRAM address, and set DDRAM address to "00H" into AC (address counter).
139    pub fn clear(&mut self) -> Result<(), E> {
140        const CLEAR_DISPLAY: u8 = 0b_00000001;
141        self.send_command(CLEAR_DISPLAY)?;
142        self.delay.delay_ms(2);
143        Ok(())
144    }
145
146    /// Set DDRAM address to "0" and return cursor to its original position if shifted.
147    /// The contents of DDRAM are not changed.
148    pub fn home(&mut self) -> Result<(), E> {
149        const RETURN_HOME: u8 = 0b_00000010;
150        self.send_command(RETURN_HOME)?;
151        self.delay.delay_ms(2);
152        Ok(())
153    }
154
155    /// Move cursor to specified location
156    pub fn move_cursor(&mut self, row: u8, col: u8) -> Result<(), E> {
157        let command = match row {
158            0 => col | 0b_10000000,
159            _ => col | 0b_11000000,
160        };
161        self.send_command(command)
162    }
163
164    /// Show cursor
165    pub fn show_cursor(&mut self, blink: bool) -> Result<(), E> {
166        self.cursor = true;
167        self.blink = blink;
168        self.send_display_mode()
169    }
170
171    /// Hide cursor
172    pub fn hide_cursor(&mut self) -> Result<(), E> {
173        self.cursor = false;
174        self.blink = false;
175        self.send_display_mode()
176    }
177
178    /// Enable autoscroll
179    pub fn enable_scroll(&mut self, entry: Direction) -> Result<(), E> {
180        self.scroll = true;
181        self.entry = entry;
182        self.send_entry_mode()
183    }
184
185    /// Disable autoscroll
186    pub fn disable_scroll(&mut self) -> Result<(), E> {
187        self.scroll = false;
188        self.send_entry_mode()
189    }
190
191    /// Shift display to specified direction
192    pub fn shift_display(&mut self, dir: Direction) -> Result<(), E> {
193        let mut command = 0b_00011000;
194        if dir == Direction::LeftToRigh {
195            command |= 0b_00000100;
196        }
197        self.send_command(command)
198    }
199
200    /// Shift cursor to specified direction
201    pub fn shift_cursor(&mut self, dir: Direction) -> Result<(), E> {
202        let mut command = 0b_00010000;
203        if dir == Direction::LeftToRigh {
204            command |= 0b_00000100;
205        }
206        self.send_command(command)
207    }
208
209    /// Create custom character in CGRAM
210    pub fn create_char(&mut self, offset: u8, bitmap: [u8; 8]) -> Result<(), E> {
211        self.send_command(0x40 | ((offset & 0x7) << 3))?;
212
213        let mut command = [0x40, 0];
214        for byte in bitmap.iter() {
215            command[1] = *byte;
216            self.i2c.write(I2C_ADRESS, &command).ok();
217        }
218
219        self.delay.delay_ms(1);
220        Ok(())
221    }
222
223    fn send_entry_mode(&mut self) -> Result<(), E> {
224        let mut command = 0b_00000100;
225        if self.scroll {
226            command |= 0b_00000001;
227        }
228        if self.entry == Direction::LeftToRigh {
229            command |= 0b_00000010;
230        }
231        self.send_command(command)
232    }
233
234    fn send_display_mode(&mut self) -> Result<(), E> {
235        let mut command = 0b_00001000;
236        if self.blink {
237            command |= 0b_00000001;
238        }
239        if self.cursor {
240            command |= 0b_00000010;
241        }
242        if self.display {
243            command |= 0b_00000100;
244        }
245        self.send_command(command)
246    }
247
248    fn send_function(&mut self, is: InstructionSet, lines: u8, dbl: bool) -> Result<(), E> {
249        let mut command = 0b_00110000;
250        if lines > 1 {
251            command |= 0b_00001000;
252        } else if dbl {
253            command |= 0b_00000100;
254        }
255        if is == InstructionSet::Extented {
256            command |= 0b_00000001;
257        }
258        self.send_command(command)
259    }
260
261    fn send_osc_config(&mut self, bias: bool, freq: u8) -> Result<(), E> {
262        assert!(freq < 8);
263        let mut command = 0b_00010000 | freq;
264        if bias {
265            command |= 0b_00001000;
266        }
267        self.send_command(command)
268    }
269
270    fn send_contrast(&mut self, contrast: u8) -> Result<(), E> {
271        assert!(contrast < 16);
272        self.send_command(0b_01110000 | contrast)
273    }
274
275    fn send_booster_config(&mut self, on: bool, icon: bool, contrast_low: u8) -> Result<(), E> {
276        assert!(contrast_low < 4);
277        let mut command = 0b_01010000 | contrast_low;
278        if on {
279            command |= 0b_00000100;
280        }
281        if icon {
282            command |= 0b_00001000;
283        }
284        self.send_command(command)
285    }
286
287    fn send_follower_config(&mut self, on: bool, ratio: u8) -> Result<(), E> {
288        assert!(ratio < 8);
289        let mut command = 0b_01100000 | ratio;
290        if on {
291            command |= 0b_00001000;
292        }
293        self.send_command(command)
294    }
295
296    fn send_command(&mut self, command: u8) -> Result<(), E> {
297        self.i2c.write(I2C_ADRESS, &[0x80, command])?;
298        self.delay.delay_ms(1);
299        Ok(())
300    }
301}
302
303impl<I2C, E, D> fmt::Write for ST7032i<I2C, D>
304where
305    I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
306    D: DelayMs<u8>,
307{
308    fn write_str(&mut self, s: &str) -> fmt::Result {
309        for c in s.chars() {
310            self.write_char(c)?;
311        }
312        self.delay.delay_ms(1);
313        Ok(())
314    }
315
316    fn write_char(&mut self, c: char) -> fmt::Result {
317        let mut command = [0x40, 0, 0, 0];
318        c.encode_utf8(&mut command[1..]);
319        self.i2c.write(I2C_ADRESS, &command[..2]).ok();
320        Ok(())
321    }
322}