use hal::{blocking::delay::DelayMs, digital::v2::OutputPin};
use crate::{
command::{AddressIncrementMode, ColorMode, Command, VcomhLevel},
displayrotation::DisplayRotation,
error::Error,
DISPLAY_HEIGHT, DISPLAY_WIDTH,
};
const BUF_SIZE: usize = 96 * 64 * 2;
pub struct Ssd1331<SPI, DC> {
buffer: [u8; BUF_SIZE],
display_rotation: DisplayRotation,
spi: SPI,
dc: DC,
}
impl<SPI, DC, CommE, PinE> Ssd1331<SPI, DC>
where
SPI: hal::blocking::spi::Write<u8, Error = CommE>,
DC: OutputPin<Error = PinE>,
{
pub fn new(spi: SPI, dc: DC, display_rotation: DisplayRotation) -> Self {
Self {
spi,
dc,
display_rotation,
buffer: [0; BUF_SIZE],
}
}
pub fn release(self) -> (SPI, DC) {
(self.spi, self.dc)
}
pub fn clear(&mut self) {
self.buffer = [0; BUF_SIZE];
}
pub fn reset<RST, DELAY>(
&mut self,
rst: &mut RST,
delay: &mut DELAY,
) -> Result<(), Error<CommE, PinE>>
where
RST: OutputPin<Error = PinE>,
DELAY: DelayMs<u8>,
{
rst.set_high().map_err(Error::Pin)?;
delay.delay_ms(1);
rst.set_low().map_err(Error::Pin)?;
delay.delay_ms(1);
rst.set_high().map_err(Error::Pin)?;
Ok(())
}
pub fn flush(&mut self) -> Result<(), Error<CommE, PinE>> {
self.set_draw_area((0, 0), (DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1))?;
self.dc.set_high().map_err(Error::Pin)?;
self.spi.write(&self.buffer).map_err(Error::Comm)?;
Ok(())
}
pub fn set_draw_area(
&mut self,
start: (u8, u8),
end: (u8, u8),
) -> Result<(), Error<CommE, PinE>> {
Command::ColumnAddress(start.0, end.0).send(&mut self.spi, &mut self.dc)?;
Command::RowAddress(start.1.into(), (end.1).into()).send(&mut self.spi, &mut self.dc)?;
Ok(())
}
pub fn set_pixel(&mut self, x: u32, y: u32, value: u16) {
let idx = match self.display_rotation {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
if x >= DISPLAY_WIDTH as u32 {
return;
}
((y as usize) * DISPLAY_WIDTH as usize) + (x as usize)
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
if y >= DISPLAY_WIDTH as u32 {
return;
}
((y as usize) * DISPLAY_HEIGHT as usize) + (x as usize)
}
} * 2;
if idx >= self.buffer.len() - 1 {
return;
}
let low = (value & 0xff) as u8;
let high = ((value & 0xff00) >> 8) as u8;
self.buffer[idx] = high;
self.buffer[idx + 1] = low;
}
pub fn init(&mut self) -> Result<(), Error<CommE, PinE>> {
let display_rotation = self.display_rotation;
Command::DisplayOn(false).send(&mut self.spi, &mut self.dc)?;
Command::DisplayClockDiv(0xF, 0x0).send(&mut self.spi, &mut self.dc)?;
Command::Multiplex(DISPLAY_HEIGHT - 1).send(&mut self.spi, &mut self.dc)?;
Command::StartLine(0).send(&mut self.spi, &mut self.dc)?;
Command::DisplayOffset(0).send(&mut self.spi, &mut self.dc)?;
self.set_rotation(display_rotation)?;
Command::Contrast(0x91, 0x50, 0x7D).send(&mut self.spi, &mut self.dc)?;
Command::PreChargePeriod(0x1, 0xF).send(&mut self.spi, &mut self.dc)?;
Command::VcomhDeselect(VcomhLevel::V071).send(&mut self.spi, &mut self.dc)?;
Command::AllOn(false).send(&mut self.spi, &mut self.dc)?;
Command::Invert(false).send(&mut self.spi, &mut self.dc)?;
Command::DisplayOn(true).send(&mut self.spi, &mut self.dc)?;
Ok(())
}
pub fn dimensions(&self) -> (u8, u8) {
match self.display_rotation {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
(DISPLAY_WIDTH, DISPLAY_HEIGHT)
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
(DISPLAY_HEIGHT, DISPLAY_WIDTH)
}
}
}
pub fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), Error<CommE, PinE>> {
self.display_rotation = rot;
match rot {
DisplayRotation::Rotate0 => {
Command::RemapAndColorDepth(
false,
false,
ColorMode::CM65k,
AddressIncrementMode::Horizontal,
)
.send(&mut self.spi, &mut self.dc)?;
}
DisplayRotation::Rotate90 => {
Command::RemapAndColorDepth(
true,
false,
ColorMode::CM65k,
AddressIncrementMode::Vertical,
)
.send(&mut self.spi, &mut self.dc)?;
}
DisplayRotation::Rotate180 => {
Command::RemapAndColorDepth(
true,
true,
ColorMode::CM65k,
AddressIncrementMode::Horizontal,
)
.send(&mut self.spi, &mut self.dc)?;
}
DisplayRotation::Rotate270 => {
Command::RemapAndColorDepth(
false,
true,
ColorMode::CM65k,
AddressIncrementMode::Vertical,
)
.send(&mut self.spi, &mut self.dc)?;
}
};
Ok(())
}
pub fn rotation(&self) -> DisplayRotation {
self.display_rotation
}
pub fn turn_on(&mut self) -> Result<(), Error<CommE, PinE>> {
Command::DisplayOn(true).send(&mut self.spi, &mut self.dc)
}
pub fn turn_off(&mut self) -> Result<(), Error<CommE, PinE>> {
Command::DisplayOn(false).send(&mut self.spi, &mut self.dc)
}
}
#[cfg(feature = "graphics")]
use embedded_graphics_core::{
draw_target::DrawTarget,
geometry::Size,
geometry::{Dimensions, OriginDimensions},
pixelcolor::{
raw::{RawData, RawU16},
Rgb565,
},
Pixel,
};
#[cfg(feature = "graphics")]
impl<SPI, DC> DrawTarget for Ssd1331<SPI, DC>
where
SPI: hal::blocking::spi::Write<u8>,
DC: OutputPin,
{
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
let bb = self.bounding_box();
pixels
.into_iter()
.filter(|Pixel(pos, _color)| bb.contains(*pos))
.for_each(|Pixel(pos, color)| {
self.set_pixel(pos.x as u32, pos.y as u32, RawU16::from(color).into_inner())
});
Ok(())
}
}
#[cfg(feature = "graphics")]
impl<SPI, DC> OriginDimensions for Ssd1331<SPI, DC>
where
SPI: hal::blocking::spi::Write<u8>,
DC: OutputPin,
{
fn size(&self) -> Size {
let (w, h) = self.dimensions();
Size::new(w.into(), h.into())
}
}