#![no_std]
#![forbid(unsafe_code)]
use embassy_time::Timer;
use embedded_hal_async::i2c::I2c;
pub const SCREEN_WIDTH: usize = 128;
pub const SCREEN_HEIGHT: usize = 64;
pub const PAGES: usize = SCREEN_HEIGHT / 8;
const FONT: [[u8; 5]; 38] = [
[0x3E, 0x51, 0x49, 0x45, 0x3E], [0x00, 0x42, 0x7F, 0x40, 0x00], [0x42, 0x61, 0x51, 0x49, 0x46], [0x21, 0x41, 0x45, 0x4B, 0x31], [0x18, 0x14, 0x12, 0x7F, 0x10], [0x27, 0x45, 0x45, 0x45, 0x39], [0x3C, 0x4A, 0x49, 0x49, 0x30], [0x01, 0x71, 0x09, 0x05, 0x03], [0x36, 0x49, 0x49, 0x49, 0x36], [0x06, 0x49, 0x49, 0x29, 0x1E], [0x08, 0x08, 0x08, 0x08, 0x08], [0x00, 0x00, 0x00, 0x00, 0x00], [0x7E, 0x11, 0x11, 0x11, 0x7E], [0x7F, 0x49, 0x49, 0x49, 0x36], [0x3E, 0x41, 0x41, 0x41, 0x22], [0x7F, 0x41, 0x41, 0x22, 0x1C], [0x7F, 0x49, 0x49, 0x49, 0x41], [0x7F, 0x09, 0x09, 0x09, 0x01], [0x3E, 0x41, 0x49, 0x49, 0x7A], [0x7F, 0x08, 0x08, 0x08, 0x7F], [0x00, 0x41, 0x7F, 0x41, 0x00], [0x20, 0x40, 0x41, 0x3F, 0x01], [0x7F, 0x08, 0x14, 0x22, 0x41], [0x7F, 0x40, 0x40, 0x40, 0x40], [0x7F, 0x02, 0x0C, 0x02, 0x7F], [0x7F, 0x04, 0x08, 0x10, 0x7F], [0x3E, 0x41, 0x41, 0x41, 0x3E], [0x7F, 0x09, 0x09, 0x09, 0x06], [0x3E, 0x41, 0x51, 0x21, 0x5E], [0x7F, 0x09, 0x19, 0x29, 0x46], [0x46, 0x49, 0x49, 0x49, 0x31], [0x01, 0x01, 0x7F, 0x01, 0x01], [0x3F, 0x40, 0x40, 0x40, 0x3F], [0x1F, 0x20, 0x40, 0x20, 0x1F], [0x3F, 0x40, 0x38, 0x40, 0x3F], [0x63, 0x14, 0x08, 0x14, 0x63], [0x07, 0x08, 0x70, 0x08, 0x07], [0x61, 0x51, 0x49, 0x45, 0x43], ];
pub struct Ssd1306<I: I2c> {
i2c: I,
pub addr: u8,
framebuffer: [u8; SCREEN_WIDTH * PAGES],
}
impl<I: I2c> Ssd1306<I> {
pub fn new(i2c: I, addr: u8) -> Self {
Self {
i2c,
addr,
framebuffer: [0u8; SCREEN_WIDTH * PAGES],
}
}
async fn cmd(&mut self, c: u8) -> Result<(), I::Error> {
self.i2c.write(self.addr, &[0x00, c]).await
}
async fn cmd2(&mut self, c: u8, d: u8) -> Result<(), I::Error> {
self.i2c.write(self.addr, &[0x00, c, d]).await
}
async fn cmd3(&mut self, c: u8, d1: u8, d2: u8) -> Result<(), I::Error> {
self.i2c.write(self.addr, &[0x00, c, d1, d2]).await
}
pub async fn init(&mut self) -> Result<(), I::Error> {
Timer::after_millis(200).await;
self.cmd(0xAE).await?; self.cmd2(0xD5, 0x80).await?; self.cmd2(0xA8, 0x3F).await?; self.cmd2(0xD3, 0x00).await?; self.cmd(0x40).await?; self.cmd2(0x8D, 0x14).await?; self.cmd2(0x20, 0x00).await?; self.cmd(0xA1).await?; self.cmd(0xC8).await?; self.cmd2(0xDA, 0x12).await?; self.cmd2(0x81, 0xCF).await?; self.cmd2(0xD9, 0xF1).await?; self.cmd2(0xDB, 0x40).await?; self.cmd(0xA4).await?; self.cmd(0xA6).await?; self.cmd(0xAF).await?;
self.clear();
self.flush().await
}
pub fn clear(&mut self) {
self.framebuffer.fill(0x00);
}
pub fn fill(&mut self) {
self.framebuffer.fill(0xFF);
}
pub fn draw_pixel(&mut self, x: u8, y: u8, on: bool) {
if x >= SCREEN_WIDTH as u8 || y >= SCREEN_HEIGHT as u8 {
return;
}
let page = (y / 8) as usize;
let bit = y % 8;
let idx = page * SCREEN_WIDTH + x as usize;
if on {
self.framebuffer[idx] |= 1 << bit;
} else {
self.framebuffer[idx] &= !(1 << bit);
}
}
pub fn draw_hline(&mut self, x: u8, y: u8, w: u8, on: bool) {
for i in 0..w {
self.draw_pixel(x + i, y, on);
}
}
pub fn draw_vline(&mut self, x: u8, y: u8, h: u8, on: bool) {
for i in 0..h {
self.draw_pixel(x, y + i, on);
}
}
pub fn draw_rect(&mut self, x: u8, y: u8, w: u8, h: u8, on: bool) {
self.draw_hline(x, y, w, on);
self.draw_hline(x, y + h - 1, w, on);
self.draw_vline(x, y, h, on);
self.draw_vline(x + w - 1, y, h, on);
}
pub fn draw_filled_rect(&mut self, x: u8, y: u8, w: u8, h: u8, on: bool) {
for row in 0..h {
self.draw_hline(x, y + row, w, on);
}
}
pub fn draw_bitmap(&mut self, x: u8, y: u8, w: u8, h: u8, data: &[u8]) {
let stride = (w as usize + 7) / 8;
for row in 0..h as usize {
for col in 0..w as usize {
let byte_idx = row * stride + col / 8;
let bit = 7 - (col % 8);
let on = byte_idx < data.len() && (data[byte_idx] >> bit) & 1 == 1;
self.draw_pixel(x + col as u8, y + row as u8, on);
}
}
}
pub fn draw_char(&mut self, x: u8, page: u8, glyph_idx: usize) {
for col in 0..5usize {
let byte = FONT[glyph_idx][col];
let fb_idx = page as usize * SCREEN_WIDTH + x as usize + col;
if fb_idx < self.framebuffer.len() {
self.framebuffer[fb_idx] = byte;
}
}
}
pub fn draw_i16(&mut self, mut x: u8, page: u8, val: i16) -> u8 {
if val < 0 {
self.draw_char(x, page, 10); x += 6;
}
let mut n = val.unsigned_abs();
let mut digits = [0u8; 5];
let mut count = 0;
loop {
digits[count] = (n % 10) as u8;
n /= 10;
count += 1;
if n == 0 { break; }
}
for i in (0..count).rev() {
self.draw_char(x, page, digits[i] as usize);
x += 6;
}
x
}
fn char_to_glyph(c: u8) -> Option<usize> {
match c {
b'0'..=b'9' => Some((c - b'0') as usize),
b'-' => Some(10),
b' ' => Some(11),
b'A'..=b'Z' => Some((c - b'A') as usize + 12),
b'a'..=b'z' => Some((c - b'a') as usize + 12), _ => None,
}
}
pub fn draw_str(&mut self, mut x: u8, page: u8, text: &[u8]) -> u8 {
for &c in text {
if let Some(idx) = Self::char_to_glyph(c) {
self.draw_char(x, page, idx);
}
x = x.saturating_add(6);
}
x
}
pub async fn flush(&mut self) -> Result<(), I::Error> {
self.cmd3(0x21, 0, 127).await?; self.cmd3(0x22, 0, 7).await?;
let mut buf = [0u8; 129];
buf[0] = 0x40; for page in 0..PAGES {
let start = page * SCREEN_WIDTH;
buf[1..129].copy_from_slice(&self.framebuffer[start..start + SCREEN_WIDTH]);
self.i2c.write(self.addr, &buf).await?;
}
Ok(())
}
}