// Intended to be used with a 1.3" OLED // resolution is 128*64 // #![no_std] #![no_main] use embedded_hal::{ digital::OutputPin, i2c::I2c }; use hal::gpio::bank0::{Gpio8, Gpio9}; use rp2040_hal::gpio::PullUp; use rp2040_hal as hal; use hal::{ pac as pac, gpio::{FunctionI2C, Pin}, fugit::RateExtU32, }; use defmt as _; use panic_probe as _; mod sprite; use crate::sprite::*; #[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 let mut pac = pac::Peripherals::take().unwrap(); let core = pac::CorePeripherals::take().unwrap(); // Set up the watchdog driver - needed by the clock setup code let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); const XTAL_FREQ_HZ: u32 = 12_000_000u32; // Configure the clocks let clocks = hal::clocks::init_clocks_and_plls( XTAL_FREQ_HZ, pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut watchdog, ) .ok() .unwrap(); let mut delay = cortex_m::delay::Delay::new(core.SYST, 133_000_000u32); let sio = hal::Sio::new(pac.SIO); let pins = hal::gpio::Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS ); // Create the I²C drive, using the two pre-configured pins. let mut i2c = hal::I2C::i2c0( pac.I2C0, pins.gpio8.reconfigure(), pins.gpio9.reconfigure(), // Try `not_an_scl_pin` here 400.kHz(), &mut pac.RESETS, &clocks.system_clock, ); 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 mut gfx_buf = [0u8; 8192]; let sprite_atlas = SpriteAtlas::new(include_bytes!("../sprites/sprites_atlas.bin")); let mut sprites = [Sprite::new(); 30]; sprites[0].sprite_index = 94; sprites[0].x = 90; sprites[0].y = 30; sprites[0].visible = true; let mut scanline = 0u8; loop { gfx_buf.iter_mut().for_each(|x| *x = 0); sprites.iter_mut().for_each(|s| { if s.visible { sprite_atlas.draw_sprite(s.sprite_index, s.x as u8, s.y as u8, s.flip, &mut gfx_buf); } }); sprite_atlas.draw_string(10, 10, "Hello World!", &mut gfx_buf); sprite_atlas.draw_textfield(10, 20, 110, 10, "I think this font has a *lot* of 'character'! ;)", &mut gfx_buf); draw_bresenham_line(0, scanline, 128, scanline, &mut gfx_buf); draw_bresenham_line(0, 64, 127, 64, &mut gfx_buf); 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); delay.delay_ms(1); // scanline += 1; // if scanline >= 64 { // scanline = 0; // } } } 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); let y0 = (y0 as i32).clamp(0, 63); let y1 = (y1 as i32).clamp(0, 63); let mut dx = x1 - x0; let mut dy = y1 - y0; let mut x = x0; let mut y = y0; let mut err = 0; let mut step_x = 1; let mut step_y = 1; if dx < 0 { dx = -dx; step_x = -1; } if dy < 0 { dy = -dy; step_y = -1; } if dx > dy { for _ in 0..dx { gfx_buf[(y*128+x) as usize] = 1; x += step_x; err += 2 * dy; if err > dx { y += step_y; err -= 2 * dx; } } } else { for _ in 0..dy { gfx_buf[(y*128+x) as usize] = 1; y += step_y; err += 2 * dx; if err > dy { x += step_x; err -= 2 * dy; } } } } fn blit_framebuffer( i2c: &mut hal::I2C, Pin)>, 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 hal::I2C, Pin)>, 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 hal::I2C, Pin)>, page: u8) { send_command(i2c, &SH1106Cmd::SetPage, page & 0b0000_0111); } fn clr_screen(i2c: &mut hal::I2C, Pin)>) { 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 hal::I2C, Pin)>, 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 hal::I2C, Pin)>, 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");