From 204f654e33a7a9f882cce17ba9c1b47024dd10b4 Mon Sep 17 00:00:00 2001 From: Wesley Moore Date: Wed, 14 Nov 2018 21:09:04 +1100 Subject: [PATCH] Partially working Inky pHAT example --- Cargo.toml | 3 + examples/raspberry_pi_inky_phat.rs | 201 +++++++++++++++++++++++++++++ src/command.rs | 19 +-- src/display.rs | 29 ++++- src/graphics.rs | 17 ++- src/interface.rs | 4 +- 6 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 examples/raspberry_pi_inky_phat.rs diff --git a/Cargo.toml b/Cargo.toml index 395d359..47dd7f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ version = "0.2.2" optional = true version = "0.4.4" +[dev-dependencies] +linux-embedded-hal = "0.2.1" # for examples + [features] default = ["graphics"] graphics = ["embedded-graphics"] diff --git a/examples/raspberry_pi_inky_phat.rs b/examples/raspberry_pi_inky_phat.rs new file mode 100644 index 0000000..8bfd0b7 --- /dev/null +++ b/examples/raspberry_pi_inky_phat.rs @@ -0,0 +1,201 @@ +// the library for the embedded linux device +extern crate linux_embedded_hal; +use linux_embedded_hal::spidev::{self, SpidevOptions}; +use linux_embedded_hal::sysfs_gpio::Direction; +use linux_embedded_hal::Delay; +use linux_embedded_hal::{Pin, Spidev}; + +// the eink library +extern crate ssd1675; +use ssd1675::{Display, DisplayInterface, Dimensions, GraphicDisplay, Color, Rotation}; + +// Graphics +extern crate embedded_graphics; +use embedded_graphics::coord::Coord; +use embedded_graphics::fonts::{Font12x16, Font6x8}; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{Circle, Line}; +use embedded_graphics::Drawing; + +// HAL (Traits) +extern crate embedded_hal; +use embedded_hal::prelude::*; + +// activate spi, gpio in raspi-config +// needs to be run with sudo because of some sysfs_gpio permission problems and follow-up timing problems +// see https://github.com/rust-embedded/rust-sysfs-gpio/issues/5 and follow-up issues + +const ROWS: u16 = 212; +const COLS: u8 = 104; + +fn main() -> Result<(), std::io::Error> { + // Configure SPI + let mut spi = Spidev::open("/dev/spidev0.0").expect("SPI device"); + let options = SpidevOptions::new() + .bits_per_word(8) + .max_speed_hz(4_000_000) + .mode(spidev::SPI_MODE_0) + .build(); + spi.configure(&options).expect("SPI configuration"); + + // https://pinout.xyz/pinout/inky_phat# + // Configure Digital I/O Pin to be used as Chip Select for SPI + let cs = Pin::new(8); // BCM8 + cs.export().expect("cs export"); + while !cs.is_exported() {} + cs.set_direction(Direction::Out).expect("CS Direction"); + cs.set_value(1).expect("CS Value set to 1"); + + let busy = Pin::new(17); // BCM17 + busy.export().expect("busy export"); + while !busy.is_exported() {} + busy.set_direction(Direction::In).expect("busy Direction"); + + let dc = Pin::new(22); // BCM22 + dc.export().expect("dc export"); + while !dc.is_exported() {} + dc.set_direction(Direction::Out).expect("dc Direction"); + dc.set_value(1).expect("dc Value set to 1"); + + let reset = Pin::new(27); // BCM27 + reset.export().expect("reset export"); + while !reset.is_exported() {} + reset.set_direction(Direction::Out).expect("reset Direction"); + reset.set_value(1).expect("reset Value set to 1"); + println!("Pins configured"); + + let mut delay = Delay {}; + + let controller = ssd1675::Interface::new(spi, cs, busy, dc, reset); + + // println!("Test all the rotations"); + let dimensions = Dimensions { rows: ROWS, cols: COLS }; + let mut black_buffer = [0u8; ROWS as usize * COLS as usize]; // FIXME: This is using 1 byte per pixel when it only needs to be one bit + let mut red_buffer = [0u8; ROWS as usize * COLS as usize]; + let display = Display::new(controller, dimensions, Rotation::Rotate270); + let mut display = GraphicDisplay::new(display, &mut black_buffer, &mut red_buffer); + display.reset(&mut delay).expect("error resetting display"); + // display.set_rotation(DisplayRotation::Rotate0); + println!("reset and initialised"); + + display.clear(Color::White); + println!("clear"); + display.draw( + Font12x16::render_str("Hello Rust") + .with_stroke(Some(Color::Red)) + .with_fill(Some(Color::White)) + .translate(Coord::new(5, 10)) + .into_iter(), + ); + println!("draw"); + + display.update(&mut delay).expect("error updating display"); + println!("update..."); + + // display.set_rotation(DisplayRotation::Rotate90); + // display.draw( + // Font::render_str("Rotate 90!") + // .with_stroke(Some(Color::Black)) + // .with_fill(Some(Color::White)) + // .translate(Coord::new(5, 50)) + // .into_iter(), + // ); + + // display.set_rotation(DisplayRotation::Rotate180); + // display.draw( + // Font6x8::render_str("Rotate 180!") + // .with_stroke(Some(Color::Black)) + // .with_fill(Some(Color::White)) + // .translate(Coord::new(5, 50)) + // .into_iter(), + // ); + + // display.set_rotation(DisplayRotation::Rotate270); + // display.draw( + // Font6x8::render_str("Rotate 270!") + // .with_stroke(Some(Color::Black)) + // .with_fill(Some(Color::White)) + // .translate(Coord::new(5, 50)) + // .into_iter(), + // ); + + // epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); + // epd4in2 + // .display_frame(&mut spi) + // .expect("display frame new graphics"); + // delay.delay_ms(5000u16); + + // println!("Now test new graphics with default rotation and some special stuff:"); + // display.clear_buffer(Color::White); + + // // draw a analog clock + // display.draw( + // Circle::new(Coord::new(64, 64), 64) + // .with_stroke(Some(Color::Black)) + // .into_iter(), + // ); + // display.draw( + // Line::new(Coord::new(64, 64), Coord::new(0, 64)) + // .with_stroke(Some(Color::Black)) + // .into_iter(), + // ); + // display.draw( + // Line::new(Coord::new(64, 64), Coord::new(80, 80)) + // .with_stroke(Some(Color::Black)) + // .into_iter(), + // ); + + // // draw white on black background + // display.draw( + // Font6x8::render_str("It's working-WoB!") + // // Using Style here + // .with_style(Style { + // fill_color: Some(Color::Black), + // stroke_color: Some(Color::White), + // stroke_width: 0u8, // Has no effect on fonts + // }) + // .translate(Coord::new(175, 250)) + // .into_iter(), + // ); + + // // use bigger/different font + // display.draw( + // Font12x16::render_str("It's working-BoW!") + // // Using Style here + // .with_style(Style { + // fill_color: Some(Color::White), + // stroke_color: Some(Color::Black), + // stroke_width: 0u8, // Has no effect on fonts + // }) + // .translate(Coord::new(50, 200)) + // .into_iter(), + // ); + + // // a moving `Hello World!` + // let limit = 10; + // for i in 0..limit { + // println!("Moving Hello World. Loop {} from {}", (i + 1), limit); + + // display.draw( + // Font6x8::render_str(" Hello World! ") + // .with_style(Style { + // fill_color: Some(Color::White), + // stroke_color: Some(Color::Black), + // stroke_width: 0u8, // Has no effect on fonts + // }) + // .translate(Coord::new(5 + i * 12, 50)) + // .into_iter(), + // ); + + // epd4in2.update_frame(&mut spi, &display.buffer()).unwrap(); + // epd4in2 + // .display_frame(&mut spi) + // .expect("display frame new graphics"); + + // delay.delay_ms(1_000u16); + // } + + println!("Finished - going to sleep"); + display.deep_sleep() +} + diff --git a/src/command.rs b/src/command.rs index e0bf611..1c9c7c4 100644 --- a/src/command.rs +++ b/src/command.rs @@ -269,8 +269,9 @@ impl Command { } // UpdateDisplayOption1(RamOption, RamOption) => { // } - // UpdateDisplayOption2(u8) => { - // } + UpdateDisplayOption2(value) => { + pack!(buf, 0x22, [value]) + } // EnterVCOMSensing => { // } // VCOMSenseDuration(u8) => { @@ -300,17 +301,19 @@ impl Command { // } // AutoWriteBlackPattern(u8) => { // } - // XAddress(u8) => { - // } - // YAddress(u8) => { - // } + XAddress(address) => { + pack!(buf, 0x4E, [address]) + } + YAddress(address) => { + pack!(buf, 0x4F, [address]) + } AnalogBlockControl(value) => { pack!(buf, 0x74, [value]) } DigitalBlockControl(value) => { pack!(buf, 0x7E, [value]) } - _ => unimplemented!(), + _ => unimplemented!() }; interface.send_command(command)?; @@ -328,11 +331,9 @@ impl<'buf> BufCommand<'buf> { let (command, data) = match self { WriteBlackData(buffer) => { - // TODO: Handle rotation (0x24, buffer) } WriteRedData(buffer) => { - // TODO: Handle rotation (0x26, buffer) } WriteLUT(buffer) => { diff --git a/src/display.rs b/src/display.rs index 0236331..d0ffc30 100644 --- a/src/display.rs +++ b/src/display.rs @@ -69,7 +69,7 @@ impl Display where I: DisplayInterface { // POR is HiZ. Need pull from config // Command::BorderWaveform(u8).execute(&mut self.interface)?; - // BufCommand::WriteLUT().execute(&mut self.interface)?; + BufCommand::WriteLUT(&LUT_RED).execute(&mut self.interface)?; Command::DataEntryMode(DataEntryMode::IncrementYIncrementX, IncrementAxis::Horizontal).execute(&mut self.interface)?; @@ -82,19 +82,20 @@ impl Display where I: DisplayInterface { pub fn update>(&mut self, black: &[u8], red: &[u8], delay: &mut D) -> Result<(), I::Error> { // Write the B/W RAM + let buf_limit = ((self.rows() * self.cols() as u16) as f32 / 8.).ceil() as usize; Command::XAddress(0).execute(&mut self.interface)?; Command::YAddress(0).execute(&mut self.interface)?; - BufCommand::WriteBlackData(&black).execute(&mut self.interface)?; + BufCommand::WriteBlackData(&black[..buf_limit]).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)?; + BufCommand::WriteRedData(&red[..buf_limit]).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); + delay.delay_ms(50); // 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 @@ -120,3 +121,23 @@ impl Display where I: DisplayInterface { self.rotation } } + +const LUT_RED: [u8; 70] = [ + // Phase 0 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 + // A B C D A B C D A B C D A B C D A B C D A B C D A B C D + 0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000, // LUT0 - Black + 0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, // LUTT1 - White + 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, // IGNORE + 0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, // LUT3 - Red + 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, // LUT4 - VCOM + + // Duration | Repeat + // A B C D | + 64, 12, 32, 12, 6, // 0 Flash + 16, 8, 4, 4, 6, // 1 clear + 4, 8, 8, 16, 16, // 2 bring in the black + 2, 2, 2, 64, 32, // 3 time for red + 2, 2, 2, 2, 2, // 4 final black sharpen phase + 0, 0, 0, 0, 0, // 5 + 0, 0, 0, 0, 0 // 6 +]; diff --git a/src/graphics.rs b/src/graphics.rs index a1ddc71..135b24b 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,3 +1,4 @@ +use hal; use color::Color; use display::{Display, Rotation}; use interface::DisplayInterface; @@ -14,8 +15,20 @@ impl<'a, I> GraphicDisplay<'a, I> where I: DisplayInterface { GraphicDisplay { display, black_buffer, red_buffer } } - pub fn update(&mut self) -> Result<(), ()> { - unimplemented!() + pub fn update>(&mut self, delay: &mut D) -> Result<(), I::Error> { + self.display.update(self.black_buffer, self.red_buffer, delay) + } + + pub fn clear(&mut self, _color: Color) { + // TODO: Support color + for byte in &mut self.black_buffer.iter_mut() { + *byte = 1; // background_color.get_byte_value(); + } + + // TODO: Combine loops + for byte in &mut self.red_buffer.iter_mut() { + *byte = 0; // background_color.get_byte_value(); + } } fn set_pixel(&mut self, x: u32, y: u32, color: Color) { diff --git a/src/interface.rs b/src/interface.rs index 004052e..9bc4278 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -47,7 +47,7 @@ where fn write(&mut self, data: &[u8]) -> Result<(), SPI::Error> { // Select the controller with chip select (CS) - self.cs.set_low(); + // self.cs.set_low(); // Linux has a default limit of 4096 bytes per SPI transfer // https://github.com/torvalds/linux/blob/ccda4af0f4b92f7b4c308d3acc262f4a7e3affad/drivers/spi/spidev.c#L93 @@ -60,7 +60,7 @@ where } // Release the controller - self.cs.set_high(); + // self.cs.set_high(); Ok(()) }