Basic src migration from private repo

This commit is contained in:
Corey Vixie 2020-03-29 11:24:42 -07:00
parent c2760375fe
commit 9df39ab4ae
6 changed files with 1202 additions and 0 deletions

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "st7701s"
version = "0.1.0"
authors = ["Corey Vixie <corey@vixie.enterprises>"]
edition = "2018"
[dependencies]
num = "0.2.0"
spidev = "0.4.0"
enum_primitive = "0.1.1"

0
rustfmt.toml Normal file
View File

861
src/instructions.rs Normal file
View File

@ -0,0 +1,861 @@
use std::convert::TryInto;
use crate::panel::Mode;
/// This is a 3-wire SPI implementation. Reads and writes share the SDA pin and
/// are performed half-duplex
///
/// Unless otherwise noted, any command pairs that set and unset a "mode" (eg.
/// DISPON/DISPOFF) will have no effect if the display is already in the mode
/// being requested. Therefore, these commands should be safe to use in a
/// "write-only, read-never" workflow.
///
/// NOTE: Some commands take many separate parameter words, most of which have at
/// least 8 bits of variability. Because of this, they aren't enumerated and
/// the vector is passed directly through.
/// The write mode of the interface means the micro controller writes
/// commands and data to the LCD driver. 3-lines serial data packet contains
/// a control bit D/CX and a transmission byte. In 4-lines serial interface,
/// data packet contains just transmission byte and control bit D/CX is
/// transferred by the D/CX pin. If D/CX is “low”, the transmission byte is
/// interpreted as a command byte. If D/CX is “high”, the transmission byte
/// is command register as parameter.
#[derive(Clone)]
pub struct Command {
pub address: u8,
pub parameters: Vec<u8>,
}
impl Command {
fn new(address: u8) -> Command {
Command {
address: address,
parameters: Vec::new(),
}
}
fn arg(mut self, arg: u8) -> Command {
self.parameters.push(arg);
self
}
fn args(mut self, args: &[u8]) -> Command {
self.parameters.extend_from_slice(args);
self
}
pub fn serialize_address(&self) -> [u8; 2] {
[(self.address as u8), 0x00]
}
pub fn serialize_parameter(parameter: u8) -> [u8; 2] {
[parameter, 0x01]
}
}
#[derive(Copy, Clone)]
pub enum GammaCurve {
/// Gamma Curve 1 (G=2.2)
One = 0x01,
/// Reserved
Two = 0x02,
/// Reserved
Three = 0x04,
/// Reserved
Four = 0x08,
}
#[derive(Copy, Clone)]
pub enum TearingEffect {
/// V-blanking only
VBlank = 0x00,
/// V-blanking and H-blanking
VHBlank = 0x01,
}
#[derive(Copy, Clone)]
pub enum DataEnable {
DE = 0x00,
HV = 0x80,
}
#[derive(Copy, Clone)]
pub enum VsyncActive {
Low = 0x00,
High = 0x08,
}
#[derive(Copy, Clone)]
pub enum HsyncActive {
Low = 0x00,
High = 0x04,
}
#[derive(Copy, Clone)]
pub enum DataPolarity {
Rising = 0x00,
Falling = 0x02,
}
#[derive(Copy, Clone)]
pub enum EnablePolarity {
Low = 0x00,
High = 0x01,
}
#[derive(Copy, Clone)]
pub enum PWMPolarity {
Low = 0x00,
High = 0x20,
}
#[derive(Copy, Clone)]
pub enum LEDPolarity {
Low = 0x00,
High = 0x10,
}
#[derive(Copy, Clone)]
pub enum PixelPinout {
Normal = 0x00,
Condensed = 0x08,
}
#[derive(Copy, Clone)]
pub enum EndPixelFormat {
SelfMSB = 0x00,
GreenMSB = 0x01,
SelfLSB = 0x02,
Zero = 0x04,
One = 0x05,
}
#[derive(Copy, Clone)]
pub enum ScanDirection {
Normal = 0x00,
Reverse = 0x10,
}
#[derive(Copy, Clone)]
pub enum ColorOrder {
/// RGB mode
Rgb = 0x00,
/// BGR mode
Bgr = 0x08,
}
#[derive(Copy, Clone)]
pub enum BitsPerPixel {
/// 16 bits per pixel (RGB565)
Rgb565 = 0x50,
/// 18 bits per pixel (RGB666)
Rgb666 = 0x60,
/// 24 bits per pixel (RGB888)
Rgb888 = 0x70,
}
#[derive(Copy, Clone)]
pub enum BrightnessControl {
/// Ignore display brightness value and soft-set it to 0x00
Off = 0x00,
/// Use display brightness value normally
On = 0x20,
}
#[derive(Copy, Clone)]
pub enum DisplayDimming {
/// Ignore display brightness value and soft-set it to 0x00
Off = 0x00,
/// Use display brightness value normally
On = 0x08,
}
#[derive(Copy, Clone)]
pub enum Backlight {
/// Disable backlight circuit. Control lines must be low.
Off = 0x00,
/// Enable backlight circuit. Normal behavior.
On = 0x04,
}
#[derive(Copy, Clone)]
pub enum Enhancement {
/// Disable color enhancement
Off = 0x00,
/// Enable color enhancement
On = 0x80,
}
#[derive(Copy, Clone)]
pub enum EnhancementMode {
Low = 0x00,
Medium = 0x10,
High = 0x30,
}
#[derive(Copy, Clone)]
pub enum AdaptiveBrightness {
/// Off
Off = 0x00,
/// User Interface Mode
UserInterface = 0x01,
/// Still Picture Mode
StillPicture = 0x02,
/// Moving Image Mode
MovingImage = 0x03,
}
#[derive(Copy, Clone)]
pub enum Inversion {
OneDot = 0x00,
TwoDot = 0x01,
Column = 0x07,
}
#[derive(Copy, Clone)]
pub enum GammaOPBias {
Off = 0x00,
Min = 0x40,
Middle = 0x80,
Max = 0xC0,
}
#[derive(Copy, Clone)]
pub enum SourceOPInput {
Off = 0x00,
Min = 0x04,
Middle = 0x08,
Max = 0x0C,
}
#[derive(Copy, Clone)]
pub enum SourceOPOutput {
Off = 0x00,
Min = 0x01,
Middle = 0x02,
Max = 0x03,
}
#[derive(Copy, Clone)]
pub enum VoltageAVDD {
Pos6_2 = 0x00,
Pos6_4 = 0x10,
Pos6_6 = 0x20,
Pos6_8 = 0x30,
}
#[derive(Copy, Clone)]
pub enum VoltageAVCL {
Neg4_4 = 0x00,
Neg4_6 = 0x01,
Neg4_8 = 0x02,
Neg5_0 = 0x03,
}
#[derive(Copy, Clone)]
pub enum SunlightReadable {
/// DEFAULT: Sunlight readable mode off
Off = 0x00,
/// Enable sunlight readable mode
On = 0x10,
}
#[derive(PartialEq)]
pub enum Command2Selection {
Disabled = 0x00,
BK0 = 0x10,
BK1 = 0x11,
}
#[derive(Copy, Clone)]
pub enum CommandsGeneral {
NOP = 0x00, // No-op
SWRESET = 0x01, // Software Reset
RDDID = 0x04, // Read Display ID
RDNUMED = 0x05, // Read Number of Errors on DSI
RDRED = 0x06, // Read the first pixel of Red Color
RDGREEN = 0x07, // Read the first pixel of Green Color
RDBLUE = 0x08, // Read the first pixel of Blue Color
RDDPM = 0x0A, // Read Display Power Mode
RDDMADCTL = 0x0B, // Read Display MADCTL
RDDCOLMOD = 0x0C, // Read Display Pixel Format
RDDIM = 0x0D, // Read Display Image Mode
RDDSM = 0x0E, // Read Display Signal Mode
RDDSDR = 0x0F, // Read Display Self-Diagnostic Result
SLPIN = 0x10, // Sleep in
SLPOUT = 0x11, // Sleep Out
PTLON = 0x12, // Partial Display Mode On
NORON = 0x13, // Normal Display Mode On
INVOFF = 0x20, // Display Inversion Off
INVON = 0x21, // Display Inversion On
ALLPOFF = 0x22, // All Pixel Off
ALLPON = 0x23, // All Pixel ON
GAMSET = 0x26, // Gamma Set
DISPOFF = 0x28, // Display Off
DISPON = 0x29, // Display On
TEOFF = 0x34, // Tearing Effect Line OFF
TEON = 0x35, // Tearing Effect Line ON
MADCTL = 0x36, // Display data access control
IDMOFF = 0x38, // Idle Mode Off
IDMON = 0x39, // Idle Mode On
COLMOD = 0x3A, // Interface Pixel Format
GSL = 0x45, // Get Scan Line
WRDISBV = 0x51, // Write Display Brightness
RDDISBV = 0x52, // Read Display Brightness Value
WRCTRLD = 0x53, // Write CTRL Display
RDCTRLD = 0x54, // Read CTRL Value Display
WRCACE = 0x55, // Write Content Adaptive Brightness Control and Color Enhancement
RDCABC = 0x56, // Read Content Adaptive Brightness Control
WRCABCMB = 0x5E, // Write CABC Minimum Brightness
RDCABCMB = 0x5F, // Read CABC Minimum Brightness
RDABCSDR = 0x68, // Read Automatic Brightness Control Self-Diagnostic Result
RDBWLB = 0x70, // Read Black/White Low Bits
RDBkx = 0x71, // Read Bkx
RDBky = 0x72, // Read Bky
RDWx = 0x73, // Read Wx
RDWy = 0x74, // Read Wy
RDRGLB = 0x75, // Read Red/Green Low Bits
RDRx = 0x76, // Read Rx
RDRy = 0x77, // Read Ry
RDGx = 0x78, // Read Gx
RDGy = 0x79, // Read Gy
RDBALB = 0x7A, // Read Blue/A Color Low Bits
RDBx = 0x7B, // Read Bx
CND2BKxSEL = 0xFF, // Set Command2 mode for BK Register
}
#[derive(Copy, Clone)]
pub enum BK0Command2 {
PVGAMCTRL = 0xB0, // Positive Voltage Gamma Control
NVGAMCTRL = 0xB1, // Negative Voltage Gamma Control
DGMEN = 0xB8, // Digital Gamma Enable
DGMLUTR = 0xB9, // Digital Gamma Look-up Table for Red
DGMLUTB = 0xBA, // Digital Gamma Look-up Table for Blue
PWMCLKSEL = 0xBC, // PWM CLK select
LNESET = 0xC0, // Display Line Setting
PORCTRL = 0xC1, // Porch Control
INVSET = 0xC2, // Inversion selection & Frame Rate Control
RGBCTRL = 0xC3, // RGB control
PARCTRL = 0xC5, // Partial Mode Control
SDIR = 0xC7, // X-direction Control
PDOSET = 0xC8, // Pseudo-Dot inversion diving setting
COLCTRL = 0xCD, // Color Control
SRECTRL = 0xE0, // Sunlight Readable Enhancement
NRCTRL = 0xE1, // Noise Reduce Control
SECTRL = 0xE2, // Sharpness Control
CCCTRL = 0xE3, // Color Calibration Control
SKCTRL = 0xE4, // Skin Tone Preservation CONTROL
}
#[derive(Copy, Clone)]
pub enum BK1Command2 {
VRHS = 0xB0, // Vop Amplitude setting
VCOMS = 0xB1, // VCOM amplitude setting
VGHSS = 0xB2, // VGH Voltage setting
TESTCMD = 0xB3, // TEST Command Setting
VGLS = 0xB5, // VGL Voltage setting
PWCTRL1 = 0xB7, // Power Control 1
PWCTRL2 = 0xB8, // Power Control 2
PCLKS1 = 0xBA, // Power pumping clk selection 1
PCLKS3 = 0xBC, // Power pumping clk selection 3
SPD1 = 0xC1, // Source pre_drive timing set1
SPD2 = 0xC2, // Source pre_drive timing set2
MIPISET1 = 0xD0, // MIPI Setting 1
MIPISET2 = 0xD1, // MIPI Setting 2
MIPISET3 = 0xD2, // MIPI Setting 3
MIPISET4 = 0xD3, // MIPI Setting 4
}
impl CommandsGeneral {
/// # NO OPERATION
///
/// This command is "empty". It has no effect on the display, but it can be
/// used to terminate parameter write commands.
pub fn no_operation() -> Result<Command, &'static str> {
Ok(Command::new(Self::NOP as u8))
}
/// # SOFTWARE RESET
///
/// The display module performs a software reset. Registers are written with
/// the default "reset" values.
///
/// - Frame buffer contents are unaffected by this command
/// - After a SWRESET command, sleep at least 5ms before the next command
/// - If the display is sleeping when a SWRESET is sent, the sleep
/// duration should be at least 120ms before sending the next command.
/// - SWRESET cannot be sent during SLPOUT
/// - (MIPI ONLY) Send a shutdown packet before SWRESET
pub fn software_reset() -> Result<Command, &'static str> {
Ok(Command::new(Self::SWRESET as u8))
}
/// # SLEEP IN
///
/// This command causes the display module to enter a minimum power state.
/// The buck converter, display oscilator, and panel scanning are all shut
/// down.
///
/// The control interface, display data, and registers remain active.
///
/// The driver may send PCLK, HS, and CS information after SLPIN, and this
/// data will be valid for the next two frames if Normal Mode is active.
///
/// Dimming will not work when changing from sleep out to sleep in.
///
/// Normally, sleep state can be read with RDDST, but MISO must be connected.
pub fn sleep_mode_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::SLPIN as u8))
}
/// # SLEEP OUT
///
/// This command turns off the minimum power state set by SLPIN.
///
/// The driver may send PCLK, HS, and CS information before SLPOUT, and this
/// data will be valid for the two frames before the command if Normal Mode
/// is active.
pub fn sleep_mode_off() -> Result<Command, &'static str> {
Ok(Command::new(Self::SLPOUT as u8))
}
/// # PARTIAL MODE ON
///
/// This command turns on Partial Mode. See PARTIAL AREA (30h) command.
pub fn partial_mode_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::PTLON as u8))
}
/// # NORMAL MODE ON (DEFAULT)
///
/// This command turns on Normal Mode and turns off Partial Mode.
pub fn normal_mode_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::NORON as u8))
}
/// # DISPLAY INVERSION OFF (DEFAULT)
///
/// This command restores normal pixel values.
pub fn invert_display_off() -> Result<Command, &'static str> {
Ok(Command::new(Self::INVOFF as u8))
}
/// # DISPLAY INVERSION ON
///
/// This command inverts the display (white becomes black, red becomes blue).
pub fn invert_display_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::INVON as u8))
}
/// # ALL PIXELS OFF (BLACK)
///
/// This command sets all pixel values to black.
///
/// ALLPOFF may be used in Sleep Mode, Normal Mode, or Partial Mode.
pub fn all_pixels_off() -> Result<Command, &'static str> {
Ok(Command::new(Self::ALLPOFF as u8))
}
/// # ALL PIXELS ON (WHITE)
///
/// This command sets all pixel values to white.
///
/// ALLPOFF may be used in Sleep Mode, Normal Mode, or Partial Mode.
pub fn all_pixels_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::ALLPON as u8))
}
/// # GAMMA CURVE SELECT
///
/// This command selects a predefined gamma curve from one of four values.
///
/// WARNING: It's not clear from the Sitronix documentation what any values
/// are aside from 01.
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | -- | -- | -- | -- | GC[3:0] |
pub fn gamma_curve_select(GC: GammaCurve) -> Result<Command, &'static str> {
Ok(Command::new(Self::GAMSET as u8).arg(GC as u8))
}
/// # DISPLAY OFF (DEFAULT?)
///
/// This command is used to enter Display Off Mode. In this mode, display
/// data is disabled and all pixels are blanked.
///
/// NOTE: It's possible that this is the default value.
pub fn display_off() -> Result<Command, &'static str> {
Ok(Command::new(Self::DISPOFF as u8))
}
/// # DISPLAY ON
///
/// WARNING: I have no idea how this behaves. The Sitronix docs monkey copied
/// and pasted the description for DISPOFF. At a guess, it should turn the
/// display back on.
pub fn display_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::DISPON as u8))
}
/// # TEARING EFFECT LINE OFF
///
/// This command is used to turn off the display module's Tearing Effect
/// output signal (vsync?) on the TE signal line (active low).
pub fn tearing_effect_off() -> Result<Command, &'static str> {
Ok(Command::new(Self::TEOFF as u8))
}
/// # TEARING EFFECT LINE ON
///
/// This command is used to turn on the display module's Tearing Effect
/// output signal line.
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | -- | -- | -- | -- | -- | -- | TE |
pub fn tearing_effect_on(TE: TearingEffect) -> Result<Command, &'static str> {
Ok(Command::new(Self::TEON as u8).arg(TE as u8))
}
/// # DISPLAY DATA ACCESS CONTROL
/// * [ML] - Scan direction
/// * []
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | -- | -- | ML | CO | -- | -- | -- |
pub fn display_data_control(
ML: ScanDirection,
CO: ColorOrder,
) -> Result<Command, &'static str> {
Ok(Command::new(Self::MADCTL as u8).arg(ML as u8 | CO as u8))
}
/// # IDLE MODE OFF
///
/// Turns off Idle Mode. Display is capable of its full 16.7 million color
/// palette
pub fn idle_mode_off() -> Result<Command, &'static str> {
Ok(Command::new(Self::IDMOFF as u8))
}
/// # IDLE MODE ON
///
/// Turns on Idle Mode. In idle mode the color palette is significantly
/// reduced. The MSB of each color will be rounded up or down, creating a
/// palette limited to 8 colors.
pub fn idle_mode_on() -> Result<Command, &'static str> {
Ok(Command::new(Self::IDMON as u8))
}
/// # SET INTERFACE PIXEL FORMAT
///
/// Defines the format for RGB pixel data.
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | BPP[2:0] | -- | -- | -- | -- |
pub fn set_color_mode(BPP: BitsPerPixel) -> Result<Command, &'static str> {
Ok(Command::new(Self::COLMOD as u8).arg(BPP as u8))
}
/// # WRDISBV
///
/// Change the display brightness to an 8-bit value.
///
/// 0x00: Lowest brightness
/// 0xFF: Hightest brightness
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| Display Brightness Value [7:0] |
pub fn set_display_brightness(DBV: u8) -> Result<Command, &'static str> {
Ok(Command::new(Self::WRDISBV as u8).arg(DBV as u8))
}
/// # WRITE CTRL DISPLAY
///
/// This command changes more general behavior of the brightness controls.
///
/// [BCTRL] Brightness control on or off
/// [DD] Display dimming (only affects manual brightness settings)
/// [BL] Backlight control on or off
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | -- | BCTRL | -- | DD | BL | -- | -- |
pub fn configure_brightness(
BCTRL: BrightnessControl,
DD: DisplayDimming,
BL: Backlight,
) -> Result<Command, &'static str> {
Ok(Command::new(Self::WRCTRLD as u8).arg(BCTRL as u8 | DD as u8 | BL as u8))
}
/// # WRITE CONTENT ADAPTIVE BRIGHTNESS CONTROL AND COLOR ENHANCEMENT
///
/// Set parameters for content-based adaptive brightness control, set
/// different color enhancement modes.
///
/// [CE] Color enhancement on or off:
/// [CEMD] Color enhancement mode
/// [CABC] Adaptive brightness control
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| CE | -- | CEMD[1:0] | -- | -- | CABC[1:0] |
pub fn configure_color_enhancement(
CE: Enhancement,
CEMD: EnhancementMode,
CABC: AdaptiveBrightness,
) -> Result<Command, &'static str> {
Ok(Command::new(Self::WRCACE as u8).arg(CE as u8 | CEMD as u8 | CABC as u8))
}
///
/// WRITE CABC MINIMUM BRIGHTNESS
///
/// Sets the minimum brightness value to be used for CABC (see WRCACE).
///
/// [MBV] Minimum Brightness Value
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| Minimum Brightness Value [7:0] |
pub fn set_minimum_brightness(MBV: u8) -> Result<Command, &'static str> {
Ok(Command::new(Self::WRCABCMB as u8).arg(MBV as u8))
}
pub fn read_display_pixel_format() -> Result<Command, &'static str> {
Ok(Command::new(Self::RDDCOLMOD as u8))
}
pub fn read_self_diagnostics() -> Result<Command, &'static str> {
Ok(Command::new(Self::RDDSDR as u8))
}
/// # SET COMMAND2 MODE
/// This is one of the most confusing attributes of the Sitronix chips.
/// BK0, BK1, and BK3 (maybe) all have "Command2" instructions that share a
/// common address space. To avoid collisions and to ensure you're sending
/// the command you think you're sending, we use a double-entry bookkeeping
/// approach, where set_command_2 will send the chip the updated Command2
/// setting AND record it back to the local flag, which is required for
/// static type checking in all Command2 instructions locally.
///
/// eg. for a BK1 Command2 instruction, "current" must be set to
/// Command2Selection::BK1.
pub fn set_command_2(set: Command2Selection) -> Result<Command, &'static str> {
Ok(Command::new(Self::CND2BKxSEL as u8).args(&[0x77, 0x01, 0x00, 0x00, set as u8]))
}
}
impl BK0Command2 {
pub fn validate<F>(CMD2: &Command2Selection, build_command: F) -> Result<Command, &'static str>
where
F: Fn() -> Command,
{
match CMD2 {
Command2Selection::BK0 => Ok(build_command()),
_ => Err("Cannot run command '{}': BK0 Command 2 mode not set"),
}
}
/// # POSITIVE GAMMA CONTROL
/// See note above about parameters
pub fn positive_gamma_control(
CMD2: &Command2Selection,
parameters: &[u8],
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(Self::PVGAMCTRL as u8).args(parameters)
})
}
/// # POSITIVE GAMMA CONTROL
/// See note above about parameters
pub fn negative_gamma_control(
CMD2: &Command2Selection,
parameters: &[u8],
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(Self::NVGAMCTRL as u8).args(parameters)
})
}
/// # DISPLAY LINE SETTING
pub fn display_line_setting(
CMD2: &Command2Selection,
LDE_EN: u8,
Line: u8,
Line_delta: u8,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(Self::LNESET as u8).args(&[LDE_EN | Line, Line_delta])
})
}
/// # PORCH CONTROL
pub fn porch_control(CMD2: &Command2Selection, mode: &Mode) -> Result<Command, &'static str> {
let front_porch: u8 = (mode.vtotal - mode.vsync_end).try_into().unwrap();
let back_porch: u8 = (mode.vsync_start - mode.vdisplay).try_into().unwrap();
Self::validate(CMD2, || {
Command::new(Self::PORCTRL as u8).args(&[front_porch, back_porch])
})
}
/// # INVERSION SELECT
/// * [LINV] - the type of inversion
/// * [RTNI] - minimum number of pclk in each line
pub fn inversion_select(
CMD2: &Command2Selection,
NLINV: Inversion,
RTNI: u8,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(Self::INVSET as u8).args(&[0x30 | NLINV as u8, RTNI])
})
}
/// # RGB CONTROLDE/HV:RGB Mode selection
/// * [DEHV]
/// 0: RGB DE mode
/// 1: RGB HV mode
/// * [VSP]: Sets the signal polarity of the VSYNC pin.
/// 0: Low active
/// 1: High active
/// * [HSP]: Sets the signal polarity of the HSYNC pin.
/// 0: Low active
/// 1: High active
/// * [DP]: Sets the signal polarity of the DOTCLK pin.
/// 0: The data is input on the positive edge of DOTCLK
/// 1: The data is input on the negative edge of DOTCLK
/// * [EP]: Sets the signal polarity of the ENABLE pin.
/// 0: The data DB23-0 is written when ENABLE = “1". Disable data write operation when ENABLE = “0”.
/// 1: The data DB23-0 is written when ENABLE = “0”. Disable data write operation when ENABLE = “1”.
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| DEHV | -- | -- | -- | VSP | HSP | DP | EP |
///| HBP |
///| VBP |
pub fn rgb_control(
CMD2: &Command2Selection,
DEHV: DataEnable,
VSP: VsyncActive,
HSP: HsyncActive,
DP: DataPolarity,
EP: EnablePolarity,
mode: &Mode,
) -> Result<Command, &'static str> {
let HBP: u8 = (mode.htotal - mode.hsync_end).try_into().unwrap();
let VBP: u8 = (mode.vsync_start - mode.vdisplay).try_into().unwrap();
Self::validate(CMD2, || {
Command::new(Self::RGBCTRL as u8).args(&[
DEHV as u8 | VSP as u8 | HSP as u8 | DP as u8 | EP as u8,
HBP,
VBP,
])
})
}
/// # COLOR CONTROL
/// * [PWM]: LEDPWM polarity control.
/// 0: polarity normal.
/// 1: polarity reverse.
/// * [LED]: LED_ON polarity control.
/// 0: polarity normal.
/// 1: polarity reverse.
/// * [MDT]: RGB pixel format argument.(for 262K).See Table 17.
/// 0: pixel format argument normal.
/// 1: pixel collect to DB[17:0].
/// * [EPF][2:0]: end of pixel format (for 65k & 262k mode)
/// 0: copy self MSB
/// 1: copy G MSB
/// 2: copy self LSB
/// 4: FIX 0
/// 5: FIX 1
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | -- | PWM | LED | MDT | EPF |
pub fn color_control(
CMD2: &Command2Selection,
PWM: PWMPolarity,
LED: LEDPolarity,
MDT: PixelPinout,
EPF: EndPixelFormat,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(Self::COLCTRL as u8).arg(PWM as u8 | LED as u8 | MDT as u8 | EPF as u8)
})
}
/// # CONFIGURE SUNLIGHT READABLE ENHANCEMENT MODE
///
/// Sets the minimum brightness value to be used for CABC (see WRCACE).
///
/// [MBV] Minimum Brightness Value
///
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | -- | -- | -- | SRE | SRE_alpha[3:0] |
pub fn configure_sunlight_ehancement(
CMD2: &Command2Selection,
SRE: SunlightReadable,
mut SRE_alpha: u8,
) -> Result<Command, &'static str> {
if SRE_alpha > 0x0F {
SRE_alpha = 0x0F;
}
Self::validate(CMD2, || {
Command::new(Self::SECTRL as u8).arg(SRE as u8 | SRE_alpha)
})
}
}
impl BK1Command2 {
pub fn validate<F>(CMD2: &Command2Selection, build_command: F) -> Result<Command, &'static str>
where
F: Fn() -> Command,
{
match CMD2 {
Command2Selection::BK1 => Ok(build_command()),
_ => Err("Cannot run command '{}': BK0 Command 2 mode not set"),
}
}
pub fn set_vop_amplitude(CMD2: &Command2Selection, VRHA: u8) -> Result<Command, &'static str> {
Self::validate(CMD2, || Command::new(BK1Command2::VRHS as u8).arg(VRHA))
}
pub fn set_vcom_amplitude(CMD2: &Command2Selection, VCOM: u8) -> Result<Command, &'static str> {
Self::validate(CMD2, || Command::new(BK1Command2::VCOMS as u8).arg(VCOM))
}
pub fn set_vgh_voltage(CMD2: &Command2Selection, VGH: u8) -> Result<Command, &'static str> {
Self::validate(CMD2, || Command::new(BK1Command2::VGHSS as u8).arg(VGH))
}
pub fn test_command_setting(CMD2: &Command2Selection) -> Result<Command, &'static str> {
Self::validate(CMD2, || Command::new(BK1Command2::TESTCMD as u8).arg(0x80))
}
pub fn set_vgl_voltage(CMD2: &Command2Selection, VGLS: u8) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(BK1Command2::VGLS as u8).arg(0x40 | VGLS)
})
}
pub fn power_control_one(
CMD2: &Command2Selection,
AP: GammaOPBias,
APIS: SourceOPInput,
APOS: SourceOPOutput,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(BK1Command2::PWCTRL1 as u8).arg(AP as u8 | APIS as u8 | APOS as u8)
})
}
pub fn power_control_two(
CMD2: &Command2Selection,
AVDD: VoltageAVDD,
AVCL: VoltageAVCL,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(BK1Command2::PWCTRL2 as u8).arg(AVDD as u8 | AVCL as u8)
})
}
/// # SET SOURCE PRE DRIVE TIMING CONTROL
/// T2D [3:0]: source pre_drive timing setting.(GND to VDD)
/// Adjust Range : 0 ~ 3 uS 1 step is 0.2uS
///| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
///| -- | 1 | 1 | 1 | T2D |
pub fn set_pre_drive_timing_one(
CMD2: &Command2Selection,
T2D: u8,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(BK1Command2::SPD1 as u8).arg(0x70 | T2D)
})
}
/// # SET SOURCE PRE DRIVE TIMING CONTROL
/// Same parameters as SPD1
pub fn set_pre_drive_timing_two(
CMD2: &Command2Selection,
T2D: u8,
) -> Result<Command, &'static str> {
Self::validate(CMD2, || {
Command::new(BK1Command2::SPD2 as u8).arg(0x70 | T2D)
})
}
}

184
src/main.rs Normal file
View File

@ -0,0 +1,184 @@
extern crate enum_primitive;
extern crate num;
extern crate spidev;
use std::{thread, time};
mod instructions;
mod panel;
mod spi;
use instructions::{BK0Command2, BK1Command2, Command, Command2Selection, CommandsGeneral};
use panel::CVTRB;
use spi::ST7701S;
/// ST7701S supports two kinds of RGB interface, DE mode (mode 1) and HV mode
/// (mode 2), and 16bit/18bit and 24 bit data format. When DE mode is selected
/// and the VSYNC, HSYNC, DOTCLK, DE, D23:0 pins can be used; when HV mode is
/// selected and the VSYNC, HSYNC, DOTCLK, D23:0 pins can be used. When using
/// RGB interface, only serial interface can be selected.
fn main() {
println!("Initializing SPI driver for ST7701S panel");
let mut CMD2: Command2Selection = Command2Selection::Disabled;
let mut display = ST7701S::new(String::from("/dev/spidev1.0"));
let mode = CVTRB;
// SOFTWARE RESET
// 5ms delay
display.write_command(CommandsGeneral::software_reset());
thread::sleep(time::Duration::from_millis(5));
// EXIT SLEEP MODE
// Variable delay (200ms is "safe")
display.write_command(CommandsGeneral::sleep_mode_off());
thread::sleep(time::Duration::from_millis(200));
// ENTER BK0 COMMAND2 MODE
display.write_command(CommandsGeneral::set_command_2(Command2Selection::BK0));
CMD2 = Command2Selection::BK0;
display.write_command(BK0Command2::positive_gamma_control(
&CMD2,
&[
0x00, 0x0E, 0x15, 0x0F, 0x11, 0x08, 0x08, 0x08, 0x08, 0x23, 0x04, 0x13, 0x12, 0x2B,
0x34, 0x1F,
],
));
display.write_command(BK0Command2::negative_gamma_control(
&CMD2,
&[
0x00, 0x0E, 0x95, 0x0F, 0x13, 0x07, 0x09, 0x08, 0x08, 0x22, 0x04, 0x10, 0x0E, 0x2C,
0x34, 0x1F,
],
));
display.write_command(BK0Command2::display_line_setting(&CMD2, 0x80, 0x69, 0x02));
display.write_command(BK0Command2::porch_control(&CMD2, &mode));
display.write_command(BK0Command2::inversion_select(
&CMD2,
instructions::Inversion::Column,
0xFF,
));
display.write_command(BK0Command2::rgb_control(
&CMD2,
instructions::DataEnable::DE,
instructions::VsyncActive::Low,
instructions::HsyncActive::Low,
instructions::DataPolarity::Rising,
instructions::EnablePolarity::Low,
&mode,
));
// ENTER BK1 COMMAND2 MODE
display.write_command(CommandsGeneral::set_command_2(Command2Selection::BK1));
CMD2 = Command2Selection::BK1;
display.write_command(BK1Command2::set_vop_amplitude(&CMD2, 0x45));
display.write_command(BK1Command2::set_vcom_amplitude(&CMD2, 0x13));
display.write_command(BK1Command2::set_vgh_voltage(&CMD2, 0x07));
display.write_command(BK1Command2::test_command_setting(&CMD2));
display.write_command(BK1Command2::set_vgl_voltage(&CMD2, 0x07));
display.write_command(BK1Command2::power_control_one(
&CMD2,
instructions::GammaOPBias::Middle,
instructions::SourceOPInput::Min,
instructions::SourceOPOutput::Off,
));
display.write_command(BK1Command2::power_control_two(
&CMD2,
instructions::VoltageAVDD::Pos6_6,
instructions::VoltageAVCL::Neg4_4,
));
display.write_command(BK1Command2::set_pre_drive_timing_one(&CMD2, 0x03));
display.write_command(BK1Command2::set_pre_drive_timing_two(&CMD2, 0x03));
// UNKNOWABLE CARGO-CULTED MYSTERY MEAT
//
// I copied this command sequence from the Linux MIPI driver for the ST7701,
// written in C. The author of that driver _also_ had no idea what this
// command sequence does or means, and claims to have himself copied it from
// a sample provided by a Sitronix engineer. Since this is a Linux SPI
// driver for the ST7701S written in Rust, there's ample opportunity for
// something to not line up.
//
// May whatever gods you pray to have mercy on our souls.
display.write_command(Ok(Command {
address: 0xE0,
parameters: vec![0x00, 0x00, 0x02],
}));
display.write_command(Ok(Command {
address: 0xE1,
parameters: vec![
0x0B, 0x00, 0x0D, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x00, 0x44, 0x44,
],
}));
display.write_command(Ok(Command {
address: 0xE2,
parameters: vec![
0x33, 0x33, 0x44, 0x44, 0x64, 0x00, 0x66, 0x00, 0x65, 0x00, 0x67, 0x00, 0x00,
],
}));
display.write_command(Ok(Command {
address: 0xE3,
parameters: vec![0x00, 0x00, 0x33, 0x33],
}));
display.write_command(Ok(Command {
address: 0xE4,
parameters: vec![0x44, 0x44],
}));
display.write_command(Ok(Command {
address: 0xE5,
parameters: vec![
0x0C, 0x78, 0x3C, 0xA0, 0x0E, 0x78, 0x3C, 0xA0, 0x10, 0x78, 0x3C, 0xA0, 0x12, 0x78,
0x3C, 0xA0,
],
}));
display.write_command(Ok(Command {
address: 0xE6,
parameters: vec![0x00, 0x00, 0x33, 0x33],
}));
display.write_command(Ok(Command {
address: 0xE7,
parameters: vec![0x44, 0x44],
}));
display.write_command(Ok(Command {
address: 0xE8,
parameters: vec![
0x0D, 0x78, 0x3C, 0xA0, 0x0F, 0x78, 0x3C, 0xA0, 0x11, 0x78, 0x3C, 0xA0, 0x13, 0x78,
0x3C, 0xA0,
],
}));
display.write_command(Ok(Command {
address: 0xEB,
parameters: vec![0x02, 0x02, 0x39, 0x39, 0xEE, 0x44, 0x00],
}));
display.write_command(Ok(Command {
address: 0xEC,
parameters: vec![0x00, 0x00],
}));
display.write_command(Ok(Command {
address: 0xED,
parameters: vec![
0xFF, 0xF1, 0x04, 0x56, 0x72, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x27, 0x65, 0x40,
0x1F, 0xFF,
],
}));
// BK1 COMMAND2 DISABLE
display.write_command(CommandsGeneral::set_command_2(Command2Selection::Disabled));
CMD2 = Command2Selection::Disabled;
// display.write_command(CommandsGeneral::set_color_mode(
// instructions::BitsPerPixel::Rgb666,
// ));
// display.write_command(CommandsGeneral::display_data_control(
// instructions::ScanDirection::Normal,
// instructions::ColorOrder::Rgb,
// ));
display.write_command(CommandsGeneral::tearing_effect_on(
instructions::TearingEffect::VHBlank,
));
display.write_command(CommandsGeneral::display_on());
thread::sleep(time::Duration::from_millis(200));
}

84
src/panel.rs Normal file
View File

@ -0,0 +1,84 @@
pub struct Mode {
pub clock: u32,
pub hdisplay: u32,
pub hsync_start: u32,
pub hsync_end: u32,
pub htotal: u32,
pub vdisplay: u32,
pub vsync_start: u32,
pub vsync_end: u32,
pub vtotal: u32,
pub width_mm: u32,
pub height_mm: u32,
}
pub const DefaultMode: Mode = Mode {
clock: 27500,
hdisplay: 480,
hsync_start: 480 + 38,
hsync_end: 480 + 38 + 12,
htotal: 480 + 38 + 12 + 12,
vdisplay: 854,
vsync_start: 854 + 18,
vsync_end: 854 + 18 + 8,
vtotal: 854 + 18 + 8 + 4,
width_mm: 69,
height_mm: 139,
};
pub const TDOMode: Mode = Mode {
clock: 16000,
hdisplay: 480,
hsync_start: 480 + 24,
hsync_end: 480 + 24 + 6,
htotal: 480 + 24 + 6 + 18,
vdisplay: 480,
vsync_start: 480 + 16,
vsync_end: 480 + 16 + 4,
vtotal: 480 + 16 + 4 + 10,
width_mm: 69,
height_mm: 139,
};
pub const CVTMode: Mode = Mode {
clock: 17000,
hdisplay: 480,
hsync_start: 480 + 8,
hsync_end: 480 + 8 + 48,
htotal: 480 + 8 + 48 + 56,
vdisplay: 480,
vsync_start: 480 + 1,
vsync_end: 480 + 1 + 3,
vtotal: 480 + 1 + 3 + 13,
width_mm: 69,
height_mm: 139,
};
pub const CVTRB: Mode = Mode {
clock: 23500,
hdisplay: 640,
hsync_start: 480 + 8,
hsync_end: 480 + 8 + 32,
htotal: 480 + 8 + 32 + 40,
vdisplay: 480,
vsync_start: 480 + 14,
vsync_end: 480 + 14 + 3,
vtotal: 480 + 14 + 3 + 4,
width_mm: 69,
height_mm: 139,
};

63
src/spi.rs Normal file
View File

@ -0,0 +1,63 @@
extern crate spidev;
use crate::instructions::Command;
use spidev::{SpiModeFlags, Spidev, SpidevOptions};
use std::io;
use std::io::prelude::*;
pub struct ST7701S {
spi: Spidev,
options: SpidevOptions,
}
impl ST7701S {
pub fn create_spi(device: String, options: &SpidevOptions) -> io::Result<Spidev> {
let mut spi = Spidev::open(device)?;
spi.configure(&options)?;
Ok(spi)
}
pub fn new(device: String) -> ST7701S {
let options = SpidevOptions::new()
.bits_per_word(9)
.lsb_first(false)
.max_speed_hz(20_000)
.mode(SpiModeFlags::SPI_MODE_0)
.build();
let spi = ST7701S::create_spi(device, &options).unwrap();
ST7701S { options, spi }
}
pub fn write_command(&mut self, command: Result<Command, &'static str>) -> Result<(), ()> {
match command {
Ok(c) => {
let address = &c.serialize_address();
self.spi.write(&c.serialize_address());
println!("address {}", c.address);
for parameter in c.parameters {
self.spi.write(&Command::serialize_parameter(parameter));
println!("parameter {}", parameter);
}
}
Err(e) => println!("{}", e),
}
Ok(())
}
pub fn read_command(&mut self, command: Result<Command, &'static str>) -> Result<(), ()> {
match command {
Ok(c) => {
let mut rx_buf = [0_u8; 10];
self.spi.write(&c.serialize_address());
self.spi.read(&mut rx_buf);
println!("{:?}", rx_buf);
}
Err(e) => println!("{}", e),
}
Ok(())
}
}