#![cfg_attr(not(test), no_std)]
#![warn(missing_docs)]
#[macro_use]
extern crate alloc;
use alloc::string::String;
use core::{borrow::Borrow, marker::PhantomData};
mod area_serializer;
mod command;
pub mod interface;
pub mod memory_converter_settings;
pub mod origin;
mod pixel_serializer;
mod register;
mod serialization_helper;
use area_serializer::{AreaSerializer, AreaSerializerIterator};
use memory_converter_settings::MemoryConverterSetting;
use pixel_serializer::{convert_color_to_pixel_iterator, PixelSerializer};
#[cfg(feature = "defmt")]
use defmt;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
Interface(interface::Error),
DisplayEngineTimeout,
}
impl From<interface::Error> for Error {
fn from(e: interface::Error) -> Self {
Error::Interface(e)
}
}
pub struct Config {
pub timeout_display_engine: core::time::Duration,
pub timeout_interface: core::time::Duration,
pub max_buffer_size: usize,
pub rotation: Rotation,
}
impl Default for Config {
fn default() -> Self {
Self {
timeout_display_engine: core::time::Duration::from_secs(15),
timeout_interface: core::time::Duration::from_secs(15),
max_buffer_size: 1024,
rotation: Rotation::Rotate0,
}
}
}
#[derive(Debug, Clone)]
pub struct DevInfo {
pub panel_width: u16,
pub panel_height: u16,
pub memory_address: u32,
pub firmware_version: String,
pub lut_version: String,
}
#[derive(Debug, PartialEq, Eq)]
pub struct AreaImgInfo {
pub area_x: u16,
pub area_y: u16,
pub area_w: u16,
pub area_h: u16,
}
#[cfg(feature = "defmt")]
impl defmt::Format for AreaImgInfo {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"Area Img Info {}x{} @ {}x{}",
self.area_w,
self.area_h,
self.area_x,
self.area_y
);
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u16)]
pub enum WaveformMode {
Init = 0,
DirectUpdate = 1,
GrayscaleClearing16 = 2,
GL16 = 3,
GLR16 = 4,
GLD16 = 5,
A2 = 6,
DU4 = 7,
}
pub enum Rotation {
Rotate0,
Rotate90,
Rotate180,
Rotate270,
}
pub struct Run;
pub struct PowerDown;
pub struct Off;
pub struct IT8951<IT8951Interface, TOrigin: Origin, State> {
interface: IT8951Interface,
dev_info: Option<DevInfo>,
marker: core::marker::PhantomData<State>,
origin: core::marker::PhantomData<TOrigin>,
config: Config,
}
impl<IT8951Interface: interface::IT8951Interface, TOrigin: Origin, TState>
IT8951<IT8951Interface, TOrigin, TState>
{
fn into_state<TNew>(self) -> IT8951<IT8951Interface, TOrigin, TNew> {
IT8951::<IT8951Interface, TOrigin, TNew> {
interface: self.interface,
dev_info: self.dev_info,
marker: PhantomData {},
origin: PhantomData {},
config: self.config,
}
}
}
impl<IT8951Interface: interface::IT8951Interface> IT8951<IT8951Interface, OriginTopLeft, Off> {
pub fn new(
interface: IT8951Interface,
config: Config,
) -> IT8951<IT8951Interface, OriginTopLeft, Off> {
Self::new_with_origin(interface, config, OriginTopLeft {})
}
}
impl<IT8951Interface: interface::IT8951Interface, TOrigin: Origin>
IT8951<IT8951Interface, TOrigin, Off>
{
pub fn new_with_origin(
mut interface: IT8951Interface,
config: Config,
_: TOrigin,
) -> IT8951<IT8951Interface, TOrigin, Off> {
interface.set_busy_timeout(config.timeout_interface);
IT8951 {
interface,
dev_info: None,
marker: PhantomData {},
origin: PhantomData {},
config,
}
}
pub fn init(self, vcom: u16) -> Result<IT8951<IT8951Interface, TOrigin, Run>, Error> {
let mut it8951 = self.init_no_vcom()?;
let current_vcom = it8951.get_vcom()?;
if vcom != current_vcom {
#[cfg(feature = "defmt")]
defmt::trace!("Overriding vcom, wanted {}, current {}", vcom, current_vcom);
it8951.set_vcom(vcom)?;
}
Ok(it8951)
}
pub fn init_no_vcom(mut self) -> Result<IT8951<IT8951Interface, TOrigin, Run>, Error> {
self.interface.reset()?;
let mut it8951 = self.into_state::<PowerDown>().sys_run()?;
let dev_info = it8951.get_system_info()?;
it8951.write_register(register::I80CPCR, 0x0001)?;
#[cfg(feature = "defmt")]
defmt::info!(
"Initialized screen Resolution {}x{}, LUT {=str}, FWV {=str} MA = {:x}",
dev_info.panel_width,
dev_info.panel_height,
dev_info.lut_version,
dev_info.firmware_version,
dev_info.memory_address,
);
it8951.dev_info = Some(dev_info);
Ok(it8951)
}
pub fn attach(
mut interface: IT8951Interface,
config: Config,
) -> Result<IT8951<IT8951Interface, OriginTopLeft, Run>, Error> {
interface.set_busy_timeout(config.timeout_interface);
let mut it8951: IT8951<IT8951Interface, OriginTopLeft, Run> = IT8951 {
interface,
dev_info: None,
marker: PhantomData {},
origin: PhantomData {},
config,
}
.sys_run()?;
it8951.dev_info = Some(it8951.get_system_info()?);
#[cfg(feature = "defmt")]
{
let dev_info = it8951.dev_info.as_ref().unwrap();
defmt::info!(
"Attached screen Resolution {}x{}, LUT {=str}, FWV {=str}",
dev_info.panel_width,
dev_info.panel_height,
dev_info.lut_version,
dev_info.firmware_version,
);
}
Ok(it8951)
}
}
impl<IT8951Interface: interface::IT8951Interface, TOrigin: Origin>
IT8951<IT8951Interface, TOrigin, Run>
{
pub fn get_dev_info(&self) -> DevInfo {
self.dev_info.clone().unwrap()
}
pub fn overwrite_default_buffer_address(&mut self, address: u32) {
if let Some(dev_info) = &mut self.dev_info {
dev_info.memory_address = address;
}
}
pub fn enhance_driving_capability(&mut self) -> Result<(), Error> {
self.write_register(0x0038, 0x0602)?;
#[cfg(feature = "defmt")]
defmt::warn!("Increased driver strength!");
Ok(())
}
pub fn reset(&mut self) -> Result<(), Error> {
self.clear(Gray4::WHITE)?;
self.display(WaveformMode::Init)?;
#[cfg(feature = "defmt")]
defmt::trace!("Cleared display");
Ok(())
}
pub fn load_image<TMemoryConverterSetting: Borrow<MemoryConverterSetting>>(
&mut self,
target_mem_addr: u32,
image_settings: TMemoryConverterSetting,
data: &[u8],
) -> Result<(), Error> {
self.set_target_memory_addr(target_mem_addr)?;
self.interface.write_command(command::IT8951_TCON_LD_IMG)?;
self.interface
.write_data(image_settings.borrow().into_arg())?;
self.interface.write_multi_data(data)?;
self.interface
.write_command(command::IT8951_TCON_LD_IMG_END)?;
#[cfg(feature = "defmt")]
defmt::trace!("Loaded full image");
Ok(())
}
pub fn load_image_area<TMemoryConverterSetting: Borrow<MemoryConverterSetting>>(
&mut self,
target_mem_addr: u32,
image_settings: TMemoryConverterSetting,
area_info: &AreaImgInfo,
data: &[u8],
) -> Result<(), Error> {
self.set_target_memory_addr(target_mem_addr)?;
self.interface.write_command_with_args(
command::IT8951_TCON_LD_IMG_AREA,
&[
image_settings.borrow().into_arg(),
area_info.area_x,
area_info.area_y,
area_info.area_w,
area_info.area_h,
],
)?;
self.interface.write_multi_data(data)?;
self.interface
.write_command(command::IT8951_TCON_LD_IMG_END)?;
#[cfg(feature = "defmt")]
defmt::trace!("Loaded image area {}", area_info);
Ok(())
}
fn set_target_memory_addr(&mut self, target_mem_addr: u32) -> Result<(), Error> {
self.write_register(register::LISAR + 2, (target_mem_addr >> 16) as u16)?;
self.write_register(register::LISAR, target_mem_addr as u16)?;
#[cfg(feature = "defmt")]
defmt::trace!("Target memory addr set {:x}", target_mem_addr);
Ok(())
}
pub fn memory_burst_read(&mut self, memory_address: u32, data: &mut [u8]) -> Result<(), Error> {
let args = [
memory_address as u16,
(memory_address >> 16) as u16,
data.len() as u16,
(data.len() >> 16) as u16,
];
self.interface
.write_command_with_args(command::IT8951_TCON_MEM_BST_RD_T, &args)?;
self.interface
.write_command(command::IT8951_TCON_MEM_BST_RD_S)?;
self.interface.read_multi_data(data)?;
self.interface
.write_command(command::IT8951_TCON_MEM_BST_END)?;
#[cfg(feature = "defmt")]
defmt::trace!(
"Read {} bytes of data from {:x}",
data.len(),
memory_address
);
Self::convert_endianness(data);
Ok(())
}
pub fn memory_burst_write(
&mut self,
memory_address: u32,
data: &mut [u8],
) -> Result<(), Error> {
let args = [
memory_address as u16,
(memory_address >> 16) as u16,
data.len() as u16,
(data.len() >> 16) as u16,
];
self.interface
.write_command_with_args(command::IT8951_TCON_MEM_BST_WR, &args)?;
Self::convert_endianness(data);
self.interface.write_multi_data(data)?;
self.interface
.write_command(command::IT8951_TCON_MEM_BST_END)?;
#[cfg(feature = "defmt")]
defmt::trace!("Wrote {} bytes of data to {:x}", data.len(), memory_address);
Ok(())
}
pub fn display_area(
&mut self,
area_info: &AreaImgInfo,
mode: WaveformMode,
) -> Result<(), Error> {
let area_info = self.rotate_area_info(area_info);
self.wait_for_display_ready()?;
let args = [
area_info.area_x,
area_info.area_y,
area_info.area_w,
area_info.area_h,
mode as u16,
];
self.interface
.write_command_with_args(command::USDEF_I80_CMD_DPY_AREA, &args)?;
#[cfg(feature = "defmt")]
defmt::trace!(
"Refreshed display area {} with mode {}",
area_info,
mode.clone()
);
Ok(())
}
pub fn display_area_buf(
&mut self,
area_info: &AreaImgInfo,
mode: WaveformMode,
target_mem_addr: u32,
) -> Result<(), Error> {
let area_info = self.rotate_area_info(area_info);
let args = [
area_info.area_x,
area_info.area_y,
area_info.area_w,
area_info.area_h,
mode as u16,
target_mem_addr as u16,
(target_mem_addr >> 16) as u16,
];
self.wait_for_display_ready()?;
self.interface
.write_command_with_args(command::USDEF_I80_CMD_DPY_BUF_AREA, &args)?;
#[cfg(feature = "defmt")]
defmt::trace!(
"Refreshed display area {} with mode {} from addr {}",
area_info,
mode,
target_mem_addr
);
Ok(())
}
pub fn display(&mut self, mode: WaveformMode) -> Result<(), Error> {
let size = self.size();
self.display_area(
&AreaImgInfo {
area_x: 0,
area_y: 0,
area_w: size.width as u16,
area_h: size.height as u16,
},
mode,
)?;
Ok(())
}
fn wait_for_display_ready(&mut self) -> Result<(), Error> {
let timeout = self.config.timeout_display_engine.as_micros() as u64;
let mut counter = 0u64;
while 0 != self.read_register(register::LUTAFSR)? {
if counter > timeout {
return Err(Error::DisplayEngineTimeout);
}
counter += 1;
self.interface.delay(core::time::Duration::from_micros(1))?;
}
Ok(())
}
pub fn sleep(mut self) -> Result<IT8951<IT8951Interface, TOrigin, PowerDown>, Error> {
self.interface.write_command(command::IT8951_TCON_SLEEP)?;
#[cfg(feature = "defmt")]
defmt::trace!("Sleep mode");
Ok(self.into_state())
}
pub fn standby(mut self) -> Result<IT8951<IT8951Interface, TOrigin, PowerDown>, Error> {
self.interface.write_command(command::IT8951_TCON_STANDBY)?;
#[cfg(feature = "defmt")]
defmt::trace!("Standby mode");
Ok(self.into_state())
}
fn get_system_info(&mut self) -> Result<DevInfo, Error> {
self.interface
.write_command(command::USDEF_I80_CMD_GET_DEV_INFO)?;
self.interface.wait_while_busy()?;
let mut buf = [0x0000; 40];
self.interface.read_multi_data(&mut buf)?;
Self::convert_endianness(&mut buf);
Ok(DevInfo {
panel_width: u16::from_be_bytes([buf[1], buf[0]]),
panel_height: u16::from_be_bytes([buf[3], buf[2]]),
memory_address: u32::from_be_bytes([buf[7], buf[6], buf[5], buf[4]]),
firmware_version: Self::buf_to_str(&buf[8..24]),
lut_version: Self::buf_to_str(&buf[25..40]),
})
}
fn convert_endianness(buffer: &mut [u8]) {
if !buffer.len().is_multiple_of(2) {
panic!("Buffer needs to be align on u16");
}
for i in (0..buffer.len() - 1).step_by(2) {
buffer.swap(i, i + 1)
}
}
fn buf_to_str(buffer: &[u8]) -> String {
String::from_iter(
buffer
.iter()
.filter(|&&raw| raw != 0x0000)
.map(|c| char::from(*c)),
)
}
pub fn get_vcom(&mut self) -> Result<u16, Error> {
self.interface.write_command(command::USDEF_I80_CMD_VCOM)?;
self.interface.write_data(0x0000)?;
let vcom = self.interface.read_data()?;
#[cfg(feature = "defmt")]
defmt::trace!("CURRENT VCOM = {}", vcom);
Ok(vcom)
}
pub fn set_vcom(&mut self, vcom: u16) -> Result<(), Error> {
self.interface.write_command(command::USDEF_I80_CMD_VCOM)?;
self.interface.write_data(0x0001)?;
self.interface.write_data(vcom)?;
#[cfg(feature = "defmt")]
defmt::trace!("VCOM Set {}", vcom);
Ok(())
}
fn read_register(&mut self, reg: u16) -> Result<u16, Error> {
self.interface.write_command(command::IT8951_TCON_REG_RD)?;
self.interface.write_data(reg)?;
let data = self.interface.read_data()?;
Ok(data)
}
fn write_register(&mut self, reg: u16, data: u16) -> Result<(), Error> {
self.interface.write_command(command::IT8951_TCON_REG_WR)?;
self.interface.write_data(reg)?;
self.interface.write_data(data)?;
Ok(())
}
fn rotate_area_info(&self, area: &AreaImgInfo) -> AreaImgInfo {
use Rotation::*;
let info = self.dev_info.as_ref().expect("Unable to load device info");
let (pw, ph) = (info.panel_width, info.panel_height);
let (x, y, w, h) = (area.area_x, area.area_y, area.area_w, area.area_h);
let (x, y, w, h) = match self.config.rotation {
Rotate0 => (x, y, w, h),
Rotate90 => (y, ph - w - x, h, w),
Rotate180 => (pw - w - x, ph - h - y, w, h),
Rotate270 => (pw - h - y, x, h, w),
};
AreaImgInfo {
area_x: x,
area_y: y,
area_w: w,
area_h: h,
}
}
}
impl<IT8951Interface: interface::IT8951Interface, TOrigin: Origin>
IT8951<IT8951Interface, TOrigin, PowerDown>
{
pub fn sys_run(mut self) -> Result<IT8951<IT8951Interface, TOrigin, Run>, Error> {
self.interface.write_command(command::IT8951_TCON_SYS_RUN)?;
#[cfg(feature = "defmt")]
defmt::trace!("Sys run");
Ok(self.into_state())
}
}
use embedded_graphics_core::{pixelcolor::Gray4, prelude::*, primitives::Rectangle};
use crate::origin::{Origin, OriginTopLeft};
impl<IT8951Interface: interface::IT8951Interface, TOrigin: Origin> DrawTarget
for IT8951<IT8951Interface, TOrigin, Run>
{
type Color = Gray4;
type Error = Error;
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
let size = self.size();
self.fill_solid(
&Rectangle::new(
Point::zero(),
Size {
width: size.width,
height: size.height,
},
),
color,
)
}
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
let area = area.intersection(&self.bounding_box());
if area.is_zero_sized() {
return Ok(());
}
let a = AreaSerializer::new(area, color, self.config.max_buffer_size);
let area_iter = AreaSerializerIterator::new(&a);
let memory_address = self
.dev_info
.as_ref()
.map(|d| d.memory_address)
.expect("Dev info not initialized");
for (area_img_info, buffer) in area_iter {
self.load_image_area(
memory_address,
MemoryConverterSetting {
rotation: (&self.config.rotation).into(),
..Default::default()
},
&area_img_info,
buffer,
)?;
}
#[cfg(feature = "defmt")]
defmt::trace!("Embedded graphics: Fill solid");
Ok(())
}
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Self::Color>,
{
let bb = self.bounding_box();
let iter = convert_color_to_pixel_iterator(area, &bb, colors.into_iter());
let memory_address = self
.dev_info
.as_ref()
.map(|d| d.memory_address)
.expect("Dev info not initialized");
let pixel = PixelSerializer::<_, TOrigin>::new(
area.intersection(&bb),
iter,
self.config.max_buffer_size,
);
for (area_img_info, buffer) in pixel {
self.load_image_area(
memory_address,
MemoryConverterSetting {
endianness: memory_converter_settings::MemoryConverterEndianness::LittleEndian,
rotation: (&self.config.rotation).into(),
..Default::default()
},
&area_img_info,
&buffer,
)?;
}
#[cfg(feature = "defmt")]
defmt::trace!("Embedded graphics: Fill contiguous");
Ok(())
}
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
{
let memory_address = self
.dev_info
.as_ref()
.map(|d| d.memory_address)
.expect("Dev info not initialized");
let size = self.size();
let width = size.width as i32;
let height = size.height as i32;
for Pixel(coord, color) in pixels.into_iter() {
if (coord.x >= 0 && coord.x < width) && (coord.y >= 0 && coord.y < height) {
let raw_color = color.luma();
let data = [raw_color << 4 | raw_color, raw_color << 4 | raw_color];
self.load_image_area(
memory_address,
MemoryConverterSetting {
rotation: (&self.config.rotation).into(),
..Default::default()
},
&AreaImgInfo {
area_x: coord.x as u16,
area_y: coord.y as u16,
area_w: 1,
area_h: 1,
},
&data,
)?;
}
}
#[cfg(feature = "defmt")]
defmt::trace!("Embedded graphics: Draw iter");
Ok(())
}
}
impl<IT8951Interface: interface::IT8951Interface, TOrigin: Origin> OriginDimensions
for IT8951<IT8951Interface, TOrigin, Run>
{
fn size(&self) -> Size {
let dev_info = self.dev_info.as_ref().unwrap();
let (w, h) = (dev_info.panel_width as u32, dev_info.panel_height as u32);
let (w, h) = match self.config.rotation {
Rotation::Rotate0 | Rotation::Rotate180 => (w, h),
Rotation::Rotate90 | Rotation::Rotate270 => (h, w),
};
Size::new(w, h)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
struct MockInterface {
commands: Vec<(u16, Vec<u16>)>,
timeout: core::time::Duration,
}
impl MockInterface {
fn new() -> Self {
MockInterface {
commands: Vec::new(),
timeout: core::time::Duration::from_secs(1),
}
}
}
impl interface::IT8951Interface for MockInterface {
fn set_busy_timeout(&mut self, timeout: core::time::Duration) {
self.timeout = timeout;
}
fn wait_while_busy(&mut self) -> Result<(), interface::Error> {
Ok(())
}
fn write_data(&mut self, _data: u16) -> Result<(), interface::Error> {
Ok(())
}
fn write_multi_data(&mut self, _data: &[u8]) -> Result<(), interface::Error> {
Ok(())
}
fn write_command(&mut self, cmd: u16) -> Result<(), interface::Error> {
self.commands.push((cmd, Vec::new()));
Ok(())
}
fn write_command_with_args(
&mut self,
cmd: u16,
args: &[u16],
) -> Result<(), interface::Error> {
self.commands.push((cmd, args.to_vec()));
Ok(())
}
fn read_data(&mut self) -> Result<u16, interface::Error> {
Ok(0)
}
fn read_multi_data(&mut self, buf: &mut [u8]) -> Result<(), interface::Error> {
if buf.len() >= 40 {
buf[0] = 0x50;
buf[1] = 0x07;
buf[2] = 0x7C;
buf[3] = 0x05;
buf[4] = 0x00;
buf[5] = 0x10;
buf[6] = 0x00;
buf[7] = 0x00;
}
Ok(())
}
fn reset(&mut self) -> Result<(), interface::Error> {
Ok(())
}
fn delay(&mut self, _duration: core::time::Duration) -> Result<(), interface::Error> {
Ok(())
}
}
#[test]
fn test_rotate_area_info_rotate0() {
let mock = MockInterface::new();
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, Config::default());
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 50,
area_h: 75,
};
let rotated = driver.rotate_area_info(&area);
assert_eq!(rotated.area_x, 100);
assert_eq!(rotated.area_y, 200);
assert_eq!(rotated.area_w, 50);
assert_eq!(rotated.area_h, 75);
}
#[test]
fn test_rotate_area_info_rotate90() {
let mock = MockInterface::new();
let mut config = Config::default();
config.rotation = Rotation::Rotate90;
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, config);
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 50,
area_h: 75,
};
let rotated = driver.rotate_area_info(&area);
assert_eq!(rotated.area_x, 200);
assert_eq!(rotated.area_y, 1404 - 50 - 100);
assert_eq!(rotated.area_w, 75);
assert_eq!(rotated.area_h, 50);
}
#[test]
fn test_rotate_area_info_rotate180() {
let mock = MockInterface::new();
let mut config = Config::default();
config.rotation = Rotation::Rotate180;
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, config);
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 50,
area_h: 75,
};
let rotated = driver.rotate_area_info(&area);
assert_eq!(rotated.area_x, 1872 - 50 - 100);
assert_eq!(rotated.area_y, 1404 - 75 - 200);
assert_eq!(rotated.area_w, 50);
assert_eq!(rotated.area_h, 75);
}
#[test]
fn test_rotate_area_info_rotate270() {
let mock = MockInterface::new();
let mut config = Config::default();
config.rotation = Rotation::Rotate270;
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, config);
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 50,
area_h: 75,
};
let rotated = driver.rotate_area_info(&area);
assert_eq!(rotated.area_x, 1872 - 75 - 200);
assert_eq!(rotated.area_y, 100);
assert_eq!(rotated.area_w, 75);
assert_eq!(rotated.area_h, 50);
}
#[test]
fn test_load_image_area_no_transform_with_rotation() {
let mock = MockInterface::new();
let mut config = Config::default();
config.rotation = Rotation::Rotate180; let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, config);
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 4,
area_h: 1,
};
let data = [0x00, 0x00, 0x00, 0x00];
let result =
driver.load_image_area(0x001236E0, MemoryConverterSetting::default(), &area, &data);
assert!(result.is_ok());
let commands = &driver.interface.commands;
assert!(
commands.iter().any(|(cmd, args)| {
*cmd == command::IT8951_TCON_LD_IMG_AREA &&
args.len() >= 5 &&
args[1] == 100 && args[2] == 200 && args[3] == 4 && args[4] == 1 }),
"load_image_area should NOT transform coordinates even with rotation enabled"
);
}
#[test]
fn test_load_image_area_no_origin_transform() {
let mock = MockInterface::new();
let driver = IT8951::<_, origin::OriginTopRight, Off>::new_with_origin(
mock,
Config::default(),
origin::OriginTopRight {},
);
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 50,
area_h: 1,
};
let data = [0x00; 100];
let result =
driver.load_image_area(0x001236E0, MemoryConverterSetting::default(), &area, &data);
assert!(result.is_ok());
let commands = &driver.interface.commands;
assert!(
commands.iter().any(|(cmd, args)| {
*cmd == command::IT8951_TCON_LD_IMG_AREA
&& args.len() >= 5
&& args[1] == 100 && args[2] == 200 && args[3] == 50 && args[4] == 1 }),
"load_image_area should NOT apply TOrigin transformation"
);
}
#[test]
fn test_display_area_applies_rotation_once() {
let mock = MockInterface::new();
let mut config = Config::default();
config.rotation = Rotation::Rotate180;
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, config);
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 100,
area_y: 200,
area_w: 50,
area_h: 75,
};
let result = driver.display_area(&area, WaveformMode::GL16);
assert!(result.is_ok());
let commands = &driver.interface.commands;
assert!(commands.iter().any(|(cmd, args)| {
*cmd == command::USDEF_I80_CMD_DPY_AREA &&
args.len() >= 5 &&
args[0] == 1872 - 50 - 100 && args[1] == 1404 - 75 - 200 && args[2] == 50 && args[3] == 75 }));
}
#[test]
fn test_bounds_checking_filters_out_of_bounds() {
let mock = MockInterface::new();
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, Config::default());
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 100,
panel_height: 100,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let pixels = vec![
Pixel(Point::new(50, 50), Gray4::WHITE), Pixel(Point::new(-1, 50), Gray4::WHITE), Pixel(Point::new(50, -1), Gray4::WHITE), Pixel(Point::new(100, 50), Gray4::WHITE), Pixel(Point::new(50, 100), Gray4::WHITE), Pixel(Point::new(99, 99), Gray4::WHITE), ];
let result = driver.draw_iter(pixels);
assert!(result.is_ok());
let load_commands = driver
.interface
.commands
.iter()
.filter(|(cmd, _)| *cmd == command::IT8951_TCON_LD_IMG_AREA)
.count();
assert_eq!(load_commands, 2, "Only 2 valid pixels should be drawn");
}
#[test]
fn test_area_img_info_at_display_edges() {
let mock = MockInterface::new();
let driver = IT8951::<_, origin::OriginTopLeft, Off>::new(mock, Config::default());
let mut driver = driver.into_state::<Run>();
driver.dev_info = Some(DevInfo {
panel_width: 1872,
panel_height: 1404,
memory_address: 0x001236E0,
firmware_version: String::from("test"),
lut_version: String::from("test"),
});
let area = AreaImgInfo {
area_x: 0,
area_y: 0,
area_w: 10,
area_h: 10,
};
assert!(driver.display_area(&area, WaveformMode::GL16).is_ok());
let area = AreaImgInfo {
area_x: 1862,
area_y: 1394,
area_w: 10,
area_h: 10,
};
assert!(driver.display_area(&area, WaveformMode::GL16).is_ok());
}
}