capsules_extra/
ssd1306.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 2023.
4
5//! SSD1306/SSD1315 OLED Screen
6
7use core::cell::Cell;
8use kernel::hil;
9use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell};
10use kernel::utilities::leasable_buffer::SubSliceMut;
11use kernel::ErrorCode;
12
13pub const BUFFER_SIZE: usize = 1032;
14
15const WIDTH: usize = 128;
16const HEIGHT: usize = 64;
17
18#[derive(Copy, Clone, PartialEq)]
19#[repr(usize)]
20pub enum Command {
21    // Charge Pump Commands
22    /// Charge Pump Setting.
23    SetChargePump { enable: bool },
24
25    // Fundamental Commands
26    /// SetContrastControl. Double byte command to select 1 out of 256 contrast
27    /// steps. Contrast increases as the value increases.
28    SetContrast { contrast: u8 },
29    /// Entire Display On.
30    EntireDisplayOn { ignore_ram: bool },
31    /// Set Normal Display.
32    SetDisplayInvert { inverse: bool },
33    /// Set Display Off.
34    SetDisplayOnOff { on: bool },
35
36    // Scrolling Commands
37    /// Continuous Horizontal Scroll. Right or Left Horizontal Scroll.
38    ContinuousHorizontalScroll {
39        left: bool,
40        page_start: u8,
41        interval: u8,
42        page_end: u8,
43    },
44    /// Continuous Vertical and Horizontal Scroll. Vertical and Right Horizontal
45    /// Scroll.
46    ContinuousVerticalHorizontalScroll {
47        left: bool,
48        page_start: u8,
49        interval: u8,
50        page_end: u8,
51        vertical_offset: u8,
52    },
53    /// Deactivate Scroll. Stop scrolling that is configured by scroll commands.
54    DeactivateScroll = 0x2e,
55    /// Activate Scroll. Start scrolling that is configured by scroll commands.
56    ActivateScroll = 0x2f,
57    /// Set Vertical Scroll Area. Set number of rows in top fixed area. The
58    /// number of rows in top fixed area is referenced to the top of the GDDRAM
59    /// (i.e. row 0).
60    SetVerticalScrollArea { rows_fixed: u8, rows_scroll: u8 },
61
62    // Addressing Setting Commands
63    /// Set Lower Column Start Address for Page Addressing Mode.
64    ///
65    /// Set the lower nibble of the column start address register for Page
66    /// Addressing Mode using `X[3:0]` as data bits. The initial display line
67    /// register is reset to 0000b after RESET.
68    SetLowerColumnStartAddress { address: u8 },
69    /// Set Higher Column Start Address for Page Addressing Mode.
70    ///
71    /// Set the higher nibble of the column start address register for Page
72    /// Addressing Mode using `X[3:0]` as data bits. The initial display line
73    /// register is reset to 0000b after RESET.
74    SetHigherColumnStartAddress { address: u8 },
75    /// Set Memory Addressing Mode.
76    SetMemoryAddressingMode { mode: u8 },
77    /// Set Column Address. Setup column start and end address.
78    SetColumnAddress { column_start: u8, column_end: u8 },
79    /// Set Page Address. Setup page start and end address.
80    SetPageAddress { page_start: u8, page_end: u8 },
81    /// Set Page Start Address for Page Addressing Mode. Set GDDRAM Page Start
82    /// Address (PAGE0~PAGE7) for Page Addressing Mode using `X[2:0]`.
83    SetPageStartAddress { address: u8 },
84
85    // Hardware Configuration Commands
86    /// Set Display Start Line. Set display RAM display start line register from
87    /// 0-63 using `X[5:0]`.
88    SetDisplayStartLine { line: u8 },
89    /// Set Segment Remap.
90    SetSegmentRemap { reverse: bool },
91    /// Set Multiplex Ratio.
92    SetMultiplexRatio { ratio: u8 },
93    /// Set COM Output Scan Direction.
94    SetComScanDirection { decrement: bool },
95    /// Set Display Offset. Set vertical shift by COM from 0-63.
96    SetDisplayOffset { vertical_shift: u8 } = 0xd3,
97    /// Set COM Pins Hardware Configuration
98    SetComPins { alternative: bool, enable_com: bool },
99
100    // Timing & Driving Scheme Setting Commands.
101    /// Set Display Clock Divide Ratio/Oscillator Frequency.
102    SetDisplayClockDivide {
103        divide_ratio: u8,
104        oscillator_frequency: u8,
105    },
106    /// Set Pre-charge Period.
107    SetPrechargePeriod { phase1: u8, phase2: u8 },
108    /// Set VCOMH Deselect Level.
109    SetVcomDeselect { level: u8 },
110}
111
112impl Command {
113    pub fn encode(self, buffer: &mut SubSliceMut<'static, u8>) {
114        let take = match self {
115            Self::SetChargePump { enable } => {
116                buffer[0] = 0x8D;
117                buffer[1] = 0x10 | ((enable as u8) << 2);
118                2
119            }
120            Self::SetContrast { contrast } => {
121                buffer[0] = 0x81;
122                buffer[1] = contrast;
123                2
124            }
125            Self::EntireDisplayOn { ignore_ram } => {
126                buffer[0] = 0xa4 | (ignore_ram as u8);
127                1
128            }
129            Self::SetDisplayInvert { inverse } => {
130                buffer[0] = 0xa6 | (inverse as u8);
131                1
132            }
133            Self::SetDisplayOnOff { on } => {
134                buffer[0] = 0xae | (on as u8);
135                1
136            }
137            Self::ContinuousHorizontalScroll {
138                left,
139                page_start,
140                interval,
141                page_end,
142            } => {
143                buffer[0] = 0x26 | (left as u8);
144                buffer[1] = 0;
145                buffer[2] = page_start;
146                buffer[3] = interval;
147                buffer[4] = page_end;
148                buffer[5] = 0;
149                buffer[6] = 0xff;
150                7
151            }
152            Self::ContinuousVerticalHorizontalScroll {
153                left,
154                page_start,
155                interval,
156                page_end,
157                vertical_offset,
158            } => {
159                buffer[0] = 0x29 | (left as u8);
160                buffer[1] = 0;
161                buffer[2] = page_start;
162                buffer[3] = interval;
163                buffer[4] = page_end;
164                buffer[5] = vertical_offset;
165                6
166            }
167            Self::DeactivateScroll => {
168                buffer[0] = 0x2e;
169                1
170            }
171            Self::ActivateScroll => {
172                buffer[0] = 0x2f;
173                1
174            }
175            Self::SetVerticalScrollArea {
176                rows_fixed,
177                rows_scroll,
178            } => {
179                buffer[0] = 0xa3;
180                buffer[1] = rows_fixed;
181                buffer[2] = rows_scroll;
182                3
183            }
184            Self::SetLowerColumnStartAddress { address } => {
185                buffer[0] = 0x00 | (address & 0xF);
186                1
187            }
188            Self::SetHigherColumnStartAddress { address } => {
189                buffer[0] = 0x10 | ((address >> 4) & 0xF);
190                1
191            }
192            Self::SetMemoryAddressingMode { mode } => {
193                buffer[0] = 0x20;
194                buffer[1] = mode;
195                2
196            }
197            Self::SetColumnAddress {
198                column_start,
199                column_end,
200            } => {
201                buffer[0] = 0x21;
202                buffer[1] = column_start;
203                buffer[2] = column_end;
204                3
205            }
206            Self::SetPageAddress {
207                page_start,
208                page_end,
209            } => {
210                buffer[0] = 0x22;
211                buffer[1] = page_start;
212                buffer[2] = page_end;
213                3
214            }
215            Self::SetPageStartAddress { address } => {
216                buffer[0] = 0xb0 | (address & 0x7);
217                1
218            }
219            Self::SetDisplayStartLine { line } => {
220                buffer[0] = 0x40 | (line & 0x3F);
221                1
222            }
223            Self::SetSegmentRemap { reverse } => {
224                buffer[0] = 0xa0 | (reverse as u8);
225                1
226            }
227            Self::SetMultiplexRatio { ratio } => {
228                buffer[0] = 0xa8;
229                buffer[1] = ratio;
230                2
231            }
232            Self::SetComScanDirection { decrement } => {
233                buffer[0] = 0xc0 | ((decrement as u8) << 3);
234                1
235            }
236            Self::SetDisplayOffset { vertical_shift } => {
237                buffer[0] = 0xd3;
238                buffer[1] = vertical_shift;
239                2
240            }
241            Self::SetComPins {
242                alternative,
243                enable_com,
244            } => {
245                buffer[0] = 0xda;
246                buffer[1] = ((alternative as u8) << 4) | ((enable_com as u8) << 5) | 0x2;
247                2
248            }
249            Self::SetDisplayClockDivide {
250                divide_ratio,
251                oscillator_frequency,
252            } => {
253                buffer[0] = 0xd5;
254                buffer[1] = ((oscillator_frequency & 0xF) << 4) | (divide_ratio & 0xf);
255                2
256            }
257            Self::SetPrechargePeriod { phase1, phase2 } => {
258                buffer[0] = 0xd9;
259                buffer[1] = ((phase2 & 0xF) << 4) | (phase1 & 0xf);
260                2
261            }
262            Self::SetVcomDeselect { level } => {
263                buffer[0] = 0xdb;
264                buffer[1] = (level & 0xF) << 4;
265                2
266            }
267        };
268
269        // Move the available region of the buffer to what is remaining after
270        // this command was encoded.
271        buffer.slice(take..);
272    }
273}
274
275// #[derive(Copy, Clone, PartialEq)]
276#[derive(Clone, Copy, PartialEq)]
277enum State {
278    Idle,
279    Init,
280    SimpleCommand,
281    Write,
282}
283
284pub struct Ssd1306<'a, I: hil::i2c::I2CDevice> {
285    i2c: &'a I,
286    state: Cell<State>,
287    client: OptionalCell<&'a dyn hil::screen::ScreenClient>,
288    setup_client: OptionalCell<&'a dyn hil::screen::ScreenSetupClient>,
289    buffer: TakeCell<'static, [u8]>,
290    write_buffer: MapCell<SubSliceMut<'static, u8>>,
291    enable_charge_pump: bool,
292}
293
294impl<'a, I: hil::i2c::I2CDevice> Ssd1306<'a, I> {
295    pub fn new(i2c: &'a I, buffer: &'static mut [u8], enable_charge_pump: bool) -> Ssd1306<'a, I> {
296        Ssd1306 {
297            i2c,
298            state: Cell::new(State::Idle),
299            client: OptionalCell::empty(),
300            setup_client: OptionalCell::empty(),
301            buffer: TakeCell::new(buffer),
302            write_buffer: MapCell::empty(),
303            enable_charge_pump,
304        }
305    }
306
307    pub fn init_screen(&self) {
308        let commands = [
309            Command::SetDisplayOnOff { on: false },
310            Command::SetDisplayClockDivide {
311                divide_ratio: 0,
312                oscillator_frequency: 0x8,
313            },
314            Command::SetMultiplexRatio {
315                ratio: HEIGHT as u8 - 1,
316            },
317            Command::SetDisplayOffset { vertical_shift: 0 },
318            Command::SetDisplayStartLine { line: 0 },
319            Command::SetChargePump {
320                enable: self.enable_charge_pump,
321            },
322            Command::SetMemoryAddressingMode { mode: 0 }, //horizontal
323            Command::SetSegmentRemap { reverse: true },
324            Command::SetComScanDirection { decrement: true },
325            Command::SetComPins {
326                alternative: true,
327                enable_com: false,
328            },
329            Command::SetContrast { contrast: 0xcf },
330            Command::SetPrechargePeriod {
331                phase1: 0x1,
332                phase2: 0xf,
333            },
334            Command::SetVcomDeselect { level: 2 },
335            Command::EntireDisplayOn { ignore_ram: false },
336            Command::SetDisplayInvert { inverse: false },
337            Command::DeactivateScroll,
338            Command::SetDisplayOnOff { on: true },
339        ];
340
341        match self.send_sequence(&commands) {
342            Ok(()) => {
343                self.state.set(State::Init);
344            }
345            Err(_e) => {}
346        }
347    }
348
349    fn send_sequence(&self, sequence: &[Command]) -> Result<(), ErrorCode> {
350        if self.state.get() == State::Idle {
351            self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
352                let mut buf_slice = SubSliceMut::new(buffer);
353
354                // Specify this is a series of command bytes.
355                buf_slice[0] = 0; // Co = 0, D/C̅ = 0
356
357                // Move the window of the subslice after the command byte header.
358                buf_slice.slice(1..);
359
360                for cmd in sequence.iter() {
361                    cmd.encode(&mut buf_slice);
362                }
363
364                // We need the amount of data that has been sliced away
365                // at the start of the subslice.
366                let remaining_len = buf_slice.len();
367                buf_slice.reset();
368                let tx_len = buf_slice.len() - remaining_len;
369
370                self.i2c.enable();
371                match self.i2c.write(buf_slice.take(), tx_len) {
372                    Ok(()) => Ok(()),
373                    Err((_e, buf)) => {
374                        self.buffer.replace(buf);
375                        self.i2c.disable();
376                        Err(ErrorCode::INVAL)
377                    }
378                }
379            })
380        } else {
381            Err(ErrorCode::BUSY)
382        }
383    }
384}
385
386impl<'a, I: hil::i2c::I2CDevice> hil::screen::ScreenSetup<'a> for Ssd1306<'a, I> {
387    fn set_client(&self, client: &'a dyn hil::screen::ScreenSetupClient) {
388        self.setup_client.set(client);
389    }
390
391    fn set_resolution(&self, _resolution: (usize, usize)) -> Result<(), ErrorCode> {
392        Err(ErrorCode::NOSUPPORT)
393    }
394
395    fn set_pixel_format(&self, _depth: hil::screen::ScreenPixelFormat) -> Result<(), ErrorCode> {
396        Err(ErrorCode::NOSUPPORT)
397    }
398
399    fn set_rotation(&self, _rotation: hil::screen::ScreenRotation) -> Result<(), ErrorCode> {
400        Err(ErrorCode::NOSUPPORT)
401    }
402
403    fn get_num_supported_resolutions(&self) -> usize {
404        1
405    }
406
407    fn get_supported_resolution(&self, index: usize) -> Option<(usize, usize)> {
408        match index {
409            0 => Some((WIDTH, HEIGHT)),
410            _ => None,
411        }
412    }
413
414    fn get_num_supported_pixel_formats(&self) -> usize {
415        1
416    }
417
418    fn get_supported_pixel_format(&self, index: usize) -> Option<hil::screen::ScreenPixelFormat> {
419        match index {
420            0 => Some(hil::screen::ScreenPixelFormat::Mono),
421            _ => None,
422        }
423    }
424}
425
426impl<'a, I: hil::i2c::I2CDevice> hil::screen::Screen<'a> for Ssd1306<'a, I> {
427    fn set_client(&self, client: &'a dyn hil::screen::ScreenClient) {
428        self.client.set(client);
429    }
430
431    fn get_resolution(&self) -> (usize, usize) {
432        (WIDTH, HEIGHT)
433    }
434
435    fn get_pixel_format(&self) -> hil::screen::ScreenPixelFormat {
436        hil::screen::ScreenPixelFormat::Mono
437    }
438
439    fn get_rotation(&self) -> hil::screen::ScreenRotation {
440        hil::screen::ScreenRotation::Normal
441    }
442
443    fn set_write_frame(
444        &self,
445        x: usize,
446        y: usize,
447        width: usize,
448        height: usize,
449    ) -> Result<(), ErrorCode> {
450        let commands = [
451            Command::SetPageAddress {
452                page_start: (y / 8) as u8,
453                page_end: ((y / 8) + (height / 8) - 1) as u8,
454            },
455            Command::SetColumnAddress {
456                column_start: x as u8,
457                column_end: (x + width - 1) as u8,
458            },
459        ];
460        match self.send_sequence(&commands) {
461            Ok(()) => {
462                self.state.set(State::SimpleCommand);
463                Ok(())
464            }
465            Err(e) => Err(e),
466        }
467    }
468
469    fn write(&self, data: SubSliceMut<'static, u8>, _continue: bool) -> Result<(), ErrorCode> {
470        self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| {
471            let mut buf_slice = SubSliceMut::new(buffer);
472
473            // Specify this is data.
474            buf_slice[0] = 0x40; // Co = 0, D/C̅ = 1
475
476            // Move the window of the subslice after the command byte header.
477            buf_slice.slice(1..);
478
479            // Figure out how much we can send.
480            let copy_len = core::cmp::min(buf_slice.len(), data.len());
481
482            for i in 0..copy_len {
483                buf_slice[i] = data[i];
484            }
485
486            let tx_len = copy_len + 1;
487
488            self.i2c.enable();
489            match self.i2c.write(buf_slice.take(), tx_len) {
490                Ok(()) => {
491                    self.state.set(State::Write);
492                    self.write_buffer.replace(data);
493                    Ok(())
494                }
495                Err((_e, buf)) => {
496                    self.buffer.replace(buf);
497                    Err(ErrorCode::INVAL)
498                }
499            }
500        })
501    }
502
503    fn set_brightness(&self, brightness: u16) -> Result<(), ErrorCode> {
504        let commands = [Command::SetContrast {
505            contrast: (brightness >> 8) as u8,
506        }];
507        match self.send_sequence(&commands) {
508            Ok(()) => {
509                self.state.set(State::SimpleCommand);
510                Ok(())
511            }
512            Err(e) => Err(e),
513        }
514    }
515
516    fn set_power(&self, enabled: bool) -> Result<(), ErrorCode> {
517        let commands = [Command::SetDisplayOnOff { on: enabled }];
518        match self.send_sequence(&commands) {
519            Ok(()) => {
520                self.state.set(State::SimpleCommand);
521                Ok(())
522            }
523            Err(e) => Err(e),
524        }
525    }
526
527    fn set_invert(&self, enabled: bool) -> Result<(), ErrorCode> {
528        let commands = [Command::SetDisplayInvert { inverse: enabled }];
529        match self.send_sequence(&commands) {
530            Ok(()) => {
531                self.state.set(State::SimpleCommand);
532                Ok(())
533            }
534            Err(e) => Err(e),
535        }
536    }
537}
538
539impl<I: hil::i2c::I2CDevice> hil::i2c::I2CClient for Ssd1306<'_, I> {
540    fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), hil::i2c::Error>) {
541        self.buffer.replace(buffer);
542        self.i2c.disable();
543
544        match self.state.get() {
545            State::Init => {
546                self.state.set(State::Idle);
547                self.client.map(|client| client.screen_is_ready());
548            }
549
550            State::SimpleCommand => {
551                self.state.set(State::Idle);
552                self.client.map(|client| client.command_complete(Ok(())));
553            }
554
555            State::Write => {
556                self.state.set(State::Idle);
557                self.write_buffer.take().map(|buf| {
558                    self.client.map(|client| client.write_complete(buf, Ok(())));
559                });
560            }
561            _ => {}
562        }
563    }
564}