oled: split out the ssd1306 driver into its own module, and reimplement the oled design with separate rendering and drawing tasks
This commit is contained in:
1
src/graphics/mod.rs
Normal file
1
src/graphics/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod ssd1306;
|
||||
251
src/graphics/ssd1306.rs
Normal file
251
src/graphics/ssd1306.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
#![cfg(feature="oled")]
|
||||
use core::{cmp::min, sync::atomic::{AtomicBool, AtomicU8}};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use display_interface::DisplayError;
|
||||
use embedded_graphics::prelude::*;
|
||||
use esp_hal::rng;
|
||||
use figments::{liber8tion::{interpolate::Fract8, noise}, mappings::embedded_graphics::Matrix2DSpace, prelude::*};
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
use figments::pixels::PixelSink;
|
||||
use figments_render::{gamma::GammaCurve, output::{Brightness, GammaCorrected, NullControls, OutputAsync}};
|
||||
use log::*;
|
||||
|
||||
pub struct SsdOutput([u8; 128 * 64 / 8], ssd1306::Ssd1306Async<ssd1306::prelude::I2CInterface<esp_hal::i2c::master::I2c<'static, esp_hal::Async>>, ssd1306::prelude::DisplaySize128x64, ssd1306::mode::BasicMode>, SsdControls);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SsdPixel(*mut u8, u8, Coordinates<Matrix2DSpace>);
|
||||
|
||||
impl SsdPixel {
|
||||
fn set_pixel(&self, value: BinaryColor) {
|
||||
if value.is_on() {
|
||||
unsafe { *self.0 |= 1 << self.1 };
|
||||
} else {
|
||||
unsafe { *self.0 &= !(1 << self.1) };
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pixel(&self) -> BinaryColor {
|
||||
if unsafe { *self.0 >> self.1 & 0x01 == 0x01 } {
|
||||
BinaryColor::On
|
||||
} else {
|
||||
BinaryColor::Off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelSink<BinaryColor> for SsdPixel {
|
||||
fn set(&mut self, pixel: &BinaryColor) {
|
||||
self.set_pixel(*pixel);
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelBlend<BinaryColor> for SsdPixel {
|
||||
fn blend_pixel(self, overlay: BinaryColor, opacity: Fract8) -> Self {
|
||||
let scale = 32;
|
||||
let x = self.2.x * scale;
|
||||
let y = self.2.y * scale;
|
||||
let stiple_idx = noise::inoise8(x as i16, y as i16);
|
||||
if opacity >= stiple_idx {
|
||||
self.set_pixel(overlay);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn multiply(self, overlay: BinaryColor) -> Self {
|
||||
if overlay != self.get_pixel() {
|
||||
self.set_pixel(overlay);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
static mut DUMMY: u8 = 0;
|
||||
|
||||
impl Default for SsdPixel {
|
||||
fn default() -> Self {
|
||||
Self (
|
||||
unsafe { &mut DUMMY },
|
||||
0,
|
||||
Coordinates::new(0, 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SsdSampler<'a> {
|
||||
buf: &'a mut SsdOutput,
|
||||
rect: figments::prelude::Rectangle<Matrix2DSpace>,
|
||||
pos: figments::prelude::Coordinates<Matrix2DSpace>,
|
||||
pixel: Option<SsdPixel>
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SsdSampler<'a> {
|
||||
type Item = (Coordinates<Matrix2DSpace>, &'a mut SsdPixel);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.pos.x > min(127, self.rect.right()) {
|
||||
self.pos.x = self.rect.left();
|
||||
self.pos.y += 1;
|
||||
}
|
||||
|
||||
if self.pos.y > min(63, self.rect.bottom()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.pixel = self.buf.get_pixel(self.pos);
|
||||
let old_pos = self.pos;
|
||||
self.pos.x += 1;
|
||||
|
||||
let pixelref = unsafe { &mut *(self.pixel.as_mut().unwrap() as *mut SsdPixel) };
|
||||
|
||||
Some((old_pos, pixelref))
|
||||
}
|
||||
}
|
||||
|
||||
impl SsdOutput {
|
||||
pub fn get_pixel(&mut self, coords: Coordinates<Matrix2DSpace>) -> Option<SsdPixel> {
|
||||
if coords.x < 0 || coords.y < 0 || coords.x >= 128 || coords.y >= 64 {
|
||||
return None;
|
||||
}
|
||||
let idx = (coords.y / 8 * 128 + coords.x) as usize;
|
||||
let bit = (coords.y % 8) as u8;
|
||||
//info!("sample {coords:?} {idx}");
|
||||
let pixref = unsafe { &mut self.0[idx] as *mut u8};
|
||||
Some(SsdPixel(pixref, bit, coords))
|
||||
}
|
||||
|
||||
pub fn new(display: ssd1306::Ssd1306Async<ssd1306::prelude::I2CInterface<esp_hal::i2c::master::I2c<'static, esp_hal::Async>>, ssd1306::prelude::DisplaySize128x64, ssd1306::mode::BasicMode>) -> Self {
|
||||
Self([0; 128 * 64 / 8], display, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sample<'a, Matrix2DSpace> for SsdOutput {
|
||||
type Output = SsdPixel;
|
||||
|
||||
fn sample(&mut self, rect: &figments::prelude::Rectangle<Matrix2DSpace>) -> impl Iterator<Item = (Coordinates<Matrix2DSpace>, &'a mut Self::Output)> {
|
||||
let bufref = unsafe {
|
||||
&mut *(self as *mut Self)
|
||||
};
|
||||
let rect = figments::geometry::Rectangle::new(
|
||||
figments::geometry::Coordinates::new(rect.left().clamp(0, 128), rect.top().clamp(0, 64)),
|
||||
figments::geometry::Coordinates::new(rect.right().clamp(0, 128), rect.bottom().clamp(0, 64)),
|
||||
);
|
||||
SsdSampler {
|
||||
buf: bufref,
|
||||
pos: rect.top_left,
|
||||
rect,
|
||||
pixel: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawTarget for SsdOutput {
|
||||
type Color = BinaryColor;
|
||||
|
||||
type Error = ();
|
||||
|
||||
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Pixel<Self::Color>> {
|
||||
for epix in pixels {
|
||||
/*let point = figments::geometry::Rectangle::new(
|
||||
epix.0.into(), epix.0.into()
|
||||
);
|
||||
for (coords, pix) in self.sample(&point) {
|
||||
pix.set_pixel(epix.1);
|
||||
}*/
|
||||
self.get_pixel(epix.0.into()).unwrap().set_pixel(epix.1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fill_contiguous<I>(&mut self, area: &embedded_graphics::primitives::Rectangle, colors: I) -> Result<(), Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = Self::Color>, {
|
||||
let rect = Rectangle::new(
|
||||
area.top_left.into(),
|
||||
area.bottom_right().unwrap().into()
|
||||
);
|
||||
for (color, (coords, pix)) in colors.into_iter().zip(self.sample(&rect)) {
|
||||
pix.set_pixel(color);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fill_solid(&mut self, area: &embedded_graphics::primitives::Rectangle, color: Self::Color) -> Result<(), Self::Error> {
|
||||
let rect = Rectangle::new(
|
||||
area.top_left.into(),
|
||||
area.bottom_right().unwrap().into()
|
||||
);
|
||||
for (coords, pix) in self.sample(&rect) {
|
||||
pix.set_pixel(color);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
|
||||
if color.is_on() {
|
||||
self.0.fill(255);
|
||||
} else {
|
||||
self.0.fill(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginDimensions for SsdOutput {
|
||||
fn size(&self) -> Size {
|
||||
Size::new(128, 64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OutputAsync<'a, Matrix2DSpace> for SsdOutput {
|
||||
type Error = DisplayError;
|
||||
|
||||
type Controls = SsdControls;
|
||||
|
||||
async fn commit_async(&mut self) -> Result<(), Self::Error> {
|
||||
self.1.set_brightness(ssd1306::prelude::Brightness::custom(1, self.2.data.brightness.load(core::sync::atomic::Ordering::Relaxed))).await.unwrap();
|
||||
self.1.set_display_on(self.2.data.is_on.load(core::sync::atomic::Ordering::Relaxed)).await.unwrap();
|
||||
self.1.draw(&self.0).await
|
||||
}
|
||||
|
||||
fn controls(&self) -> Option<&Self::Controls> {
|
||||
Some(&self.2)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We can probably just replace this entirely with a DisplayControls instance
|
||||
#[derive(Debug)]
|
||||
struct SsdControlData {
|
||||
is_on: AtomicBool,
|
||||
brightness: AtomicU8
|
||||
}
|
||||
|
||||
impl Default for SsdControlData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_on: AtomicBool::new(true),
|
||||
brightness: AtomicU8::new(255)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SsdControls {
|
||||
data: Arc<SsdControlData>
|
||||
}
|
||||
|
||||
impl Brightness for SsdControls {
|
||||
fn set_brightness(&mut self, brightness: u8) {
|
||||
self.data.brightness.store(brightness, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set_on(&mut self, is_on: bool) {
|
||||
self.data.is_on.store(is_on, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl GammaCorrected for SsdControls {
|
||||
fn set_gamma(&mut self, gamma: GammaCurve) {} // Not handled
|
||||
}
|
||||
Reference in New Issue
Block a user