diff --git a/src/command.rs b/src/command.rs index b3721b1..a98e4c3 100644 --- a/src/command.rs +++ b/src/command.rs @@ -42,9 +42,12 @@ pub enum RamOption { #[derive(Clone, Copy)] pub enum DeepSleepMode { - Mode1, - Mode2, + /// Not sleeping Normal, + /// Deep sleep with RAM preserved + Mode1, + /// Deep sleep RAM not preserved + Mode2, } pub enum Command { @@ -204,13 +207,14 @@ macro_rules! pack { } impl Command { - fn execute(self, interface: &mut I) -> Result<(), I::Error> { + pub(crate) fn execute(self, interface: &mut I) -> Result<(), I::Error> { use self::Command::*; let mut buf = [0u8; 4]; let (command, data) = match self { DriverOutputControl(gate_lines, scanning_seq_and_dir) => { - pack!(buf, 0x01, [0xD3, 0x00, 0x00]) + let [upper, lower] = u16_as_u8(gate_lines); + pack!(buf, 0x01, [lower, upper, scanning_seq_and_dir]) } GateDrivingVoltage(voltages) => { pack!(buf, 0x03, [voltages]) @@ -271,8 +275,9 @@ impl Command { // } // VCOMSenseDuration(u8) => { // } - // WriteVCOM(u8) => { - // } + WriteVCOM(value) => { + pack!(buf, 0x2C, [value]) + } DummyLinePeriod(period) => { debug_assert!(Contains::contains(&(0..=MAX_DUMMY_LINE_PERIOD), period)); pack!(buf, 0x3A, [period]) @@ -317,6 +322,33 @@ impl Command { } } +impl<'buf> BufCommand<'buf> { + pub(crate) fn execute(self, interface: &mut I) -> Result<(), I::Error> { + use self::BufCommand::*; + + let (command, data) = match self { + WriteBlackData(buffer) => { + // TODO: Handle rotation + (0x24, buffer) + } + WriteRedData(buffer) => { + // TODO: Handle rotation + (0x26, buffer) + } + WriteLUT(buffer) => { + (0x32, buffer) + } + }; + + interface.send_command(command)?; + if data.len() == 0 { + Ok(()) + } else { + interface.send_data(data) + } + } +} + impl Contains for core::ops::Range where C: Copy + PartialOrd { fn contains(&self, item: C) -> bool { item >= self.start && item < self.end diff --git a/src/display.rs b/src/display.rs index 563f5cc..da6ca6d 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,12 +1,21 @@ use hal; +use command::{BufCommand, Command, DataEntryMode, IncrementAxis}; use interface::DisplayInterface; +// Max display resolution is 160x296 +const MAX_SOURCE_OUTPUTS: usize = 160; +const MAX_GATE_OUTPUTS: usize = 296; + +// Magic numbers from the data sheet +const ANALOG_BLOCK_CONTROL_MAGIC: u8 = 0x54; +const DIGITAL_BLOCK_CONTROL_MAGIC: u8 = 0x3B; + struct Config {} struct Dimensions { - rows: usize, - cols: usize, + rows: u16, + cols: u8, } pub struct Display where I: DisplayInterface { @@ -20,12 +29,62 @@ impl Display where I: DisplayInterface { Self { interface, dimensions, rotation } } - fn reset>(&mut self, delay: &mut D) { - self.interface.reset(delay) + /// Perform a hardware reset followed by software reset + fn reset>(&mut self, delay: &mut D) -> Result<(), I::Error> { + self.interface.reset(delay); + Command::SoftReset.execute(&mut self.interface)?; + self.interface.busy_wait(); + Ok(()) } + /// Initialise the controller according to Section 9: Typical Operating Sequence + /// from the data sheet fn init(&mut self, config: Config) -> Result<(), I::Error> { + Command::AnalogBlockControl(ANALOG_BLOCK_CONTROL_MAGIC).execute(&mut self.interface)?; + Command::DigitalBlockControl(DIGITAL_BLOCK_CONTROL_MAGIC).execute(&mut self.interface)?; + Command::DriverOutputControl(self.dimensions.rows, 0x00).execute(&mut self.interface)?; + + Command::DummyLinePeriod(0x07).execute(&mut self.interface)?; + Command::GateLineWidth(0x04).execute(&mut self.interface)?; + + Command::SourceDrivingVoltage(0x2D, 0xB2, 0x22).execute(&mut self.interface)?; + Command::WriteVCOM(0x3C).execute(&mut self.interface)?; + + // POR is HiZ. Need pull from config + // Command::BorderWaveform(u8).execute(&mut self.interface)?; + + // BufCommand::WriteLUT().execute(&mut self.interface)?; + + Command::DataEntryMode(DataEntryMode::IncrementYIncrementX, IncrementAxis::Horizontal).execute(&mut self.interface)?; + + let end = self.dimensions.cols / 8 - 1; + Command::StartEndXPosition(0, end).execute(&mut self.interface)?; + Command::StartEndYPosition(0, self.dimensions.rows).execute(&mut self.interface)?; + + Ok(()) + } + + fn update>(&mut self, black: &[u8], red: &[u8], delay: &mut D) -> Result<(), I::Error> { + // Write the B/W RAM + Command::XAddress(0).execute(&mut self.interface)?; + Command::YAddress(0).execute(&mut self.interface)?; + BufCommand::WriteBlackData(&black).execute(&mut self.interface)?; + + // Write the Red RAM + Command::XAddress(0).execute(&mut self.interface)?; + Command::YAddress(0).execute(&mut self.interface)?; + BufCommand::WriteRedData(&red).execute(&mut self.interface)?; + + // Kick off the display update + Command::UpdateDisplayOption2(0xC7).execute(&mut self.interface)?; + Command::UpdateDisplay.execute(&mut self.interface)?; + delay.delay_ms(5); // Needed? + // TODO: We don't really need to wait here... the program can go off and do other things + // and only busy wait if it wants to talk to the display again. Could possibly treat + // the interface like a smart pointer in which "acquiring" it would wait until it's not + // busy. + self.interface.busy_wait(); Ok(()) } @@ -35,5 +94,3 @@ impl Display where I: DisplayInterface { unimplemented!() } } - - diff --git a/src/interface.rs b/src/interface.rs index 8b0ba03..004052e 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -3,12 +3,15 @@ use hal; // Section 15.2 of the HINK-E0213A07 data sheet says to hold for 10ms const RESET_DELAY_MS: u8 = 10; +const MAX_SPI_SPEED_HZ: u32 = 20_000_000; + pub trait DisplayInterface { type Error; fn send_command(&mut self, command: u8) -> Result<(), Self::Error>; fn send_data(&mut self, data: &[u8]) -> Result<(), Self::Error>; fn reset>(&mut self, delay: &mut D); + fn busy_wait(&self); } pub struct Interface { @@ -62,10 +65,6 @@ where Ok(()) } - - fn busy_wait(&self) { - while self.busy.is_high() {} - } } impl DisplayInterface for Interface @@ -97,4 +96,8 @@ where self.dc.set_high(); self.write(data) } + + fn busy_wait(&self) { + while self.busy.is_high() {} + } }