1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//! Unbuffered terminal display mode
//!
//! This mode uses the 7x7 pixel [MarioChrome](https://github.com/techninja/MarioChron/) font to
//! draw characters to the display without needing a framebuffer. It will write characters from top
//! left to bottom right in an 8x8 pixel grid, restarting at the top left of the display once full.
//! The display itself takes care of wrapping lines.
//!
//! ```rust,ignore
//! let i2c = /* I2C interface from your HAL of choice */;
//! let display: TerminalMode<_> = Builder::new().connect_i2c(i2c).into();
//!
//! display.init().unwrap();
//! display.clear().unwrap();
//!
//! // Print a-zA-Z
//! for c in 97..123 {
//!     display.write_str(unsafe { core::str::from_utf8_unchecked(&[c]) }).unwrap();
//! }
//! ```

use crate::displayrotation::DisplayRotation;
use crate::displaysize::DisplaySize;
use crate::interface::DisplayInterface;
use crate::mode::displaymode::DisplayModeTrait;
use crate::properties::DisplayProperties;
use core::fmt;
use hal::blocking::delay::DelayMs;
use hal::digital::OutputPin;

/// A trait to convert from a character to 8x8 bitmap
pub trait CharacterBitmap<T> {
    /// Turn input of type T into a displayable 8x8 bitmap
    fn to_bitmap(input: T) -> [u8; 8];
}

/// A 7x7 font shamelessly borrowed from https://github.com/techninja/MarioChron/
impl<DI> CharacterBitmap<char> for TerminalMode<DI>
where
    DI: DisplayInterface,
{
    fn to_bitmap(input: char) -> [u8; 8] {
        // Populate the array with the data from the character array at the right index
        match input {
            '!' => [0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00],
            '"' => [0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00],
            '#' => [0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00, 0x00],
            '$' => [0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00, 0x00],
            '%' => [0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00, 0x00],
            '&' => [0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00, 0x00],
            '\'' => [0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00],
            '(' => [0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00],
            ')' => [0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00],
            '*' => [0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00, 0x00],
            '+' => [0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00],
            ',' => [0x00, 0x50, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00],
            '-' => [0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00],
            '.' => [0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00],
            '/' => [0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00],
            '0' => [0x1C, 0x3E, 0x61, 0x41, 0x43, 0x3E, 0x1C, 0x00],
            '1' => [0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00],
            '2' => [0x62, 0x73, 0x79, 0x59, 0x5D, 0x4F, 0x46, 0x00],
            '3' => [0x20, 0x61, 0x49, 0x4D, 0x4F, 0x7B, 0x31, 0x00],
            '4' => [0x18, 0x1C, 0x16, 0x13, 0x7F, 0x7F, 0x10, 0x00],
            '5' => [0x27, 0x67, 0x45, 0x45, 0x45, 0x7D, 0x38, 0x00],
            '6' => [0x3C, 0x7E, 0x4B, 0x49, 0x49, 0x79, 0x30, 0x00],
            '7' => [0x03, 0x03, 0x71, 0x79, 0x0D, 0x07, 0x03, 0x00],
            '8' => [0x36, 0x7F, 0x49, 0x49, 0x49, 0x7F, 0x36, 0x00],
            '9' => [0x06, 0x4F, 0x49, 0x49, 0x69, 0x3F, 0x1E, 0x00],
            ':' => [0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00],
            ';' => [0x00, 0x56, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00],
            '<' => [0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00],
            '=' => [0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00],
            '>' => [0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00, 0x00],
            '?' => [0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00, 0x00],
            '@' => [0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00, 0x00],
            'A' => [0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, 0x00, 0x00],
            'B' => [0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, 0x00],
            'C' => [0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00, 0x00],
            'D' => [0x7F, 0x7F, 0x41, 0x41, 0x63, 0x3E, 0x1C, 0x00],
            'E' => [0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00, 0x00],
            'F' => [0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00],
            'G' => [0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, 0x00, 0x00],
            'H' => [0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00, 0x00],
            'I' => [0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, 0x00],
            'J' => [0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00, 0x00],
            'K' => [0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00],
            'L' => [0x7F, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00],
            'M' => [0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, 0x00, 0x00],
            'N' => [0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00, 0x00],
            'O' => [0x3E, 0x7F, 0x41, 0x41, 0x41, 0x7F, 0x3E, 0x00],
            'P' => [0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00],
            'Q' => [0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00, 0x00],
            'R' => [0x7F, 0x7F, 0x11, 0x31, 0x79, 0x6F, 0x4E, 0x00],
            'S' => [0x46, 0x49, 0x49, 0x49, 0x31, 0x00, 0x00, 0x00],
            'T' => [0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00, 0x00],
            'U' => [0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00, 0x00],
            'V' => [0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00, 0x00],
            'W' => [0x7F, 0x7F, 0x38, 0x1C, 0x38, 0x7F, 0x7F, 0x00],
            'X' => [0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00, 0x00],
            'Y' => [0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00, 0x00],
            'Z' => [0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00, 0x00],
            '[' => [0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00],
            '\\' => [0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00],
            ']' => [0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00],
            '^' => [0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00],
            '_' => [0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00],
            '`' => [0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00],
            'a' => [0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00, 0x00],
            'b' => [0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00],
            'c' => [0x38, 0x44, 0x44, 0x44, 0x20, 0x00, 0x00, 0x00],
            'd' => [0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00, 0x00],
            'e' => [0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00, 0x00],
            'f' => [0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, 0x00, 0x00],
            'g' => [0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, 0x00, 0x00],
            'h' => [0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00, 0x00],
            'i' => [0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, 0x00, 0x00],
            'j' => [0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, 0x00, 0x00],
            'k' => [0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00],
            'l' => [0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00],
            'm' => [0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00, 0x00],
            'n' => [0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00, 0x00],
            'o' => [0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00],
            'p' => [0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, 0x00, 0x00],
            'q' => [0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, 0x00, 0x00],
            'r' => [0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, 0x00, 0x00],
            's' => [0x48, 0x54, 0x54, 0x54, 0x20, 0x00, 0x00, 0x00],
            't' => [0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, 0x00, 0x00],
            'u' => [0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, 0x00, 0x00],
            'v' => [0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00, 0x00],
            'w' => [0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00, 0x00],
            'x' => [0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00],
            'y' => [0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, 0x00, 0x00],
            'z' => [0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00, 0x00],
            '{' => [0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x00],
            '|' => [0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00],
            '}' => [0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, 0x00],
            _ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
        }
    }
}

// TODO: Add to prelude
/// Terminal mode handler
pub struct TerminalMode<DI> {
    properties: DisplayProperties<DI>,
}

impl<DI> DisplayModeTrait<DI> for TerminalMode<DI>
where
    DI: DisplayInterface,
{
    /// Create new TerminalMode instance
    fn new(properties: DisplayProperties<DI>) -> Self {
        TerminalMode { properties }
    }

    /// Release all resources used by TerminalMode
    fn release(self) -> DisplayProperties<DI> {
        self.properties
    }
}

impl<DI> TerminalMode<DI>
where
    DI: DisplayInterface,
{
    /// Clear the display
    pub fn clear(&mut self) -> Result<(), ()> {
        let display_size = self.properties.get_size();

        let numchars = match display_size {
            DisplaySize::Display128x64 => 128,
            DisplaySize::Display128x32 => 64,
            DisplaySize::Display96x16 => 24,
        };

        // Reset position so we don't end up in some random place of our cleared screen
        let (display_width, display_height) = self.properties.get_size().dimensions();
        self.properties
            .set_draw_area((0, 0), (display_width, display_height))?;

        for _ in 0..numchars {
            self.properties.draw(&[0; 8])?;
        }

        Ok(())
    }

    /// Reset display
    pub fn reset<RST, DELAY>(&mut self, rst: &mut RST, delay: &mut DELAY)
    where
        RST: OutputPin,
        DELAY: DelayMs<u8>,
    {
        rst.set_high();
        delay.delay_ms(1);
        rst.set_low();
        delay.delay_ms(10);
        rst.set_high();
    }

    /// Write out data to display. This is a noop in terminal mode.
    pub fn flush(&mut self) -> Result<(), ()> {
        Ok(())
    }

    /// Print a character to the display
    pub fn print_char<T>(&mut self, c: T) -> Result<(), ()>
    where
        TerminalMode<DI>: CharacterBitmap<T>,
    {
        // Send the pixel data to the display
        self.properties.draw(&Self::to_bitmap(c))?;
        Ok(())
    }

    /// Initialise the display in column mode (i.e. a byte walks down a column of 8 pixels) with
    /// column 0 on the left and column _(display_width - 1)_ on the right.
    pub fn init(&mut self) -> Result<(), ()> {
        self.properties.init_column_mode()?;
        Ok(())
    }

    /// Set the display rotation
    pub fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), ()> {
        self.properties.set_rotation(rot)
    }
}

impl<DI> fmt::Write for TerminalMode<DI>
where
    DI: DisplayInterface,
{
    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
        s.chars().map(move |c| self.print_char(c)).last();
        Ok(())
    }
}