mirror of
https://github.com/Feliix42/ssd1675.git
synced 2024-11-22 02:46:30 +00:00
commit
b7e0c3162e
9 changed files with 282 additions and 30 deletions
16
Cargo.toml
16
Cargo.toml
|
@ -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"]
|
||||||
|
|
33
README.md
33
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)
|
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,27 @@ 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 with Inky pHAT
|
||||||
|
|
||||||
|
The [Raspberry Pi Inky pHAT
|
||||||
|
example](https://github.com/wezm/ssd1675/blob/master/examples/raspberry_pi_inky_phat.rs),
|
||||||
|
shows how to display information on an [Inky pHAT] using this crate. The photo
|
||||||
|
at the top of the page shows this example in action. To avoid the need to
|
||||||
|
compile on the Raspberry Pi itself I recommend cross-compiling with the [cross]
|
||||||
|
tool. With `cross` installed build the example as follows:
|
||||||
|
|
||||||
|
cross build --target=arm-unknown-linux-gnueabi --release --example raspberry_pi_inky_phat --features examples
|
||||||
|
|
||||||
|
After it is built copy
|
||||||
|
`target/arm-unknown-linux-gnueabi/release/examples/raspberry_pi_inky_phat` to
|
||||||
|
the Raspberry Pi.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Waveshare EPD driver](https://github.com/caemor/epd-waveshare)
|
* [Waveshare EPD driver](https://github.com/caemor/epd-waveshare)
|
||||||
|
@ -36,9 +56,10 @@ The library has been tested and confirmed working on these devices:
|
||||||
http://www.apache.org/licenses/LICENSE-2.0)
|
http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
[SSD1675]: http://www.solomon-systech.com/en/product/advanced-display/bistable-display-driver-ic/SSD1675/
|
|
||||||
[embedded-hal]: https://crates.io/crates/embedded-hal
|
|
||||||
[Inky pHat]: https://shop.pimoroni.com/products/inky-phat
|
|
||||||
[crate-docs]: https://docs.rs/ssd1675
|
[crate-docs]: https://docs.rs/ssd1675
|
||||||
[LICENSE-MIT]: https://github.com/wezm/ssd1675/blob/master/LICENSE-MIT
|
[cross]: https://github.com/rust-embedded/cross
|
||||||
|
[embedded-hal]: https://crates.io/crates/embedded-hal
|
||||||
|
[Inky pHAT]: https://shop.pimoroni.com/products/inky-phat
|
||||||
[LICENSE-APACHE]: https://github.com/wezm/ssd1675/blob/master/LICENSE-APACHE
|
[LICENSE-APACHE]: https://github.com/wezm/ssd1675/blob/master/LICENSE-APACHE
|
||||||
|
[LICENSE-MIT]: https://github.com/wezm/ssd1675/blob/master/LICENSE-MIT
|
||||||
|
[SSD1675]: http://www.solomon-systech.com/en/product/advanced-display/bistable-display-driver-ic/SSD1675/
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -53,6 +53,7 @@ pub enum DeepSleepMode {
|
||||||
DiscardRAM,
|
DiscardRAM,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A command that can be issued to the controller.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Set the MUX of gate lines, scanning sequence and direction
|
/// Set the MUX of gate lines, scanning sequence and direction
|
||||||
|
@ -125,7 +126,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),
|
||||||
|
@ -211,7 +212,8 @@ macro_rules! pack {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub(crate) fn execute<I: DisplayInterface>(&self, interface: &mut I) -> Result<(), I::Error> {
|
/// Execute the command, transmitting any associated data as well.
|
||||||
|
pub fn execute<I: DisplayInterface>(&self, interface: &mut I) -> Result<(), I::Error> {
|
||||||
use self::Command::*;
|
use self::Command::*;
|
||||||
|
|
||||||
let mut buf = [0u8; 4];
|
let mut buf = [0u8; 4];
|
||||||
|
@ -304,7 +306,8 @@ impl Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'buf> BufCommand<'buf> {
|
impl<'buf> BufCommand<'buf> {
|
||||||
pub(crate) fn execute<I: DisplayInterface>(&self, interface: &mut I) -> Result<(), I::Error> {
|
/// Execute the command, transmitting the associated buffer as well.
|
||||||
|
pub fn execute<I: DisplayInterface>(&self, interface: &mut I) -> Result<(), I::Error> {
|
||||||
use self::BufCommand::*;
|
use self::BufCommand::*;
|
||||||
|
|
||||||
let (command, data) = match self {
|
let (command, data) = match self {
|
||||||
|
|
|
@ -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,9 +30,15 @@ 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 {}
|
||||||
|
|
||||||
|
/// Display configuration.
|
||||||
|
///
|
||||||
|
/// Passed to Display::new. Use `Builder` to construct a `Config`.
|
||||||
pub struct Config<'a> {
|
pub struct Config<'a> {
|
||||||
pub(crate) dummy_line_period: Command,
|
pub(crate) dummy_line_period: Command,
|
||||||
pub(crate) gate_line_width: Command,
|
pub(crate) gate_line_width: Command,
|
||||||
|
@ -42,10 +67,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 +82,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 +92,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 +102,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 +116,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 +131,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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -5,25 +5,91 @@ const RESET_DELAY_MS: u8 = 10;
|
||||||
|
|
||||||
const MAX_SPI_SPEED_HZ: u32 = 20_000_000;
|
const MAX_SPI_SPEED_HZ: u32 = 20_000_000;
|
||||||
|
|
||||||
|
/// Trait implemented by displays to provide implemenation of core functionality.
|
||||||
pub trait DisplayInterface {
|
pub trait DisplayInterface {
|
||||||
type Error;
|
type Error;
|
||||||
|
|
||||||
|
/// Send a command to the controller.
|
||||||
|
///
|
||||||
|
/// Prefer calling `execute` on a [Commmand](../command/enum.Command.html) over calling this
|
||||||
|
/// directly.
|
||||||
fn send_command(&mut self, command: u8) -> Result<(), Self::Error>;
|
fn send_command(&mut self, command: u8) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
/// Send data for a command.
|
||||||
fn send_data(&mut self, data: &[u8]) -> Result<(), Self::Error>;
|
fn send_data(&mut self, data: &[u8]) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
/// Reset the controller.
|
||||||
fn reset<D: hal::blocking::delay::DelayMs<u8>>(&mut self, delay: &mut D);
|
fn reset<D: hal::blocking::delay::DelayMs<u8>>(&mut self, delay: &mut D);
|
||||||
|
|
||||||
|
/// Wait for the controller to indicate it is not busy.
|
||||||
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 +101,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,
|
||||||
|
|
47
src/lib.rs
47
src/lib.rs
|
@ -1,5 +1,42 @@
|
||||||
#![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][embedded-graphics]. 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:
|
||||||
|
//!
|
||||||
|
//! 1. [reset](display/struct.Display.html#method.reset)
|
||||||
|
//! 1. [clear](graphics/struct.GraphicDisplay.html#method.clear)
|
||||||
|
//! 1. [update](graphics/struct.GraphicDisplay.html#method.update)
|
||||||
|
//! 1. [sleep](display/struct.Display.html#method.deep_sleep)
|
||||||
|
//!
|
||||||
|
//! [Interface]: interface/struct.Interface.html
|
||||||
|
//! [Display]: display/struct.Display.html
|
||||||
|
//! [GraphicDisplay]: display/struct.GraphicDisplay.html
|
||||||
|
//! [Config]: config/struct.Config.html
|
||||||
|
//! [Builder]: config/struct.Builder.html
|
||||||
|
//! [embedded-graphics]: https://crates.io/crates/embedded-graphics
|
||||||
|
|
||||||
extern crate embedded_hal as hal;
|
extern crate embedded_hal as hal;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -7,11 +44,11 @@ extern crate embedded_hal as hal;
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
mod color;
|
mod color;
|
||||||
mod command;
|
pub mod command;
|
||||||
mod config;
|
pub mod config;
|
||||||
mod display;
|
pub mod display;
|
||||||
mod graphics;
|
pub mod graphics;
|
||||||
mod interface;
|
pub mod interface;
|
||||||
|
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
pub use config::Builder;
|
pub use config::Builder;
|
||||||
|
|
Loading…
Reference in a new issue