From 4ed15fae4266770974960d31090ada32dfa5ea83 Mon Sep 17 00:00:00 2001 From: JP Stringham Date: Mon, 29 Dec 2025 12:12:49 -0500 Subject: [PATCH] Updated for better use as a module --- src/graphics.rs | 2 +- src/graphics/sprite.rs | 1 + src/sh1106.rs | 126 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/sh1106.rs diff --git a/src/graphics.rs b/src/graphics.rs index 6504144..ce025bc 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -4,7 +4,7 @@ use sprite::*; pub struct GraphicsBuf { buffer: [u8; 8192], - sprites: [Sprite; 30], + pub sprites: [Sprite; 30], sprite_atlas: SpriteAtlas, } diff --git a/src/graphics/sprite.rs b/src/graphics/sprite.rs index 3480468..8389b03 100644 --- a/src/graphics/sprite.rs +++ b/src/graphics/sprite.rs @@ -25,6 +25,7 @@ impl Sprite { #[repr(u8)] #[derive(Copy, Clone)] +#[allow(unused)] pub enum SpriteFlip { None = 0, Horizontal = 1, diff --git a/src/sh1106.rs b/src/sh1106.rs new file mode 100644 index 0000000..5fa2bca --- /dev/null +++ b/src/sh1106.rs @@ -0,0 +1,126 @@ +use cortex_m::delay::Delay; +use embedded_hal::i2c::I2c; +use rp2040_hal::i2c::Error; + +use defmt as _; + +use crate::graphics::GraphicsBuf; + +#[allow(unused)] +#[repr(u8)] +#[derive(PartialEq, Copy, Clone)] +pub enum SH1106Cmd { + SetColumnLo = 0, + SetColumnHi = 0x10, + DisplayInvert = 0xA6, + DisplayOnOff = 0xAE, + ColDirectionNorm = 0xC0, + ColDirectionRev = 0xC8, + SetPage = 0xB0, +} + +pub const SH1106_I2C_ADDR: u8 = 0x78u8 >> 1; + +pub struct SH1106Dev { + ready: bool, +} + +impl SH1106Dev { + pub fn new(delay: &mut Delay, mut i2c: &mut dyn I2c) -> Self { + send_command(&mut i2c, &SH1106Cmd::DisplayOnOff, 0x00); + delay.delay_ms(100); + send_command(&mut i2c, &SH1106Cmd::DisplayInvert, 0); + send_command(&mut i2c, &SH1106Cmd::ColDirectionNorm, 0); + + set_col(&mut i2c, 2); + set_page(&mut i2c, 0); + clr_screen(&mut i2c); + delay.delay_ms(500); + send_command(&mut i2c, &SH1106Cmd::DisplayOnOff, 0x01); + + SH1106Dev { ready: true } + } + + pub fn blit_framebuffer(&self, i2c: &mut dyn I2c, gfx_buf: &GraphicsBuf) { + if !self.ready { + panic!("Attempted to use SSH1106 before initialized."); + } + + let buf = gfx_buf.get_px_buffer(); + // for each row of 8x8 pixels + for r in 0..8 { + set_page(i2c, r); + set_col(i2c, 2); + // for each cel of 8x8 pixels + for c in 0..16 { + let mut cel = [0u8; 8]; + + // so our x,y here would be r * 128 + c * 8 + + // start assembling the 8x8 cel + for x in 0..8 { + let mut col_byte = 0u8; + + // for each row of the cel + for y in 0..8 { + let pix_x = (c as usize) * 8 + x; + let pix_y = (r as usize) * 8 + y; + col_byte |= buf[pix_y * 128 + pix_x] << y; + } + + cel[x] = col_byte; + } + + write_cel_pixels(i2c, &cel); + } + } + } +} + +fn send_command(i2c: &mut dyn I2c, command: &SH1106Cmd, data: u8) { + let mut writebuf: [u8; 2] = [0; 2]; + + writebuf[0] = 0b1000_0000; + writebuf[1] = (command.clone() as u8) | data; + + match i2c.write(SH1106_I2C_ADDR, &writebuf) { + _ => (), + }; +} + +fn set_col(i2c: &mut dyn I2c, col: u8) { + let col_lo = col & 0b0000_1111; + let col_hi = (col & 0b1111_0000) >> 4; + + send_command(i2c, &SH1106Cmd::SetColumnLo, col_lo); + send_command(i2c, &SH1106Cmd::SetColumnHi, col_hi); +} + +fn set_page(i2c: &mut dyn I2c, page: u8) { + send_command(i2c, &SH1106Cmd::SetPage, page & 0b0000_0111); +} + +fn clr_screen(i2c: &mut dyn I2c) { + let mut writebuf = [0u8; 132 * 8 + 1]; + + writebuf[0] = 0b0100_0000; + + for i in 0..8 { + match i2c.write(SH1106_I2C_ADDR, &writebuf) { + _ => (), + }; + + set_page(i2c, i); + } +} + +fn write_cel_pixels(i2c: &mut dyn I2c, data: &[u8]) { + let mut writebuf = [0b0100_0000u8; 9]; + + writebuf[0] = 0b0100_0000; + writebuf[1..].copy_from_slice(&data[0..8]); + + match i2c.write(SH1106_I2C_ADDR, &writebuf) { + _ => (), + }; +}