From 07c5210d7b59612a2b9d000ff0113d316b4b7042 Mon Sep 17 00:00:00 2001 From: lukas Date: Sat, 22 Aug 2026 18:25:48 +0200 Subject: [PATCH] Implemented tertiary LCD display --- Cargo.lock | 19 +++- Cargo.toml | 1 + diagram.json | 173 +++++++++++++++++++---------------- src/bin/main.rs | 32 +++++-- src/display/tertiary_lcd.rs | 89 +++++++++++++++++- src/drivers/nfc_pn532.rs | 50 +++++----- src/navigation/navigation.rs | 6 +- 7 files changed, 251 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 831ec41..c607e2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "bitfield" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" + [[package]] name = "bitfield" version = "0.19.5" @@ -320,6 +326,7 @@ dependencies = [ "esp-hal-wifimanager", "esp-println", "esp-rtos", + "i2c-character-display", "ili9341", "log", "ndef", @@ -1098,7 +1105,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bfcf2a0842903717f4663f6a08512c32b0f6b2d7fb7db3c8a6895d2e6d49f72" dependencies = [ - "bitfield", + "bitfield 0.19.5", "bitflags 2.13.1", "bytemuck", "cfg-if", @@ -1680,6 +1687,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "i2c-character-display" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a620ec16541e28c0e52ce21cd23d734a5d7efaae3d609b2a58694b0ef67739" +dependencies = [ + "bitfield 0.17.0", + "embedded-hal 1.0.0", +] + [[package]] name = "ident_case" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 298d773..c4d40b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ pn532 = "0.5.0" ndef = "0.5.0" num_enum = { version = "0.7.6", default-features = false } embassy-sync = "0.8.0" +i2c-character-display = "0.5.1" # For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html [profile.dev] diff --git a/diagram.json b/diagram.json index 67de93d..4959541 100644 --- a/diagram.json +++ b/diagram.json @@ -1,82 +1,93 @@ { -"version": 1, -"author": "CreatureDex", -"editor": "wokwi", -"parts": [ - { - "type": "board-esp32-s3-devkitc-1", - "id": "esp1", - "top": 67.2, - "left": -263.96, - "attrs": {} - }, - { "type": "wokwi-ili9341", "id": "lcd1", "top": -200.8, "left": 354.9, "attrs": {} }, - { - "type": "wokwi-pushbutton", - "id": "btn-up", - "top": -99.4, - "left": -76.8, - "attrs": { "color": "RebeccaPurple", "xray": "1", "key": "1", "label": "Up" } - }, - { - "type": "wokwi-pushbutton", - "id": "btn-down", - "top": 25.4, - "left": -76.8, - "attrs": { "color": "green", "xray": "1", "key": "1", "label": "Down" } - }, - { - "type": "wokwi-pushbutton", - "id": "btn-left", - "top": -32.2, - "left": -163.2, - "attrs": { "color": "yellow", "xray": "1", "key": "1", "label": "Left" } - }, - { - "type": "wokwi-pushbutton", - "id": "btn-right", - "top": -32.2, - "left": 19.2, - "attrs": { "color": "red", "xray": "1", "key": "1", "label": "Right" } - }, - { - "type": "wokwi-pushbutton", - "id": "btn-back", - "top": -99.4, - "left": 115.2, - "attrs": { "color": "Magenta", "xray": "1", "key": "1", "label": "Back" } - }, - { - "type": "wokwi-pushbutton", - "id": "btn-ok", - "top": 25.4, - "left": 115.2, - "attrs": { "color": "Lime", "xray": "1", "key": "1", "label": "Ok" } - } -], -"connections": [ - [ "esp1:TX", "$serialMonitor:RX", "", [] ], - [ "esp1:RX", "$serialMonitor:TX", "", [] ], - [ "lcd1:VCC", "esp1:3V3", "red", [ "v19.2", "h-57.6", "v-105.6", "h-124.95" ] ], - [ "lcd1:GND", "esp1:GND.2", "black", [ "v28.8", "h-76.8", "v-76.8" ] ], - [ "lcd1:CS", "esp1:11", "orange", [ "v0" ] ], - [ "lcd1:D/C", "esp1:12", "gold", [ "v0" ] ], - [ "lcd1:RESET", "esp1:10", "brown", [ "v0" ] ], - [ "lcd1:MOSI", "esp1:35", "violet", [ "v105.6", "h-124.8", "v-144" ] ], - [ "lcd1:SCK", "esp1:36", "white", [ "v115.2", "h-144", "v-86.4" ] ], - [ "esp1:37", "lcd1:MISO", "magenta", [ "h57.6", "v105.6", "h172.8" ] ], - [ "btn-up:2.l", "esp1:38", "purple", [ "h-9.6", "v19.4" ] ], - [ "btn-up:1.l", "esp1:GND.2", "black", [ "h0" ] ], - [ "btn-down:1.l", "esp1:GND.2", "black", [ "h0" ] ], - [ "btn-down:2.l", "esp1:39", "green", [ "h-19.2", "v115.4" ] ], - [ "btn-left:2.l", "esp1:40", "yellow", [ "h0", "v173" ] ], - [ "btn-left:1.l", "esp1:GND.2", "black", [ "h0" ] ], - [ "btn-right:2.l", "esp1:41", "red", [ "h-19.2", "v153.8" ] ], - [ "btn-right:1.l", "btn-left:1.r", "black", [ "h-96", "v28.8" ] ], - [ "btn-back:2.l", "esp1:42", "Magenta", [ "h-19.2", "v211.58" ] ], - [ "btn-back:1.l", "btn-up:1.r", "black", [ "h0" ] ], - [ "btn-ok:1.l", "btn-down:1.r", "black", [ "h0" ] ], - [ "btn-ok:2.l", "esp1:45", "Lime", [ "h-9.6", "v173" ] ] -], -"dependencies": {} -} + "version": 1, + "author": "CreatureDex", + "editor": "wokwi", + "parts": [ + { + "type": "board-esp32-s3-devkitc-1", + "id": "esp1", + "top": 67.2, + "left": -263.96, + "attrs": {} + }, + { "type": "wokwi-ili9341", "id": "lcd1", "top": -200.8, "left": 354.9, "attrs": {} }, + { + "type": "wokwi-pushbutton", + "id": "btn-up", + "top": -99.4, + "left": -76.8, + "attrs": { "color": "RebeccaPurple", "xray": "1", "key": "1", "label": "Up" } + }, + { + "type": "wokwi-pushbutton", + "id": "btn-down", + "top": 25.4, + "left": -76.8, + "attrs": { "color": "green", "xray": "1", "key": "1", "label": "Down" } + }, + { + "type": "wokwi-pushbutton", + "id": "btn-left", + "top": -32.2, + "left": -163.2, + "attrs": { "color": "yellow", "xray": "1", "key": "1", "label": "Left" } + }, + { + "type": "wokwi-pushbutton", + "id": "btn-right", + "top": -32.2, + "left": 19.2, + "attrs": { "color": "red", "xray": "1", "key": "1", "label": "Right" } + }, + { + "type": "wokwi-pushbutton", + "id": "btn-back", + "top": -99.4, + "left": 115.2, + "attrs": { "color": "Magenta", "xray": "1", "key": "1", "label": "Back" } + }, + { + "type": "wokwi-pushbutton", + "id": "btn-ok", + "top": 25.4, + "left": 115.2, + "attrs": { "color": "Lime", "xray": "1", "key": "1", "label": "Ok" } + }, + { + "type": "wokwi-lcd1602", + "id": "lcd2", + "top": -291.2, + "left": -167.2, + "attrs": { "pins": "i2c" } + } + ], + "connections": [ + [ "esp1:TX", "$serialMonitor:RX", "", [] ], + [ "esp1:RX", "$serialMonitor:TX", "", [] ], + [ "lcd1:VCC", "esp1:3V3", "red", [ "v19.2", "h-57.6", "v-105.6", "h-124.95" ] ], + [ "lcd1:GND", "esp1:GND.2", "black", [ "v28.8", "h-76.8", "v-76.8" ] ], + [ "lcd1:CS", "esp1:11", "orange", [ "v0" ] ], + [ "lcd1:D/C", "esp1:12", "gold", [ "v0" ] ], + [ "lcd1:RESET", "esp1:10", "brown", [ "v0" ] ], + [ "lcd1:MOSI", "esp1:35", "violet", [ "v105.6", "h-124.8", "v-144" ] ], + [ "lcd1:SCK", "esp1:36", "white", [ "v115.2", "h-144", "v-86.4" ] ], + [ "esp1:37", "lcd1:MISO", "magenta", [ "h57.6", "v105.6", "h172.8" ] ], + [ "btn-up:2.l", "esp1:38", "purple", [ "h-9.6", "v19.4" ] ], + [ "btn-up:1.l", "esp1:GND.2", "black", [ "h0" ] ], + [ "btn-down:1.l", "esp1:GND.2", "black", [ "h0" ] ], + [ "btn-down:2.l", "esp1:39", "green", [ "h-19.2", "v115.4" ] ], + [ "btn-left:2.l", "esp1:40", "yellow", [ "h0", "v173" ] ], + [ "btn-left:1.l", "esp1:GND.2", "black", [ "h0" ] ], + [ "btn-right:2.l", "esp1:41", "red", [ "h-19.2", "v153.8" ] ], + [ "btn-right:1.l", "btn-left:1.r", "black", [ "h-96", "v28.8" ] ], + [ "btn-back:2.l", "esp1:42", "Magenta", [ "h-19.2", "v211.58" ] ], + [ "btn-back:1.l", "btn-up:1.r", "black", [ "h0" ] ], + [ "btn-ok:1.l", "btn-down:1.r", "black", [ "h0" ] ], + [ "btn-ok:2.l", "esp1:45", "Lime", [ "h-9.6", "v173" ] ], + [ "lcd2:GND", "esp1:GND.2", "black", [ "h0" ] ], + [ "lcd2:VCC", "esp1:3V3.1", "red", [ "h0" ] ], + [ "lcd2:SDA", "esp1:17", "green", [ "h-134.4", "v413" ] ], + [ "lcd2:SCL", "esp1:18", "green", [ "h-124.8", "v38.7" ] ] + ], + "dependencies": {} +} \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs index 6da0014..56441ea 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -11,14 +11,19 @@ use alloc::boxed::Box; use creaturedex::card::decoder::split_nfc_hex; use creaturedex::card::model::Card; use creaturedex::display::primary_lcd::{PrimaryLcdDisplay, init_primary_lcd}; +use creaturedex::display::tertiary_lcd::{init_tertiary_lcd, write_wrapped}; use creaturedex::drivers::nfc_pn532::NfcPn532Driver; use creaturedex::navigation::inputs::Inputs; use creaturedex::navigation::navigation::{self, CARD_DATA}; use creaturedex::network::wifi::setup_wifi; use embassy_executor::Spawner; +use embedded_hal_bus::i2c::AtomicDevice; +use embedded_hal_bus::util::AtomicCell; +use esp_hal::Blocking; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; use esp_hal::timer::timg::TimerGroup; +use esp_hal::i2c::master::{Config as I2cConfig, I2c}; use log::error; #[panic_handler] @@ -63,14 +68,29 @@ async fn main(spawner: Spawner) { peripherals.GPIO12, // shared dc ))); - setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await; + let config = I2cConfig::default().with_frequency(esp_hal::time::Rate::from_khz(20)); + let i2c = match I2c::new(peripherals.I2C0, config) { + Ok(bus) => bus.with_sda(peripherals.GPIO17).with_scl(peripherals.GPIO18), + Err(e) => { + log::error!("Shared I2C bus initialization failed for GPIO17/GPIO18: {e:?}"); + panic!("Shared I2C bus initialization failed"); + } + }; + let shared_i2c = Box::leak(Box::new(AtomicCell::new(i2c))); + log::info!("Shared I2C bus ready on GPIO17 (SDA) and GPIO18 (SCL)"); - let mut nfc_driver = - NfcPn532Driver::new(peripherals.I2C0, peripherals.GPIO17, peripherals.GPIO18); + let mut lcd = init_tertiary_lcd(AtomicDevice::new(shared_i2c)); + if let Err(_) = write_wrapped(&mut lcd, "Testing more than 16 chars what happens now?") { + log::error!("Tertiary LCD startup text write failed over shared I2C bus; check the shared bus, wiring, and device responses"); + } + + // setup_wifi(peripherals.WIFI, peripherals.FLASH, &spawner).await; + + let mut nfc_driver = NfcPn532Driver::new(AtomicDevice::new(shared_i2c)); if nfc_driver.configure_sam().await.is_ok() { - log::info!("NFC SAM configuration successful"); + log::info!("NFC SAM configuration successful on the shared I2C bus"); } else { - log::error!("NFC SAM configuration failed"); + log::error!("NFC SAM configuration failed on the shared I2C bus"); } let button_config = InputConfig::default().with_pull(Pull::Up); @@ -90,7 +110,7 @@ async fn main(spawner: Spawner) { #[embassy_executor::task] -async fn nfc_driver_task(mut nfc_driver: NfcPn532Driver<'static>) { +async fn nfc_driver_task(mut nfc_driver: NfcPn532Driver>>) { loop { while CARD_DATA.lock().await.is_none() { match nfc_driver.read_card_data().await { diff --git a/src/display/tertiary_lcd.rs b/src/display/tertiary_lcd.rs index 2255c24..d3326ce 100644 --- a/src/display/tertiary_lcd.rs +++ b/src/display/tertiary_lcd.rs @@ -1 +1,88 @@ -// Tertiary LCD module +use embedded_hal::i2c::I2c; +use esp_hal::delay::Delay; +use i2c_character_display::{CharacterDisplayPCF8574T, LcdDisplayType}; +use log::error; + +pub type TertiaryLcd = CharacterDisplayPCF8574T; + +pub fn init_tertiary_lcd(i2c: I2C) -> TertiaryLcd +where + I2C: I2c, +{ + let mut lcd = CharacterDisplayPCF8574T::new(i2c, LcdDisplayType::Lcd16x2, Delay::new()); + match lcd.init() { + Ok(()) => { + log::info!("I2C tertiary LCD initialized on PCF8574T at address 0x27"); + lcd + } + Err(_) => { + error!("I2C tertiary LCD init failed on PCF8574T at address 0x27; check bus wiring, power, and device address"); + panic!("tertiary LCD initialization failed"); + } + } +} + +pub fn write_line( + lcd: &mut TertiaryLcd, + line: u8, + text: &str, +) -> Result<(), i2c_character_display::CharacterDisplayError> +where + I2C: I2c, +{ + if let Err(e) = lcd.set_cursor(0, line) { + error!("I2C LCD cursor move failed on line {line}; bus or device may be unavailable"); + return Err(e); + } + if let Err(e) = lcd.print(text) { + error!("I2C LCD print failed on line {line}; payload length={} and device may be busy or disconnected", text.len()); + return Err(e); + } + Ok(()) +} + +pub fn write_wrapped( + lcd: &mut TertiaryLcd, + text: &str, +) -> Result<(), i2c_character_display::CharacterDisplayError> +where + I2C: I2c, +{ + const DISPLAY_WIDTH: usize = 16; + const DISPLAY_ROWS: usize = 2; + + if let Err(e) = lcd.clear() { + error!("I2C LCD clear failed before wrapped write; check the shared bus and display response"); + return Err(e); + } + + let mut offset = 0; + for row in 0..DISPLAY_ROWS { + let remaining = &text[offset..]; + if remaining.is_empty() { + return Ok(()); + } + + let chunk = if remaining.len() > DISPLAY_WIDTH { + &remaining[..DISPLAY_WIDTH] + } else { + remaining + }; + + if let Err(e) = lcd.set_cursor(0, row as u8) { + error!("I2C LCD cursor move failed while writing wrapped row {row}; bus may be busy or device unresponsive"); + return Err(e); + } + if let Err(e) = lcd.print(chunk) { + error!("I2C LCD wrapped write failed on row {row}; chunk length={} and text may exceed display width", chunk.len()); + return Err(e); + } + offset += chunk.len(); + + if offset >= text.len() { + return Ok(()); + } + } + + Ok(()) +} diff --git a/src/drivers/nfc_pn532.rs b/src/drivers/nfc_pn532.rs index 1b6a789..3af876d 100644 --- a/src/drivers/nfc_pn532.rs +++ b/src/drivers/nfc_pn532.rs @@ -1,9 +1,7 @@ use alloc::vec::Vec; use core::convert::Infallible; -use esp_hal::gpio::interconnect::{PeripheralInput, PeripheralOutput}; -use esp_hal::i2c::master::{Config as I2cConfig, I2c}; -use esp_hal::peripherals::I2C0; -use esp_hal::time::{Duration, Instant, Rate}; +use embedded_hal::i2c::I2c; +use esp_hal::time::{Duration, Instant}; use log::{error, info}; use pn532::i2c::I2CInterface; use pn532::requests::SAMMode; @@ -55,25 +53,21 @@ impl pn532::CountDown for TimerWrapper { } } -pub type Pn532Device<'a> = Pn532>, (), 32>; +pub type Pn532Device = Pn532, (), 32>; /// PN532 NFC reader driver. -pub struct NfcPn532Driver<'a> { - pn532: Pn532Device<'a>, +pub struct NfcPn532Driver +where + I2C: I2c, +{ + pn532: Pn532Device, } -impl<'a> NfcPn532Driver<'a> { - pub fn new(i2c0: I2C0<'static>, sda: SDA, scl: SCL) -> Self - where - SDA: PeripheralInput<'static> + PeripheralOutput<'static>, - SCL: PeripheralInput<'static> + PeripheralOutput<'static>, - { - let config = I2cConfig::default().with_frequency(Rate::from_khz(20)); - let i2c = I2c::new(i2c0, config) - .expect("Failed to init I2C") - .with_sda(sda) - .with_scl(scl); - +impl NfcPn532Driver +where + I2C: I2c, +{ + pub fn new(i2c: I2C) -> Self { let pn532 = Pn532::new_async(I2CInterface { i2c }); Self { pn532 } } @@ -83,10 +77,10 @@ impl<'a> NfcPn532Driver<'a> { &Request::sam_configuration(SAMMode::Normal, false), 0, ).await { - error!("Could not initialize PN532: {e:?}"); + error!("PN532 SAM configuration failed over I2C: {e:?}"); Err(()) } else { - info!("Successfully init'ed PN532"); + info!("PN532 SAM configuration successful on shared I2C bus"); Ok(()) } } @@ -94,11 +88,11 @@ impl<'a> NfcPn532Driver<'a> { pub async fn poll_target(&mut self) -> Result<(), ()> { match self.pn532.process_async(&Request::INLIST_ONE_ISO_A_TARGET, 23).await { Ok(uid) => { - info!("uid = {uid:?}"); + info!("PN532 detected NFC target UID: {uid:?}"); Ok(()) } Err(e) => { - info!("No NFC card found: {e:?}"); + info!("No NFC card found on shared I2C bus: {e:?}"); Err(()) } } @@ -117,12 +111,14 @@ impl<'a> NfcPn532Driver<'a> { } pub async fn read_card_data(&mut self) -> Result, ()> { - self.poll_target().await?; + if let Err(e) = self.poll_target().await { + error!("PN532 I2C target poll failed before reading card data: {e:?}"); + return Err(e); + } let mut data: Vec = Vec::with_capacity(858); for i in 0x0B..=230 { - let mut parse_result = Err(()); while parse_result.is_err() { let Ok(result) = self.pn532.process_async(&Request::ntag_read(i), 17).await else { @@ -139,11 +135,11 @@ impl<'a> NfcPn532Driver<'a> { data.extend(bytes); } - info!("Full data: {data:02x?}"); + info!("PN532 full card payload read over I2C: {:02x?}", data); Ok(data) } - pub fn device_mut(&mut self) -> &mut Pn532Device<'a> { + pub fn device_mut(&mut self) -> &mut Pn532Device { &mut self.pn532 } } diff --git a/src/navigation/navigation.rs b/src/navigation/navigation.rs index 794dacf..2dcec57 100644 --- a/src/navigation/navigation.rs +++ b/src/navigation/navigation.rs @@ -62,15 +62,15 @@ pub async fn run(mut inputs: Inputs, display: &'static mut PrimaryLcdDisplay<'st Either::First(selection_buttons) => match selection_buttons { Either6::First(_) => Action::Up, Either6::Second(_) => Action::Down, - Either6::Third(_) => Action::Back, - Either6::Fourth(_) => Action::Ok, + Either6::Third(_) => Action::Left, + Either6::Fourth(_) => Action::Right, Either6::Fifth(_) => Action::Ok, Either6::Sixth(_) => Action::Back, }, Either::Second(_) => Action::Timer, }; - log::info!("Action: {:?}", action); + log::info!("Action: {:?}", action); let new_state = if action == Action::Back { if state.screens.len() > 1 {