#![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::FrameBufferOperations;
use bitfield::bitfield;
#[cfg(not(feature = "esp-hal-dma"))]
use embedded_dma::ReadBuffer;
use embedded_graphics::pixelcolor::Rgb888;
use embedded_graphics::pixelcolor::RgbColor;
use embedded_graphics::prelude::Point;
#[cfg(feature = "esp-hal-dma")]
use esp_hal::dma::ReadBuffer;
bitfield! {
#[derive(Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
struct Address(u8);
impl Debug;
pub output_enable, set_output_enable: 7;
pub latch, set_latch: 6;
pub addr, set_addr: 4, 0;
}
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: 7;
pub latch, set_latch: 6;
pub blu2, set_blu2: 5;
pub grn2, set_grn2: 4;
pub red2, set_red2: 3;
pub blu1, set_blu1: 2;
pub grn1, set_grn1: 1;
pub red1, set_red1: 0;
}
impl Entry {
pub const fn new() -> Self {
Self(0)
}
const COLOR0_MASK: u8 = 0b0000_0111; const COLOR1_MASK: u8 = 0b0011_1000;
#[inline]
fn set_color0_bits(&mut self, bits: u8) {
self.0 = (self.0 & !Self::COLOR0_MASK) | (bits & Self::COLOR0_MASK);
}
#[inline]
fn set_color1_bits(&mut self, bits: u8) {
self.0 = (self.0 & !Self::COLOR1_MASK) | ((bits << 3) & Self::COLOR1_MASK);
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct Row<const COLS: usize> {
data: [Entry; COLS],
address: [Address; 4],
}
#[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 { 1u8 << 6 } else { 0u8 };
tbl[addr][mapped_i].0 = latch_bit | addr as u8;
i += 1;
}
addr += 1;
}
tbl
}
static ADDR_TABLE: [[Address; 4]; 32] = make_addr_table();
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 == COLS - 1 { 0 } else { 0b1000_0000 }; i += 1;
}
data
}
impl<const COLS: usize> Row<COLS> {
pub const fn new() -> Self {
Self {
address: [Address::new(); 4],
data: [Entry::new(); COLS],
}
}
#[inline]
pub fn format(&mut self, addr: u8) {
self.address.copy_from_slice(&ADDR_TABLE[addr as usize]);
let data_template = make_data_template::<COLS>();
self.data.copy_from_slice(&data_template);
}
#[inline]
pub fn clear_colors(&mut self) {
const COLOR_CLEAR_MASK: u8 = !0b0011_1111;
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 fn format(&mut self) {
for (addr, row) in self.rows.iter_mut().enumerate() {
row.format(addr as u8);
}
}
#[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 = &mut self.rows[if y < NROWS { y } else { y - NROWS }];
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 fn new() -> Self {
let mut fb = Self {
frames: [Frame::new(); FRAME_COUNT],
};
fb.format();
fb
}
#[cfg(feature = "esp-hal-dma")]
#[must_use]
pub const fn dma_buffer_size_bytes() -> usize {
core::mem::size_of::<[Frame<ROWS, COLS, NROWS>; FRAME_COUNT]>()
}
pub fn format(&mut self) {
for frame in &mut self.frames {
frame.format();
}
}
#[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<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
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 {
self.set_pixel_internal(pixel.0.x as usize, pixel.0.y as usize, pixel.1);
}
Ok(())
}
}
unsafe impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> ReadBuffer for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[cfg(not(feature = "esp-hal-dma"))]
type Word = u8;
unsafe fn read_buffer(&self) -> (*const u8, usize) {
let ptr = (&raw const self.frames).cast::<u8>();
let len = core::mem::size_of_val(&self.frames);
(ptr, len)
}
}
unsafe impl<
const ROWS: usize,
const COLS: usize,
const NROWS: usize,
const BITS: u8,
const FRAME_COUNT: usize,
> ReadBuffer for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
#[cfg(not(feature = "esp-hal-dma"))]
type Word = u8;
unsafe fn read_buffer(&self) -> (*const u8, usize) {
let ptr = (&raw const self.frames).cast::<u8>();
let len = core::mem::size_of_val(&self.frames);
(ptr, len)
}
}
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,
> super::FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
for DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn get_word_size(&self) -> super::WordSize {
super::WordSize::Eight
}
}
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,
> super::FrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
for &mut DmaFrameBuffer<ROWS, COLS, NROWS, BITS, FRAME_COUNT>
{
fn get_word_size(&self) -> super::WordSize {
super::WordSize::Eight
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::format;
use std::vec;
use super::*;
use crate::{FrameBuffer, WordSize};
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 = 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>;
#[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_false_count = row
.data
.iter()
.filter(|entry| !entry.output_enable())
.count();
assert_eq!(oe_false_count, 1);
}
#[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 (addr, row) in frame.rows.iter().enumerate() {
for address in &row.address {
assert_eq!(address.addr() as usize, 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[5].data[mapped_col_10].red1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(frame.rows[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[5].data[mapped_col_15].red2(), false);
assert_eq!(frame.rows[5].data[mapped_col_15].grn2(), true);
assert_eq!(frame.rows[5].data[mapped_col_15].blu2(), false);
}
#[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]
#[cfg(feature = "esp-hal-dma")]
fn test_dma_framebuffer_dma_buffer_size() {
let expected_size =
core::mem::size_of::<[Frame<TEST_ROWS, TEST_COLS, TEST_NROWS>; TEST_FRAME_COUNT]>();
assert_eq!(TestFrameBuffer::dma_buffer_size_bytes(), expected_size);
}
#[test]
fn test_dma_framebuffer_format() {
let mut fb = TestFrameBuffer {
frames: [Frame::new(); TEST_FRAME_COUNT],
};
fb.format();
for frame in &fb.frames {
for (addr, row) in frame.rows.iter().enumerate() {
for address in &row.address {
assert_eq!(address.addr() as usize, 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[5].data[mapped_col_10].red1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(frame.rows[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[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[2].data[col_idx].red1(),
Color::RED.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[2].data[col_idx].grn1(),
Color::RED.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[2].data[col_idx].blu1(),
Color::RED.b() >= first_frame_threshold
);
let col_idx = map_index(10);
assert_eq!(
first_frame.rows[5].data[col_idx].red1(),
Color::GREEN.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[5].data[col_idx].grn1(),
Color::GREEN.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[5].data[col_idx].blu1(),
Color::GREEN.b() >= first_frame_threshold
);
let col_idx = map_index(15);
assert_eq!(
first_frame.rows[8].data[col_idx].red1(),
Color::BLUE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[8].data[col_idx].grn1(),
Color::BLUE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[8].data[col_idx].blu1(),
Color::BLUE.b() >= first_frame_threshold
);
let col_idx = map_index(20);
assert_eq!(
first_frame.rows[10].data[col_idx].red1(),
Color::WHITE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[10].data[col_idx].grn1(),
Color::WHITE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[10].data[col_idx].blu1(),
Color::WHITE.b() >= first_frame_threshold
);
let col_idx = map_index(25);
assert_eq!(
first_frame.rows[3].data[col_idx].red2(),
Color::RED.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[3].data[col_idx].grn2(),
Color::RED.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[3].data[col_idx].blu2(),
Color::RED.b() >= first_frame_threshold
);
let col_idx = map_index(30);
assert_eq!(
first_frame.rows[7].data[col_idx].red2(),
Color::GREEN.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[7].data[col_idx].grn2(),
Color::GREEN.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[7].data[col_idx].blu2(),
Color::GREEN.b() >= first_frame_threshold
);
let col_idx = map_index(35);
assert_eq!(
first_frame.rows[12].data[col_idx].red2(),
Color::BLUE.r() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[12].data[col_idx].grn2(),
Color::BLUE.g() >= first_frame_threshold
);
assert_eq!(
first_frame.rows[12].data[col_idx].blu2(),
Color::BLUE.b() >= first_frame_threshold
);
let col_idx = map_index(40);
assert_eq!(first_frame.rows[1].data[col_idx].red1(), false);
assert_eq!(first_frame.rows[1].data[col_idx].grn1(), false);
assert_eq!(first_frame.rows[1].data[col_idx].blu1(), false);
let col_idx = map_index(45);
assert_eq!(
first_frame.rows[3].data[col_idx].red1(),
16 >= first_frame_threshold
); assert_eq!(
first_frame.rows[3].data[col_idx].grn1(),
16 >= first_frame_threshold
); assert_eq!(
first_frame.rows[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_read_buffer_implementation() {
let fb = TestFrameBuffer::new();
unsafe {
let (ptr, len) = fb.read_buffer();
assert!(!ptr.is_null());
assert_eq!(len, core::mem::size_of_val(&fb.frames));
}
let mut fb = TestFrameBuffer::new();
let fb_ref = &mut fb;
unsafe {
let (ptr, len) = fb_ref.read_buffer();
assert!(!ptr.is_null());
assert_eq!(len, core::mem::size_of_val(&fb.frames));
}
}
#[test]
fn test_framebuffer_trait() {
let fb = TestFrameBuffer::new();
assert_eq!(fb.get_word_size(), WordSize::Eight);
let mut fb = TestFrameBuffer::new();
let fb_ref = &mut fb;
assert_eq!(fb_ref.get_word_size(), WordSize::Eight);
}
#[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[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
fb.set_pixel_internal(10, 5, Color::BLACK);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[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[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
fb.set_pixel_internal(10, 5, Color::BLACK);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[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[5].data[mapped_col_10].red1(), true);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), true);
assert_eq!(frame.rows[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[5].data[mapped_col_10].red1(), should_be_active);
assert_eq!(frame.rows[5].data[mapped_col_10].grn1(), should_be_active);
assert_eq!(frame.rows[5].data[mapped_col_10].blu1(), should_be_active);
}
for frame_idx in 0..4 {
assert_eq!(
fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
true
);
}
for frame_idx in 4..TEST_FRAME_COUNT {
assert_eq!(
fb.frames[frame_idx].rows[5].data[mapped_col_10].red1(),
false
);
}
}
#[test]
fn test_new_auto_formats() {
let fb = TestFrameBuffer::new();
for frame in &fb.frames {
for (addr, row) in frame.rows.iter().enumerate() {
for address in &row.address {
assert_eq!(address.addr() as usize, 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[5].data[mapped_col_10].red1(), true);
assert_eq!(fb.frames[0].rows[10].data[mapped_col_20].grn1(), true);
fb.erase();
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].red1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].grn1(), false);
assert_eq!(fb.frames[0].rows[5].data[mapped_col_10].blu1(), false);
assert_eq!(fb.frames[0].rows[10].data[mapped_col_20].red1(), false);
assert_eq!(fb.frames[0].rows[10].data[mapped_col_20].grn1(), false);
assert_eq!(fb.frames[0].rows[10].data[mapped_col_20].blu1(), false);
for frame in &fb.frames {
for (addr, row) in frame.rows.iter().enumerate() {
for address in &row.address {
assert_eq!(address.addr() as usize, addr);
}
let oe_false_count = row
.data
.iter()
.filter(|entry| !entry.output_enable())
.count();
assert_eq!(oe_false_count, 1);
}
}
}
#[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_false_count = template
.iter()
.filter(|entry| !entry.output_enable())
.count();
assert_eq!(oe_false_count, 1);
let small_template = make_data_template::<4>();
assert_eq!(small_template.len(), 4);
let oe_false_count = small_template
.iter()
.filter(|entry| !entry.output_enable())
.count();
assert_eq!(oe_false_count, 1);
#[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(), false); 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);
}
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[gy].data[map_index(gx)]
} else {
&frame0.rows[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<
TEST_ROWS,
TEST_COLS,
TEST_NROWS,
TEST_BITS,
TEST_FRAME_COUNT,
>>::erase(&mut fb);
let mc10 = map_index(10);
let mc20 = map_index(20);
assert_eq!(fb.frames[0].rows[5].data[mc10].red1(), false);
assert_eq!(fb.frames[0].rows[10].data[mc20].grn1(), false);
let row0 = &fb.frames[0].rows[0];
let oe_false_count = row0
.data
.iter()
.filter(|entry| !entry.output_enable())
.count();
assert_eq!(oe_false_count, 1);
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<
TEST_ROWS,
TEST_COLS,
TEST_NROWS,
TEST_BITS,
TEST_FRAME_COUNT,
>>::set_pixel(&mut fb, Point::new(8, 3), Color::BLUE);
let idx = map_index(8);
assert_eq!(fb.frames[0].rows[3].data[idx].blu1(), true);
assert_eq!(fb.frames[0].rows[3].data[idx].red1(), false);
assert_eq!(fb.frames[0].rows[3].data[idx].grn1(), false);
}
}