Initial implementation of OLED driver with shared SPI bus

This commit is contained in:
2026-08-02 17:20:40 +02:00
parent 7ce3a60a08
commit 020ca5cd4b
7 changed files with 301 additions and 48 deletions
Generated
+91
View File
@@ -322,6 +322,7 @@ dependencies = [
"log",
"ndef",
"pn532",
"ssd1306",
]
[[package]]
@@ -606,6 +607,17 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba2aab1ef3793e6f7804162debb5ac5edb93b3d650fbcc5aeb72fcd0e6c03a0"
[[package]]
name = "display-interface-i2c"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d964fa85bbbb5a6ecd06e58699407ac5dc3e3ad72dac0ab7e6b0d00a1cd262d"
dependencies = [
"display-interface",
"embedded-hal 1.0.0",
"embedded-hal-async",
]
[[package]]
name = "display-interface-spi"
version = "0.5.0"
@@ -1916,6 +1928,30 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]]
name = "manyhow"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587"
dependencies = [
"manyhow-macros",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.119",
]
[[package]]
name = "manyhow-macros"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495"
dependencies = [
"proc-macro-utils",
"proc-macro2",
"quote",
]
[[package]]
name = "matchers"
version = "0.2.0"
@@ -1931,6 +1967,19 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "maybe-async-cfg"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dbfaa67a76e2623580df07d6bb5e7956c0a4bae4b418314083a9c619bd66627"
dependencies = [
"manyhow",
"proc-macro2",
"pulldown-cmark",
"quote",
"syn 1.0.109",
]
[[package]]
name = "memchr"
version = "2.8.3"
@@ -2690,6 +2739,17 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-utils"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071"
dependencies = [
"proc-macro2",
"quote",
"smallvec",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
@@ -2719,6 +2779,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "pulldown-cmark"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
dependencies = [
"bitflags 2.13.1",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.47"
@@ -3133,6 +3204,20 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0f368519fc6c85fc1afdb769fb5a51123f6158013e143656e25a3485a0d401c"
[[package]]
name = "ssd1306"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ed51935bdc6e9e976d3d9a747e768589eaea0cf26f483213bfededc947c8624"
dependencies = [
"display-interface",
"display-interface-i2c",
"display-interface-spi",
"embedded-graphics-core",
"embedded-hal 1.0.0",
"maybe-async-cfg",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -3400,6 +3485,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-id-start"
version = "1.4.0"
+1
View File
@@ -34,6 +34,7 @@ esp-hal-wifimanager = { git = "https://github.com/InvisibleUSA/esp-hal-wifimanag
esp-println = { version = "0.17.0", features = ["esp32s3", "log-04"] }
display-interface-spi = "0.5.0"
ili9341 = "0.6.0"
ssd1306 = "0.9.0"
embedded-hal = "1.0.0"
embedded-hal-bus = "0.3.0"
embedded-graphics = "0.8.2"
+29 -11
View File
@@ -8,7 +8,9 @@
#![deny(clippy::large_stack_frames)]
use creaturedex::card::mock::{MOCK_PAYLOAD, MOCK_PAYLOAD2};
use creaturedex::display::primary_lcd::init_primary_lcd;
use creaturedex::display::shared_bus::init_dual_displays;
use creaturedex::display::views::card_view::render_nfc_payload;
use creaturedex::drivers::nfc_pn532::NfcPn532Driver;
use creaturedex::navigation::input::init_navigation_button;
@@ -16,8 +18,13 @@ use creaturedex::navigation::state::NavigationState;
use creaturedex::network::wifi::setup_wifi;
use embassy_executor::Spawner;
use embassy_time::Timer;
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyle},
prelude::*,
text::Text,
};
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::pixelcolor::{BinaryColor, Rgb565};
use embedded_graphics::prelude::RgbColor;
use esp_hal::time::Duration;
use esp_hal::clock::CpuClock;
@@ -68,23 +75,32 @@ async fn main(spawner: Spawner) {
esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0);
let mut display = init_primary_lcd(
let (mut display, mut oled) = init_dual_displays(
peripherals.SPI2,
peripherals.GPIO36,
peripherals.GPIO35,
peripherals.GPIO37,
peripherals.GPIO11,
peripherals.GPIO10,
peripherals.GPIO12,
peripherals.GPIO36, // sck
peripherals.GPIO35, // mosi
peripherals.GPIO37, // miso
peripherals.GPIO11, // primary cs
peripherals.GPIO5, // oled cs
peripherals.GPIO10, // shared reset
peripherals.GPIO12, // shared dc
&mut delay,
);
setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await;
log::info!("Setup complete, entering main loop");
let mut nav_state = NavigationState::new();
oled.clear(BinaryColor::Off).unwrap();
let text_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
Text::new("Hello OLED", Point::new(10, 20), text_style)
.draw(&mut oled)
.unwrap();
oled.flush().unwrap();
let mut nfc_driver = NfcPn532Driver::new(peripherals.I2C0, peripherals.GPIO17, peripherals.GPIO18);
let timeout = Duration::from_millis(1000);
if nfc_driver.configure_sam(timeout).is_ok() {
@@ -93,6 +109,8 @@ async fn main(spawner: Spawner) {
log::error!("NFC SAM configuration failed");
}
log::info!("Setup complete, entering main loop");
loop {
if let Ok(card_data) = nfc_driver.read_card_data() {
log::info!("NFC Card Read: {:?}", card_data);
+2
View File
@@ -1,6 +1,8 @@
pub mod palette;
pub mod primary_lcd;
pub mod secondary_oled;
pub mod shared_bus;
pub mod sprite;
pub mod tertiary_lcd;
pub mod views;
+26 -36
View File
@@ -1,54 +1,42 @@
use core::cell::RefCell;
use display_interface_spi::SPIInterface;
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::RgbColor;
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_hal::digital::OutputPin as EhOutputPin;
use embedded_hal_bus::spi::RefCellDevice;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::peripherals::{GPIO10, GPIO11, GPIO12, GPIO35, GPIO36, GPIO37, SPI2};
use esp_hal::spi::{master::{Config, Spi}, Mode};
use esp_hal::time::Rate;
use esp_hal::gpio::{Level, Output, OutputConfig, OutputPin};
use ili9341::{DisplaySize240x320, Ili9341, Orientation};
pub type PrimaryLcdDisplay<'a> = Ili9341<
pub type PrimaryLcdDisplay<'a, BUS, DC, RES> = Ili9341<
SPIInterface<
ExclusiveDevice<Spi<'a, esp_hal::Blocking>, Output<'a>, Delay>,
Output<'a>,
RefCellDevice<'a, BUS, Output<'a>, Delay>,
DC,
>,
Output<'a>,
RES,
>;
pub fn init_primary_lcd(
spi2: SPI2<'static>,
sck: GPIO36<'static>,
mosi: GPIO35<'static>,
miso: GPIO37<'static>,
cs: GPIO11<'static>,
reset: GPIO10<'static>,
dc: GPIO12<'static>,
pub fn init_primary_lcd_on_bus<'a, BUS, CS, RES, DC>(
spi_bus: &'a RefCell<BUS>,
cs: CS,
reset: RES,
dc: DC,
delay: &mut Delay,
) -> PrimaryLcdDisplay<'static> {
let freq = Rate::from_khz(60_000);
log::info!("Display freq: {freq}");
let spi_bus = Spi::new(
spi2,
Config::default().with_frequency(freq).with_mode(Mode::_0),
)
.unwrap()
.with_sck(sck)
.with_mosi(mosi)
.with_miso(miso);
let cs_main = Output::new(cs, Level::High, OutputConfig::default());
let spi_dev = ExclusiveDevice::new(spi_bus, cs_main, *delay).unwrap();
let reset_main = Output::new(reset, Level::High, OutputConfig::default());
let spi_dc = Output::new(dc, Level::High, OutputConfig::default());
let iface = SPIInterface::new(spi_dev, spi_dc);
) -> PrimaryLcdDisplay<'a, BUS, DC, RES>
where
BUS: embedded_hal::spi::SpiBus,
CS: OutputPin + 'static,
RES: EhOutputPin,
DC: EhOutputPin,
{
let cs_pin = Output::new(cs, Level::High, OutputConfig::default());
let spi_dev = RefCellDevice::new(spi_bus, cs_pin, *delay).unwrap();
let iface = SPIInterface::new(spi_dev, dc);
let mut display = Ili9341::new(
iface,
reset_main,
reset,
delay,
Orientation::Portrait,
DisplaySize240x320,
@@ -58,3 +46,5 @@ pub fn init_primary_lcd(
display.clear(Rgb565::BLACK).unwrap();
display
}
+54 -1
View File
@@ -1 +1,54 @@
// Secondary OLED module
use core::cell::RefCell;
use display_interface_spi::SPIInterface;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
use embedded_hal::digital::OutputPin as EhOutputPin;
use embedded_hal_bus::spi::RefCellDevice;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Level, Output, OutputConfig, OutputPin};
use ssd1306::mode::BufferedGraphicsMode;
use ssd1306::prelude::*;
use ssd1306::Ssd1306;
pub type SecondaryOledDisplay<'a, BUS, DC> = Ssd1306<
SPIInterface<
RefCellDevice<'a, BUS, Output<'a>, Delay>,
DC,
>,
DisplaySize128x64,
BufferedGraphicsMode<DisplaySize128x64>,
>;
pub fn init_secondary_oled_on_bus<'a, BUS, CS, RES, DC>(
spi_bus: &'a RefCell<BUS>,
cs: CS,
mut res: RES,
dc: DC,
delay: &mut Delay,
) -> SecondaryOledDisplay<'a, BUS, DC>
where
BUS: embedded_hal::spi::SpiBus,
CS: OutputPin + 'static,
RES: EhOutputPin,
DC: EhOutputPin,
{
let cs_pin = Output::new(cs, Level::High, OutputConfig::default());
let spi_dev = RefCellDevice::new(spi_bus, cs_pin, *delay).unwrap();
let interface = SPIInterface::new(spi_dev, dc);
let mut display = Ssd1306::new(
interface,
DisplaySize128x64,
DisplayRotation::Rotate0,
)
.into_buffered_graphics_mode();
display.reset(&mut res, delay).unwrap();
display.init().unwrap();
display.clear(BinaryColor::Off).unwrap();
display.flush().unwrap();
display
}
+98
View File
@@ -0,0 +1,98 @@
use alloc::boxed::Box;
use core::cell::RefCell;
use embedded_hal::digital::{ErrorType, OutputPin as EhOutputPin, PinState};
use esp_hal::delay::Delay;
use esp_hal::gpio::interconnect::{PeripheralInput, PeripheralOutput};
use esp_hal::gpio::{Level, Output, OutputConfig, OutputPin};
use esp_hal::spi::master::{Config as SpiConfig, Spi};
use esp_hal::spi::Mode;
use esp_hal::time::Rate;
use crate::display::primary_lcd::{init_primary_lcd_on_bus, PrimaryLcdDisplay};
use crate::display::secondary_oled::{init_secondary_oled_on_bus, SecondaryOledDisplay};
pub struct SharedPin<'a>(pub &'a RefCell<Output<'static>>);
impl<'a> ErrorType for SharedPin<'a> {
type Error = core::convert::Infallible;
}
impl<'a> EhOutputPin for SharedPin<'a> {
fn set_low(&mut self) -> Result<(), Self::Error> {
self.0.borrow_mut().set_low();
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
self.0.borrow_mut().set_high();
Ok(())
}
fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> {
self.0.borrow_mut().set_state(state);
Ok(())
}
}
pub type DisplaySpiBus = Spi<'static, esp_hal::Blocking>;
pub type DualPrimaryDisplay<'a> = PrimaryLcdDisplay<'a, DisplaySpiBus, SharedPin<'a>, SharedPin<'a>>;
pub type DualSecondaryDisplay<'a> = SecondaryOledDisplay<'a, DisplaySpiBus, SharedPin<'a>>;
pub fn init_dual_displays<SPI, SCK, MOSI, MISO, CS1, CS2, RES, DC>(
spi_peripheral: SPI,
sck: SCK,
mosi: MOSI,
miso: MISO,
cs_primary: CS1,
cs_secondary: CS2,
reset_pin: RES,
dc_pin: DC,
delay: &mut Delay,
) -> (DualPrimaryDisplay<'static>, DualSecondaryDisplay<'static>)
where
SPI: esp_hal::spi::master::Instance + 'static,
SCK: PeripheralOutput<'static>,
MOSI: PeripheralOutput<'static>,
MISO: PeripheralInput<'static>,
CS1: OutputPin + 'static,
CS2: OutputPin + 'static,
RES: OutputPin + 'static,
DC: OutputPin + 'static,
{
let spi = Spi::new(
spi_peripheral,
SpiConfig::default().with_frequency(Rate::from_khz(20_000)).with_mode(Mode::_0),
)
.unwrap()
.with_sck(sck)
.with_mosi(mosi)
.with_miso(miso);
let bus_static: &'static RefCell<DisplaySpiBus> = Box::leak(Box::new(RefCell::new(spi)));
let reset_static: &'static RefCell<Output<'static>> = Box::leak(Box::new(RefCell::new(
Output::new(reset_pin, Level::High, OutputConfig::default()),
)));
let dc_static: &'static RefCell<Output<'static>> = Box::leak(Box::new(RefCell::new(
Output::new(dc_pin, Level::High, OutputConfig::default()),
)));
let primary = init_primary_lcd_on_bus(
bus_static,
cs_primary,
SharedPin(reset_static),
SharedPin(dc_static),
delay,
);
let secondary = init_secondary_oled_on_bus(
bus_static,
cs_secondary,
SharedPin(reset_static),
SharedPin(dc_static),
delay,
);
(primary, secondary)
}