1#![no_std]
5#![forbid(unsafe_code)]
6
7use embassy_time::Timer;
30use embedded_hal_async::i2c::I2c;
31
32pub const SCREEN_WIDTH: usize = 128;
34
35pub const SCREEN_HEIGHT: usize = 64;
37
38pub const PAGES: usize = SCREEN_HEIGHT / 8;
40
41const FONT: [[u8; 5]; 55] = [
44 [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], [0x00, 0x00, 0x60, 0x60, 0x00], [0x00, 0x3E, 0x41, 0x41, 0x00], [0x00, 0x41, 0x41, 0x3E, 0x00], [0x00, 0x40, 0x50, 0x30, 0x00], [0x00, 0x7F, 0x41, 0x41, 0x00], [0x00, 0x41, 0x41, 0x7F, 0x00], [0x23, 0x13, 0x08, 0x64, 0x62], [0x08, 0x14, 0x22, 0x41, 0x00], [0x00, 0x41, 0x22, 0x14, 0x08], [0x00, 0x24, 0x24, 0x24, 0x00], [0x02, 0x01, 0x51, 0x09, 0x06], [0x00, 0x00, 0x5F, 0x00, 0x00], [0x00, 0x36, 0x36, 0x00, 0x00], [0x08, 0x08, 0x3E, 0x08, 0x08], [0x20, 0x10, 0x08, 0x04, 0x02], [0x00, 0x00, 0x7F, 0x00, 0x00], [0x40, 0x40, 0x40, 0x40, 0x40], ];
101
102pub struct Ssd1306<I: I2c> {
107 i2c: I,
108 pub addr: u8,
110 framebuffer: [u8; SCREEN_WIDTH * PAGES],
111}
112
113impl<I: I2c> Ssd1306<I> {
114 pub fn new(i2c: I, addr: u8) -> Self {
120 Self {
121 i2c,
122 addr,
123 framebuffer: [0u8; SCREEN_WIDTH * PAGES],
124 }
125 }
126
127 async fn cmd(&mut self, c: u8) -> Result<(), I::Error> {
130 self.i2c.write(self.addr, &[0x00, c]).await
131 }
132
133 async fn cmd2(&mut self, c: u8, d: u8) -> Result<(), I::Error> {
134 self.i2c.write(self.addr, &[0x00, c, d]).await
135 }
136
137 async fn cmd3(&mut self, c: u8, d1: u8, d2: u8) -> Result<(), I::Error> {
138 self.i2c.write(self.addr, &[0x00, c, d1, d2]).await
139 }
140
141 pub async fn init(&mut self) -> Result<(), I::Error> {
147 Timer::after_millis(200).await;
148
149 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();
167 self.flush().await
168 }
169
170 pub fn clear(&mut self) {
174 self.framebuffer.fill(0x00);
175 }
176
177 pub fn fill(&mut self) {
179 self.framebuffer.fill(0xFF);
180 }
181
182 pub fn draw_pixel(&mut self, x: u8, y: u8, on: bool) {
186 if x >= SCREEN_WIDTH as u8 || y >= SCREEN_HEIGHT as u8 {
187 return;
188 }
189 let page = (y / 8) as usize;
190 let bit = y % 8;
191 let idx = page * SCREEN_WIDTH + x as usize;
192 if on {
193 self.framebuffer[idx] |= 1 << bit;
194 } else {
195 self.framebuffer[idx] &= !(1 << bit);
196 }
197 }
198
199 pub fn draw_hline(&mut self, x: u8, y: u8, w: u8, on: bool) {
203 for i in 0..w {
204 self.draw_pixel(x + i, y, on);
205 }
206 }
207
208 pub fn draw_vline(&mut self, x: u8, y: u8, h: u8, on: bool) {
210 for i in 0..h {
211 self.draw_pixel(x, y + i, on);
212 }
213 }
214
215 pub fn draw_rect(&mut self, x: u8, y: u8, w: u8, h: u8, on: bool) {
217 self.draw_hline(x, y, w, on);
218 self.draw_hline(x, y + h - 1, w, on);
219 self.draw_vline(x, y, h, on);
220 self.draw_vline(x + w - 1, y, h, on);
221 }
222
223 pub fn draw_filled_rect(&mut self, x: u8, y: u8, w: u8, h: u8, on: bool) {
225 for row in 0..h {
226 self.draw_hline(x, y + row, w, on);
227 }
228 }
229
230 pub fn draw_bitmap(&mut self, x: u8, y: u8, w: u8, h: u8, data: &[u8]) {
235 let stride = (w as usize + 7) / 8;
236 for row in 0..h as usize {
237 for col in 0..w as usize {
238 let byte_idx = row * stride + col / 8;
239 let bit = 7 - (col % 8);
240 let on = byte_idx < data.len() && (data[byte_idx] >> bit) & 1 == 1;
241 self.draw_pixel(x + col as u8, y + row as u8, on);
242 }
243 }
244 }
245
246 pub fn draw_char(&mut self, x: u8, page: u8, glyph_idx: usize) {
252 for col in 0..5usize {
253 let byte = FONT[glyph_idx][col];
254 let fb_idx = page as usize * SCREEN_WIDTH + x as usize + col;
255 if fb_idx < self.framebuffer.len() {
256 self.framebuffer[fb_idx] = byte;
257 }
258 }
259 }
260
261 pub fn draw_i16(&mut self, mut x: u8, page: u8, val: i16) -> u8 {
265 if val < 0 {
266 self.draw_char(x, page, 10); x += 6;
268 }
269
270 let mut n = val.unsigned_abs();
271 let mut digits = [0u8; 5];
272 let mut count = 0;
273
274 loop {
275 digits[count] = (n % 10) as u8;
276 n /= 10;
277 count += 1;
278 if n == 0 { break; }
279 }
280
281 for i in (0..count).rev() {
282 self.draw_char(x, page, digits[i] as usize);
283 x += 6;
284 }
285 x
286 }
287
288 fn char_to_glyph(c: u8) -> Option<usize> {
291 match c {
292 b'0'..=b'9' => Some((c - b'0') as usize),
293 b'-' => Some(10),
294 b' ' => Some(11),
295 b'A'..=b'Z' => Some((c - b'A') as usize + 12),
296 b'a'..=b'z' => Some((c - b'a') as usize + 12), b'.' => Some(38),
298 b'(' => Some(39),
299 b')' => Some(40),
300 b',' => Some(41),
301 b'[' => Some(42),
302 b']' => Some(43),
303 b'%' => Some(44),
304 b'<' => Some(45),
305 b'>' => Some(46),
306 b'=' => Some(47),
307 b'?' => Some(48),
308 b'!' => Some(49),
309 b':' => Some(50),
310 b'+' => Some(51),
311 b'/' => Some(52),
312 b'|' => Some(53),
313 b'_' => Some(54),
314 _ => None,
315 }
316 }
317
318 pub fn draw_str(&mut self, mut x: u8, page: u8, text: &[u8]) -> u8 {
322 for &c in text {
323 if let Some(idx) = Self::char_to_glyph(c) {
324 self.draw_char(x, page, idx);
325 }
326 x = x.saturating_add(6);
327 }
328 x
329 }
330
331
332 pub async fn flush(&mut self) -> Result<(), I::Error> {
338 self.cmd3(0x21, 0, 127).await?; self.cmd3(0x22, 0, 7).await?; let mut buf = [0u8; 129];
342 buf[0] = 0x40; for page in 0..PAGES {
344 let start = page * SCREEN_WIDTH;
345 buf[1..129].copy_from_slice(&self.framebuffer[start..start + SCREEN_WIDTH]);
346 self.i2c.write(self.addr, &buf).await?;
347 }
348 Ok(())
349 }
350}