#![no_std]
use num_derive::ToPrimitive;
use num_traits::ToPrimitive;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::SpiDevice;
#[derive(Debug)]
pub enum Error<CommError, PinError> {
Comm(CommError),
Pin(PinError),
}
#[derive(ToPrimitive)]
enum Instruction {
BasicFunction = 0x30,
ExtendedFunction = 0x34,
ClearScreen = 0x01,
EntryMode = 0x06,
DisplayOnCursorOff = 0x0C,
GraphicsOn = 0x36,
SetGraphicsAddress = 0x80,
}
pub const WIDTH: u32 = 128;
pub const HEIGHT: u32 = 64;
const ROW_SIZE: usize = (WIDTH / 8) as usize;
const BUFFER_SIZE: usize = ROW_SIZE * HEIGHT as usize;
const X_ADDR_DIV: u8 = 16;
pub struct ST7920<SPI, RST, CS>
where
SPI: SpiDevice,
RST: OutputPin,
CS: OutputPin,
{
spi: SPI,
rst: RST,
cs: Option<CS>,
buffer: [u8; BUFFER_SIZE],
flip: bool,
}
impl<SPI, RST, CS, PinError, SPIError> ST7920<SPI, RST, CS>
where
SPI: SpiDevice<Error = SPIError>,
RST: OutputPin<Error = PinError>,
CS: OutputPin<Error = PinError>,
{
pub fn new(spi: SPI, rst: RST, cs: Option<CS>, flip: bool) -> Self {
let buffer = [0; BUFFER_SIZE];
ST7920 {
spi,
rst,
cs,
buffer,
flip,
}
}
fn enable_cs<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
if let Some(cs) = self.cs.as_mut() {
cs.set_high().map_err(Error::Pin)?;
delay.delay_us(1);
}
Ok(())
}
fn disable_cs<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
if let Some(cs) = self.cs.as_mut() {
delay.delay_us(1);
cs.set_low().map_err(Error::Pin)?;
}
Ok(())
}
#[inline]
fn do_init<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
self.hard_reset(delay)?;
self.write_command(Instruction::BasicFunction)?;
delay.delay_us(200);
self.write_command(Instruction::DisplayOnCursorOff)?;
delay.delay_us(100);
self.write_command(Instruction::ClearScreen)?;
delay.delay_ms(10);
self.write_command(Instruction::EntryMode)?;
delay.delay_us(100);
self.write_command(Instruction::ExtendedFunction)?;
delay.delay_ms(10);
self.write_command(Instruction::GraphicsOn)?;
delay.delay_ms(100);
Ok(())
}
pub fn init<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
self.enable_cs(delay)?;
let result = self.do_init(delay);
self.disable_cs(delay)?;
result
}
fn hard_reset<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
self.rst.set_low().map_err(Error::Pin)?;
delay.delay_ms(40);
self.rst.set_high().map_err(Error::Pin)?;
delay.delay_ms(40);
Ok(())
}
fn write_command(&mut self, command: Instruction) -> Result<(), Error<SPIError, PinError>> {
self.write_command_param(command, 0)
}
fn write_command_param(
&mut self,
command: Instruction,
param: u8,
) -> Result<(), Error<SPIError, PinError>> {
let command_param = command.to_u8().unwrap() | param;
let cmd: u8 = 0xF8;
self.spi
.write(&[cmd, command_param & 0xF0, (command_param << 4) & 0xF0])
.map_err(Error::Comm)?;
Ok(())
}
fn write_data(&mut self, data: u8) -> Result<(), Error<SPIError, PinError>> {
self.spi
.write(&[0xFA, data & 0xF0, (data << 4) & 0xF0])
.map_err(Error::Comm)?;
Ok(())
}
fn set_address(&mut self, x: u8, y: u8) -> Result<(), Error<SPIError, PinError>> {
const HALF_HEIGHT: u8 = HEIGHT as u8 / 2;
self.write_command_param(
Instruction::SetGraphicsAddress,
if y < HALF_HEIGHT { y } else { y - HALF_HEIGHT },
)?;
self.write_command_param(
Instruction::SetGraphicsAddress,
if y < HALF_HEIGHT {
x / X_ADDR_DIV
} else {
x / X_ADDR_DIV + (WIDTH as u8 / X_ADDR_DIV)
},
)?;
Ok(())
}
pub fn modify_buffer(&mut self, f: fn(x: u8, y: u8, v: u8) -> u8) {
for i in 0..BUFFER_SIZE {
let row = i / ROW_SIZE;
let column = i - (row * ROW_SIZE);
self.buffer[i] = f(column as u8, row as u8, self.buffer[i]);
}
}
pub fn clear_buffer(&mut self) {
for i in 0..BUFFER_SIZE {
self.buffer[i] = 0;
}
}
pub fn clear<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
self.clear_buffer();
self.flush(delay)?;
Ok(())
}
pub fn clear_buffer_region(
&mut self,
x: u8,
mut y: u8,
mut w: u8,
mut h: u8,
) -> Result<(), Error<SPIError, PinError>> {
if x < WIDTH as u8 && y < HEIGHT as u8 && w > 0 && h > 0 {
if x.saturating_add(w) > WIDTH as u8 {
w = WIDTH as u8 - x;
}
if y.saturating_add(h) > HEIGHT as u8 {
h = HEIGHT as u8 - y;
}
let mut adj_x = x;
if self.flip {
y = HEIGHT as u8 - (y + h);
adj_x = WIDTH as u8 - (x + w);
}
let start = adj_x / 8;
let mut right = adj_x + w;
let end = (right / 8) + 1;
let start_gap = adj_x % 8;
right = end * 8;
let end_gap = 8 - (right % 8);
let mut row_start = y as usize * ROW_SIZE;
for _ in y..y + h {
for x in start..end {
let mut mask = 0xFF_u8;
if x == start {
mask = 0xFF_u8 >> start_gap;
}
if x == end {
mask &= 0xFF_u8 >> end_gap;
}
let pos = row_start + x as usize;
self.buffer[pos] &= !mask;
}
row_start += ROW_SIZE;
}
}
Ok(())
}
#[inline]
pub fn set_pixel(&mut self, x: u8, y: u8, val: u8) {
if x < WIDTH as u8 && y < HEIGHT as u8 {
self.set_pixel_unchecked(x, y, val);
}
}
#[inline]
pub fn set_pixel_unchecked(&mut self, mut x: u8, mut y: u8, val: u8) {
if self.flip {
y = (HEIGHT - 1) as u8 - y;
x = (WIDTH - 1) as u8 - x;
}
let idx = y as usize * ROW_SIZE + x as usize / 8;
let x_mask = 0x80 >> (x % 8);
if val != 0 {
self.buffer[idx] |= x_mask;
} else {
self.buffer[idx] &= !x_mask;
}
}
#[inline]
fn do_flush(&mut self) -> Result<(), Error<SPIError, PinError>> {
for y in 0..HEIGHT as u8 / 2 {
self.set_address(0, y)?;
let mut row_start = y as usize * ROW_SIZE;
for x in 0..ROW_SIZE {
self.write_data(self.buffer[row_start + x])?;
}
row_start += (HEIGHT as usize / 2) * ROW_SIZE;
for x in 0..ROW_SIZE {
self.write_data(self.buffer[row_start + x])?;
}
}
Ok(())
}
pub fn flush<Delay: DelayNs>(
&mut self,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
self.enable_cs(delay)?;
let result = self.do_flush();
self.disable_cs(delay)?;
result
}
#[inline]
fn do_flush_region(
&mut self,
x: u8,
mut y: u8,
w: u8,
h: u8,
) -> Result<(), Error<SPIError, PinError>> {
let mut adj_x = x;
if self.flip {
y = HEIGHT as u8 - (y + h);
adj_x = WIDTH as u8 - (x + w);
}
let mut left = adj_x - adj_x % X_ADDR_DIV;
let mut right = (adj_x + w) - 1;
right -= right % X_ADDR_DIV;
right += X_ADDR_DIV;
if left > adj_x {
left -= X_ADDR_DIV; }
let mut row_start = y as usize * ROW_SIZE;
self.set_address(adj_x, y)?;
for y in y..(y + h) {
self.set_address(adj_x, y)?;
for x in left / 8..right / 8 {
self.write_data(self.buffer[row_start + x as usize])?;
}
row_start += ROW_SIZE;
}
Ok(())
}
pub fn flush_region<Delay: DelayNs>(
&mut self,
x: u8,
y: u8,
mut w: u8,
mut h: u8,
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
if x < WIDTH as u8 && y < HEIGHT as u8 && w > 0 && h > 0 {
if x.saturating_add(w) > WIDTH as u8 {
w = WIDTH as u8 - x;
}
if y.saturating_add(h) > HEIGHT as u8 {
h = HEIGHT as u8 - y;
}
self.enable_cs(delay)?;
let result = self.do_flush_region(x, y, w, h);
self.disable_cs(delay)?;
result
} else {
Ok(())
}
}
}
#[cfg(feature = "graphics")]
use embedded_graphics::{
self, draw_target::DrawTarget, geometry::Point, pixelcolor::BinaryColor, prelude::*,
};
#[cfg(feature = "graphics")]
impl<SPI, CS, RST, PinError, SPIError> OriginDimensions for ST7920<SPI, CS, RST>
where
SPI: SpiDevice<Error = SPIError>,
RST: OutputPin<Error = PinError>,
CS: OutputPin<Error = PinError>,
{
fn size(&self) -> Size {
Size {
width: WIDTH,
height: HEIGHT,
}
}
}
#[cfg(feature = "graphics")]
impl<SPI, CS, RST, PinError, SPIError> DrawTarget for ST7920<SPI, CS, RST>
where
SPI: SpiDevice<Error = SPIError>,
RST: OutputPin<Error = PinError>,
CS: OutputPin<Error = PinError>,
{
type Error = core::convert::Infallible;
type Color = BinaryColor;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for p in pixels {
let Pixel(coord, color) = p;
#[cfg(not(feature = "graphics-unchecked"))]
let in_bounds =
coord.x >= 0 && coord.x < WIDTH as i32 && coord.y >= 0 && coord.y < HEIGHT as i32;
#[cfg(feature = "graphics-unchecked")]
let in_bounds = true;
if in_bounds {
let x = coord.x as u8;
let y = coord.y as u8;
let c = match color {
BinaryColor::Off => 0,
BinaryColor::On => 1,
};
self.set_pixel_unchecked(x, y, c);
}
}
Ok(())
}
}
#[cfg(feature = "graphics")]
impl<SPI, RST, CS, PinError, SPIError> ST7920<SPI, RST, CS>
where
SPI: SpiDevice<Error = SPIError>,
RST: OutputPin<Error = PinError>,
CS: OutputPin<Error = PinError>,
{
pub fn flush_region_graphics<Delay: DelayNs>(
&mut self,
region: (Point, Size),
delay: &mut Delay,
) -> Result<(), Error<SPIError, PinError>> {
let mut width: u32 = region.1.width;
let mut height: u32 = region.1.height;
let mut x: i32 = region.0.x;
let mut y: i32 = region.0.y;
if x < 0 {
width = width.saturating_sub((-x) as u32);
x = 0;
}
if y < 0 {
height = height.saturating_sub((-y) as u32);
y = 0;
}
x = x.min(u8::MAX as i32);
y = y.min(u8::MAX as i32);
width = width.min(u8::MAX as u32);
height = height.min(u8::MAX as u32);
self.flush_region(x as u8, y as u8, width as u8, height as u8, delay)
}
}