#![no_std]
#![allow(clippy::result_unit_err)]
#![allow(clippy::too_many_arguments)]
pub mod instruction;
use crate::instruction::Instruction;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::SpiDevice;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PowerMode {
Lpm,
Hpm,
}
const COL_MAX: u16 = 59;
const ROW_MAX: u16 = 199;
const PX_PER_COL: u16 = 12;
const PX_PER_ROW: u16 = 2;
struct AddrWindow {
col_start: u16,
col_end: u16,
row_start: u16,
row_end: u16,
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum HpmFps {
Sixteen = 0b00000000,
ThirtyTwo = 0b00010000,
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LpmFps {
Quarter = 0b000,
Half = 0b001,
One = 0b010,
Two = 0b011,
Four = 0b100,
Eight = 0b101,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FpsConfig {
pub hpm: HpmFps,
pub lpm: LpmFps,
}
impl FpsConfig {
pub fn as_u8(&self) -> u8 {
(self.hpm as u8) + (self.lpm as u8)
}
pub fn from_u8(byte: u8) -> Option<Self> {
let lpm = match byte & 0b111 {
0b000 => LpmFps::Quarter,
0b001 => LpmFps::Half,
0b010 => LpmFps::One,
0b011 => LpmFps::Two,
0b100 => LpmFps::Four,
0b101 => LpmFps::Eight,
_ => return None,
};
let hpm = match byte & 0b00010000 {
0b00000000 => HpmFps::Sixteen,
0b00010000 => HpmFps::ThirtyTwo,
_ => return None,
};
Some(Self { hpm, lpm })
}
}
pub struct ST7306<SPI, DC, RST, const COLS: usize, const ROWS: usize>
where
DC: OutputPin,
RST: OutputPin,
{
pub spi: SPI,
pub dc: DC,
pub rst: RST,
inverted: bool,
framebuffer: [[[u8; 3]; COLS]; ROWS],
autopowerdown: bool,
te_enable: bool,
fps: FpsConfig,
width: u16,
height: u16,
addr_window: AddrWindow,
sleeping: bool,
power_mode: PowerMode,
display_on: bool,
}
#[derive(Clone, Copy)]
pub enum Orientation {
Portrait = 0x00,
Landscape = 0x60,
PortraitSwapped = 0xC0,
LandscapeSwapped = 0xA0,
}
impl<SPI, DC, RST, const COLS: usize, const ROWS: usize> ST7306<SPI, DC, RST, COLS, ROWS>
where
SPI: SpiDevice,
DC: OutputPin,
RST: OutputPin,
{
pub fn new(
spi: SPI,
dc: DC,
rst: RST,
inverted: bool,
autopowerdown: bool,
te_enable: bool,
fps: FpsConfig,
width: u16,
height: u16,
col_start: u16,
row_start: u16,
) -> Self {
let col_end = col_start + (width / PX_PER_COL) - 1;
let row_end = row_start + (height / PX_PER_ROW) - 1;
assert!(col_end <= COL_MAX);
assert!(row_end <= ROW_MAX);
let addr_window = AddrWindow {
col_start,
col_end,
row_start,
row_end,
};
ST7306 {
spi,
dc,
rst,
inverted,
framebuffer: [[[0; 3]; COLS]; ROWS],
fps,
autopowerdown,
te_enable,
width,
height,
sleeping: true,
power_mode: PowerMode::Hpm,
display_on: false,
addr_window,
}
}
pub fn draw_pixels<I>(&mut self, pixels: I, flush: bool) -> Result<(), ()>
where
I: IntoIterator<Item = Pixel<Rgb565>>,
{
for Pixel(coord, color) in pixels.into_iter() {
if coord.x >= 0
&& coord.y >= 0
&& coord.x < self.width as i32
&& coord.y < self.height as i32
{
self.set_pixel(
coord.x as u16,
coord.y as u16,
RawU16::from(color).into_inner() as u8,
)?;
}
}
if flush {
self.flush()?;
}
Ok(())
}
pub fn flush(&mut self) -> Result<(), ()> {
self.write_command(Instruction::RAMWR, &[])?;
self.start_data()?;
for row in 0..ROWS {
for col in 0..COLS {
self.write_ram(&[(
self.framebuffer[row][col][0],
self.framebuffer[row][col][1],
self.framebuffer[row][col][2],
)])?;
}
}
Ok(())
}
pub fn init<DELAY>(&mut self, delay: &mut DELAY) -> Result<(), ()>
where
DELAY: DelayNs,
{
self.hard_reset(delay)?;
self.write_command(Instruction::SWRESET, &[])?;
delay.delay_ms(200);
self.write_command(Instruction::NVMLOADCTRL, &[0b10001, 0])?;
self.write_command(Instruction::BSTEN, &[0x01])?;
self.write_command(Instruction::GCTRL, &[0x08, 0x02])?;
self.write_command(Instruction::VSHPCTRL, &[0x0B, 0x0B, 0x0B, 0x0B])?;
self.write_command(Instruction::VSLPCTRL, &[0x23, 0x23, 0x23, 0x23])?;
self.write_command(Instruction::VSHNCTRL, &[0x27, 0x27, 0x27, 0x27])?;
self.write_command(Instruction::VSLNCTRL, &[0x35, 0x35, 0x35, 0x35])?;
self.write_command(Instruction::OSCSET, &[0xA6, 0xE9])?;
self.write_command(Instruction::FRCTRL, &[self.fps.as_u8()])?;
self.write_command(
Instruction::GTUPEQH,
&[0xE5, 0xF6, 0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45],
)?;
self.write_command(
Instruction::GTUPEQL,
&[0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45],
)?;
self.write_command(Instruction::SOUEQ, &[0x13])?;
self.write_command(Instruction::GATESET, &[0x64])?;
self.write_command(Instruction::SLPOUT, &[])?;
self.sleeping = false;
delay.delay_ms(255);
self.write_command(Instruction::LOWPOWER, &[0xC1, 0x4A, 0x26])?;
self.write_command(Instruction::VSHLSEL, &[0x00])?;
let madctl: u8 = 0b01001000;
self.write_command(Instruction::MADCTL, &[madctl])?;
self.write_command(Instruction::DTFORM, &[0x11])?;
self.write_command(Instruction::GAMAMS, &[0x20])?;
self.write_command(Instruction::PNLSET, &[0x29])?;
self.write_command(
Instruction::CASET,
&[
self.addr_window.col_start as u8,
self.addr_window.col_end as u8,
],
)?;
self.write_command(
Instruction::RASET,
&[
self.addr_window.row_start as u8,
self.addr_window.row_end as u8,
],
)?;
if self.autopowerdown {
self.write_command(Instruction::AUTOPWRCTRL, &[0xFF])?;
} else {
self.write_command(Instruction::AUTOPWRCTRL, &[0x7F])?;
}
if self.te_enable {
self.write_command(Instruction::TEON, &[0x00])?;
} else {
self.write_command(Instruction::TEOFF, &[])?;
}
self.write_command(Instruction::LPM, &[])?;
self.power_mode = PowerMode::Lpm;
self.invert_screen(self.inverted)?;
self.on_off(true)?;
Ok(())
}
pub fn on_off(&mut self, on: bool) -> Result<(), ()> {
if on {
self.write_command(Instruction::DISPON, &[])?;
} else {
self.write_command(Instruction::DISPOFF, &[])?;
}
self.display_on = on;
Ok(())
}
pub fn sleep_in<DELAY>(&mut self, delay: &mut DELAY) -> Result<(), ()>
where
DELAY: DelayNs,
{
match self.power_mode {
PowerMode::Hpm => {
self.write_command(Instruction::SLPIN, &[])?;
delay.delay_ms(100);
}
PowerMode::Lpm => {
self.switch_mode(delay, PowerMode::Hpm)?;
delay.delay_ms(255);
self.sleep_in(delay)?;
}
}
self.sleeping = true;
Ok(())
}
pub fn sleep_out<DELAY>(&mut self, delay: &mut DELAY) -> Result<(), ()>
where
DELAY: DelayNs,
{
self.write_command(Instruction::SLPOUT, &[])?;
delay.delay_ms(100);
self.sleeping = false;
Ok(())
}
pub fn switch_mode<DELAY>(
&mut self,
delay: &mut DELAY,
target_mode: PowerMode,
) -> Result<(), ()>
where
DELAY: DelayNs,
{
if target_mode == self.power_mode {
return Ok(());
}
match target_mode {
PowerMode::Hpm => {
self.write_command(Instruction::HPM, &[])?;
delay.delay_ms(255);
}
PowerMode::Lpm => {
self.write_command(Instruction::LPM, &[])?;
delay.delay_ms(100);
}
}
self.power_mode = target_mode;
Ok(())
}
pub fn invert_screen(&mut self, inverted: bool) -> Result<(), ()> {
if inverted {
self.write_command(Instruction::INVON, &[])?;
} else {
self.write_command(Instruction::INVOFF, &[])?;
}
self.inverted = inverted;
Ok(())
}
pub fn set_fps(&mut self, fps: FpsConfig) -> Result<(), ()> {
self.fps = fps;
self.write_command(Instruction::FRCTRL, &[self.fps.as_u8()])?;
Ok(())
}
fn hard_reset<DELAY>(&mut self, delay: &mut DELAY) -> Result<(), ()>
where
DELAY: DelayNs,
{
self.rst.set_high().map_err(|_| ())?;
delay.delay_ms(10);
self.rst.set_low().map_err(|_| ())?;
delay.delay_ms(10);
self.rst.set_high().map_err(|_| ())
}
pub fn write_command(&mut self, command: Instruction, params: &[u8]) -> Result<(), ()> {
self.dc.set_low().map_err(|_| ())?;
self.spi.write(&[command as u8]).map_err(|_| ())?;
if !params.is_empty() {
self.start_data()?;
self.write_command_data(params)?;
}
Ok(())
}
pub fn start_data(&mut self) -> Result<(), ()> {
self.dc.set_high().map_err(|_| ())
}
fn write_command_data(&mut self, data: &[u8]) -> Result<(), ()> {
data.iter().try_fold((), |res, byte| {
self.spi.write(&[*byte]).map_err(|_| ())?;
Ok(res)
})
}
pub fn write_ram(&mut self, data: &[(u8, u8, u8)]) -> Result<(), ()> {
data.iter().try_fold((), |res, (first, second, third)| {
self.spi.write(&[*first]).map_err(|_| ())?;
self.spi.write(&[*second]).map_err(|_| ())?;
self.spi.write(&[*third]).map_err(|_| ())?;
Ok(res)
})
}
pub fn clear_ram(&mut self) -> Result<(), ()> {
self.on_off(false)?;
self.clear_ram_cmd(true)?;
self.on_off(true)?;
Ok(())
}
pub fn clear_ram_cmd(&mut self, clear: bool) -> Result<(), ()> {
let byte = 0b01001111;
let enable_clear_mask = 0b10000000;
if clear {
self.write_command(Instruction::CLRAM, &[byte + enable_clear_mask])?;
} else {
self.write_command(Instruction::CLRAM, &[byte])?;
}
Ok(())
}
pub fn set_orientation(&mut self, _orientation: &Orientation) -> Result<(), ()> {
panic!("TODO: Not yet implemented");
}
pub fn set_pixel(&mut self, x: u16, y: u16, color: u8) -> Result<(), ()> {
let row = (y / PX_PER_ROW) as usize;
let col = (x / PX_PER_COL) as usize;
let black = color < 1;
let (byte, bitmask) = match (x % PX_PER_COL, y % PX_PER_ROW) {
(0, 0) => (0, 0x80),
(0, 1) => (0, 0x40),
(1, 0) => (0, 0x20),
(1, 1) => (0, 0x10),
(2, 0) => (0, 0x08),
(2, 1) => (0, 0x04),
(3, 0) => (0, 0x02),
(3, 1) => (0, 0x01),
(4, 0) => (1, 0x80),
(4, 1) => (1, 0x40),
(5, 0) => (1, 0x20),
(5, 1) => (1, 0x10),
(6, 0) => (1, 0x08),
(6, 1) => (1, 0x04),
(7, 0) => (1, 0x02),
(7, 1) => (1, 0x01),
(8, 0) => (2, 0x80),
(8, 1) => (2, 0x40),
(9, 0) => (2, 0x20),
(9, 1) => (2, 0x10),
(10, 0) => (2, 0x08),
(10, 1) => (2, 0x04),
(11, 0) => (2, 0x02),
(11, 1) => (2, 0x01),
_ => panic!("Impossible to reach"),
};
if black {
self.framebuffer[row][col][byte] |= bitmask
} else {
self.framebuffer[row][col][byte] &= !bitmask;
}
Ok(())
}
}
#[cfg(feature = "graphics")]
extern crate embedded_graphics;
#[cfg(feature = "graphics")]
use self::embedded_graphics::{
draw_target::DrawTarget,
pixelcolor::{
raw::{RawData, RawU16},
Rgb565,
},
prelude::*,
};
fn col_to_bright(color: Rgb565) -> u8 {
((color.r() as u16) + (color.g() as u16) + (color.b() as u16) / 3) as u8
}
#[cfg(feature = "graphics")]
impl<SPI, DC, RST, const COLS: usize, const ROWS: usize> DrawTarget
for ST7306<SPI, DC, RST, COLS, ROWS>
where
SPI: SpiDevice,
DC: OutputPin,
RST: OutputPin,
{
type Error = ();
type Color = Rgb565;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
self.draw_pixels(pixels, false)
}
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
let brightness = col_to_bright(color);
let black = if brightness < 128 { 0xFF } else { 0x00 };
if black == 0xFF {
return self.clear_ram();
}
for col in 0..COLS {
for row in 0..ROWS {
self.framebuffer[row][col][0] = black;
self.framebuffer[row][col][1] = black;
self.framebuffer[row][col][2] = black;
}
}
self.flush()
}
}
#[cfg(feature = "graphics")]
impl<SPI, DC, RST, const COLS: usize, const ROWS: usize> OriginDimensions
for ST7306<SPI, DC, RST, COLS, ROWS>
where
SPI: SpiDevice,
DC: OutputPin,
RST: OutputPin,
{
fn size(&self) -> Size {
Size::new(self.width as u32, self.height as u32)
}
}