Document most public items

This commit is contained in:
Wesley Moore 2018-12-26 12:54:30 +11:00
parent b6d8d7aabb
commit ca8175ca3e
No known key found for this signature in database
GPG key ID: BF67766C0BC2D0EE
9 changed files with 232 additions and 19 deletions

View file

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Wesley Moore <wes@wezm.net>"] authors = ["Wesley Moore <wes@wezm.net>"]
[dependencies] [dependencies]
libm = "0.1.2"
[dependencies.embedded-hal] [dependencies.embedded-hal]
features = ["unproven"] features = ["unproven"]
@ -13,10 +14,19 @@ version = "0.2.2"
optional = true optional = true
version = "0.4.4" version = "0.4.4"
[dev-dependencies] [dependencies.linux-embedded-hal]
linux-embedded-hal = "0.2.1" # for examples optional = true
profont = "0.1" version = "0.2.1"
[dependencies.profont]
optional = true
version = "0.1"
[features] [features]
default = ["graphics"] default = ["graphics"]
graphics = ["embedded-graphics"] graphics = ["embedded-graphics"]
examples = ["linux-embedded-hal", "profont"]
[[example]]
name = "raspberry_pi_inky_phat"
required-features = ["examples"]

View file

@ -1,4 +1,4 @@
# SSD1675 EPD display driver # SSD1675 ePaper Display Driver
Rust driver for the [Solomon Systech SSD1675][SSD1675] e-Paper display (EPD) Rust driver for the [Solomon Systech SSD1675][SSD1675] e-Paper display (EPD)
controller, for use with [embedded-hal]. controller, for use with [embedded-hal].
@ -8,7 +8,6 @@ controller, for use with [embedded-hal].
<img src="https://raw.githubusercontent.com/wezm/ssd1675/master/IMG_2198.jpg" width="459" alt="Photo of Inky pHAT ePaper display on Raspberry Pi Zero W" /> <img src="https://raw.githubusercontent.com/wezm/ssd1675/master/IMG_2198.jpg" width="459" alt="Photo of Inky pHAT ePaper display on Raspberry Pi Zero W" />
## Description ## Description
This driver is intended to work on embedded platforms using the `embedded-hal` This driver is intended to work on embedded platforms using the `embedded-hal`
@ -21,6 +20,14 @@ The library has been tested and confirmed working on these devices:
* Red/Black/White [Inky pHAT] version 2 on Raspberry Pi Zero (pictured above) * Red/Black/White [Inky pHAT] version 2 on Raspberry Pi Zero (pictured above)
## Examples
**Note:** To build the examples the `examples` feature needs to be enabled. E.g.
cargo build --release --examples --features examples
* [Raspberry Pi Inky pHAT example](https://github.com/wezm/ssd1675/blob/master/examples/raspberry_pi_inky_phat.rs).
## Credits ## Credits
* [Waveshare EPD driver](https://github.com/caemor/epd-waveshare) * [Waveshare EPD driver](https://github.com/caemor/epd-waveshare)

View file

@ -1,3 +1,4 @@
/// Represents the state of a pixel in the display
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub enum Color { pub enum Color {
Black, Black,

View file

@ -125,7 +125,7 @@ pub enum Command {
// WriteDisplayOption, // WriteDisplayOption,
// WriteUserId, // WriteUserId,
// OTPProgramMode, // OTPProgramMode,
/// Set the number dummy line period in terms of gate line width (TGate) /// Set the number of dummy line period in terms of gate line width (TGate)
DummyLinePeriod(u8), DummyLinePeriod(u8),
/// Set the gate line width (TGate) /// Set the gate line width (TGate)
GateLineWidth(u8), GateLineWidth(u8),

View file

@ -1,6 +1,25 @@
use command::{BufCommand, Command, DataEntryMode, IncrementAxis}; use command::{BufCommand, Command, DataEntryMode, IncrementAxis};
use display::{Dimensions, Rotation}; use display::{self, Dimensions, Rotation};
/// Builder for constructing a display Config.
///
/// Dimensions must supplied, all other settings will use a default value if not supplied. However
/// it's likely that LUT values will need to be supplied to successfully use a display.
///
/// ### Example
///
/// ```
/// use ssd1675::{Builder, Dimensions, Rotation};
///
/// let config = Builder::new()
/// .dimensions(Dimensions {
/// rows: 212,
/// cols: 104,
/// })
/// .rotation(Rotation::Rotate270)
/// .build()
/// .expect("invalid configuration");
/// ```
pub struct Builder<'a> { pub struct Builder<'a> {
dummy_line_period: Command, dummy_line_period: Command,
gate_line_width: Command, gate_line_width: Command,
@ -11,6 +30,9 @@ pub struct Builder<'a> {
rotation: Rotation, rotation: Rotation,
} }
/// Error returned if Builder configuration is invalid.
///
/// Currently only returned if a configuration is built without dimensions.
#[derive(Debug)] #[derive(Debug)]
pub struct BuilderError {} pub struct BuilderError {}
@ -42,10 +64,14 @@ impl<'a> Default for Builder<'a> {
} }
impl<'a> Builder<'a> { impl<'a> Builder<'a> {
/// Create a new Builder.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Set the number of dummy line period in terms of gate line width (TGate).
///
/// Defaults to 0x07. Corresponds to command 0x3A.
pub fn dummy_line_period(self, dummy_line_period: u8) -> Self { pub fn dummy_line_period(self, dummy_line_period: u8) -> Self {
Self { Self {
dummy_line_period: Command::DummyLinePeriod(dummy_line_period), dummy_line_period: Command::DummyLinePeriod(dummy_line_period),
@ -53,6 +79,9 @@ impl<'a> Builder<'a> {
} }
} }
/// Set the gate line width (TGate).
///
/// Defaults to 0x04. Corresponds to command 0x3B.
pub fn gate_line_width(self, gate_line_width: u8) -> Self { pub fn gate_line_width(self, gate_line_width: u8) -> Self {
Self { Self {
gate_line_width: Command::GateLineWidth(gate_line_width), gate_line_width: Command::GateLineWidth(gate_line_width),
@ -60,6 +89,9 @@ impl<'a> Builder<'a> {
} }
} }
/// Set VCOM register value.
///
/// Defaults to 0x3C. Corresponds to command 0x2C.
pub fn vcom(self, value: u8) -> Self { pub fn vcom(self, value: u8) -> Self {
Self { Self {
write_vcom: Command::WriteVCOM(value), write_vcom: Command::WriteVCOM(value),
@ -67,6 +99,13 @@ impl<'a> Builder<'a> {
} }
} }
/// Set lookup table (70 bytes).
///
/// **Note:** The supplied slice must be exactly 70 bytes long.
///
/// There is no default for the lookup table. Corresponds to command 0x32. If not supplied then
/// the default in the controller is used. Apparently the display manufacturer will normally
/// supply the LUT values for a particular display batch.
pub fn lut(self, lut: &'a [u8]) -> Self { pub fn lut(self, lut: &'a [u8]) -> Self {
Self { Self {
write_lut: Some(BufCommand::WriteLUT(lut)), write_lut: Some(BufCommand::WriteLUT(lut)),
@ -74,6 +113,10 @@ impl<'a> Builder<'a> {
} }
} }
/// Define data entry sequence.
///
/// Defaults to DataEntryMode::IncrementAxis, IncrementAxis::Horizontal. Corresponds to command
/// 0x11.
pub fn data_entry_mode( pub fn data_entry_mode(
self, self,
data_entry_mode: DataEntryMode, data_entry_mode: DataEntryMode,
@ -85,17 +128,41 @@ impl<'a> Builder<'a> {
} }
} }
/// Set the display dimensions.
///
/// There is no default for this setting. The dimensions must be set for the builder to
/// successfully build a Config.
pub fn dimensions(self, dimensions: Dimensions) -> Self { pub fn dimensions(self, dimensions: Dimensions) -> Self {
assert!(
dimensions.cols % 8 == 0,
"columns must be evenly divisible by 8"
);
assert!(
dimensions.rows <= display::MAX_GATE_OUTPUTS,
"rows must be less than MAX_GATE_OUTPUTS"
);
assert!(
dimensions.cols <= display::MAX_SOURCE_OUTPUTS,
"cols must be less than MAX_SOURCE_OUTPUTS"
);
Self { Self {
dimensions: Some(dimensions), dimensions: Some(dimensions),
..self ..self
} }
} }
/// Set the display rotation.
///
/// Defaults to no rotation (`Rotation::Rotate0`). Use this to translate between the physical
/// rotation of the display and how the data is displayed on the display.
pub fn rotation(self, rotation: Rotation) -> Self { pub fn rotation(self, rotation: Rotation) -> Self {
Self { rotation, ..self } Self { rotation, ..self }
} }
/// Build the display Config.
///
/// Will fail if dimensions are not set.
pub fn build(self) -> Result<Config<'a>, BuilderError> { pub fn build(self) -> Result<Config<'a>, BuilderError> {
Ok(Config { Ok(Config {
dummy_line_period: self.dummy_line_period, dummy_line_period: self.dummy_line_period,

View file

@ -1,22 +1,38 @@
extern crate libm;
use hal; use hal;
use command::{BufCommand, Command, DataEntryMode, DeepSleepMode, IncrementAxis}; use command::{BufCommand, Command, DeepSleepMode};
use config::Config; use config::Config;
use interface::DisplayInterface; use interface::DisplayInterface;
// Max display resolution is 160x296 // Max display resolution is 160x296
const MAX_SOURCE_OUTPUTS: usize = 160; /// The maximum number of rows supported by the controller
const MAX_GATE_OUTPUTS: usize = 296; pub const MAX_GATE_OUTPUTS: u16 = 296;
/// The maximum number of columns supported by the controller
pub const MAX_SOURCE_OUTPUTS: u8 = 160;
// Magic numbers from the data sheet // Magic numbers from the data sheet
const ANALOG_BLOCK_CONTROL_MAGIC: u8 = 0x54; const ANALOG_BLOCK_CONTROL_MAGIC: u8 = 0x54;
const DIGITAL_BLOCK_CONTROL_MAGIC: u8 = 0x3B; const DIGITAL_BLOCK_CONTROL_MAGIC: u8 = 0x3B;
/// Represents the dimensions of the display.
pub struct Dimensions { pub struct Dimensions {
/// The number of rows the display has.
///
/// Must be less than or equal to MAX_GATE_OUTPUTS.
pub rows: u16, pub rows: u16,
/// The number of columns the display has.
///
/// Must be less than or equal to MAX_SOURCE_OUTPUTS.
pub cols: u8, pub cols: u8,
} }
/// Represents the physical rotation of the display relative to the native orientation.
///
/// For example the native orientation of the Inky pHAT display is a tall (portrait) 104x212
/// display. `Rotate270` can be used to make it the right way up when attached to a Raspberry Pi
/// Zero with the ports on the top.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Rotation { pub enum Rotation {
Rotate0, Rotate0,
@ -26,11 +42,13 @@ pub enum Rotation {
} }
impl Default for Rotation { impl Default for Rotation {
/// Default is no rotation (`Rotate0`).
fn default() -> Self { fn default() -> Self {
Rotation::Rotate0 Rotation::Rotate0
} }
} }
/// A configured display with a hardware interface.
pub struct Display<'a, I> pub struct Display<'a, I>
where where
I: DisplayInterface, I: DisplayInterface,
@ -43,12 +61,16 @@ impl<'a, I> Display<'a, I>
where where
I: DisplayInterface, I: DisplayInterface,
{ {
/// Create a new display instance from a DisplayInterface and Config.
///
/// The `Config` is typically created with `config::Builder`.
pub fn new(interface: I, config: Config<'a>) -> Self { pub fn new(interface: I, config: Config<'a>) -> Self {
// TODO: Assert dimensions are evenly divisible by 8
Self { interface, config } Self { interface, config }
} }
/// Perform a hardware reset followed by software reset /// Perform a hardware reset followed by software reset.
///
/// This will wake a controller that has previously entered deep sleep.
pub fn reset<D: hal::blocking::delay::DelayMs<u8>>( pub fn reset<D: hal::blocking::delay::DelayMs<u8>>(
&mut self, &mut self,
delay: &mut D, delay: &mut D,
@ -79,7 +101,6 @@ where
// POR is HiZ. Need pull from config // POR is HiZ. Need pull from config
// Command::BorderWaveform(u8).execute(&mut self.interface)?; // Command::BorderWaveform(u8).execute(&mut self.interface)?;
// BufCommand::WriteLUT(&LUT_RED).execute(&mut self.interface)?;
if let Some(ref write_lut) = self.config.write_lut { if let Some(ref write_lut) = self.config.write_lut {
write_lut.execute(&mut self.interface)?; write_lut.execute(&mut self.interface)?;
} }
@ -93,6 +114,10 @@ where
Ok(()) Ok(())
} }
/// Update the display by writing the supplied B/W and Red buffers to the controller.
///
/// This method will write the two buffers to the controller then initiate the update
/// display command. Currently it will busy wait until the update has completed.
pub fn update<D: hal::blocking::delay::DelayMs<u8>>( pub fn update<D: hal::blocking::delay::DelayMs<u8>>(
&mut self, &mut self,
black: &[u8], black: &[u8],
@ -100,7 +125,7 @@ where
delay: &mut D, delay: &mut D,
) -> Result<(), I::Error> { ) -> Result<(), I::Error> {
// Write the B/W RAM // Write the B/W RAM
let buf_limit = ((self.rows() * self.cols() as u16) as f32 / 8.).ceil() as usize; let buf_limit = libm::ceilf((self.rows() * self.cols() as u16) as f32 / 8.) as usize;
Command::XAddress(0).execute(&mut self.interface)?; Command::XAddress(0).execute(&mut self.interface)?;
Command::YAddress(0).execute(&mut self.interface)?; Command::YAddress(0).execute(&mut self.interface)?;
BufCommand::WriteBlackData(&black[..buf_limit]).execute(&mut self.interface)?; BufCommand::WriteBlackData(&black[..buf_limit]).execute(&mut self.interface)?;
@ -123,18 +148,25 @@ where
Ok(()) Ok(())
} }
/// Enter deep sleep mode.
///
/// This puts the display controller into a low power mode. `reset` must be called to wake it
/// from sleep.
pub fn deep_sleep(&mut self) -> Result<(), I::Error> { pub fn deep_sleep(&mut self) -> Result<(), I::Error> {
Command::DeepSleepMode(DeepSleepMode::PreserveRAM).execute(&mut self.interface) Command::DeepSleepMode(DeepSleepMode::PreserveRAM).execute(&mut self.interface)
} }
/// Returns the number of rows the display has.
pub fn rows(&self) -> u16 { pub fn rows(&self) -> u16 {
self.config.dimensions.rows self.config.dimensions.rows
} }
/// Returns the number of columns the display has.
pub fn cols(&self) -> u8 { pub fn cols(&self) -> u8 {
self.config.dimensions.cols self.config.dimensions.cols
} }
/// Returns the rotation the display was configured with.
pub fn rotation(&self) -> Rotation { pub fn rotation(&self) -> Rotation {
self.config.rotation self.config.rotation
} }

View file

@ -4,6 +4,11 @@ use display::{Display, Rotation};
use hal; use hal;
use interface::DisplayInterface; use interface::DisplayInterface;
/// A display that holds buffers for drawing into and updating the display from.
///
/// When the `graphics` feature is enabled `GraphicDisplay` implements the `Draw` trait from
/// [embedded-graphics](https://crates.io/crates/embedded-graphics). This allows basic shapes and
/// text to be drawn on the display.
pub struct GraphicDisplay<'a, I> pub struct GraphicDisplay<'a, I>
where where
I: DisplayInterface, I: DisplayInterface,
@ -17,6 +22,10 @@ impl<'a, I> GraphicDisplay<'a, I>
where where
I: DisplayInterface, I: DisplayInterface,
{ {
/// Promote a `Display` to a `GraphicDisplay`.
///
/// B/W and Red buffers for drawing into must be supplied. These should be `rows` * `cols` in
/// length.
pub fn new( pub fn new(
display: Display<'a, I>, display: Display<'a, I>,
black_buffer: &'a mut [u8], black_buffer: &'a mut [u8],
@ -29,6 +38,7 @@ where
} }
} }
/// Update the display by writing the buffers to the controller.
pub fn update<D: hal::blocking::delay::DelayMs<u8>>( pub fn update<D: hal::blocking::delay::DelayMs<u8>>(
&mut self, &mut self,
delay: &mut D, delay: &mut D,
@ -37,6 +47,7 @@ where
.update(self.black_buffer, self.red_buffer, delay) .update(self.black_buffer, self.red_buffer, delay)
} }
/// Clear the buffers, filling them a single color.
pub fn clear(&mut self, color: Color) { pub fn clear(&mut self, color: Color) {
let (black, red) = match color { let (black, red) = match color {
Color::White => (0xFF, 0x00), Color::White => (0xFF, 0x00),

View file

@ -14,16 +14,71 @@ pub trait DisplayInterface {
fn busy_wait(&self); fn busy_wait(&self);
} }
/// The hardware interface to a display.
///
/// ### Example
///
/// This example uses the Linux implementation of the embedded HAL traits to build a display
/// interface. For a complete example see [the Raspberry Pi Inky pHAT example](https://github.com/wezm/ssd1675/blob/master/examples/raspberry_pi_inky_phat.rs).
///
/// ```ignore
/// 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};
///
/// extern crate ssd1675;
/// use ssd1675::{Builder, Color, Dimensions, Display, GraphicDisplay, Rotation};
///
/// // 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 Pins
/// 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");
///
/// // Build the interface from the pins and SPI device
/// let controller = ssd1675::Interface::new(spi, cs, busy, dc, reset);
pub struct Interface<SPI, CS, BUSY, DC, RESET> { pub struct Interface<SPI, CS, BUSY, DC, RESET> {
/// SPI /// SPI interface
spi: SPI, spi: SPI,
/// CS for SPI /// CS (chip select) for SPI (output)
cs: CS, cs: CS,
/// Low for busy, Wait until display is ready! /// Active low busy pin (input)
busy: BUSY, busy: BUSY,
/// Data/Command Control Pin (High for data, Low for command) /// Data/Command Control Pin (High for data, Low for command) (output)
dc: DC, dc: DC,
/// Pin for Reseting /// Pin for reseting the controller (output)
reset: RESET, reset: RESET,
} }
@ -35,6 +90,7 @@ where
DC: hal::digital::OutputPin, DC: hal::digital::OutputPin,
RESET: hal::digital::OutputPin, RESET: hal::digital::OutputPin,
{ {
/// Create a new Interface from embedded hal traits.
pub fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, reset: RESET) -> Self { pub fn new(spi: SPI, cs: CS, busy: BUSY, dc: DC, reset: RESET) -> Self {
Self { Self {
spi, spi,

View file

@ -1,5 +1,34 @@
#![no_std] #![no_std]
//! SSD1675 ePaper Display Driver
//!
//! For a complete example see
//! [the Raspberry Pi Inky pHAT example](https://github.com/wezm/ssd1675/blob/master/examples/raspberry_pi_inky_phat.rs).
//!
//! ### Usage
//!
//! To control a display you will need:
//!
//! * An [Interface] to the controller
//! * A [display configuration][Config]
//! * A [Display]
//!
//! The `Interface` captures the details of the hardware connection to the SSD1675 controller. This
//! includes an SPI device and some GPIO pins. The SSD1675 can control many different displays that
//! vary in dimensions, rotation, and driving characteristics. The [Config] captures these details.
//! To aid in constructing the `Config` there is a [Builder] interface. Finally when you have an
//! interface and a Config a Display instance can be created. Optionally the Display can be
//! promoted to a [GraphicDisplay], which allows it to use the functionality from the
//! [embedded-graphics crate]. The plain display only provides the ability to update the display by
//! passing black/white and red buffers.
//!
//! To update the display you will typically follow this flow:
//!
//! * [reset]
//! * [clear]
//! * [update]
//! * [sleep]
extern crate embedded_hal as hal; extern crate embedded_hal as hal;
#[cfg(test)] #[cfg(test)]