Skip to content

Commit

Permalink
Merge pull request #6 from pbert519/optimize_memory_usage
Browse files Browse the repository at this point in the history
Memory Management Improvements
  • Loading branch information
pbert519 authored Jun 19, 2024
2 parents 15d615f + 96c4a00 commit f190511
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 145 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "it8951"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
license = "MIT"
description = "A IT8951 E-Paper driver"
Expand Down
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,29 @@
This crate is mainly developed for the waveshare 7.8" epaper display using spi:
https://www.waveshare.com/wiki/7.8inch_e-Paper_HAT
The driver uses the embedded_hal traits as hardware abstraction layer.
This driver can be used with the embedded graphics trait.
This driver can be used with the embedded graphics trait, currently only supporing Gray4 (16bit grayscale).

## Details
- IT8951 has a image load engine which can convert pixel data before storing it in the local frame buffer
- IT8951 has a image load engine which can convert pixel data before storing it in the local frame buffer.
- It is possible to read and write the memory directly without using the image load engine
- **Important** Data must be always aligned to 16bit words!
- **Important** Data must be always aligned to 16bit words!
- The crates uses the alloc feature to allocate memory on the heap:
- Firmware and LUT version string read from the controller
- Staging buffers to write pixel to the controller. The buffers are allocated as needed, but only one buffer at a time and with up to 1kByte of size.
- When reading controller memory a staging buffer with the size of of the requested data is created.


## TODOs
- Support Gray2 and Gray8 with embedded-graphics
- Support display engine fill area
- Support display engine 1 bit per pixel mode
- Support static buffer allocations

## Changelog

### 0.4.0
- **Public API** `new` expects a `Config` parameter to set timeout and buffer size. Default is implemented with timeouts of 15s and buffer size is 1024 Bytes.
- Buffer data type changed from u16 to u8
- **Public API**: `load_image_area`, `load_image`, and `memory_burst_write` functions are now using u8 as buffer type
- Memory usage is reduced by half (1kByte max. instead of 2kByte)
- **Behavior** Calling `init` no longer clears the eink display. Instead call `reset` directly.
3 changes: 2 additions & 1 deletion examples/esp32/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use esp_idf_hal::{delay::Ets, gpio::PinDriver, prelude::*, spi::*};
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use it8951::{interface::*, *};
use embedded_graphics::{prelude::*, primitives::{Rectangle, PrimitiveStyle}, pixelcolor::Gray4};
use it8951::Config;

fn main() -> ! {
// Bind the log crate to the ESP Logging facilities
Expand Down Expand Up @@ -33,7 +34,7 @@ fn main() -> ! {
reset,
Ets,
);
let mut epd = IT8951::new(display_interface).init(1605).unwrap();
let mut epd = IT8951::new(display_interface, Config::default()).init(1605).unwrap();

log::info!("Initialized display: {:?}", epd.get_dev_info());

Expand Down
5 changes: 4 additions & 1 deletion examples/test_eink.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use it8951::Config;
use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
use linux_embedded_hal::spidev::{SpiModeFlags, SpidevOptions};
use linux_embedded_hal::{CdevPin, Delay, SpidevDevice};
Expand Down Expand Up @@ -34,7 +35,9 @@ fn main() -> Result<(), Box<dyn Error>> {
let busy = CdevPin::new(busy_input_handle)?;

let driver = it8951::interface::IT8951SPIInterface::new(spi, busy, rst, Delay);
let mut epd = it8951::IT8951::new(driver).init(1670).unwrap();
let mut epd = it8951::IT8951::new(driver, Config::default())
.init(1670)
.unwrap();

println!(
"Reset and initalized E-Ink Display: \n\r {:?}",
Expand Down
112 changes: 69 additions & 43 deletions src/area_serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,17 @@ use embedded_graphics_core::{
pub struct AreaSerializer {
area: Rectangle,
rows_per_step: usize,
buffer: Vec<u16>,
buffer: Vec<u8>,
}

impl AreaSerializer {
pub fn new(area: Rectangle, color: Gray4) -> Self {
let max_entries: usize = 512; // 1 KByte

Self::with_buffer_size(area, color, max_entries)
}
pub fn with_buffer_size(area: Rectangle, color: Gray4, buffer_size: usize) -> Self {
let raw_color: u16 = color.luma() as u16;
let data_entry = raw_color << 12 | raw_color << 8 | raw_color << 4 | raw_color;
pub fn new(area: Rectangle, color: Gray4, buffer_size: usize) -> Self {
let raw_color = color.luma();
let data_entry = raw_color << 4 | raw_color;

assert!(buffer_size % 2 == 0, "Buffer size must be aligned to u16");
// calculate the buffer size
let entries_per_row = get_entires_per_row(area) as usize;
let entries_per_row = get_entires_per_row(area) as usize * 2; // convert length from u16 to u8
let rows_per_step = (buffer_size / entries_per_row).min(area.size.height as usize);
assert!(rows_per_step > 0, "Buffer size to small for one row");
let buffer = vec![data_entry; entries_per_row * rows_per_step];
Expand All @@ -50,7 +46,7 @@ impl<'a> AreaSerializerIterator<'a> {
}

impl<'a> Iterator for AreaSerializerIterator<'a> {
type Item = (AreaImgInfo, &'a [u16]);
type Item = (AreaImgInfo, &'a [u8]);

fn next(&mut self) -> Option<Self::Item> {
let area_height = self.area_serializer.area.size.height;
Expand Down Expand Up @@ -97,7 +93,11 @@ mod tests {
height: 1,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -108,7 +108,7 @@ mod tests {
area_w: 1,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -124,7 +124,11 @@ mod tests {
height: 1,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -135,7 +139,7 @@ mod tests {
area_w: 1,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -150,7 +154,11 @@ mod tests {
height: 1,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -161,7 +169,7 @@ mod tests {
area_w: 1,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -176,7 +184,11 @@ mod tests {
height: 1,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -187,7 +199,7 @@ mod tests {
area_w: 1,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -203,7 +215,11 @@ mod tests {
height: 1,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);

assert_eq!(
Expand All @@ -215,7 +231,7 @@ mod tests {
area_w: 4,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -231,7 +247,11 @@ mod tests {
height: 1,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -242,7 +262,7 @@ mod tests {
area_w: 3,
area_h: 1
},
[0xAAAAu16, 0xAAAAu16].as_slice()
[0xAA, 0xAA, 0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -258,11 +278,8 @@ mod tests {
height: 2,
},
};
let area_s = AreaSerializer::with_buffer_size(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1,
);
let area_s =
AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA), 2);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -273,7 +290,7 @@ mod tests {
area_w: 4,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(
Expand All @@ -285,7 +302,7 @@ mod tests {
area_w: 4,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -301,11 +318,8 @@ mod tests {
height: 2,
},
};
let area_s = AreaSerializer::with_buffer_size(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
2,
);
let area_s =
AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA), 4);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -316,7 +330,7 @@ mod tests {
area_w: 3,
area_h: 1
},
[0xAAAAu16, 0xAAAAu16].as_slice()
[0xAA, 0xAA, 0xAA, 0xAA].as_slice()
))
);
assert_eq!(
Expand All @@ -328,7 +342,7 @@ mod tests {
area_w: 3,
area_h: 1
},
[0xAAAAu16, 0xAAAAu16].as_slice()
[0xAA, 0xAA, 0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -344,7 +358,11 @@ mod tests {
height: 2,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -355,7 +373,7 @@ mod tests {
area_w: 4,
area_h: 2
},
[0xAAAAu16, 0xAAAAu16].as_slice()
[0xAA, 0xAA, 0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -371,7 +389,11 @@ mod tests {
height: 2,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -382,7 +404,7 @@ mod tests {
area_w: 3,
area_h: 2
},
[0xAAAAu16, 0xAAAAu16, 0xAAAAu16, 0xAAAAu16].as_slice()
[0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand All @@ -398,7 +420,11 @@ mod tests {
height: 2,
},
};
let area_s = AreaSerializer::new(area.intersection(&BOUNDING_BOX_DEFAULT), Gray4::new(0xA));
let area_s = AreaSerializer::new(
area.intersection(&BOUNDING_BOX_DEFAULT),
Gray4::new(0xA),
1024,
);
let mut s = AreaSerializerIterator::new(&area_s);
assert_eq!(
s.next(),
Expand All @@ -409,7 +435,7 @@ mod tests {
area_w: 2,
area_h: 1
},
[0xAAAAu16].as_slice()
[0xAA, 0xAA].as_slice()
))
);
assert_eq!(s.next(), None);
Expand Down
Loading

0 comments on commit f190511

Please sign in to comment.