| 1 | //! SSD1306 128x64 OLED over the shared I2C0 bus. |
| 2 | //! |
| 3 | //! Parity reference: `src-arduino/ui/display.cpp` (u8g2 `..._F_HW_I2C`, font |
| 4 | //! `u8g2_font_6x12_tr`). We approximate u8g2 with `embedded-graphics` |
| 5 | //! `MonoTextStyle` + `FONT_6X10`, keeping the Arduino draw coordinates so menu |
| 6 | //! layout matches 1:1. Drawing uses u8g2-style baseline coordinates |
| 7 | //! (`Baseline::Alphabetic`): the given `y` is the text baseline, as in |
| 8 | //! `u8g2.drawStr(x, y, ...)`. |
| 9 | |
| 10 | use embedded_graphics::{ |
| 11 | mono_font::{ |
| 12 | ascii::{FONT_6X10, FONT_9X18_BOLD}, |
| 13 | MonoFont, MonoTextStyle, |
| 14 | }, |
| 15 | pixelcolor::BinaryColor, |
| 16 | prelude::*, |
| 17 | primitives::{PrimitiveStyle, Rectangle}, |
| 18 | text::{Baseline, Text}, |
| 19 | }; |
| 20 | use embedded_hal_bus::i2c::RefCellDevice; |
| 21 | use esp_idf_hal::i2c::I2cDriver; |
| 22 | use ssd1306::{ |
| 23 | mode::{BufferedGraphicsMode, DisplayConfig}, |
| 24 | prelude::*, |
| 25 | I2CDisplayInterface, Ssd1306, |
| 26 | }; |
| 27 | |
| 28 | use crate::board::board::SharedI2c; |
| 29 | |
| 30 | /// The I2C device the OLED talks over: a `RefCellDevice` sharing the `'static` |
| 31 | /// bus so PN532 can later take a second device on the same `RefCell`. |
| 32 | type OledI2c = RefCellDevice<'static, I2cDriver<'static>>; |
| 33 | |
| 34 | type Oled = |
| 35 | Ssd1306<I2CInterface<OledI2c>, DisplaySize128x64, BufferedGraphicsMode<DisplaySize128x64>>; |
| 36 | |
| 37 | pub struct Display { |
| 38 | raw: Oled, |
| 39 | } |
| 40 | |
| 41 | impl Display { |
| 42 | /// Build + initialise the SSD1306 over the shared bus and clear the screen. |
| 43 | pub fn new(bus: SharedI2c) -> Self { |
| 44 | let dev = RefCellDevice::new(bus); |
| 45 | let iface = I2CDisplayInterface::new(dev); |
| 46 | let mut raw = Ssd1306::new(iface, DisplaySize128x64, DisplayRotation::Rotate0) |
| 47 | .into_buffered_graphics_mode(); |
| 48 | raw.init().expect("ssd1306 init"); |
| 49 | |
| 50 | let mut display = Self { raw }; |
| 51 | display.clear(); |
| 52 | display.flush(); |
| 53 | display |
| 54 | } |
| 55 | |
| 56 | /// Clear the off-screen buffer (does not touch the panel until `flush`). |
| 57 | pub fn clear(&mut self) { |
| 58 | self.raw.clear_buffer(); |
| 59 | } |
| 60 | |
| 61 | /// Push the off-screen buffer to the panel (u8g2 `sendBuffer`). |
| 62 | pub fn flush(&mut self) { |
| 63 | let _ = self.raw.flush(); |
| 64 | } |
| 65 | |
| 66 | /// Normal text: foreground pixels on, baseline at `y`. |
| 67 | pub fn text(&mut self, x: i32, y: i32, s: &str) { |
| 68 | self.draw_text(x, y, s, &FONT_6X10, BinaryColor::On); |
| 69 | } |
| 70 | |
| 71 | /// Reversed text (off pixels) for drawing over a filled box |
| 72 | /// (u8g2 `setDrawColor(0)`). |
| 73 | pub fn text_rev(&mut self, x: i32, y: i32, s: &str) { |
| 74 | self.draw_text(x, y, s, &FONT_6X10, BinaryColor::Off); |
| 75 | } |
| 76 | |
| 77 | /// Large title text (approximates u8g2 `logisoso20`) for the splash screen. |
| 78 | pub fn title(&mut self, x: i32, y: i32, s: &str) { |
| 79 | self.draw_text(x, y, s, &FONT_9X18_BOLD, BinaryColor::On); |
| 80 | } |
| 81 | |
| 82 | /// Filled rectangle (u8g2 `drawBox`). |
| 83 | pub fn fill_rect(&mut self, x: i32, y: i32, w: u32, h: u32) { |
| 84 | let _ = Rectangle::new(Point::new(x, y), Size::new(w, h)) |
| 85 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) |
| 86 | .draw(&mut self.raw); |
| 87 | } |
| 88 | |
| 89 | fn draw_text(&mut self, x: i32, y: i32, s: &str, font: &MonoFont<'static>, color: BinaryColor) { |
| 90 | let style = MonoTextStyle::new(font, color); |
| 91 | let _ = Text::with_baseline(s, Point::new(x, y), style, Baseline::Alphabetic) |
| 92 | .draw(&mut self.raw); |
| 93 | } |
| 94 | } |