#![cfg_attr(feature = "doc-images",
cfg_attr(all(),
doc = ::embed_doc_image::embed_image!("latch-circuit", "images/latch-circuit.png")))]
#![cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile with feature `doc-images` and Rust version >= 1.54 \
to enable."
)]
use core::convert::Infallible;
use super::Color;
use crate::{
map_row_index, slot_addresses, BcmLenReps, FrameBuffer, FrameBufferOperations,
MutableFrameBuffer, BCM_SEQUENCE_CAPACITY, INTER_ROW_BLANK, LEAD_BLANK_DELAY,
TRAIL_BLANK_DELAY,
};
use bitfield::bitfield;
use embedded_graphics::pixelcolor::Rgb888;
use embedded_graphics::pixelcolor::RgbColor;
use embedded_graphics::prelude::Point;
use crate::pinmap::{
LATCHED_COLOR0_MASK, LATCHED_COLOR1_MASK, LATCHED_COLOR_MASK, LATCHED_LATCH_BIT,
LATCHED_OE_BIT, LATCHED_PINMAP,
};
#[cfg(not(feature = "invert-oe"))]
const OE_ACTIVE: u8 = LATCHED_OE_BIT;
#[cfg(not(feature = "invert-oe"))]
const OE_BLANK: u8 = 0;
#[cfg(feature = "invert-oe")]
const OE_ACTIVE: u8 = 0;
#[cfg(feature = "invert-oe")]
const OE_BLANK: u8 = LATCHED_OE_BIT;
bitfield! {
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
struct Address(u8);
impl Debug;
pub output_enable, set_output_enable: LATCHED_PINMAP.oe;
pub latch, set_latch: LATCHED_PINMAP.latch;
pub addr, set_addr: LATCHED_PINMAP.addr_msb(), LATCHED_PINMAP.addr_lsb;
}
impl Address {
pub const fn new() -> Self {
Self(0)
}
}
bitfield! {
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
struct Entry(u8);
impl Debug;
pub output_enable, set_output_enable: LATCHED_PINMAP.oe;
pub latch, set_latch: LATCHED_PINMAP.latch;
pub blu2, set_blu2: LATCHED_PINMAP.blu1;
pub grn2, set_grn2: LATCHED_PINMAP.grn1;
pub red2, set_red2: LATCHED_PINMAP.red1;
pub blu1, set_blu1: LATCHED_PINMAP.blu0;
pub grn1, set_grn1: LATCHED_PINMAP.grn0;
pub red1, set_red1: LATCHED_PINMAP.red0;
}
impl Entry {
pub const fn new() -> Self {
Self(0)
}
#[inline]
fn set_color0_bits(&mut self, bits: u8) {
self.0 = (self.0 & !LATCHED_COLOR0_MASK) | (bits & LATCHED_COLOR0_MASK);
}
#[inline]
fn set_color1_bits(&mut self, bits: u8) {
self.0 = (self.0 & !LATCHED_COLOR1_MASK) | ((bits << 3) & LATCHED_COLOR1_MASK);
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct Row<const COLS: usize> {
data: [Entry; COLS],
address: [Address; 4],
gap: [Entry; INTER_ROW_BLANK],
}
#[inline]
const fn map_index(index: usize) -> usize {
#[cfg(feature = "esp32-ordering")]
{
index ^ 2
}
#[cfg(not(feature = "esp32-ordering"))]
{
index
}
}
const fn make_addr_table() -> [[Address; 4]; 32] {
let mut tbl = [[Address::new(); 4]; 32];
let mut addr = 0;
while addr < 32 {
let mut i = 0;
while i < 4 {
let latch = i != 3;
let mapped_i = map_index(i);
let latch_bit = if latch { LATCHED_LATCH_BIT } else { 0u8 };
tbl[addr][mapped_i].0 = OE_BLANK | latch_bit | addr as u8;
i += 1;
}
addr += 1;
}
tbl
}
const ADDR_TABLE: [[Address; 4]; 32] = make_addr_table();
#[allow(clippy::absurd_extreme_comparisons)]
const fn make_data_template<const COLS: usize>() -> [Entry; COLS] {
let mut data = [Entry::new(); COLS];
let mut i = 0;
while i < COLS {
let mapped_i = map_index(i);
data[mapped_i].0 =
if i >= TRAIL_BLANK_DELAY && i < COLS.saturating_sub(LEAD_BLANK_DELAY + 1) {
OE_ACTIVE
} else {
OE_BLANK
};
i += 1;
}
data
}
impl<const COLS: usize> Row<COLS> {
pub const fn new() -> Self {
Self {
address: [Address::new(); 4],
data: [Entry::new(); COLS],
gap: [Entry::new(); INTER_ROW_BLANK],
}
}
#[inline]
pub const fn format(&mut self, addr: u8) {
let src_addr = ADDR_TABLE[addr as usize];
let mut i = 0;
while i < 4 {
self.address[i] = src_addr[i];
i += 1;
}
let data_template = make_data_template::<COLS>();
let mut i = 0;
while i < COLS {
self.data[i] = data_template[i];
i += 1;
}
let mut i = 0;
#[allow(clippy::absurd_extreme_comparisons)]
while i < INTER_ROW_BLANK {
self.gap[i].0 = OE_BLANK;
i += 1;
}
}
#[inline]
pub fn clear_colors(&mut self) {
const COLOR_CLEAR_MASK: u8 = !LATCHED_COLOR_MASK;
for entry in &mut self.data {
entry.0 &= COLOR_CLEAR_MASK;
}
}
#[inline]
pub fn set_color0(&mut self, col: usize, r: bool, g: bool, b: bool) {
let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
let col = map_index(col);
self.data[col].set_color0_bits(bits);
}
#[inline]
pub fn set_color1(&mut self, col: usize, r: bool, g: bool, b: bool) {
let bits = (u8::from(b) << 2) | (u8::from(g) << 1) | u8::from(r);
let col = map_index(col);
self.data[col].set_color1_bits(bits);
}
}
impl<const COLS: usize> Default for Row<COLS> {
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
struct Frame<const ROWS: usize, const COLS: usize, const NROWS: usize> {
rows: [Row<COLS>; NROWS],
}
impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Frame<ROWS, COLS, NROWS> {
pub const fn new() -> Self {
Self {
rows: [Row::new(); NROWS],
}
}
#[inline]
pub const fn format(&mut self) {
let mut slot = 0;
while slot < NROWS {
let (_, addr) = slot_addresses::<NROWS>(slot);
self.rows[slot].format(addr);
slot += 1;
}
}
#[inline]
pub fn clear_colors(&mut self) {
for row in &mut self.rows {
row.clear_colors();
}
}
#[inline]
pub fn set_pixel(&mut self, y: usize, x: usize, red: bool, green: bool, blue: bool) {
let row_idx = map_row_index::<NROWS>(if y < NROWS { y } else { y - NROWS });
let row = &mut self.rows[row_idx];
if y < NROWS {
row.set_color0(x, red, green, blue);
} else {
row.set_color1(x, red, green, blue);
}
}
}
impl<const ROWS: usize, const COLS: usize, const NROWS: usize> Default
for Frame<ROWS, COLS, NROWS>
{
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone)]
#[repr(C)]
#[repr(align(4))]
pub struct DmaFrameBuffer<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> {
frames: [Frame<ROWS, COLS, NROWS>; FRAME_COUNT],
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> Default for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn default() -> Self {
Self::new()
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[must_use]
pub const fn new() -> Self {
#[cfg(feature = "esp32-ordering")]
assert!(
COLS % 4 == 0,
"esp32-ordering feature requires COLS to be divisible by 4"
);
let mut fb = Self {
frames: [Frame::new(); FRAME_COUNT],
};
fb.format();
fb
}
#[must_use]
#[deprecated(since = "0.11.0", note = "use BCM_SEQUENCE instead")]
pub const fn bcm_chunk_count() -> usize {
1
}
#[must_use]
#[deprecated(since = "0.11.0", note = "use BCM_SEQUENCE instead")]
pub const fn bcm_chunk_bytes() -> usize {
core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
}
pub const fn format(&mut self) {
let mut i = 0;
while i < FRAME_COUNT {
self.frames[i].format();
i += 1;
}
}
#[inline]
pub fn erase(&mut self) {
for frame in &mut self.frames {
frame.clear_colors();
}
}
pub fn set_pixel(&mut self, p: Point, color: Color) {
if p.x < 0 || p.y < 0 {
return;
}
self.set_pixel_internal(p.x as usize, p.y as usize, color);
}
#[inline]
fn frames_on(v: u8) -> usize {
(v as usize) >> (8 - BITS)
}
#[inline]
fn set_pixel_internal(&mut self, x: usize, y: usize, color: Rgb888) {
if x >= COLS || y >= ROWS {
return;
}
#[cfg(feature = "skip-black-pixels")]
if color == Rgb888::BLACK {
return;
}
let red_frames = Self::frames_on(color.r());
let green_frames = Self::frames_on(color.g());
let blue_frames = Self::frames_on(color.b());
for (frame_idx, frame) in self.frames.iter_mut().enumerate() {
frame.set_pixel(
y,
x,
frame_idx < red_frames,
frame_idx < green_frames,
frame_idx < blue_frames,
);
}
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> FrameBufferOperations for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[inline]
fn erase(&mut self) {
DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::erase(self);
}
#[inline]
fn set_pixel(&mut self, p: Point, color: Color) {
DmaFrameBuffer::<ROWS, COLS, NROWS, BITS, FRAME_COUNT>::set_pixel(self, p, color);
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> embedded_graphics::prelude::OriginDimensions
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn size(&self) -> embedded_graphics::prelude::Size {
embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> embedded_graphics::draw_target::DrawTarget
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
type Color = Color;
type Error = Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
{
for pixel in pixels {
if pixel.0.x < 0 || pixel.0.y < 0 {
continue;
}
self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
}
Ok(())
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> core::fmt::Debug for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let brightness_step = 1 << (8 - BITS);
f.debug_struct("DmaFrameBuffer")
.field("size", &core::mem::size_of_val(&self.frames))
.field("frame_count", &self.frames.len())
.field("frame_size", &core::mem::size_of_val(&self.frames[0]))
.field("brightness_step", &&brightness_step)
.finish()
}
}
#[cfg(feature = "defmt")]
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> defmt::Format for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn format(&self, f: defmt::Formatter) {
let brightness_step = 1 << (8 - BITS);
defmt::write!(
f,
"DmaFrameBuffer<{}, {}, {}, {}, {}>",
ROWS,
COLS,
NROWS,
BITS,
FRAME_COUNT
);
defmt::write!(f, " size: {}", core::mem::size_of_val(&self.frames));
defmt::write!(
f,
" frame_size: {}",
core::mem::size_of_val(&self.frames[0])
);
defmt::write!(f, " brightness_step: {}", brightness_step);
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> embedded_graphics::prelude::OriginDimensions
for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn size(&self) -> embedded_graphics::prelude::Size {
embedded_graphics::prelude::Size::new(COLS as u32, ROWS as u32)
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> FrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
type Word = u8;
const BCM_SEQUENCE: [BcmLenReps; BCM_SEQUENCE_CAPACITY] = {
let mut seq = [BcmLenReps::ZERO; BCM_SEQUENCE_CAPACITY];
seq[0] = BcmLenReps {
len: core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>(),
reps: 1,
};
seq
};
const BCM_SEQUENCE_LEN: usize = 1;
const BCM_SEQUENCE_COUNT: usize = 1;
fn bcm_segment_ptr(&self, index: usize) -> *const u8 {
assert!(index == 0, "threshold DmaFrameBuffer has only 1 segment");
(&raw const self.frames).cast::<u8>()
}
}
impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> MutableFrameBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
}
#[cfg(test)]
mod tests {
extern crate std;
use std::format;
use std::vec;
use super::*;
use embedded_graphics::pixelcolor::RgbColor;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle};
const TEST_ROWS: usize = 32;
const TEST_COLS: usize = if LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 2 > 64 {
128
} else {
64
};
const TEST_NROWS: usize = TEST_ROWS / 2;
const TEST_BITS: u8 = 3;
const TEST_FRAME_COUNT: usize = (1 << TEST_BITS) - 1;
type TestFrameBuffer =
DmaFrameBuffer<TEST_ROWS, TEST_COLS, TEST_NROWS, TEST_BITS, TEST_FRAME_COUNT>;
fn get_mapped_row(row_idx: usize) -> usize {
map_row_index::<TEST_NROWS>(row_idx)
}
#[test]
fn test_address_construction() {
let addr = Address::new();
assert_eq!(addr.0, 0);
assert_eq!(addr.latch(), false);
assert_eq!(addr.addr(), 0);
}
#[test]
fn test_address_setters() {
let mut addr = Address::new();
addr.set_latch(true);
assert_eq!(addr.latch(), true);
assert_eq!(addr.0 & 0b01000000, 0b01000000);
addr.set_addr(0b11111);
assert_eq!(addr.addr(), 0b11111);
assert_eq!(addr.0 & 0b00011111, 0b00011111);
}
#[test]
fn test_address_bit_isolation() {
let mut addr = Address::new();
addr.set_addr(0b11111);
addr.set_latch(true);
assert_eq!(addr.addr(), 0b11111);
assert_eq!(addr.latch(), true);
}
#[test]
fn test_entry_construction() {
let entry = Entry::new();
assert_eq!(entry.0, 0);
assert_eq!(entry.output_enable(), false);
assert_eq!(entry.latch(), false);
assert_eq!(entry.red1(), false);
assert_eq!(entry.grn1(), false);
assert_eq!(entry.blu1(), false);
assert_eq!(entry.red2(), false);
assert_eq!(entry.grn2(), false);
assert_eq!(entry.blu2(), false);
}
#[test]
fn test_entry_setters() {
let mut entry = Entry::new();
entry.set_output_enable(true);
assert_eq!(entry.output_enable(), true);
assert_eq!(entry.0 & 0b10000000, 0b10000000);
entry.set_latch(true);
assert_eq!(entry.latch(), true);
assert_eq!(entry.0 & 0b01000000, 0b01000000);
entry.set_red1(true);
entry.set_grn1(true);
entry.set_blu1(true);
assert_eq!(entry.red1(), true);
assert_eq!(entry.grn1(), true);
assert_eq!(entry.blu1(), true);
assert_eq!(entry.0 & 0b00000111, 0b00000111);
entry.set_red2(true);
entry.set_grn2(true);
entry.set_blu2(true);
assert_eq!(entry.red2(), true);
assert_eq!(entry.grn2(), true);
assert_eq!(entry.blu2(), true);
assert_eq!(entry.0 & 0b00111000, 0b00111000);
}
#[test]
fn test_entry_set_color0() {
let mut entry = Entry::new();
let bits = (u8::from(true) << 2) | (u8::from(false) << 1) | u8::from(true); entry.set_color0_bits(bits);
assert_eq!(entry.red1(), true);
assert_eq!(entry.grn1(), false);
assert_eq!(entry.blu1(), true);
assert_eq!(entry.0 & 0b00000111, 0b00000101); }
#[test]
fn test_entry_set_color1() {
let mut entry = Entry::new();
let bits = (u8::from(true) << 2) | (u8::from(true) << 1) | u8::from(false); entry.set_color1_bits(bits);
assert_eq!(entry.red2(), false);
assert_eq!(entry.grn2(), true);
assert_eq!(entry.blu2(), true);
assert_eq!(entry.0 & 0b00111000, 0b00110000); }
#[test]
fn test_row_construction() {
let row: Row<TEST_COLS> = Row::new();
assert_eq!(row.data.len(), TEST_COLS);
assert_eq!(row.address.len(), 4);
for entry in &row.data {
assert_eq!(entry.0, 0);
}
for addr in &row.address {
assert_eq!(addr.0, 0);
}
}
#[test]
fn test_row_format() {
let mut row: Row<TEST_COLS> = Row::new();
let test_addr = 5;
row.format(test_addr);
for addr in &row.address {
assert_eq!(addr.addr(), test_addr);
}
let latch_false_count = row.address.iter().filter(|addr| !addr.latch()).count();
assert_eq!(latch_false_count, 1);
for entry in &row.data {
assert_eq!(entry.latch(), false);
}
let oe_active = !cfg!(feature = "invert-oe");
let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
let blank_count = TEST_COLS - active_count;
let oe_blank_count = row
.data
.iter()
.filter(|entry| entry.output_enable() != oe_active)
.count();
assert_eq!(oe_blank_count, blank_count);
}
#[test]
fn test_row_set_color0() {
let mut row: Row<TEST_COLS> = Row::new();
row.set_color0(0, true, false, true);
let mapped_col_0 = map_index(0);
assert_eq!(row.data[mapped_col_0].red1(), true);
assert_eq!(row.data[mapped_col_0].grn1(), false);
assert_eq!(row.data[mapped_col_0].blu1(), true);
row.set_color0(1, false, true, false);
let mapped_col_1 = map_index(1);
assert_eq!(row.data[mapped_col_1].red1(), false);
assert_eq!(row.data[mapped_col_1].grn1(), true);
assert_eq!(row.data[mapped_col_1].blu1(), false);
}
#[test]
fn test_row_set_color1() {
let mut row: Row<TEST_COLS> = Row::new();
row.set_color1(0, true, true, false);
let mapped_col_0 = map_index(0);
assert_eq!(row.data[mapped_col_0].red2(), true);
assert_eq!(row.data[mapped_col_0].grn2(), true);
assert_eq!(row.data[mapped_col_0].blu2(), false);
}
#[test]
fn test_frame_construction() {
let frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
assert_eq!(frame.rows.len(), TEST_NROWS);
}
#[test]
fn test_frame_format() {
let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
frame.format();
for (slot, row) in frame.rows.iter().enumerate() {
let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
for address in &row.address {
assert_eq!(address.addr(), addr);
}
}
}
#[test]
fn test_frame_set_pixel() {
let mut frame: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
frame.set_pixel(5, 10, true, false, true);
let mapped_col_10 = map_index(10);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
true
);
frame.set_pixel(TEST_NROWS + 5, 15, false, true, false);
let mapped_col_15 = map_index(15);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_15].red2(),
false
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_15].grn2(),
true
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_15].blu2(),
false
);
}
#[test]
#[cfg(feature = "reverse-row-order")]
fn test_reverse_row_order() {
let mut fb = TestFrameBuffer::new();
let first = &fb.frames[0].rows[0];
for address in &first.address {
assert_eq!(address.addr(), (TEST_NROWS - 1) as u8);
}
let last = &fb.frames[0].rows[TEST_NROWS - 1];
for address in &last.address {
assert_eq!(address.addr(), 0);
}
fb.set_pixel(Point::new(3, 0), Color::RED);
let col3 = map_index(3);
assert!(fb.frames[0].rows[TEST_NROWS - 1].data[col3].red1());
assert!(!fb.frames[0].rows[0].data[col3].red1());
}
#[test]
fn test_row_default() {
let row1: Row<TEST_COLS> = Row::new();
let row2: Row<TEST_COLS> = Row::default();
assert_eq!(row1, row2);
assert_eq!(row1.data.len(), row2.data.len());
assert_eq!(row1.address.len(), row2.address.len());
for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
assert_eq!(entry1.0, entry2.0);
assert_eq!(entry1.0, 0);
}
for (addr1, addr2) in row1.address.iter().zip(row2.address.iter()) {
assert_eq!(addr1.0, addr2.0);
assert_eq!(addr1.0, 0);
}
}
#[test]
fn test_frame_default() {
let frame1: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::new();
let frame2: Frame<TEST_ROWS, TEST_COLS, TEST_NROWS> = Frame::default();
assert_eq!(frame1.rows.len(), frame2.rows.len());
for (row1, row2) in frame1.rows.iter().zip(frame2.rows.iter()) {
assert_eq!(row1, row2);
for (entry1, entry2) in row1.data.iter().zip(row2.data.iter()) {
assert_eq!(entry1.0, entry2.0);
assert_eq!(entry1.0, 0);
}
for (addr1, addr2) in row1.address.iter().zip(row2.address.iter()) {
assert_eq!(addr1.0, addr2.0);
assert_eq!(addr1.0, 0);
}
}
}
#[test]
fn test_dma_framebuffer_construction() {
let fb = TestFrameBuffer::new();
assert_eq!(fb.frames.len(), TEST_FRAME_COUNT);
}
#[test]
#[allow(deprecated)]
fn test_bcm_chunk_info() {
let expected_size =
core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
assert_eq!(TestFrameBuffer::bcm_chunk_bytes(), expected_size);
assert_eq!(TestFrameBuffer::bcm_chunk_count(), 1);
}
#[test]
fn test_dma_framebuffer_format() {
let mut fb = TestFrameBuffer {
frames: [Frame::new(); TEST_FRAME_COUNT],
};
fb.format();
for frame in &fb.frames {
for (slot, row) in frame.rows.iter().enumerate() {
let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
for address in &row.address {
assert_eq!(address.addr(), addr);
}
}
}
}
#[test]
fn test_dma_framebuffer_set_pixel_bounds() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel(Point::new(-1, 5), Color::RED);
fb.set_pixel(Point::new(5, -1), Color::RED);
fb.set_pixel(Point::new(TEST_COLS as i32, 5), Color::RED);
fb.set_pixel(Point::new(5, TEST_ROWS as i32), Color::RED);
}
#[test]
fn test_dma_framebuffer_set_pixel_internal() {
let mut fb = TestFrameBuffer::new();
let red_color = Rgb888::new(255, 0, 0);
fb.set_pixel_internal(10, 5, red_color);
for frame in &fb.frames {
let mapped_col_10 = map_index(10);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
false
);
}
}
#[test]
fn test_dma_framebuffer_brightness_modulation() {
let mut fb = TestFrameBuffer::new();
let brightness_step = 1 << (8 - TEST_BITS); let test_brightness = brightness_step * 3; let color = Rgb888::new(test_brightness, 0, 0);
fb.set_pixel_internal(0, 0, color);
for (frame_idx, frame) in fb.frames.iter().enumerate() {
let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
let should_be_active = test_brightness >= frame_threshold;
let mapped_col_0 = map_index(0);
assert_eq!(
frame.rows[get_mapped_row(0)].data[mapped_col_0].red1(),
should_be_active
);
}
}
#[test]
fn test_origin_dimensions() {
let fb = TestFrameBuffer::new();
let size = fb.size();
assert_eq!(size.width, TEST_COLS as u32);
assert_eq!(size.height, TEST_ROWS as u32);
let mut fb = TestFrameBuffer::new();
let fb_ref = &mut fb;
let size = fb_ref.size();
assert_eq!(size.width, TEST_COLS as u32);
assert_eq!(size.height, TEST_ROWS as u32);
}
#[test]
fn test_draw_target() {
let mut fb = TestFrameBuffer::new();
let pixels = vec![
embedded_graphics::Pixel(Point::new(0, 0), Color::RED),
embedded_graphics::Pixel(Point::new(1, 1), Color::GREEN),
embedded_graphics::Pixel(Point::new(2, 2), Color::BLUE),
];
let result = fb.draw_iter(pixels);
assert!(result.is_ok());
}
#[test]
fn test_draw_iter_pixel_verification() {
let mut fb = TestFrameBuffer::new();
let pixels = vec![
embedded_graphics::Pixel(Point::new(5, 2), Color::RED), embedded_graphics::Pixel(Point::new(10, 5), Color::GREEN), embedded_graphics::Pixel(Point::new(15, 8), Color::BLUE), embedded_graphics::Pixel(Point::new(20, 10), Color::WHITE), embedded_graphics::Pixel(Point::new(25, (TEST_NROWS + 3) as i32), Color::RED), embedded_graphics::Pixel(Point::new(30, (TEST_NROWS + 7) as i32), Color::GREEN), embedded_graphics::Pixel(Point::new(35, (TEST_NROWS + 12) as i32), Color::BLUE), embedded_graphics::Pixel(Point::new(40, 1), Color::BLACK), embedded_graphics::Pixel(Point::new(45, 3), Rgb888::new(16, 16, 16)), ];
let result = fb.draw_iter(pixels);
assert!(result.is_ok());
let first_frame = &fb.frames[0];
let brightness_step = 1 << (8 - TEST_BITS); let first_frame_threshold = brightness_step;
let col_idx = map_index(5);
assert_eq!(
first_frame.rows[get_mapped_row(2)].data[col_idx].red1(),
Color::RED.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(2)].data[col_idx].grn1(),
Color::RED.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(2)].data[col_idx].blu1(),
Color::RED.b() >= first_frame_threshold
);
let col_idx = map_index(10);
assert_eq!(
first_frame.rows[get_mapped_row(5)].data[col_idx].red1(),
Color::GREEN.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(5)].data[col_idx].grn1(),
Color::GREEN.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(5)].data[col_idx].blu1(),
Color::GREEN.b() >= first_frame_threshold
);
let col_idx = map_index(15);
assert_eq!(
first_frame.rows[get_mapped_row(8)].data[col_idx].red1(),
Color::BLUE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(8)].data[col_idx].grn1(),
Color::BLUE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(8)].data[col_idx].blu1(),
Color::BLUE.b() >= first_frame_threshold
);
let col_idx = map_index(20);
assert_eq!(
first_frame.rows[get_mapped_row(10)].data[col_idx].red1(),
Color::WHITE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(10)].data[col_idx].grn1(),
Color::WHITE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(10)].data[col_idx].blu1(),
Color::WHITE.b() >= first_frame_threshold
);
let col_idx = map_index(25);
assert_eq!(
first_frame.rows[get_mapped_row(3)].data[col_idx].red2(),
Color::RED.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(3)].data[col_idx].grn2(),
Color::RED.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(3)].data[col_idx].blu2(),
Color::RED.b() >= first_frame_threshold
);
let col_idx = map_index(30);
assert_eq!(
first_frame.rows[get_mapped_row(7)].data[col_idx].red2(),
Color::GREEN.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(7)].data[col_idx].grn2(),
Color::GREEN.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(7)].data[col_idx].blu2(),
Color::GREEN.b() >= first_frame_threshold
);
let col_idx = map_index(35);
assert_eq!(
first_frame.rows[get_mapped_row(12)].data[col_idx].red2(),
Color::BLUE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(12)].data[col_idx].grn2(),
Color::BLUE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[get_mapped_row(12)].data[col_idx].blu2(),
Color::BLUE.b() >= first_frame_threshold
);
let col_idx = map_index(40);
assert_eq!(
first_frame.rows[get_mapped_row(1)].data[col_idx].red1(),
false
);
assert_eq!(
first_frame.rows[get_mapped_row(1)].data[col_idx].grn1(),
false
);
assert_eq!(
first_frame.rows[get_mapped_row(1)].data[col_idx].blu1(),
false
);
let col_idx = map_index(45);
assert_eq!(
first_frame.rows[get_mapped_row(3)].data[col_idx].red1(),
16 >= first_frame_threshold
); assert_eq!(
first_frame.rows[get_mapped_row(3)].data[col_idx].grn1(),
16 >= first_frame_threshold
); assert_eq!(
first_frame.rows[get_mapped_row(3)].data[col_idx].blu1(),
16 >= first_frame_threshold
); }
#[test]
fn test_embedded_graphics_integration() {
let mut fb = TestFrameBuffer::new();
let result = Rectangle::new(Point::new(5, 5), Size::new(10, 8))
.into_styled(PrimitiveStyle::with_fill(Color::RED))
.draw(&mut fb);
assert!(result.is_ok());
let result = Circle::new(Point::new(30, 15), 8)
.into_styled(PrimitiveStyle::with_fill(Color::BLUE))
.draw(&mut fb);
assert!(result.is_ok());
}
#[test]
fn test_debug_formatting() {
let fb = TestFrameBuffer::new();
let debug_string = format!("{:?}", fb);
assert!(debug_string.contains("DmaFrameBuffer"));
assert!(debug_string.contains("frame_count"));
assert!(debug_string.contains("frame_size"));
assert!(debug_string.contains("brightness_step"));
}
#[test]
fn test_default_implementation() {
let fb1 = TestFrameBuffer::new();
let fb2 = TestFrameBuffer::default();
assert_eq!(fb1.frames.len(), fb2.frames.len());
}
#[cfg(feature = "esp32-ordering")]
#[test]
fn test_esp32_mapping() {
assert_eq!(map_index(0), 2);
assert_eq!(map_index(1), 3);
assert_eq!(map_index(2), 0);
assert_eq!(map_index(3), 1);
assert_eq!(map_index(4), 6); assert_eq!(map_index(5), 7); }
#[test]
fn test_memory_alignment() {
let fb = TestFrameBuffer::new();
let ptr = &fb as *const _ as usize;
assert_eq!(ptr % 4, 0);
}
#[test]
fn test_color_values() {
let mut fb = TestFrameBuffer::new();
let colors = [
(Color::RED, (255, 0, 0)),
(Color::GREEN, (0, 255, 0)),
(Color::BLUE, (0, 0, 255)),
(Color::WHITE, (255, 255, 255)),
(Color::BLACK, (0, 0, 0)),
];
for (i, (color, (r, g, b))) in colors.iter().enumerate() {
fb.set_pixel(Point::new(i as i32, 0), *color);
assert_eq!(color.r(), *r);
assert_eq!(color.g(), *g);
assert_eq!(color.b(), *b);
}
}
#[test]
fn test_bits_assertion() {
assert!(TEST_BITS <= 8);
}
#[test]
#[cfg(feature = "skip-black-pixels")]
fn test_skip_black_pixels_enabled() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
let mapped_col_10 = map_index(10);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
false
);
fb.set_pixel_internal(10, 5, Color::BLACK);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
false
);
}
#[test]
#[cfg(not(feature = "skip-black-pixels"))]
fn test_skip_black_pixels_disabled() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
let mapped_col_10 = map_index(10);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
false
);
fb.set_pixel_internal(10, 5, Color::BLACK);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
false
);
}
#[test]
fn test_bcm_frame_overwrite() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::WHITE);
let mapped_col_10 = map_index(10);
for frame in fb.frames.iter() {
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
true
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
true
);
}
let half_white = embedded_graphics::pixelcolor::Rgb888::new(128, 128, 128);
fb.set_pixel_internal(10, 5, half_white);
let brightness_step = 1 << (8 - TEST_BITS); for (frame_idx, frame) in fb.frames.iter().enumerate() {
let frame_threshold = (frame_idx as u8 + 1) * brightness_step;
let should_be_active = 128 >= frame_threshold;
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].red1(),
should_be_active
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
should_be_active
);
assert_eq!(
frame.rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
should_be_active
);
}
for frame_idx in 0..4 {
assert_eq!(
fb.frames[frame_idx].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
}
for frame_idx in 4..TEST_FRAME_COUNT {
assert_eq!(
fb.frames[frame_idx].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
false
);
}
}
#[test]
fn test_new_auto_formats() {
let fb = TestFrameBuffer::new();
for frame in &fb.frames {
for (slot, row) in frame.rows.iter().enumerate() {
let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
for address in &row.address {
assert_eq!(address.addr(), addr);
}
}
}
}
#[test]
fn test_erase() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
fb.set_pixel_internal(20, 10, Color::GREEN);
let mapped_col_10 = map_index(10);
let mapped_col_20 = map_index(20);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
true
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].grn1(),
true
);
fb.erase();
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].red1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].grn1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mapped_col_10].blu1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].red1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].grn1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(10)].data[mapped_col_20].blu1(),
false
);
let oe_active = !cfg!(feature = "invert-oe");
let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
let blank_count = TEST_COLS - active_count;
for frame in &fb.frames {
for (slot, row) in frame.rows.iter().enumerate() {
let (_, addr) = slot_addresses::<TEST_NROWS>(slot);
for address in &row.address {
assert_eq!(address.addr(), addr);
}
let oe_blank_count = row
.data
.iter()
.filter(|entry| entry.output_enable() != oe_active)
.count();
assert_eq!(oe_blank_count, blank_count);
}
}
}
#[test]
fn test_row_clear_colors() {
let mut row: Row<TEST_COLS> = Row::new();
row.format(5);
row.set_color0(0, true, false, true);
row.set_color1(1, false, true, false);
let mapped_col_0 = map_index(0);
let mapped_col_1 = map_index(1);
assert_eq!(row.data[mapped_col_0].red1(), true);
assert_eq!(row.data[mapped_col_0].blu1(), true);
assert_eq!(row.data[mapped_col_1].grn2(), true);
let original_oe_0 = row.data[mapped_col_0].output_enable();
let original_latch_0 = row.data[mapped_col_0].latch();
let original_oe_1 = row.data[mapped_col_1].output_enable();
let original_latch_1 = row.data[mapped_col_1].latch();
row.clear_colors();
assert_eq!(row.data[mapped_col_0].red1(), false);
assert_eq!(row.data[mapped_col_0].grn1(), false);
assert_eq!(row.data[mapped_col_0].blu1(), false);
assert_eq!(row.data[mapped_col_1].red2(), false);
assert_eq!(row.data[mapped_col_1].grn2(), false);
assert_eq!(row.data[mapped_col_1].blu2(), false);
assert_eq!(row.data[mapped_col_0].output_enable(), original_oe_0);
assert_eq!(row.data[mapped_col_0].latch(), original_latch_0);
assert_eq!(row.data[mapped_col_1].output_enable(), original_oe_1);
assert_eq!(row.data[mapped_col_1].latch(), original_latch_1);
}
#[test]
fn test_make_addr_table_function() {
let table = make_addr_table();
assert_eq!(table.len(), 32);
let addr_0 = &table[0];
assert_eq!(addr_0.len(), 4);
let latch_false_count = addr_0.iter().filter(|addr| !addr.latch()).count();
assert_eq!(latch_false_count, 1);
for addr in addr_0 {
assert_eq!(addr.addr(), 0);
}
let addr_31 = &table[31];
let latch_false_count = addr_31.iter().filter(|addr| !addr.latch()).count();
assert_eq!(latch_false_count, 1);
for addr in addr_31 {
assert_eq!(addr.addr(), 31);
}
}
#[test]
fn test_make_data_template_function() {
let template = make_data_template::<TEST_COLS>();
assert_eq!(template.len(), TEST_COLS);
for entry in &template {
assert_eq!(entry.latch(), false);
}
let oe_active = !cfg!(feature = "invert-oe");
let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
let blank_count = TEST_COLS - active_count;
let oe_blank_count = template
.iter()
.filter(|entry| entry.output_enable() != oe_active)
.count();
assert_eq!(oe_blank_count, blank_count);
let small_template = make_data_template::<4>();
assert_eq!(small_template.len(), 4);
let small_active = 4_usize.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
let small_blank = 4 - small_active;
let oe_blank_count = small_template
.iter()
.filter(|entry| entry.output_enable() != oe_active)
.count();
assert_eq!(oe_blank_count, small_blank);
#[cfg(not(feature = "esp32-ordering"))]
{
let single_template = make_data_template::<1>();
assert_eq!(single_template.len(), 1);
assert_eq!(single_template[0].output_enable(), !oe_active);
assert_eq!(single_template[0].latch(), false);
}
}
#[test]
fn test_addr_table_correctness() {
for addr in 0..32 {
let mut expected_addresses = [Address::new(); 4];
for i in 0..4 {
let latch = !matches!(i, 3);
#[cfg(feature = "esp32-ordering")]
let mapped_i = map_index(i);
#[cfg(not(feature = "esp32-ordering"))]
let mapped_i = i;
expected_addresses[mapped_i].set_latch(latch);
expected_addresses[mapped_i].set_addr(addr);
expected_addresses[mapped_i].0 |= OE_BLANK;
}
let table_addresses = &ADDR_TABLE[addr as usize];
for i in 0..4 {
assert_eq!(table_addresses[i].0, expected_addresses[i].0);
}
}
}
const CHAR_W: i32 = 6;
const CHAR_H: i32 = 10;
fn verify_glyph_at(fb: &mut TestFrameBuffer, origin: Point) {
use embedded_graphics::mock_display::MockDisplay;
use embedded_graphics::mono_font::ascii::FONT_6X10;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::text::{Baseline, Text};
let style = MonoTextStyle::new(&FONT_6X10, Color::WHITE);
Text::with_baseline("A", origin, style, Baseline::Top)
.draw(fb)
.unwrap();
let mut reference: MockDisplay<Color> = MockDisplay::new();
Text::with_baseline("A", Point::zero(), style, Baseline::Top)
.draw(&mut reference)
.unwrap();
for dy in 0..CHAR_H {
for dx in 0..CHAR_W {
let expected_on = reference
.get_pixel(Point::new(dx, dy))
.unwrap_or(Color::BLACK)
!= Color::BLACK;
let gx = (origin.x + dx) as usize;
let gy = (origin.y + dy) as usize;
let frame0 = &fb.frames[0];
let e = if gy < TEST_NROWS {
&frame0.rows[get_mapped_row(gy)].data[map_index(gx)]
} else {
&frame0.rows[get_mapped_row(gy - TEST_NROWS)].data[map_index(gx)]
};
let (r, g, b) = if gy >= TEST_NROWS {
(e.red2(), e.grn2(), e.blu2())
} else {
(e.red1(), e.grn1(), e.blu1())
};
if expected_on {
assert!(r && g && b);
} else {
assert!(!r && !g && !b);
}
}
}
}
#[test]
fn test_draw_char_corners() {
let upper_left = Point::new(0, 0);
let lower_right = Point::new(TEST_COLS as i32 - CHAR_W, TEST_ROWS as i32 - CHAR_H);
let mut fb = TestFrameBuffer::new();
verify_glyph_at(&mut fb, upper_left);
verify_glyph_at(&mut fb, lower_right);
}
#[test]
fn test_framebuffer_operations_trait_erase() {
let mut fb = TestFrameBuffer::new();
fb.set_pixel_internal(10, 5, Color::RED);
fb.set_pixel_internal(20, 10, Color::GREEN);
<TestFrameBuffer as FrameBufferOperations>::erase(&mut fb);
let mc10 = map_index(10);
let mc20 = map_index(20);
assert_eq!(
fb.frames[0].rows[get_mapped_row(5)].data[mc10].red1(),
false
);
assert_eq!(
fb.frames[0].rows[get_mapped_row(10)].data[mc20].grn1(),
false
);
let oe_active = !cfg!(feature = "invert-oe");
let active_count = TEST_COLS.saturating_sub(LEAD_BLANK_DELAY + TRAIL_BLANK_DELAY + 1);
let blank_count = TEST_COLS - active_count;
let row0 = &fb.frames[0].rows[get_mapped_row(0)];
let oe_blank_count = row0
.data
.iter()
.filter(|entry| entry.output_enable() != oe_active)
.count();
assert_eq!(oe_blank_count, blank_count);
assert!(row0.data.iter().all(|e| !e.latch()));
for (i, addr) in row0.address.iter().enumerate() {
assert_eq!(addr.0, ADDR_TABLE[0][i].0);
}
}
#[test]
fn test_framebuffer_operations_trait_set_pixel() {
let mut fb = TestFrameBuffer::new();
<TestFrameBuffer as FrameBufferOperations>::set_pixel(
&mut fb,
Point::new(8, 3),
Color::BLUE,
);
let idx = map_index(8);
assert_eq!(fb.frames[0].rows[get_mapped_row(3)].data[idx].blu1(), true);
assert_eq!(fb.frames[0].rows[get_mapped_row(3)].data[idx].red1(), false);
assert_eq!(fb.frames[0].rows[get_mapped_row(3)].data[idx].grn1(), false);
}
#[test]
fn test_blanking_delay() {
let mut row: Row<TEST_COLS> = Row::new();
row.format(5);
let oe_active = !cfg!(feature = "invert-oe");
if TRAIL_BLANK_DELAY > 0 {
let trail_blank_idx = map_index(TRAIL_BLANK_DELAY - 1);
assert_eq!(row.data[trail_blank_idx].output_enable(), !oe_active);
}
let first_active_idx = map_index(TRAIL_BLANK_DELAY);
assert_eq!(row.data[first_active_idx].output_enable(), oe_active);
let last_active_idx = map_index(TEST_COLS - LEAD_BLANK_DELAY - 2);
assert_eq!(row.data[last_active_idx].output_enable(), oe_active);
let lead_blank_idx = map_index(TEST_COLS - LEAD_BLANK_DELAY - 1);
assert_eq!(row.data[lead_blank_idx].output_enable(), !oe_active);
let last_pixel_idx = map_index(TEST_COLS - 1);
assert_eq!(row.data[last_pixel_idx].output_enable(), !oe_active);
}
static STATIC_FB: TestFrameBuffer = TestFrameBuffer::new();
#[test]
fn test_static_construction_is_formatted() {
let runtime_fb = TestFrameBuffer::new();
for (fi, frame) in STATIC_FB.frames.iter().enumerate() {
for (ri, row) in frame.rows.iter().enumerate() {
assert_eq!(
row.data, runtime_fb.frames[fi].rows[ri].data,
"static vs runtime data mismatch at frame {fi}, row {ri}"
);
assert_eq!(
row.address, runtime_fb.frames[fi].rows[ri].address,
"static vs runtime address mismatch at frame {fi}, row {ri}"
);
assert_eq!(
row.gap, runtime_fb.frames[fi].rows[ri].gap,
"static vs runtime gap mismatch at frame {fi}, row {ri}"
);
}
}
}
#[test]
fn test_format_reinitializes_at_runtime() {
let mut fb = TestFrameBuffer::new();
fb.erase();
fb.format();
for (fi, frame) in fb.frames.iter().enumerate() {
for (ri, row) in frame.rows.iter().enumerate() {
assert_eq!(
row.data, STATIC_FB.frames[fi].rows[ri].data,
"re-formatted vs static mismatch at frame {fi}, row {ri}"
);
}
}
}
#[test]
fn bcm_segment_count_is_one() {
use crate::FrameBuffer;
let fb = TestFrameBuffer::new();
assert_eq!(fb.bcm_segment_count(), 1);
}
#[test]
fn bcm_segment_covers_entire_buffer() {
use crate::FrameBuffer;
let fb = TestFrameBuffer::new();
let seg = fb.bcm_segment(0);
let expected_ptr = (&raw const fb.frames).cast::<u8>();
let expected_len = core::mem::size_of_val(&fb.frames);
assert_eq!(seg.ptr, expected_ptr);
assert_eq!(seg.len, expected_len);
assert_eq!(seg.reps, 1);
}
#[test]
#[should_panic(expected = "out of range")]
fn bcm_segment_panics_for_invalid_index() {
use crate::FrameBuffer;
let fb = TestFrameBuffer::new();
let _ = fb.bcm_segment(1);
}
#[test]
fn bcm_sequence_matches_runtime_segments() {
use crate::FrameBuffer;
let fb = TestFrameBuffer::new();
assert_eq!(
TestFrameBuffer::BCM_SEGMENT_COUNT,
TestFrameBuffer::BCM_SEQUENCE_LEN * TestFrameBuffer::BCM_SEQUENCE_COUNT
);
assert_eq!(fb.bcm_segment_count(), TestFrameBuffer::BCM_SEGMENT_COUNT);
for i in 0..TestFrameBuffer::BCM_SEGMENT_COUNT {
let entry = TestFrameBuffer::BCM_SEQUENCE[i % TestFrameBuffer::BCM_SEQUENCE_LEN];
let seg = fb.bcm_segment(i);
assert_eq!((seg.len, seg.reps), (entry.len, entry.reps), "segment {i}");
assert!(!seg.ptr.is_null(), "segment {i} has null pointer");
}
for entry in &TestFrameBuffer::BCM_SEQUENCE[TestFrameBuffer::BCM_SEQUENCE_LEN..] {
assert_eq!(*entry, crate::BcmLenReps::ZERO, "padding must be zero");
}
}
}