mini_oled/
command.rs

1//! # Commands
2//!
3//! This module defines the commands that can be sent to the SH1106 display controller.
4//! It includes the `Command` enum and the `CommandBuffer` struct for batching commands.
5//!
6//! ## Example
7//!
8//! Sending a manual command sequence (pseudo-code as `CommunicationInterface` is needed).
9//!
10//! ```rust
11//! use mini_oled::command::{Command, CommandBuffer};
12//!
13//! let commands: CommandBuffer<2> = [
14//!     Command::TurnDisplayOn,
15//!     Command::Contrast(0xFF),
16//! ].into();
17//!
18//! // Write commands using the interface...
19//! // interface.write_command(&commands).unwrap();
20//! ```
21
22use crate::error::MiniOledError;
23
24/// A buffer for storing commands to be sent to the display.
25///
26/// This struct holds an array of `Command`s.
27#[derive(Debug, Clone, Copy)]
28pub struct CommandBuffer<const N: usize> {
29    buffer: [Command; N],
30}
31
32impl From<Command> for CommandBuffer<1> {
33    fn from(value: Command) -> Self {
34        CommandBuffer { buffer: [value] }
35    }
36}
37
38impl<const N: usize> From<[Command; N]> for CommandBuffer<N> {
39    fn from(value: [Command; N]) -> Self {
40        CommandBuffer { buffer: value }
41    }
42}
43
44impl<const N: usize> CommandBuffer<N> {
45    /// Serializes the command buffer into a byte slice.
46    ///
47    /// # Arguments
48    ///
49    /// * `buffer` - A mutable byte slice to write the serialized commands into.
50    ///
51    /// # Returns
52    ///
53    /// A slice containing the written bytes on success, or `MiniOledError` if the buffer is too small.
54    pub fn to_bytes<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], MiniOledError> {
55        let mut output_length = 1usize;
56        for command in &self.buffer {
57            let (command_bytes, bytes_length) = command.to_bytes();
58            if output_length + bytes_length > buffer.len() {
59                return Err(MiniOledError::CommandBufferSizeError);
60            }
61            buffer[output_length..output_length + bytes_length]
62                .copy_from_slice(&command_bytes[0..bytes_length]);
63            output_length += bytes_length;
64        }
65        Ok(&buffer[..output_length])
66    }
67}
68
69/// Enum representing commands that can be sent to the SH1106 controller.
70#[derive(Debug, Clone, Copy)]
71pub enum Command {
72    /// Set contrast. Higher number is higher contrast.
73    /// Default is `0x7F`.
74    Contrast(u8),
75    /// Forces the entire display to be on regardless of the contents of the display RAM.
76    /// It does not overwrite the RAM. Often used for testing pixels or creating a flash effect.
77    /// Sending `DisableTestSceen` resumes displaying the RAM content.
78    EnableTestScreen,
79    /// Disables test screen mode.
80    DisableTestScreen,
81    /// Inverts the display data.
82    /// Normally, a 1 in memory means a lit pixel. (`PositiveImageMode`)
83    /// When inverted, 0 means lit and 1 means off. (`NegativeImageMode`)
84    /// Default is `PositiveImageMode`.
85    PositiveImageMode,
86    /// Enable negative image mode.
87    NegativeImageMode,
88    /// Turns the display on.
89    TurnDisplayOn,
90    /// Puts the display into sleep mode.
91    /// In sleep mode (0xAE), the internal circuit is active but the driving circuit is off,
92    /// reducing power consumption drastically (< 20µA). RAM content is preserved.
93    TurnDisplayOff,
94    /// Set column address lower 4 bits.
95    ColumnAddressLow(u8),
96    /// Set column address higher 4 bits.
97    ColumnAddressHigh(u8),
98    /// Set page address.
99    PageAddress(Page),
100    /// Set display start line from 0-63.
101    StartLine(u8),
102    /// Reverse columns from 127-0, mirrors the display horizontally (X-axis).
103    /// Default is `DisableSegmentRemap`.
104    EnableSegmentRemap,
105    /// Disable segment remap (normal column order).
106    DisableSegmentRemap,
107    /// Set multipex ratio from 15-63 (MUX-1).
108    Multiplex(u8),
109    /// Scan from COM[n-1] to COM0 (where N is mux ratio).
110    /// Used together with `EnableSegmentRemap` to rotate the display 180 degrees.
111    /// Default is `DisableReverseComDir`.
112    EnableReverseComDir,
113    /// Disable reverse COM direction (normal scan).
114    DisableReverseComDir,
115    /// Set vertical display offset.
116    DisplayOffset(u8),
117    /// Setup COM hardware configuration.
118    /// Value indicates sequential (`SequentialComPinConfig`) or alternative (`AlternativeComPinConfig`)
119    /// pin configuration.
120    /// Default is `AlternativeComPinConfig`.
121    AlternativeComPinConfig,
122    /// Sequential COM pin configuration.
123    SequentialComPinConfig,
124    /// Set up display clock.
125    /// First value is oscillator frequency, increasing with higher value.
126    /// Second value is divide ratio - 1.
127    DisplayClockDiv(u8, u8),
128    /// Set up phase 1 and 2 of precharge period. Each value is from 0-63.
129    PreChargePeriod(u8, u8),
130    /// Set Vcomh Deselect level.
131    VcomhDeselect(VcomhLevel),
132    /// No Operation.
133    Noop,
134    /// Enable charge pump.
135    /// Display must be off when performing this command.
136    /// Default is `EnableChargePump`.
137    EnableChargePump,
138    /// Disable charge pump.
139    DisableChargePump,
140}
141
142impl Command {
143    pub fn to_bytes(&self) -> ([u8; 2], usize) {
144        match self {
145            Command::Contrast(val) => ([0x81, *val], self.get_byte_size()),
146            Command::EnableTestScreen => ([0xA5, 0], self.get_byte_size()),
147            Command::DisableTestScreen => ([0xA4, 0], self.get_byte_size()),
148            Command::PositiveImageMode => ([0xA6, 0], self.get_byte_size()),
149            Command::NegativeImageMode => ([0xA7, 0], self.get_byte_size()),
150            Command::TurnDisplayOn => ([0xAF, 0], self.get_byte_size()),
151            Command::TurnDisplayOff => ([0xAE, 0], self.get_byte_size()),
152            Command::ColumnAddressLow(addr) => ([0xF & addr, 0], self.get_byte_size()),
153            Command::ColumnAddressHigh(addr) => ([0x10 | (0xF & addr), 0], self.get_byte_size()),
154            Command::PageAddress(page) => ([0xB0 | (*page as u8), 0], self.get_byte_size()),
155            Command::StartLine(line) => ([0x40 | (0x3F & line), 0], self.get_byte_size()),
156            Command::EnableSegmentRemap => ([0xA1, 0], self.get_byte_size()),
157            Command::DisableSegmentRemap => ([0xA0, 0], self.get_byte_size()),
158            Command::Multiplex(ratio) => ([0xA8, *ratio], self.get_byte_size()),
159            Command::EnableReverseComDir => ([0xC8, 0], self.get_byte_size()),
160            Command::DisableReverseComDir => ([0xC0, 0], self.get_byte_size()),
161            Command::DisplayOffset(offset) => ([0xD3, *offset], self.get_byte_size()),
162            Command::AlternativeComPinConfig => ([0xDA, 0x12], self.get_byte_size()),
163            Command::SequentialComPinConfig => ([0xDA, 0x02], self.get_byte_size()),
164            Command::DisplayClockDiv(fosc, div) => (
165                [0xD5, ((0xF & fosc) << 4) | (0xF & div)],
166                self.get_byte_size(),
167            ),
168            Command::PreChargePeriod(phase1, phase2) => (
169                [0xD9, ((0xF & phase2) << 4) | (0xF & phase1)],
170                self.get_byte_size(),
171            ),
172            Command::VcomhDeselect(level) => ([0xDB, (*level as u8) << 4], self.get_byte_size()),
173            Command::Noop => ([0xE3, 0], self.get_byte_size()),
174            Command::EnableChargePump => ([0xAD, 0x8B], self.get_byte_size()),
175            Command::DisableChargePump => ([0xAD, 0x8A], self.get_byte_size()),
176        }
177    }
178
179    /// Returns the size in bytes of the command when serialized.
180    pub const fn get_byte_size(&self) -> usize {
181        match self {
182            Command::Contrast(_) => 2,
183            Command::EnableTestScreen => 1,
184            Command::DisableTestScreen => 1,
185            Command::PositiveImageMode => 1,
186            Command::NegativeImageMode => 1,
187            Command::TurnDisplayOn => 1,
188            Command::TurnDisplayOff => 1,
189            Command::ColumnAddressLow(_) => 1,
190            Command::ColumnAddressHigh(_) => 1,
191            Command::PageAddress(_) => 1,
192            Command::StartLine(_) => 1,
193            Command::EnableSegmentRemap => 1,
194            Command::DisableSegmentRemap => 1,
195            Command::Multiplex(_) => 2,
196            Command::EnableReverseComDir => 1,
197            Command::DisableReverseComDir => 1,
198            Command::DisplayOffset(_) => 2,
199            Command::AlternativeComPinConfig => 2,
200            Command::SequentialComPinConfig => 2,
201            Command::DisplayClockDiv(_, _) => 2,
202            Command::PreChargePeriod(_, _) => 2,
203            Command::VcomhDeselect(_) => 2,
204            Command::Noop => 1,
205            Command::EnableChargePump => 2,
206            Command::DisableChargePump => 2,
207        }
208    }
209}
210
211/// Display page address (0-7).
212///
213/// The display memory is divided into 8 pages, each 8 pixels high.
214///
215/// # Example
216///
217/// ```rust
218/// use mini_oled::command::Page;
219///
220/// let page = Page::Page0;
221/// assert_eq!(page as u8, 0);
222/// ```
223#[repr(u8)]
224#[derive(Debug, Clone, Copy)]
225pub enum Page {
226    /// Page 0
227    Page0 = 0,
228    /// Page 1
229    Page1 = 1,
230    /// Page 2
231    Page2 = 2,
232    /// Page 3
233    Page3 = 3,
234    /// Page 4
235    Page4 = 4,
236    /// Page 5
237    Page5 = 5,
238    /// Page 6
239    Page6 = 6,
240    /// Page 7
241    Page7 = 7,
242}
243
244impl Page {
245    /// Returns an iterator over a range of pages.
246    pub fn range(start: Page, end: Page) -> impl Iterator<Item = Page> {
247        (start as u8..=end as u8).map(Page::from)
248    }
249
250    /// Returns an iterator over all 8 pages (0-7).
251    pub fn all() -> impl Iterator<Item = Page> {
252        (0..8).map(Page::from)
253    }
254}
255
256impl From<u8> for Page {
257    fn from(val: u8) -> Page {
258        // Faster way the casting u8 to Page
259        // ```rust
260        // 0x00 => Page::Page0,
261        // 0x08 => Page::Page1,
262        // 0x09 => Page::Page2,
263        // 0x0A => Page::Page3,
264        // ```
265        let new_val = val & 0b111;
266        unsafe { core::mem::transmute(new_val) }
267    }
268}
269
270/// Frame interval configuration for the display clock.
271///
272/// This determines how often the display refreshes.
273///
274/// # Example
275///
276/// ```rust
277/// use mini_oled::command::NFrames;
278///
279/// let frames = NFrames::F5;
280/// ```
281#[repr(u8)]
282#[derive(Debug, Clone, Copy)]
283
284pub enum NFrames {
285    /// 2 Frames
286    F2 = 0b111,
287    /// 3 Frames
288    F3 = 0b100,
289    /// 4 Frames
290    F4 = 0b101,
291    /// 5 Frames
292    F5 = 0b000,
293    /// 25 Frames
294    F25 = 0b110,
295    /// 64 Frames
296    F64 = 0b001,
297    /// 128 Frames
298    F128 = 0b010,
299    /// 256 Frames
300    F256 = 0b011,
301}
302
303/// Vcomh Deselect level.
304///
305/// This adjusts the Vcomh regulator output.
306///
307/// # Example
308///
309/// ```rust
310/// use mini_oled::command::VcomhLevel;
311///
312/// let level = VcomhLevel::V077;
313/// ```
314#[repr(u8)]
315#[derive(Debug, Clone, Copy)]
316
317pub enum VcomhLevel {
318    /// 0.65 * Vcc
319    V065 = 0b001,
320    /// 0.77 * Vcc
321    V077 = 0b010,
322    /// 0.83 * Vcc
323    V083 = 0b011,
324    /// Auto
325    Auto = 0b100,
326}