#![crate_type = "lib"]
#![no_std]
#![deny(missing_docs)]
use core::convert::TryInto;
use embedded_hal::{
blocking::delay::DelayUs, blocking::spi, digital::v2::InputPin, digital::v2::OutputPin,
};
use num_derive::ToPrimitive;
#[derive(Debug)]
pub enum Error<CommError, PinError> {
Comm(CommError),
Pin(PinError),
}
#[derive(ToPrimitive)]
enum Instruction {
GateDrivingVoltageControl = 0x03,
DeepSleepModeDisable = 0x10,
DateEntryModeSetting = 17,
MasterActivation = 0x20,
DisplayUpdateDisableRamBypass = 0x21,
DisplayUpdateControl2 = 0x22,
WriteRam = 0x24,
WriteVCOMRegister = 44,
WriteLUTRegister = 50,
BorderWaveform = 0x3C,
SetRamXStartEndAddress = 0x44,
SetRamYStartEndAddress = 69,
SetRamXAddressCounter = 0x4E,
SetRamYAddressCounter = 0x4F,
BoosterInternalFeedbackSel = 0xF0,
}
#[derive(ToPrimitive, Clone, Copy)]
pub enum Color {
#[doc(hidden)]
BLACK = 0b00,
#[doc(hidden)]
DARKGRAY = 0b01,
#[doc(hidden)]
LIGHTGRAY = 0b10,
#[doc(hidden)]
WHITE = 0b11,
}
const WF_LUT: [u8; 90] = [
0x82, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0x00,
0x55, 0xAA, 0xAA, 0x00, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
0xAA, 0xAA, 0xAA, 0xAA, 0x15, 0x15, 0x15, 0x15, 0x05, 0x05, 0x05, 0x05, 0x01, 0x01, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x41, 0x45, 0xF1, 0xFF, 0x5F, 0x55, 0x01, 0x00, 0x00, 0x00,
];
const WIDTH: u16 = 172;
const GEOM_WIDTH: i32 = WIDTH as i32;
const HEIGHT: u16 = 18;
const GEOM_HEIGHT: i32 = (4 * HEIGHT) as i32;
fn pixel_to_byte(position: usize, color: Color) -> u8 {
let val: u8 = color as u8;
let p = position << 1;
val << p
}
fn position_mask(position: usize) -> u8 {
let val: u8 = 0b000_0011;
let p = position << 1;
!(val << p)
}
fn overwrite_pixel_in_byte(byte: u8, position: usize, color: Color) -> u8 {
let val: u8 = byte & position_mask(position);
val | pixel_to_byte(position, color)
}
pub struct GDE021A1<SPI, RST, CS, DC, BSY>
where
SPI: spi::Write<u8>,
RST: OutputPin,
CS: OutputPin,
DC: OutputPin,
BSY: InputPin,
{
spi: SPI,
rst: RST,
cs: Option<CS>,
bsy: BSY,
dc: DC,
buffer: [u8; (WIDTH * HEIGHT) as usize],
}
impl<SPI, RST, CS, DC, BSY, PinError, SPIError> GDE021A1<SPI, RST, CS, DC, BSY>
where
SPI: spi::Write<u8, Error = SPIError>,
RST: OutputPin<Error = PinError>,
CS: OutputPin<Error = PinError>,
DC: OutputPin<Error = PinError>,
BSY: InputPin<Error = PinError>,
{
pub fn new(spi: SPI, rst: RST, cs: Option<CS>, dc: DC, bsy: BSY) -> Self {
GDE021A1 {
spi,
rst,
cs,
dc,
bsy,
buffer: [0xFF; (WIDTH * HEIGHT) as usize],
}
}
pub fn init(&mut self, delay: &mut dyn DelayUs<u32>) -> Result<(), Error<SPIError, PinError>> {
self.enable_cs(delay)?;
self.hard_reset(delay)?;
delay.delay_us(10_000);
self.write_command(Instruction::DeepSleepModeDisable)?;
self.write_data(0x00)?;
self.write_command(Instruction::DateEntryModeSetting)?;
self.write_data(0x03)?;
self.write_command(Instruction::SetRamXStartEndAddress)?;
self.write_data(0x00)?;
self.write_data(0x11)?;
self.write_command(Instruction::SetRamYStartEndAddress)?;
self.write_data(0x00)?;
self.write_data(0xAB)?;
self.write_command(Instruction::SetRamXAddressCounter)?;
self.write_data(0x00)?;
self.write_command(Instruction::SetRamYAddressCounter)?;
self.write_data(0x00)?;
self.write_command(Instruction::BoosterInternalFeedbackSel)?;
self.write_data(0x1F)?;
self.write_command(Instruction::DisplayUpdateDisableRamBypass)?;
self.write_data(0x03)?;
self.write_command(Instruction::WriteVCOMRegister)?;
self.write_data(0xA0)?;
self.write_command(Instruction::BorderWaveform)?;
self.write_data(0x64)?;
self.write_command(Instruction::WriteLUTRegister)?;
for elem in WF_LUT.iter() {
self.write_data(*elem)?;
}
self.disable_cs(delay)?;
Ok(())
}
pub fn clear(&mut self) {
self.clear_with_color(Color::WHITE);
}
pub fn refresh(
&mut self,
delay: &mut dyn DelayUs<u32>,
) -> Result<(), Error<SPIError, PinError>> {
self.write_buffer(delay)?;
self.enable_cs(delay)?;
self.write_command(Instruction::DisplayUpdateDisableRamBypass)?;
self.write_data(0x03)?;
self.write_command(Instruction::DisplayUpdateControl2)?;
self.write_data(0xC4)?;
self.write_command(Instruction::MasterActivation)?;
self.disable_cs(delay)?;
self.busy_wait();
self.close_charge_pump(delay)?;
Ok(())
}
fn enable_cs(&mut self, delay: &mut dyn DelayUs<u32>) -> Result<(), Error<SPIError, PinError>> {
if let Some(cs) = self.cs.as_mut() {
cs.set_low().map_err(Error::Pin)?;
delay.delay_us(100);
}
Ok(())
}
fn disable_cs(
&mut self,
delay: &mut dyn DelayUs<u32>,
) -> Result<(), Error<SPIError, PinError>> {
if let Some(cs) = self.cs.as_mut() {
delay.delay_us(100);
cs.set_high().map_err(Error::Pin)?;
}
Ok(())
}
fn hard_reset(
&mut self,
delay: &mut dyn DelayUs<u32>,
) -> Result<(), Error<SPIError, PinError>> {
self.rst.set_low().map_err(Error::Pin)?;
delay.delay_us(10_000);
self.rst.set_high().map_err(Error::Pin)?;
delay.delay_us(10_000);
Ok(())
}
fn write_command(&mut self, command: Instruction) -> Result<(), Error<SPIError, PinError>> {
self.dc.set_low().map_err(Error::Pin)?;
self.spi.write(&[command as u8]).map_err(Error::Comm)?;
Ok(())
}
fn write_data(&mut self, data: u8) -> Result<(), Error<SPIError, PinError>> {
self.dc.set_high().map_err(Error::Pin)?;
self.spi.write(&[data]).map_err(Error::Comm)?;
Ok(())
}
fn clear_with_color(&mut self, color: Color) {
let val: u8 = pixel_to_byte(0, color);
let val: u8 = pixel_to_byte(1, color) | val;
let val: u8 = pixel_to_byte(2, color) | val;
let val: u8 = pixel_to_byte(3, color) | val;
for byte in self.buffer.iter_mut() {
*byte = val;
}
}
fn set_display_window(
&mut self,
x_pos: u8,
y_pos: u8,
width: u8,
height: u8,
) -> Result<(), Error<SPIError, PinError>> {
self.write_command(Instruction::SetRamXStartEndAddress)?;
self.write_data(y_pos)?;
self.write_data(height)?;
self.write_command(Instruction::SetRamYStartEndAddress)?;
self.write_data(x_pos)?;
self.write_data(width)?;
self.write_command(Instruction::SetRamXAddressCounter)?;
self.write_data(y_pos)?;
self.write_command(Instruction::SetRamYAddressCounter)?;
self.write_data(x_pos)?;
Ok(())
}
fn write_buffer(
&mut self,
delay: &mut dyn DelayUs<u32>,
) -> Result<(), Error<SPIError, PinError>> {
self.enable_cs(delay)?;
self.set_display_window(0, 0, WIDTH as u8 - 1, HEIGHT as u8 - 1)?;
self.write_command(Instruction::WriteRam)?;
for elem in 0..self.buffer.len() {
self.write_data(self.buffer[elem])?;
}
self.disable_cs(delay)?;
Ok(())
}
fn busy_wait(&self) {
while match self.bsy.is_high() {
Ok(x) => x,
_ => false,
} {}
}
fn close_charge_pump(
&mut self,
delay: &mut dyn DelayUs<u32>,
) -> Result<(), Error<SPIError, PinError>> {
self.enable_cs(delay)?;
self.write_command(Instruction::DisplayUpdateControl2)?;
self.write_data(0x03)?;
self.write_command(Instruction::MasterActivation)?;
self.disable_cs(delay)?;
delay.delay_us(400_000);
self.busy_wait();
Ok(())
}
pub fn alt_init(
&mut self,
delay: &mut dyn DelayUs<u32>,
) -> Result<(), Error<SPIError, PinError>> {
self.enable_cs(delay)?;
self.hard_reset(delay)?;
delay.delay_us(1_000);
self.write_command(Instruction::DisplayUpdateDisableRamBypass)?;
self.write_data(0x8F)?;
self.write_command(Instruction::GateDrivingVoltageControl)?;
self.write_data(0x00)?;
self.write_command(Instruction::WriteRam)?;
for _i in 0..5760 {
self.write_data(0xFF)?;
}
self.write_data(0xF8)?;
self.write_command(Instruction::MasterActivation)?;
self.disable_cs(delay)?;
Ok(())
}
}
#[cfg(feature = "graphics")]
extern crate embedded_graphics;
#[cfg(feature = "graphics")]
use self::embedded_graphics::{drawable, geometry::Size, pixelcolor::BinaryColor, DrawTarget};
#[cfg(feature = "graphics")]
impl<SPI, CS, RST, DC, BSY, PinError, SPIError> DrawTarget<BinaryColor>
for GDE021A1<SPI, CS, RST, DC, BSY>
where
SPI: spi::Write<u8, Error = SPIError>,
RST: OutputPin<Error = PinError>,
CS: OutputPin<Error = PinError>,
DC: OutputPin<Error = PinError>,
BSY: InputPin<Error = PinError>,
{
type Error = Error<SPIError, PinError>;
fn size(&self) -> Size {
Size::new(WIDTH.try_into().unwrap(), (4 * HEIGHT).try_into().unwrap())
}
fn draw_pixel(
&mut self,
pixel: drawable::Pixel<BinaryColor>,
) -> Result<(), Error<SPIError, PinError>> {
let drawable::Pixel(coord, color) = pixel;
if let Ok((x @ 0..=GEOM_WIDTH, y @ 0..=GEOM_HEIGHT)) = coord.try_into() {
let c: Color = match color {
BinaryColor::On => Color::BLACK,
BinaryColor::Off => Color::WHITE,
};
let p: usize = (y % 4).try_into().unwrap();
let p: usize = 3 - p;
let y: usize = (y / 4).try_into().unwrap();
let x: usize = (GEOM_WIDTH - x).try_into().unwrap();
let byte: u8 = self.buffer[y + x * HEIGHT as usize];
self.buffer[y + x * HEIGHT as usize] = overwrite_pixel_in_byte(byte, p, c);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{pixel_to_byte, position_mask, Color};
#[test]
fn it_should_convert_pixel_to_byte_at_zero_pos() {
assert_eq!(0b0000_0000, pixel_to_byte(0, Color::BLACK));
assert_eq!(0b0000_0001, pixel_to_byte(0, Color::DARKGRAY));
assert_eq!(0b0000_0010, pixel_to_byte(0, Color::LIGHTGRAY));
assert_eq!(0b0000_0011, pixel_to_byte(0, Color::WHITE));
}
#[test]
fn it_should_convert_pixel_to_byte_at_one_pos() {
assert_eq!(0b0000_0000, pixel_to_byte(1, Color::BLACK));
assert_eq!(0b0000_0100, pixel_to_byte(1, Color::DARKGRAY));
assert_eq!(0b0000_1000, pixel_to_byte(1, Color::LIGHTGRAY));
assert_eq!(0b0000_1100, pixel_to_byte(1, Color::WHITE));
}
#[test]
fn it_should_convert_pixel_to_byte_at_two_pos() {
assert_eq!(0b0000_0000, pixel_to_byte(2, Color::BLACK));
assert_eq!(0b0001_0000, pixel_to_byte(2, Color::DARKGRAY));
assert_eq!(0b0010_0000, pixel_to_byte(2, Color::LIGHTGRAY));
assert_eq!(0b0011_0000, pixel_to_byte(2, Color::WHITE));
}
#[test]
fn it_should_convert_pixel_to_byte_at_three_pos() {
assert_eq!(0b0000_0000, pixel_to_byte(3, Color::BLACK));
assert_eq!(0b0100_0000, pixel_to_byte(3, Color::DARKGRAY));
assert_eq!(0b1000_0000, pixel_to_byte(3, Color::LIGHTGRAY));
assert_eq!(0b1100_0000, pixel_to_byte(3, Color::WHITE));
}
#[test]
fn it_should_compute_a_position_mask() {
assert_eq!(0b1111_1100, position_mask(0));
assert_eq!(0b1111_0011, position_mask(1));
assert_eq!(0b1100_1111, position_mask(2));
assert_eq!(0b0011_1111, position_mask(3));
}
}