-
Notifications
You must be signed in to change notification settings - Fork 140
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ilya
committed
Jan 19, 2022
1 parent
f49b6e9
commit 03c8244
Showing
5 changed files
with
635 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
#![deny(warnings)] | ||
|
||
use embedded_graphics::{ | ||
mono_font::MonoTextStyleBuilder, | ||
prelude::*, | ||
primitives::{Circle, Line, PrimitiveStyle}, | ||
text::{Baseline, Text, TextStyleBuilder}, | ||
}; | ||
use embedded_hal::prelude::*; | ||
use epd_waveshare::{ | ||
color::TriColor, | ||
epd2in13bc_v3::{Display2in13bc, Epd2in13bc}, | ||
graphics::DisplayRotation, | ||
prelude::*, | ||
}; | ||
use linux_embedded_hal::{ | ||
spidev::{self, SpidevOptions}, | ||
sysfs_gpio::Direction, | ||
Delay, Pin, Spidev, | ||
}; | ||
|
||
// The pins in this example are for the Universal e-Paper Raw Panel Driver HAT | ||
// 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 | ||
|
||
fn main() -> Result<(), std::io::Error> { | ||
// Configure SPI | ||
// Settings are taken from | ||
let mut spi = Spidev::open("/dev/spidev0.0").expect("spidev directory"); | ||
let options = SpidevOptions::new() | ||
.bits_per_word(8) | ||
.max_speed_hz(4_000_000) | ||
.mode(spidev::SpiModeFlags::SPI_MODE_0) | ||
.build(); | ||
spi.configure(&options).expect("spi configuration"); | ||
|
||
// Configure Digital I/O Pin to be used as Chip Select for SPI | ||
let cs = Pin::new(26); //BCM7 CE0 | ||
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(24); // GPIO 24, board J-18 | ||
busy.export().expect("busy export"); | ||
while !busy.is_exported() {} | ||
busy.set_direction(Direction::In).expect("busy Direction"); | ||
//busy.set_value(1).expect("busy Value set to 1"); | ||
|
||
let dc = Pin::new(25); // GPIO 25, board J-22 | ||
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 rst = Pin::new(17); // GPIO 17, board J-11 | ||
rst.export().expect("rst export"); | ||
while !rst.is_exported() {} | ||
rst.set_direction(Direction::Out).expect("rst Direction"); | ||
rst.set_value(1).expect("rst Value set to 1"); | ||
|
||
let mut delay = Delay {}; | ||
|
||
let mut epd2in13 = | ||
Epd2in13bc::new(&mut spi, cs, busy, dc, rst, &mut delay).expect("eink initalize error"); | ||
|
||
println!("Test all the rotations"); | ||
let mut display = Display2in13bc::default(); | ||
|
||
display.set_rotation(DisplayRotation::Rotate0); | ||
draw_text(&mut display, "Rotate 0!", 5, 5); | ||
|
||
display.set_rotation(DisplayRotation::Rotate90); | ||
draw_text(&mut display, "Rotate 90!", 5, 5); | ||
|
||
display.set_rotation(DisplayRotation::Rotate180); | ||
draw_text(&mut display, "Rotate 180!", 5, 5); | ||
|
||
display.set_rotation(DisplayRotation::Rotate270); | ||
draw_text(&mut display, "Rotate 270!", 5, 5); | ||
|
||
epd2in13.update_frame(&mut spi, display.buffer(), &mut delay)?; | ||
epd2in13 | ||
.display_frame(&mut spi, &mut delay) | ||
.expect("display frame new graphics"); | ||
delay.delay_ms(5000u16); | ||
|
||
display.clear_buffer(TriColor::White); | ||
println!("Now test new graphics with default rotation and some special stuff:"); | ||
|
||
// draw a analog clock | ||
let _ = Circle::with_center(Point::new(52, 52), 80) | ||
.into_styled(PrimitiveStyle::with_fill(TriColor::Chromatic)) | ||
.draw(&mut display); | ||
let _ = Circle::with_center(Point::new(52, 52), 80) | ||
.into_styled(PrimitiveStyle::with_stroke(TriColor::Black, 3)) | ||
.draw(&mut display); | ||
let _ = Line::new(Point::new(52, 52), Point::new(18, 28)) | ||
.into_styled(PrimitiveStyle::with_stroke(TriColor::White, 5)) | ||
.draw(&mut display); | ||
let _ = Line::new(Point::new(52, 52), Point::new(68, 28)) | ||
.into_styled(PrimitiveStyle::with_stroke(TriColor::White, 1)) | ||
.draw(&mut display); | ||
|
||
// draw white on black background | ||
let style = MonoTextStyleBuilder::new() | ||
.font(&embedded_graphics::mono_font::ascii::FONT_6X10) | ||
.text_color(TriColor::White) | ||
.background_color(TriColor::Chromatic) | ||
.build(); | ||
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); | ||
|
||
let _ = Text::with_text_style("Hello World!", Point::new(112, 10), style, text_style) | ||
.draw(&mut display); | ||
|
||
// use bigger/different font | ||
let style = MonoTextStyleBuilder::new() | ||
.font(&embedded_graphics::mono_font::ascii::FONT_10X20) | ||
.text_color(TriColor::White) | ||
.background_color(TriColor::Chromatic) | ||
.build(); | ||
|
||
let _ = Text::with_text_style("Hello\nWorld!", Point::new(112, 40), style, text_style) | ||
.draw(&mut display); | ||
|
||
epd2in13.update_color_frame(&mut spi, display.bw_buffer(), display.chromatic_buffer()).unwrap(); | ||
epd2in13.display_frame(&mut spi, &mut delay).unwrap(); | ||
|
||
println!("Finished tests - going to sleep"); | ||
epd2in13.clear_frame(&mut spi, &mut delay).unwrap(); | ||
epd2in13.sleep(&mut spi, &mut delay) | ||
} | ||
|
||
fn draw_text(display: &mut Display2in13bc, text: &str, x: i32, y: i32) { | ||
let style = MonoTextStyleBuilder::new() | ||
.font(&embedded_graphics::mono_font::ascii::FONT_6X10) | ||
.text_color(TriColor::White) | ||
.background_color(TriColor::Black) | ||
.build(); | ||
|
||
let text_style = TextStyleBuilder::new().baseline(Baseline::Top).build(); | ||
|
||
let _ = Text::with_text_style(text, Point::new(x, y), style, text_style).draw(display); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
|
||
use crate::traits; | ||
|
||
/// Epd7in5b_v3 commands | ||
/// | ||
/// Should rarely (never?) be needed directly. | ||
/// | ||
/// For more infos about the addresses and what they are doing look into the PDFs. | ||
#[allow(dead_code)] | ||
#[derive(Copy, Clone)] | ||
pub(crate) enum Command { | ||
/// Set Resolution, LUT selection, BWR pixels, gate scan direction, source shift | ||
/// direction, booster switch, soft reset. | ||
PanelSetting = 0x00, | ||
|
||
/// Selecting internal and external power | ||
PowerSetting = 0x01, | ||
|
||
/// After the Power Off command, the driver will power off following the Power Off | ||
/// Sequence; BUSY signal will become "0". This command will turn off charge pump, | ||
/// T-con, source driver, gate driver, VCOM, and temperature sensor, but register | ||
/// data will be kept until VDD becomes OFF. Source Driver output and Vcom will remain | ||
/// as previous condition, which may have 2 conditions: 0V or floating. | ||
PowerOff = 0x02, | ||
|
||
/// Setting Power OFF sequence | ||
PowerOffSequenceSetting = 0x03, | ||
|
||
/// Turning On the Power | ||
/// | ||
/// After the Power ON command, the driver will power on following the Power ON | ||
/// sequence. Once complete, the BUSY signal will become "1". | ||
PowerOn = 0x04, | ||
|
||
/// Starting data transmission | ||
BoosterSoftStart = 0x06, | ||
|
||
/// This command makes the chip enter the deep-sleep mode to save power. | ||
/// | ||
/// The deep sleep mode would return to stand-by by hardware reset. | ||
/// | ||
/// The only one parameter is a check code, the command would be excuted if check code = 0xA5. | ||
DeepSleep = 0x07, | ||
|
||
/// This command starts transmitting data and write them into SRAM. To complete data | ||
/// transmission, command DSP (Data Stop) must be issued. Then the chip will start to | ||
/// send data/VCOM for panel. | ||
/// | ||
/// BLACK/WHITE or OLD_DATA | ||
DataStartTransmissionBlackWhite = 0x10, | ||
|
||
/// To stop data transmission, this command must be issued to check the `data_flag`. | ||
/// | ||
/// After this command, BUSY signal will become "0" until the display update is | ||
/// finished. | ||
DataStop = 0x11, | ||
|
||
/// After this command is issued, driver will refresh display (data/VCOM) according to | ||
/// SRAM data and LUT. | ||
/// | ||
/// After Display Refresh command, BUSY signal will become "0" until the display | ||
/// update is finished. | ||
DisplayRefresh = 0x12, | ||
|
||
/// RED or NEW_DATA | ||
DataStartTransmissionChromatic = 0x13, | ||
|
||
/// Dual SPI - what for? | ||
DualSpi = 0x15, | ||
|
||
/// This command builds the VCOM Look-Up Table (LUTC). | ||
LutForVcom = 0x20, | ||
/// This command builds the White to White Look-Up Table (LUTWW). | ||
LutWhiteToWhite = 0x21, | ||
/// This command builds the Black to White Look-Up Table (LUTKW). | ||
LutBlackToWhite = 0x22, | ||
/// This command builds the White to Black Look-Up Table (LUTWK). | ||
LutWhiteToBlack = 0x23, | ||
/// This command builds the Black to Black Look-Up Table (LUTKK). | ||
LutBlackToBlack = 0x24, | ||
/// This command builds the Border? Look-Up Table (LUTR0). | ||
LutBorder = 0x25, | ||
|
||
/// This command builds the XON Look-Up Table (LUTXON). | ||
LutXon = 0x2A, | ||
|
||
/// The command controls the PLL clock frequency. | ||
PllControl = 0x30, | ||
|
||
/// This command reads the temperature sensed by the temperature sensor. | ||
TemperatureSensor = 0x40, | ||
|
||
/// This command indicates the interval of Vcom and data output. When setting the | ||
/// vertical back porch, the total blanking will be kept (20 Hsync). | ||
VcomAndDataIntervalSetting = 0x50, | ||
|
||
/// This command defines non-overlap period of Gate and Source. | ||
TconSetting = 0x60, | ||
/// This command defines alternative resolution and this setting is of higher priority | ||
/// than the RES\[1:0\] in R00H (PSR). | ||
TconResolution = 0x61, | ||
//TODO /// This command defines MCU host direct access external memory mode. | ||
GateSourceStart = 0x65, | ||
|
||
/// The LUT_REV / Chip Revision is read from OTP address = 25001 and 25000. | ||
Revision = 0x70, | ||
|
||
/// This command sets `VCOM_DC` value. | ||
VcmDcSetting = 0x82, | ||
|
||
// TODO | ||
ProgramMode = 0xA0, | ||
// | ||
// TODO | ||
ActiveProgram = 0xA1, | ||
|
||
// TODO | ||
ReadOtpData = 0xA2, | ||
// /// This is in all the Waveshare controllers for Epd7in5, but it's not documented | ||
// /// anywhere in the datasheet `¯\_(ツ)_/¯` | ||
// FlashMode = 0xE5, | ||
} | ||
|
||
impl traits::Command for Command { | ||
/// Returns the address of the command | ||
fn address(self) -> u8 { | ||
self as u8 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::traits::Command as CommandTrait; | ||
|
||
#[test] | ||
fn command_addr() { | ||
assert_eq!(Command::PanelSetting.address(), 0x00); | ||
assert_eq!(Command::DisplayRefresh.address(), 0x12); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use crate::epd2in13bc_v3::{DEFAULT_BACKGROUND_COLOR, HEIGHT, WIDTH, NUM_DISPLAY_BITS}; | ||
use crate::graphics::{DisplayColorRendering, DisplayRotation, TriDisplay}; | ||
use crate::color::TriColor; | ||
use embedded_graphics_core::prelude::*; | ||
|
||
/// Full size buffer for use with the 7in5 EPD | ||
/// | ||
/// Can also be manually constructed: | ||
/// `buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); WIDTH / 8 * HEIGHT]` | ||
pub struct Display2in13bc { | ||
buffer: [u8; 2 * NUM_DISPLAY_BITS as usize], | ||
rotation: DisplayRotation, | ||
} | ||
|
||
impl Default for Display2in13bc { | ||
fn default() -> Self { | ||
Display2in13bc { | ||
buffer: [DEFAULT_BACKGROUND_COLOR.get_byte_value(); | ||
2 * NUM_DISPLAY_BITS as usize], | ||
rotation: DisplayRotation::default(), | ||
} | ||
} | ||
} | ||
|
||
impl DrawTarget for Display2in13bc { | ||
type Color = TriColor; | ||
type Error = core::convert::Infallible; | ||
|
||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> | ||
where | ||
I: IntoIterator<Item = Pixel<Self::Color>>, | ||
{ | ||
for pixel in pixels { | ||
self.draw_helper_tri(WIDTH, HEIGHT, pixel, DisplayColorRendering::Positive)?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl OriginDimensions for Display2in13bc { | ||
fn size(&self) -> Size { | ||
Size::new(WIDTH, HEIGHT) | ||
} | ||
} | ||
|
||
impl TriDisplay for Display2in13bc { | ||
fn buffer(&self) -> &[u8] { | ||
&self.buffer | ||
} | ||
|
||
fn get_mut_buffer(&mut self) -> &mut [u8] { | ||
&mut self.buffer | ||
} | ||
|
||
fn set_rotation(&mut self, rotation: DisplayRotation) { | ||
self.rotation = rotation; | ||
} | ||
|
||
fn rotation(&self) -> DisplayRotation { | ||
self.rotation | ||
} | ||
|
||
fn chromatic_offset(&self) -> usize { | ||
NUM_DISPLAY_BITS as usize | ||
} | ||
|
||
fn bw_buffer(&self) -> &[u8] { | ||
&self.buffer[0..self.chromatic_offset()] | ||
} | ||
|
||
fn chromatic_buffer(&self) -> &[u8] { | ||
&self.buffer[self.chromatic_offset()..] | ||
} | ||
} |
Oops, something went wrong.