mirror of
https://github.com/Feliix42/ssd1675.git
synced 2024-11-21 10:26:30 +00:00
Document most public items
This commit is contained in:
parent
b6d8d7aabb
commit
ca8175ca3e
9 changed files with 232 additions and 19 deletions
16
Cargo.toml
16
Cargo.toml
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
authors = ["Wesley Moore <wes@wezm.net>"]
|
||||
|
||||
[dependencies]
|
||||
libm = "0.1.2"
|
||||
|
||||
[dependencies.embedded-hal]
|
||||
features = ["unproven"]
|
||||
|
@ -13,10 +14,19 @@ version = "0.2.2"
|
|||
optional = true
|
||||
version = "0.4.4"
|
||||
|
||||
[dev-dependencies]
|
||||
linux-embedded-hal = "0.2.1" # for examples
|
||||
profont = "0.1"
|
||||
[dependencies.linux-embedded-hal]
|
||||
optional = true
|
||||
version = "0.2.1"
|
||||
|
||||
[dependencies.profont]
|
||||
optional = true
|
||||
version = "0.1"
|
||||
|
||||
[features]
|
||||
default = ["graphics"]
|
||||
graphics = ["embedded-graphics"]
|
||||
examples = ["linux-embedded-hal", "profont"]
|
||||
|
||||
[[example]]
|
||||
name = "raspberry_pi_inky_phat"
|
||||
required-features = ["examples"]
|
||||
|
|
11
README.md
11
README.md
|
@ -1,4 +1,4 @@
|
|||
# SSD1675 EPD display driver
|
||||
# SSD1675 ePaper Display Driver
|
||||
|
||||
Rust driver for the [Solomon Systech SSD1675][SSD1675] e-Paper display (EPD)
|
||||
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" />
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
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)
|
||||
|
||||
## 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
|
||||
|
||||
* [Waveshare EPD driver](https://github.com/caemor/epd-waveshare)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// Represents the state of a pixel in the display
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
|
|
|
@ -125,7 +125,7 @@ pub enum Command {
|
|||
// WriteDisplayOption,
|
||||
// WriteUserId,
|
||||
// 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),
|
||||
/// Set the gate line width (TGate)
|
||||
GateLineWidth(u8),
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
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> {
|
||||
dummy_line_period: Command,
|
||||
gate_line_width: Command,
|
||||
|
@ -11,6 +30,9 @@ pub struct Builder<'a> {
|
|||
rotation: Rotation,
|
||||
}
|
||||
|
||||
/// Error returned if Builder configuration is invalid.
|
||||
///
|
||||
/// Currently only returned if a configuration is built without dimensions.
|
||||
#[derive(Debug)]
|
||||
pub struct BuilderError {}
|
||||
|
||||
|
@ -42,10 +64,14 @@ impl<'a> Default for Builder<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Builder<'a> {
|
||||
/// Create a new Builder.
|
||||
pub fn new() -> Self {
|
||||
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 {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
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(
|
||||
self,
|
||||
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 {
|
||||
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 {
|
||||
dimensions: Some(dimensions),
|
||||
..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 {
|
||||
Self { rotation, ..self }
|
||||
}
|
||||
|
||||
/// Build the display Config.
|
||||
///
|
||||
/// Will fail if dimensions are not set.
|
||||
pub fn build(self) -> Result<Config<'a>, BuilderError> {
|
||||
Ok(Config {
|
||||
dummy_line_period: self.dummy_line_period,
|
||||
|
|
|
@ -1,22 +1,38 @@
|
|||
extern crate libm;
|
||||
|
||||
use hal;
|
||||
|
||||
use command::{BufCommand, Command, DataEntryMode, DeepSleepMode, IncrementAxis};
|
||||
use command::{BufCommand, Command, DeepSleepMode};
|
||||
use config::Config;
|
||||
use interface::DisplayInterface;
|
||||
|
||||
// Max display resolution is 160x296
|
||||
const MAX_SOURCE_OUTPUTS: usize = 160;
|
||||
const MAX_GATE_OUTPUTS: usize = 296;
|
||||
/// The maximum number of rows supported by the controller
|
||||
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
|
||||
const ANALOG_BLOCK_CONTROL_MAGIC: u8 = 0x54;
|
||||
const DIGITAL_BLOCK_CONTROL_MAGIC: u8 = 0x3B;
|
||||
|
||||
/// Represents the dimensions of the display.
|
||||
pub struct Dimensions {
|
||||
/// The number of rows the display has.
|
||||
///
|
||||
/// Must be less than or equal to MAX_GATE_OUTPUTS.
|
||||
pub rows: u16,
|
||||
/// The number of columns the display has.
|
||||
///
|
||||
/// Must be less than or equal to MAX_SOURCE_OUTPUTS.
|
||||
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)]
|
||||
pub enum Rotation {
|
||||
Rotate0,
|
||||
|
@ -26,11 +42,13 @@ pub enum Rotation {
|
|||
}
|
||||
|
||||
impl Default for Rotation {
|
||||
/// Default is no rotation (`Rotate0`).
|
||||
fn default() -> Self {
|
||||
Rotation::Rotate0
|
||||
}
|
||||
}
|
||||
|
||||
/// A configured display with a hardware interface.
|
||||
pub struct Display<'a, I>
|
||||
where
|
||||
I: DisplayInterface,
|
||||
|
@ -43,12 +61,16 @@ impl<'a, I> Display<'a, I>
|
|||
where
|
||||
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 {
|
||||
// TODO: Assert dimensions are evenly divisible by 8
|
||||
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>>(
|
||||
&mut self,
|
||||
delay: &mut D,
|
||||
|
@ -79,7 +101,6 @@ where
|
|||
// POR is HiZ. Need pull from config
|
||||
// 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 {
|
||||
write_lut.execute(&mut self.interface)?;
|
||||
}
|
||||
|
@ -93,6 +114,10 @@ where
|
|||
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>>(
|
||||
&mut self,
|
||||
black: &[u8],
|
||||
|
@ -100,7 +125,7 @@ where
|
|||
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;
|
||||
let buf_limit = libm::ceilf((self.rows() * self.cols() as u16) as f32 / 8.) as usize;
|
||||
Command::XAddress(0).execute(&mut self.interface)?;
|
||||
Command::YAddress(0).execute(&mut self.interface)?;
|
||||
BufCommand::WriteBlackData(&black[..buf_limit]).execute(&mut self.interface)?;
|
||||
|
@ -123,18 +148,25 @@ where
|
|||
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> {
|
||||
Command::DeepSleepMode(DeepSleepMode::PreserveRAM).execute(&mut self.interface)
|
||||
}
|
||||
|
||||
/// Returns the number of rows the display has.
|
||||
pub fn rows(&self) -> u16 {
|
||||
self.config.dimensions.rows
|
||||
}
|
||||
|
||||
/// Returns the number of columns the display has.
|
||||
pub fn cols(&self) -> u8 {
|
||||
self.config.dimensions.cols
|
||||
}
|
||||
|
||||
/// Returns the rotation the display was configured with.
|
||||
pub fn rotation(&self) -> Rotation {
|
||||
self.config.rotation
|
||||
}
|
||||
|
|
|
@ -4,6 +4,11 @@ use display::{Display, Rotation};
|
|||
use hal;
|
||||
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>
|
||||
where
|
||||
I: DisplayInterface,
|
||||
|
@ -17,6 +22,10 @@ impl<'a, I> GraphicDisplay<'a, I>
|
|||
where
|
||||
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(
|
||||
display: Display<'a, I>,
|
||||
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>>(
|
||||
&mut self,
|
||||
delay: &mut D,
|
||||
|
@ -37,6 +47,7 @@ where
|
|||
.update(self.black_buffer, self.red_buffer, delay)
|
||||
}
|
||||
|
||||
/// Clear the buffers, filling them a single color.
|
||||
pub fn clear(&mut self, color: Color) {
|
||||
let (black, red) = match color {
|
||||
Color::White => (0xFF, 0x00),
|
||||
|
|
|
@ -14,16 +14,71 @@ pub trait DisplayInterface {
|
|||
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> {
|
||||
/// SPI
|
||||
/// SPI interface
|
||||
spi: SPI,
|
||||
/// CS for SPI
|
||||
/// CS (chip select) for SPI (output)
|
||||
cs: CS,
|
||||
/// Low for busy, Wait until display is ready!
|
||||
/// Active low busy pin (input)
|
||||
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,
|
||||
/// Pin for Reseting
|
||||
/// Pin for reseting the controller (output)
|
||||
reset: RESET,
|
||||
}
|
||||
|
||||
|
@ -35,6 +90,7 @@ where
|
|||
DC: 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 {
|
||||
Self {
|
||||
spi,
|
||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -1,5 +1,34 @@
|
|||
#![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;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue