use avr_oxide::alloc::boxed::Box;
use core::cell::RefCell;
use core::marker::PhantomData;
use avr_oxide::devices::serialbus::SerialBusClient;
use avr_oxide::hal::generic::port::{InterruptMode, Pin, PinMode};
use avr_oxide::hal::generic::twi::TwiAddr;
use avr_oxide::sync::Mutex;
use avr_oxide::util::datatypes::{BitField, BitFieldAccess, BitIndex, BitRange};
use avr_oxide::util::OwnOrBorrow;
use avrox_display::displaydevice::{DisplayDevice, PowerLevel};
use avrox_display::gfx::{Area, Renderable, RenderOrderingHint, RenderPlane, XCoord, Point, YCoord};
use avrox_display::gfx::pixels::{Grey, Monochromatic};
use avrox_display::GfxResult;
use avr_oxide::OxideResult::Ok;
pub enum PixelByteOrder {
LsbLeft,
MsbLeft,
LsbTop,
MsbTop
}
pub enum StartLineCmdType {
SingleByte0x40,
DoubleByteA1
}
pub trait SolomonPixelConversion {
const BITS_PER_PIXEL: u16;
fn to_ls_bits(&self) -> u8;
fn to_ms_bits(&self) -> u8;
}
#[allow(non_camel_case_types)]
pub trait GenericConfig {
const CP_VOLTAGE : u8;
const COL_ORDER: u8;
const SEG_ORDER: u8;
const MPLEX_RATIO: u8;
const WIDTH: usize;
const HEIGHT: usize;
const RAMWIDTH_BYTES: usize;
const RAMHEIGHT_BYTES : usize;
const PIXEL_ORDER: PixelByteOrder;
const DEFAULT_I2C: TwiAddr;
const STARTLINE_CMD_TYPE: StartLineCmdType;
const SET_PAGE_COL_CMD: u8;
const SET_PAGE_ROW_CMD: u8;
const RESET_CMD: &'static [u8] = &[
0x00u8,
0xA8, Self::MPLEX_RATIO,
0x8D, Self::CP_VOLTAGE,
0xDA,
0x02, Self::COL_ORDER, Self::SEG_ORDER,
0x20, 0x00, ];
const WINDOW_LAST_ROW_DECREMENT: u8 = 1;
const WINDOW_LAST_COL_DECREMENT: u8 = 1;
}
const BUFFER_SIZE : usize = 32;
pub struct SolomonDisplay<CONFIG, CLIENT, DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
inner: Mutex<RefCell<SolomonDisplayInner<CONFIG,CLIENT,DPIXEL>>>
}
struct SolomonDisplayInner<CONFIG, CLIENT, DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
reset_pin: OwnOrBorrow<'static,dyn Pin>,
bus: RefCell<CLIENT>,
displayed_page: usize,
flags: BitField,
ph_config: PhantomData<CONFIG>,
ph_dpixel: PhantomData<DPIXEL>,
buffer: RefCell<Box<[u8; BUFFER_SIZE]>>
}
pub const CP_DISABLED : u8 = 0b0001_0000;
pub const CP_7V5 : u8 = 0b0001_0100;
pub const CP_6V : u8 = 0b0001_0101;
pub const CP_8V5 : u8 = 0b1001_0100;
pub const CP_9V : u8 = 0b1001_0101;
pub const COL_LEFTTORIGHT: u8 = 0xC0;
pub const COL_RIGHTTOLEFT: u8 = 0xC8;
pub const SEG_SEG0COL0 :u8 = 0xA0;
pub const SEG_SEG0COL127 :u8 = 0xA1;
const FLAG_DBLBUFFER : BitIndex = BitIndex::bit_c(0);
const FLAG_DISABLE_DISP : BitIndex = BitIndex::bit_c(1);
const FLAG_HIBERNATE : BitIndex = BitIndex::bit_c(2);
const FLAG_LOWPOWER : BitIndex = BitIndex::bit_c(3);
const FLAG_BRIGHTNESS : BitRange = BitRange::range_c(4,7);
const LOPOWER_BRIGHTNESS : u8 = 0x11;
const DEFAULT_BRIGHTNESS : u8 = 0x77;
impl PixelByteOrder {
const fn vpixels_per_byte<P:SolomonPixelConversion>(&self) -> u16 {
match self {
PixelByteOrder::LsbLeft => 1,
PixelByteOrder::MsbLeft => 1,
PixelByteOrder::LsbTop => 8 / P::BITS_PER_PIXEL,
PixelByteOrder::MsbTop => 8 / P::BITS_PER_PIXEL
}
}
const fn hpixels_per_byte<P:SolomonPixelConversion>(&self) -> u16 {
match self {
PixelByteOrder::LsbLeft => 8 / P::BITS_PER_PIXEL,
PixelByteOrder::MsbLeft => 8 / P::BITS_PER_PIXEL,
PixelByteOrder::LsbTop => 1,
PixelByteOrder::MsbTop => 1
}
}
}
impl SolomonPixelConversion for Monochromatic {
const BITS_PER_PIXEL: u16 = 1;
fn to_ls_bits(&self) -> u8 {
if self.is_set() {
0b00000001
} else {
0b00000000
}
}
fn to_ms_bits(&self) -> u8 {
if self.is_set() {
0b10000000
} else {
0b00000000
}
}
}
impl SolomonPixelConversion for Grey {
const BITS_PER_PIXEL: u16 = 4;
fn to_ls_bits(&self) -> u8 {
self.get_value() >> 4
}
fn to_ms_bits(&self) -> u8 {
self.get_value() & 0xf0
}
}
impl<CONFIG,CLIENT,DPIXEL> SolomonDisplayInner<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
const VPIX_PER_BYTE: u16 = CONFIG::PIXEL_ORDER.vpixels_per_byte::<DPIXEL>();
const HPIX_PER_BYTE: u16 = CONFIG::PIXEL_ORDER.hpixels_per_byte::<DPIXEL>();
const PAGEHEIGHT_BYTES: usize = ((CONFIG::HEIGHT-1) / Self::VPIX_PER_BYTE as usize) + 1;
const PAGEHEIGHT_BITS: usize = Self::PAGEHEIGHT_BYTES * (Self::VPIX_PER_BYTE as usize);
const PAGES : usize = CONFIG::RAMHEIGHT_BYTES / Self::PAGEHEIGHT_BYTES;
fn using_pin_and_client<OP: Into<OwnOrBorrow<'static,dyn Pin>>>(pin: OP, client: CLIENT) -> Self {
let pin : OwnOrBorrow<dyn Pin> = pin.into();
pin.set_interrupt_mode(InterruptMode::Disabled);
pin.set_mode(PinMode::Output);
pin.set_low(); SolomonDisplayInner {
reset_pin: pin,
bus: RefCell::new(client),
displayed_page: usize::MAX,
flags: BitField::all_clr(),
ph_config: PhantomData::default(),
ph_dpixel: PhantomData::default(),
buffer: RefCell::new(Box::new([0x00; BUFFER_SIZE]))
}
}
fn set_brightness(&mut self, brightness: u8) -> GfxResult<()> {
self.flags.set_to(FLAG_BRIGHTNESS, brightness >> 4);
Ok(())
}
fn set_double_buffering(&mut self, double_buffer: bool) -> GfxResult<()> {
self.flags.set_or_clr(FLAG_DBLBUFFER, double_buffer);
Ok(())
}
fn reset_command(&mut self) -> GfxResult<()> {
self.reset_pin.set_high();
for _i in 0..2000 {
unsafe { core::arch::asm!("nop"); }
}
self.reset_pin.set_low();
for _i in 0..2000 {
unsafe { core::arch::asm!("nop"); }
}
self.reset_pin.set_high();
for _i in 0..2000 {
unsafe { core::arch::asm!("nop"); }
}
self.bus.borrow_mut().write_from(CONFIG::RESET_CMD)?;
self.set_display_page(0)?;
Ok(())
}
fn set_power_mode(&self) -> GfxResult<()> {
let power_command = [
0x00u8, if self.flags.is_set(FLAG_DISABLE_DISP) { 0xAE } else { 0xAF }, 0x81u8, if self.flags.is_set(FLAG_LOWPOWER) {
LOPOWER_BRIGHTNESS
} else {
self.flags.get_val(FLAG_BRIGHTNESS) << 4
},
];
self.bus.borrow_mut().write_from(&power_command)?;
Ok(())
}
fn select_write_page_area(&self, page: usize, area: &Area) -> GfxResult<()> {
let page_base = (page * Self::PAGEHEIGHT_BYTES) as u8;
let height_rows = (((area.h-1) / Self::VPIX_PER_BYTE)+1) as u8;
let first_row = (area.tl.1 / Self::VPIX_PER_BYTE) as u8;
let width_cols = (((area.w-1) / Self::HPIX_PER_BYTE)+1) as u8;
let first_col = (area.tl.0 / Self::HPIX_PER_BYTE) as u8;
let page_command = [
0x00, CONFIG::SET_PAGE_ROW_CMD, page_base + first_row, page_base + first_row + height_rows - CONFIG::WINDOW_LAST_ROW_DECREMENT, CONFIG::SET_PAGE_COL_CMD, first_col, first_col + width_cols - CONFIG::WINDOW_LAST_COL_DECREMENT, ];
self.bus.borrow_mut().write_from(&page_command)?;
Ok(())
}
fn set_display_page(&mut self, page: usize) -> GfxResult<()> {
match CONFIG::STARTLINE_CMD_TYPE {
StartLineCmdType::SingleByte0x40 => {
if self.displayed_page != page {
let display_command = [
0x00, 0b01000000 | ((page * Self::PAGEHEIGHT_BITS) as u8) ];
self.bus.borrow_mut().write_from(&display_command)?;
self.displayed_page = page;
}
Ok(())
},
StartLineCmdType::DoubleByteA1 => {
if self.displayed_page != page {
let display_command = [
0x00, 0xA1, ((page * Self::PAGEHEIGHT_BITS) as u8)
];
self.bus.borrow_mut().write_from(&display_command)?;
self.displayed_page = page;
}
Ok(())
}
}
}
fn reset(&mut self) -> GfxResult<()> {
self.flags = BitField::all_clr();
self.set_brightness(DEFAULT_BRIGHTNESS)?;
self.reset_command()?;
self.set_power_mode()?;
Ok(())
}
fn request_power_level(&mut self, level: PowerLevel) -> GfxResult<()> {
match level {
PowerLevel::Hibernate => {
self.flags.set(FLAG_HIBERNATE);
self.flags.set(FLAG_DISABLE_DISP);
}
PowerLevel::Sleep => {
self.flags.clr(FLAG_HIBERNATE);
self.flags.set(FLAG_DISABLE_DISP);
}
PowerLevel::Reduced => {
self.flags.clr(FLAG_HIBERNATE);
self.flags.clr(FLAG_DISABLE_DISP);
self.flags.set(FLAG_LOWPOWER);
}
PowerLevel::Normal => {
self.flags.clr(FLAG_HIBERNATE);
self.flags.clr(FLAG_DISABLE_DISP);
self.flags.clr(FLAG_LOWPOWER);
}
}
self.set_power_mode()
}
fn render<RPIXEL, RENDERABLE>(&mut self, scene: &RENDERABLE, area: Option<Area>) -> GfxResult<()>
where
RPIXEL: Into<DPIXEL>,
RENDERABLE: Renderable<PIXEL=RPIXEL> {
if !self.flags.is_set(FLAG_HIBERNATE){
let render_page = if self.flags.is_set(FLAG_DBLBUFFER) && area.is_none() {
(self.displayed_page+1)%Self::PAGES
} else {
self.displayed_page
};
let area = match area {
None => Area {
tl: Point(0, 0),
w: Self::WIDTH,
h: Self::HEIGHT
},
Some(a) => Area {
tl: Point((a.tl.0 / Self::HPIX_PER_BYTE) * Self::HPIX_PER_BYTE,
(a.tl.1 / Self::VPIX_PER_BYTE) * Self::VPIX_PER_BYTE),
w: (((a.tl.0 + a.w - 1) / Self::HPIX_PER_BYTE) + 1) * Self::HPIX_PER_BYTE,
h: (((a.tl.1 + a.h - 1) / Self::VPIX_PER_BYTE) + 1) * Self::VPIX_PER_BYTE
}
};
self.select_write_page_area(render_page, &area)?;
self.render_area::<RPIXEL,RENDERABLE>(scene, &area)?;
self.set_display_page(render_page)?;
}
Ok(())
}
fn render_area<RPIXEL, RENDERABLE>(&mut self, scene: &RENDERABLE, area: &Area) -> GfxResult<()>
where
RPIXEL: Into<DPIXEL>,
RENDERABLE: Renderable<PIXEL=RPIXEL>
{
let mut data_buffer = self.buffer.borrow_mut();
data_buffer[0] = 0b0100_0000u8;
let mut data_buffer_offset = 1usize;
let first_data_row = (area.tl.1 / Self::VPIX_PER_BYTE) as u16;
let last_data_row = ((area.tl.1 + area.h - 1) / Self::VPIX_PER_BYTE) as u16;
let first_data_col = (area.tl.0 / Self::HPIX_PER_BYTE) as u16;
let last_data_col = ((area.tl.0 + area.w - 1) / Self::HPIX_PER_BYTE) as u16;
for data_row in first_data_row..=last_data_row {
for data_col in first_data_col..=last_data_col {
let mut byte = 0x00u8;
match CONFIG::PIXEL_ORDER {
PixelByteOrder::MsbLeft => {
for col in ((data_col * Self::HPIX_PER_BYTE) as XCoord)..(((data_col +1) * Self::HPIX_PER_BYTE) as XCoord) {
byte <<= 8 / Self::HPIX_PER_BYTE;
let pix : DPIXEL = scene.get_pixel_at::<Self>(Point(col, data_row))?.into();
byte |= pix.to_ls_bits();
}
},
PixelByteOrder::LsbLeft => {
for col in ((data_col * Self::HPIX_PER_BYTE) as XCoord)..(((data_col +1) * Self::HPIX_PER_BYTE) as XCoord) {
byte >>= 8 / Self::HPIX_PER_BYTE;
let pix : DPIXEL = scene.get_pixel_at::<Self>(Point(col, data_row))?.into();
byte |= pix.to_ms_bits();
}
},
PixelByteOrder::LsbTop => {
for row in ((data_row * Self::VPIX_PER_BYTE) as YCoord)..(((data_row +1) * Self::VPIX_PER_BYTE) as YCoord) {
byte >>= 8 / Self::VPIX_PER_BYTE;
let pix : DPIXEL = scene.get_pixel_at::<Self>(Point(data_col, row))?.into();
byte |= pix.to_ms_bits();
}
},
PixelByteOrder::MsbTop => {
for row in ((data_row * Self::VPIX_PER_BYTE) as YCoord)..(((data_row +1) * Self::VPIX_PER_BYTE) as YCoord) {
byte <<= 8 / Self::VPIX_PER_BYTE;
let pix : DPIXEL = scene.get_pixel_at::<Self>(Point(data_col, row))?.into();
byte |= pix.to_ls_bits();
}
}
}
data_buffer[data_buffer_offset] = byte;
data_buffer_offset += 1;
if data_buffer_offset == BUFFER_SIZE {
self.bus.borrow_mut().write_from(&**data_buffer)?;
data_buffer_offset = 1;
}
}
}
if data_buffer_offset > 1 {
self.bus.borrow_mut().write_from(&data_buffer[0..data_buffer_offset])?;
}
Ok(())
}
}
impl<CONFIG,CLIENT,DPIXEL> SolomonDisplay<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
pub fn using_pin_and_client<OP: Into<OwnOrBorrow<'static,dyn Pin>>>(pin: OP, client: CLIENT) -> Self {
Self {
inner: Mutex::new(RefCell::new(SolomonDisplayInner::using_pin_and_client(pin, client)))
}
}
pub fn set_brightness(&self, brightness: u8) -> GfxResult<()> {
self.inner.lock().borrow_mut().set_brightness(brightness)
}
pub fn set_double_buffering(&self, double_buffer: bool) -> GfxResult<()> {
self.inner.lock().borrow_mut().set_double_buffering(double_buffer)
}
}
impl<CONFIG,CLIENT,DPIXEL> DisplayDevice for SolomonDisplay<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
type PIXEL = DPIXEL;
fn reset(&self) -> GfxResult<()> {
self.inner.lock().borrow_mut().reset()
}
fn request_power_level(&self, level: PowerLevel) -> GfxResult<()> {
self.inner.lock().borrow_mut().request_power_level(level)
}
fn render<RPIXEL, RENDERABLE>(&self, scene: &RENDERABLE) -> GfxResult<()> where RPIXEL: Into<Self::PIXEL>, RENDERABLE: Renderable<PIXEL=RPIXEL> {
self.inner.lock().borrow_mut().render(scene, None)
}
fn render_changed<RPIXEL,RENDERABLE>(&self, scene: &RENDERABLE) -> GfxResult<()>
where
RPIXEL: Into<Self::PIXEL>,
RENDERABLE: Renderable<PIXEL=RPIXEL> {
let change_area = scene.get_change_area::<Self>()?;
self.inner.lock().borrow_mut().render(scene, change_area)
}
}
impl<CONFIG,CLIENT,DPIXEL> RenderPlane for SolomonDisplayInner<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
const WIDTH: XCoord = CONFIG::WIDTH as XCoord;
const HEIGHT: YCoord = CONFIG::HEIGHT as YCoord;
const ORDERHINT: RenderOrderingHint = RenderOrderingHint::Columns;
}
impl<CONFIG,CLIENT,DPIXEL> RenderPlane for SolomonDisplay<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{
const WIDTH: XCoord = CONFIG::WIDTH as XCoord;
const HEIGHT: YCoord = CONFIG::HEIGHT as YCoord;
const ORDERHINT: RenderOrderingHint = RenderOrderingHint::Columns;
}
unsafe impl<CONFIG,CLIENT,DPIXEL> Send for SolomonDisplay<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{}
unsafe impl<CONFIG,CLIENT,DPIXEL> Sync for SolomonDisplay<CONFIG,CLIENT,DPIXEL>
where
CONFIG: GenericConfig,
CLIENT: SerialBusClient,
DPIXEL: SolomonPixelConversion
{}