From 3804321fcc846c6f661c303f829a2cc02a2c0ed6 Mon Sep 17 00:00:00 2001 From: JP Stringham Date: Sun, 28 Dec 2025 09:31:29 -0500 Subject: [PATCH] Some refactoring to move ssh1106 to its own mod --- src/main.rs | 121 +++++-------------------------------------------- src/ssh1106.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 110 deletions(-) create mode 100644 src/ssh1106.rs diff --git a/src/main.rs b/src/main.rs index 4ef2bd4..3745442 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ #![no_std] #![no_main] +use defmt::println; use embedded_hal::{digital::OutputPin, i2c::I2c}; use hal::{fugit::RateExtU32, pac}; use rp2040_hal as hal; @@ -13,27 +14,15 @@ use defmt as _; use panic_probe as _; mod sprite; +mod ssh1106; + use crate::sprite::*; +use crate::ssh1106::*; #[link_section = ".boot_loader"] #[used] pub static BOOT_LOADER: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; -#[allow(unused)] -#[repr(u8)] -#[derive(PartialEq, Copy, Clone)] -enum SH1106Cmd { - SetColumnLo = 0, - SetColumnHi = 0x10, - DisplayInvert = 0xA6, - DisplayOnOff = 0xAE, - ColDirectionNorm = 0xC0, - ColDirectionRev = 0xC8, - SetPage = 0xB0, -} - -const SH1106_I2C_ADDR: u8 = 0x78u8 >> 1; - #[rp2040_hal::entry] fn main() -> ! { // Grab our singleton objects @@ -69,29 +58,22 @@ fn main() -> ! { &mut pac.RESETS, ); - // Create the I²C drive, using the two pre-configured pins. + // Create the I²C drive let mut i2c = hal::I2C::i2c0( pac.I2C0, pins.gpio8.reconfigure(), - pins.gpio9.reconfigure(), // Try `not_an_scl_pin` here + pins.gpio9.reconfigure(), 400.kHz(), &mut pac.RESETS, &clocks.system_clock, ); + // show life on the board let mut led_pin = pins.gpio25.into_push_pull_output(); led_pin.set_high().unwrap(); - 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); + let ssh1106_dev = SSH1106Dev::new(&mut delay, &mut i2c); + println!("SSH1106 initialized."); let mut gfx_buf = [0u8; 8192]; @@ -134,12 +116,13 @@ fn main() -> ! { draw_bresenham_line(0, 63, 128, 63, &mut gfx_buf); draw_bresenham_line(0, 0, 0, 63, &mut gfx_buf); draw_bresenham_line(127, 0, 127, 63, &mut gfx_buf); - blit_framebuffer(&mut i2c, &gfx_buf); + ssh1106_dev.blit_framebuffer(&mut i2c, &gfx_buf); delay.delay_ms(1); } } +// should probably go into some kind of graphics module fn draw_bresenham_line(x0: u8, y0: u8, x1: u8, y1: u8, gfx_buf: &mut [u8; 8192]) { let x0 = (x0 as i32).clamp(0, 127); let x1 = (x1 as i32).clamp(0, 127); @@ -193,85 +176,3 @@ fn draw_bresenham_line(x0: u8, y0: u8, x1: u8, y1: u8, gfx_buf: &mut [u8; 8192]) } } } - -fn blit_framebuffer(i2c: &mut dyn I2c, graphic: &[u8; 8192]) { - // 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 |= graphic[pix_y * 128 + pix_x] << y; - } - - cel[x] = col_byte; - } - - write_cel_pixels(i2c, &cel); - } - } -} - -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) { - _ => (), - }; -} - -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) { - _ => (), - }; -} - -// static GRAPHIC:[u8;8192] = *include_bytes!("../image.bin"); -// static TILES_COMPRESSED:[u8;12800] = *include_bytes!("../kenney_comp.bin"); -// static TILES:[u8;102400] = *include_bytes!("../kenney_mono.bin"); diff --git a/src/ssh1106.rs b/src/ssh1106.rs new file mode 100644 index 0000000..0d314c0 --- /dev/null +++ b/src/ssh1106.rs @@ -0,0 +1,121 @@ +use cortex_m::delay::Delay; +use embedded_hal::i2c::I2c; +use rp2040_hal::i2c::Error; + +#[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 SSH1106Dev { + ready: bool, +} + +impl SSH1106Dev { + 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); + + SSH1106Dev { ready: true } + } + + pub fn blit_framebuffer(&self, i2c: &mut dyn I2c, graphic: &[u8; 8192]) { + if !self.ready { + panic!("Attempted to use SSH1106 before initialized."); + } + + // 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 |= graphic[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) { + _ => (), + }; +}