#![no_std]
use core::marker::PhantomData;
use embedded_graphics_core::pixelcolor::raw::RawData;
use embedded_graphics_core::pixelcolor::Rgb565;
use embedded_graphics_core::prelude::{DrawTarget, OriginDimensions, Size};
use embedded_graphics_core::Pixel as EgPixel;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::Error as SpiError;
#[cfg(not(feature = "async"))]
use embedded_hal::spi::SpiDevice;
#[cfg(feature = "async")]
use embedded_hal_async::spi::SpiDevice;
#[maybe_async_cfg::maybe(
sync(cfg(not(feature = "async")), self = "Timer",),
async(feature = "async", keep_self)
)]
pub trait Timer {
fn after_millis(milliseconds: u64) -> impl core::future::Future<Output = ()>;
}
pub const FRAME_BUF_SIZE: usize = 160 * 40 * 2;
pub const MAX_FRAME_PIXELS: usize = FRAME_BUF_SIZE / 2;
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum Instruction {
Nop = 0x00,
SwReset = 0x01,
ReadDisplayId = 0x04,
ReadDisplayStatus = 0x09,
ReadDisplayPowerMode = 0x0A,
ReadDisplayMadctl = 0x0B,
ReadDisplayPixelFormat = 0x0C,
ReadDisplayImageMode = 0x0D,
ReadDisplaySignalMode = 0x0E,
ReadDisplaySelfDiagnosticResult = 0x0F,
SleepIn = 0x10,
SleepOut = 0x11,
PartialModeOn = 0x12,
NormalDisplayOn = 0x13,
DisplayInversionOff = 0x20,
DisplayInversionOn = 0x21,
DisplayOff = 0x28,
DisplayOn = 0x29,
ColumnAddressSet = 0x2A,
RowAddressSet = 0x2B,
MemoryWrite = 0x2C,
MemoryRead = 0x2E,
PartialArea = 0x30,
VerticalScrollingDefinition = 0x33,
TearingEffectLineOff = 0x34,
TearingEffectLineOn = 0x35,
MemoryAccessControl = 0x36,
VerticalScrollingStartAddress = 0x37,
IdleModeOff = 0x38,
IdleModeOn = 0x39,
PixelFormatSet = 0x3A,
WriteMemoryContinue = 0x3C,
ReadMemoryContinue = 0x3E,
SetTearScanline = 0x44,
GetScanline = 0x45,
WriteDisplayBrightness = 0x51,
ReadDisplayBrightness = 0x52,
WriteCtrlDisplay = 0x53,
ReadCtrlDisplay = 0x54,
WriteCabc = 0x55,
ReadCabc = 0x56,
WriteCabcMinBrightness = 0x5E,
ReadCabcMinBrightness = 0x5F,
RgbInterfaceSignalControl = 0xB0,
Spi2DataControl = 0xB1,
TearingEffectControl = 0xB4,
BlankingPorchControl = 0xB5,
DisplayFunctionControl = 0xB6,
DualSingleGateSelect = 0xBF,
PowerControl1 = 0xC1,
PowerControl2 = 0xC3,
PowerControl3 = 0xC4,
PowerControl4 = 0xC9,
ReadId1 = 0xDA,
ReadId2 = 0xDB,
ReadId3 = 0xDC,
Inversion = 0xEC,
InterRegisterEnable2 = 0xEF,
SetGamma1 = 0xF0,
SetGamma2 = 0xF1,
SetGamma3 = 0xF2,
SetGamma4 = 0xF3,
InterfaceControl = 0xF6,
InterRegisterEnable1 = 0xFE,
Cmd80 = 0x80,
Cmd81 = 0x81,
Cmd82 = 0x82,
Cmd83 = 0x83,
Cmd84 = 0x84,
Cmd85 = 0x85,
Cmd86 = 0x86,
Cmd87 = 0x87,
Cmd88 = 0x88,
Cmd89 = 0x89,
Cmd8A = 0x8A,
Cmd8B = 0x8B,
Cmd8C = 0x8C,
Cmd8D = 0x8D,
Cmd8E = 0x8E,
Cmd8F = 0x8F,
Cmd7E = 0x7E,
Cmd74 = 0x74,
Cmd98 = 0x98,
Cmd99 = 0x99,
Cmd60 = 0x60,
Cmd63 = 0x63,
Cmd64 = 0x64,
Cmd66 = 0x66,
Cmd6A = 0x6A,
Cmd68 = 0x68,
Cmd6C = 0x6C,
Cmd6E = 0x6E,
CmdA9 = 0xA9,
CmdA8 = 0xA8,
CmdA7 = 0xA7,
CmdAD = 0xAD,
CmdAF = 0xAF,
CmdAC = 0xAC,
CmdA3 = 0xA3,
CmdCB = 0xCB,
CmdCD = 0xCD,
CmdC2 = 0xC2,
CmdC5 = 0xC5,
CmdC6 = 0xC6,
CmdC7 = 0xC7,
CmdC8 = 0xC8,
CmdF9 = 0xF9,
Cmd9B = 0x9B,
Cmd93 = 0x93,
Cmd70 = 0x70,
Cmd71 = 0x71,
Cmd91 = 0x91,
}
#[derive(Clone, Copy)]
pub enum Orientation {
Portrait = 0x00,
Landscape = 0x60,
PortraitSwapped = 0x80,
LandscapeSwapped = 0xA0,
}
#[derive(Clone, Copy)]
pub struct Config {
pub rgb: bool,
pub inverted: bool,
pub orientation: Orientation,
pub height: u16,
pub width: u16,
pub dx: u16,
pub dy: u16,
}
impl Default for Config {
fn default() -> Self {
Self {
rgb: false,
inverted: false,
orientation: Orientation::Landscape,
height: 160,
width: 60,
dx: 0,
dy: 0,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<BusE, PinE>
where
BusE: core::fmt::Debug,
PinE: core::fmt::Debug,
{
Bus(BusE),
Pin(PinE),
}
pub struct GC9D01<'b, BUS, DC, RST, TIMER>
where
BUS: SpiDevice, DC: OutputPin,
RST: OutputPin,
TIMER: crate::Timer, {
bus: BUS,
dc: DC,
rst: RST,
config: Config,
frame_buffer: &'b mut [Rgb565], _timer: PhantomData<TIMER>,
}
#[maybe_async_cfg::maybe(
sync(cfg(not(feature = "async")), self = "GC9D01",),
async(feature = "async", keep_self)
)]
impl<'b, BUS, DC, RST, TIMER, BusE, PinE> GC9D01<'b, BUS, DC, RST, TIMER>
where
BUS: SpiDevice<Error = BusE>,
DC: OutputPin<Error = PinE>,
RST: OutputPin<Error = PinE>,
TIMER: crate::Timer,
BusE: core::fmt::Debug + SpiError,
PinE: core::fmt::Debug,
{
fn convert_color_byte_order(color: Rgb565) -> Rgb565 {
let raw_pixel: embedded_graphics_core::pixelcolor::raw::RawU16 = color.into();
let pixel_value: u16 = raw_pixel.into_inner();
let swapped_value = pixel_value.swap_bytes();
embedded_graphics_core::pixelcolor::raw::RawU16::new(swapped_value).into()
}
pub fn new(config: Config, bus: BUS, dc: DC, rst: RST, frame_buffer: &'b mut [Rgb565]) -> Self {
Self {
bus,
dc,
rst,
config,
frame_buffer,
_timer: PhantomData,
}
}
pub async fn init(&mut self) -> Result<(), Error<BusE, PinE>> {
self.reset().await?;
self.write_command(Instruction::InterRegisterEnable1, &[])
.await?; self.write_command(Instruction::InterRegisterEnable2, &[])
.await?;
self.write_command(Instruction::Cmd80, &[0xFF]).await?;
self.write_command(Instruction::Cmd81, &[0xFF]).await?;
self.write_command(Instruction::Cmd82, &[0xFF]).await?;
self.write_command(Instruction::Cmd83, &[0xFF]).await?;
self.write_command(Instruction::Cmd84, &[0xFF]).await?;
self.write_command(Instruction::Cmd85, &[0xFF]).await?;
self.write_command(Instruction::Cmd86, &[0xFF]).await?;
self.write_command(Instruction::Cmd87, &[0xFF]).await?;
self.write_command(Instruction::Cmd88, &[0xFF]).await?;
self.write_command(Instruction::Cmd89, &[0xFF]).await?;
self.write_command(Instruction::Cmd8A, &[0xFF]).await?;
self.write_command(Instruction::Cmd8B, &[0xFF]).await?;
self.write_command(Instruction::Cmd8C, &[0xFF]).await?;
self.write_command(Instruction::Cmd8D, &[0xFF]).await?;
self.write_command(Instruction::Cmd8E, &[0xFF]).await?;
self.write_command(Instruction::Cmd8F, &[0xFF]).await?;
self.write_command(Instruction::PixelFormatSet, &[0x05])
.await?;
self.write_command(Instruction::Cmd7E, &[0x7A]).await?;
self.write_command(
Instruction::Cmd74,
&[0x02, 0x0E, 0x00, 0x00, 0x28, 0x00, 0x00],
)
.await?;
self.write_command(Instruction::Cmd98, &[0x3E]).await?;
self.write_command(Instruction::Cmd99, &[0x3E]).await?;
self.write_command(Instruction::BlankingPorchControl, &[0x0E, 0x0E])
.await?;
self.write_command(Instruction::Cmd60, &[0x38, 0x09, 0x6D, 0x67])
.await?;
self.write_command(Instruction::Cmd63, &[0x38, 0xAD, 0x6D, 0x67, 0x05])
.await?;
self.write_command(Instruction::Cmd64, &[0x38, 0x0B, 0x70, 0xAB, 0x6D, 0x67])
.await?;
self.write_command(Instruction::Cmd66, &[0x38, 0x0F, 0x70, 0xAF, 0x6D, 0x67])
.await?;
self.write_command(Instruction::Cmd6A, &[0x00, 0x00])
.await?;
self.write_command(
Instruction::Cmd68,
&[0x3B, 0x08, 0x04, 0x00, 0x04, 0x64, 0x67],
)
.await?;
self.write_command(
Instruction::Cmd6C,
&[0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50],
)
.await?;
self.write_command(
Instruction::Cmd6E,
&[
0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x13, 0x11, 0x0B, 0x09, 0x16, 0x15, 0x1D, 0x1E,
0x00, 0x00, 0x00, 0x00, 0x1E, 0x1D, 0x15, 0x16, 0x0A, 0x0C, 0x12, 0x14, 0x02, 0x08,
0x00, 0x00, 0x00, 0x00,
],
)
.await?;
self.write_command(Instruction::CmdA9, &[0x1B]).await?;
self.write_command(Instruction::CmdA8, &[0x6B]).await?; self.write_command(Instruction::CmdA8, &[0x6D]).await?; self.write_command(Instruction::CmdA7, &[0x40]).await?;
self.write_command(Instruction::CmdAD, &[0x47]).await?;
self.write_command(Instruction::CmdAF, &[0x73]).await?; self.write_command(Instruction::CmdAF, &[0x73]).await?; self.write_command(Instruction::CmdAC, &[0x44]).await?;
self.write_command(Instruction::CmdA3, &[0x6C]).await?;
self.write_command(Instruction::CmdCB, &[0x00]).await?;
self.write_command(Instruction::CmdCD, &[0x22]).await?;
self.write_command(Instruction::CmdC2, &[0x10]).await?;
self.write_command(Instruction::CmdC5, &[0x00]).await?;
self.write_command(Instruction::CmdC6, &[0x0E]).await?;
self.write_command(Instruction::CmdC7, &[0x1F]).await?;
self.write_command(Instruction::CmdC8, &[0x0E]).await?;
self.write_command(Instruction::DualSingleGateSelect, &[0x00])
.await?;
self.write_command(Instruction::CmdF9, &[0x20]).await?;
self.write_command(Instruction::Cmd9B, &[0x3B]).await?;
self.write_command(Instruction::Cmd93, &[0x33, 0x7F, 0x00])
.await?;
self.write_command(Instruction::Cmd70, &[0x0E, 0x0F, 0x03, 0x0E, 0x0F, 0x03])
.await?;
self.write_command(Instruction::Cmd71, &[0x0E, 0x16, 0x03])
.await?;
self.write_command(Instruction::Cmd91, &[0x0E, 0x09])
.await?;
self.write_command(Instruction::PowerControl2, &[0x2C])
.await?; self.write_command(Instruction::PowerControl3, &[0x1A])
.await?;
self.write_command(
Instruction::SetGamma1,
&[0x51, 0x13, 0x0C, 0x06, 0x00, 0x2F],
)
.await?; self.write_command(
Instruction::SetGamma3,
&[0x51, 0x13, 0x0C, 0x06, 0x00, 0x33],
)
.await?; self.write_command(
Instruction::SetGamma2,
&[0x3C, 0x94, 0x4F, 0x33, 0x34, 0xCF], )
.await?; self.write_command(
Instruction::SetGamma4,
&[0x4D, 0x94, 0x4F, 0x33, 0x34, 0xCF],
)
.await?;
self.write_command(Instruction::MemoryAccessControl, &[0x40]) .await?;
self.write_command(
Instruction::DisplayFunctionControl,
&[0x0A, 0x80, 0x27, 0x00],
) .await?;
self.write_command(Instruction::SleepOut, &[]).await?; #[allow(unused_must_use)]
{
TIMER::after_millis(200).await;
}
self.write_command(Instruction::DisplayOn, &[]).await?;
self.write_command(Instruction::MemoryWrite, &[]).await?; #[allow(unused_must_use)]
{
TIMER::after_millis(100).await;
}
Ok(()) }
pub async fn reset(&mut self) -> Result<(), Error<BusE, PinE>> {
self.rst.set_low().map_err(Error::Pin)?;
#[allow(unused_must_use)]
{
TIMER::after_millis(10).await;
}
self.rst.set_high().map_err(Error::Pin)?;
#[allow(unused_must_use)]
{
TIMER::after_millis(120).await;
}
Ok(())
}
async fn write_command(
&mut self,
instruction: Instruction,
params: &[u8],
) -> Result<(), Error<BusE, PinE>> {
self.dc.set_low().map_err(Error::Pin)?;
let cmd_bytes = [instruction as u8];
let cmd_res = self.bus.write(&cmd_bytes).await.map_err(Error::Bus);
if cmd_res.is_ok() && !params.is_empty() {
self.dc.set_high().map_err(Error::Pin)?;
let param_res = self.bus.write(params).await.map_err(Error::Bus);
if param_res.is_err() {
param_res
} else {
Ok(())
}
} else if cmd_res.is_err() {
cmd_res
} else {
Ok(())
}
}
fn start_data_internal(&mut self) -> Result<(), PinE> {
self.dc.set_high()
}
fn transform_coordinates(&self, x: u16, y: u16) -> (u16, u16) {
match self.config.orientation {
Orientation::Portrait => {
(39 - y, 159 - x)
}
Orientation::Landscape => {
(y, 159 - x)
}
Orientation::PortraitSwapped => {
(x, y)
}
Orientation::LandscapeSwapped => {
(y, self.config.width - 1 - x)
}
}
}
pub async fn set_address_window(
&mut self,
sx: u16,
sy: u16,
ex: u16,
ey: u16,
) -> Result<(), Error<BusE, PinE>> {
let (phys_sx, phys_sy) = self.transform_coordinates(sx, sy);
let (phys_ex, phys_ey) = self.transform_coordinates(ex, ey);
let final_sx = phys_sx + self.config.dx;
let final_ex = phys_ex + self.config.dx;
let final_sy = phys_sy + self.config.dy;
let final_ey = phys_ey + self.config.dy;
let (min_x, max_x) = if final_sx <= final_ex {
(final_sx, final_ex)
} else {
(final_ex, final_sx)
};
let (min_y, max_y) = if final_sy <= final_ey {
(final_sy, final_ey)
} else {
(final_ey, final_sy)
};
#[cfg(feature = "defmt")]
defmt::debug!(
"Address window: logical ({},{}) to ({},{}) -> physical ({},{}) to ({},{})",
sx,
sy,
ex,
ey,
min_x,
min_y,
max_x,
max_y
);
self.write_command(
Instruction::RowAddressSet,
&[
(min_x >> 8) as u8,
min_x as u8,
(max_x >> 8) as u8,
max_x as u8,
],
)
.await?;
self.write_command(
Instruction::ColumnAddressSet,
&[
(min_y >> 8) as u8,
min_y as u8,
(max_y >> 8) as u8,
max_y as u8,
],
)
.await
}
pub fn clear_frame_buffer(&mut self, color: Rgb565) {
let converted_color = Self::convert_color_byte_order(color);
for pixel in self.frame_buffer.iter_mut() {
*pixel = converted_color;
}
}
pub fn set_pixel(&mut self, x: u16, y: u16, color: Rgb565) {
if x < self.config.width && y < self.config.height {
let converted_color = Self::convert_color_byte_order(color);
let physical_x = 39 - y;
let physical_y = 159 - x;
let index = (physical_y as usize) * 40 + (physical_x as usize);
if index < self.frame_buffer.len() {
self.frame_buffer[index] = converted_color;
}
}
}
pub fn fill_rect(&mut self, x: u16, y: u16, width: u16, height: u16, color: Rgb565) {
let converted_color = Self::convert_color_byte_order(color);
for row in y..(y + height) {
for col in x..(x + width) {
if col < self.config.width && row < self.config.height {
let physical_x = 39 - row;
let physical_y = 159 - col;
let index = (physical_y as usize) * 40 + (physical_x as usize);
if index < self.frame_buffer.len() {
self.frame_buffer[index] = converted_color;
}
}
}
}
}
pub fn write_rect(&mut self, x: u16, y: u16, width: u16, height: u16, data: &[Rgb565]) {
let mut data_index = 0;
for row in y..(y + height) {
for col in x..(x + width) {
if col < self.config.width && row < self.config.height && data_index < data.len() {
let converted_color = Self::convert_color_byte_order(data[data_index]);
let physical_x = 39 - row;
let physical_y = 159 - col;
let index = (physical_y as usize) * 40 + (physical_x as usize);
if index < self.frame_buffer.len() {
self.frame_buffer[index] = converted_color;
}
data_index += 1;
}
}
}
}
pub async fn flush(&mut self) -> Result<(), Error<BusE, PinE>> {
self.write_command(
Instruction::ColumnAddressSet,
&[0, 0, 0, 39], )
.await?;
self.write_command(
Instruction::RowAddressSet,
&[0, 0, 0, 159], )
.await?;
self.write_command(Instruction::MemoryWrite, &[]).await?;
self.start_data_internal().map_err(Error::Pin)?;
let frame_bytes = unsafe {
core::slice::from_raw_parts(
self.frame_buffer.as_ptr() as *const u8,
self.frame_buffer.len() * 2,
)
};
const CHUNK_SIZE: usize = 4096;
let mut offset = 0;
while offset < frame_bytes.len() {
let chunk_end = core::cmp::min(offset + CHUNK_SIZE, frame_bytes.len());
self.bus
.write(&frame_bytes[offset..chunk_end])
.await
.map_err(Error::Bus)?;
offset = chunk_end;
}
Ok(())
}
pub fn fill_color(&mut self, color: Rgb565) {
self.clear_frame_buffer(color);
}
pub fn write_area(&mut self, x: u16, y: u16, width: u16, height: u16, data: &[Rgb565]) {
self.write_rect(x, y, width, height, data);
}
}
impl<BUS, DC, RST, TIMER, BusE, PinE> OriginDimensions for GC9D01<'_, BUS, DC, RST, TIMER>
where
BUS: SpiDevice<Error = BusE>,
DC: OutputPin<Error = PinE>,
RST: OutputPin<Error = PinE>,
TIMER: crate::Timer,
BusE: core::fmt::Debug + SpiError,
PinE: core::fmt::Debug,
{
fn size(&self) -> Size {
Size::new(self.config.width as u32, self.config.height as u32)
}
}
impl<BUS, DC, RST, TIMER, BusE, PinE> DrawTarget for GC9D01<'_, BUS, DC, RST, TIMER>
where
BUS: SpiDevice<Error = BusE>,
DC: OutputPin<Error = PinE>,
RST: OutputPin<Error = PinE>,
TIMER: crate::Timer,
BusE: core::fmt::Debug + SpiError,
PinE: core::fmt::Debug,
{
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = EgPixel<Self::Color>>,
{
for EgPixel(point, color) in pixels {
if point.x >= 0 && point.y >= 0 {
let x = point.x as u16;
let y = point.y as u16;
if x < self.config.width && y < self.config.height {
self.set_pixel(x, y, color);
}
}
}
Ok(())
}
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
self.clear_frame_buffer(color);
Ok(())
}
}