Files
sh1106-pico-rs/src/main.rs

304 lines
7.4 KiB
Rust
Raw Normal View History

// Intended to be used with a 1.3" OLED
// resolution is 128*64
//
2025-12-26 17:38:22 -05:00
#![no_std]
#![no_main]
use embedded_hal::{
digital::OutputPin,
i2c::I2c
};
use hal::gpio::bank0::{Gpio8, Gpio9};
use rp2040_hal::gpio::PullUp;
2025-12-26 17:38:22 -05:00
use rp2040_hal as hal;
use hal::{
pac as pac,
gpio::{FunctionI2C, Pin},
fugit::RateExtU32,
};
use defmt as _;
2025-12-26 17:55:26 -05:00
use panic_probe as _;
2025-12-26 17:38:22 -05:00
mod sprite;
use crate::sprite::*;
#[link_section = ".boot_loader"]
#[used]
pub static BOOT_LOADER: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
#[allow(unused)]
2025-12-26 17:38:22 -05:00
#[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;
2025-12-26 17:38:22 -05:00
#[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.
2025-12-26 17:38:22 -05:00
let mut i2c = hal::I2C::i2c0(
pac.I2C0,
pins.gpio8.reconfigure(),
pins.gpio9.reconfigure(), // Try `not_an_scl_pin` here
2025-12-26 17:38:22 -05:00
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;
2025-12-26 17:38:22 -05:00
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);
2025-12-26 17:38:22 -05:00
blit_framebuffer(&mut i2c, &gfx_buf);
delay.delay_ms(1);
// scanline += 1;
// if scanline >= 64 {
// scanline = 0;
// }
2025-12-26 17:38:22 -05:00
}
}
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;
2025-12-26 17:38:22 -05:00
let mut x = x0;
let mut y = y0;
2025-12-26 17:38:22 -05:00
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<pac::I2C0, (Pin<Gpio8, FunctionI2C, PullUp>, Pin<Gpio9, FunctionI2C, PullUp>)>,
2025-12-26 17:38:22 -05:00
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<pac::I2C0, (Pin<Gpio8, FunctionI2C, PullUp>, Pin<Gpio9, FunctionI2C, PullUp>)>,
2025-12-26 17:38:22 -05:00
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<pac::I2C0, (Pin<Gpio8, FunctionI2C, PullUp>, Pin<Gpio9, FunctionI2C, PullUp>)>,
2025-12-26 17:38:22 -05:00
page: u8) {
send_command(i2c, &SH1106Cmd::SetPage, page & 0b0000_0111);
}
fn clr_screen(i2c: &mut hal::I2C<pac::I2C0, (Pin<Gpio8, FunctionI2C, PullUp>, Pin<Gpio9, FunctionI2C, PullUp>)>) {
2025-12-26 17:38:22 -05:00
let mut writebuf = [0u8; 132*8+1];
writebuf[0] = 0b0100_0000;
for i in 0..8 {
match i2c.write(SH1106_I2C_ADDR, &writebuf) {
2025-12-26 17:38:22 -05:00
_ => ()
};
set_page(i2c, i);
}
}
fn write_cel_pixels(
i2c: &mut hal::I2C<pac::I2C0, (Pin<Gpio8, FunctionI2C, PullUp>, Pin<Gpio9, FunctionI2C, PullUp>)>,
2025-12-26 17:38:22 -05:00
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) {
2025-12-26 17:38:22 -05:00
_ => ()
};
}
fn send_command(i2c: &mut hal::I2C<pac::I2C0, (Pin<Gpio8, FunctionI2C, PullUp>, Pin<Gpio9, FunctionI2C, PullUp>)>,
2025-12-26 17:38:22 -05:00
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) {
2025-12-26 17:38:22 -05:00
_ => ()
};
}
// 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");