diff --git a/Cargo.lock b/Cargo.lock index cb1baf7..19bc895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,6 +320,8 @@ dependencies = [ "esp-rtos", "ili9341", "log", + "ndef", + "pn532", ] [[package]] @@ -568,6 +570,26 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "digest" version = "0.10.7" @@ -1980,6 +2002,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "ndef" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39081400340c14a9fcbb0122c311aca4e34ec2d19e3136613638ae7e2a362dbc" +dependencies = [ + "derive_more", + "heapless 0.8.0", + "rustversion", +] + [[package]] name = "nonmax" version = "0.5.5" @@ -2584,6 +2617,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pn532" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce656b2f9335bedbd8806597f6f78e644a955366673cd9280a0fb67a0d7b35a1" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + [[package]] name = "portable-atomic" version = "1.14.0" diff --git a/Cargo.toml b/Cargo.toml index 2ecb546..eeaf461 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,10 @@ embedded-hal-bus = "0.3.0" embedded-graphics = "0.8.2" embedded-graphics-core = "0.4.1" base64 = { version = "0.22", default-features = false, features = ["alloc"] } +pn532 = "0.5.0" +ndef = "0.5.0" + + # For fine tuning these settings, please refer to https://doc.rust-lang.org/cargo/reference/profiles.html diff --git a/src/bin/main.rs b/src/bin/main.rs index 4070797..e09f649 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -10,6 +10,7 @@ use creaturedex::card::mock::{MOCK_PAYLOAD, MOCK_PAYLOAD2}; use creaturedex::display::primary_lcd::init_primary_lcd; use creaturedex::display::views::card_view::render_nfc_payload; +use creaturedex::drivers::nfc_pn532::NfcPn532Driver; use creaturedex::navigation::input::init_navigation_button; use creaturedex::navigation::state::NavigationState; use creaturedex::network::wifi::setup_wifi; @@ -18,6 +19,7 @@ use embassy_time::Timer; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::RgbColor; +use esp_hal::time::Duration; use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; use esp_hal::timer::timg::TimerGroup; @@ -83,7 +85,19 @@ async fn main(spawner: Spawner) { let mut nav_state = NavigationState::new(); + 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() { + log::info!("NFC SAM configuration successful"); + } else { + log::error!("NFC SAM configuration failed"); + } + loop { + if let Ok(card_data) = nfc_driver.read_card_data() { + log::info!("NFC Card Read: {:?}", card_data); + } + if button1.is_low() { log::info!("Button pressed!"); display.clear(Rgb565::BLACK).unwrap(); diff --git a/src/drivers/nfc_pn532.rs b/src/drivers/nfc_pn532.rs index 36d4478..831d70b 100644 --- a/src/drivers/nfc_pn532.rs +++ b/src/drivers/nfc_pn532.rs @@ -1 +1,146 @@ -// Drivers nfc_pn532 module +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 log::{error, info}; +use pn532::i2c::I2CInterface; +use pn532::requests::SAMMode; +use pn532::{nb, Pn532, Request}; + +/// Timer implementation required by `pn532::CountDown`. +pub struct TimerWrapper { + start: Instant, + duration: Duration, + counter: u32, +} + +impl TimerWrapper { + pub fn new() -> Self { + Self { + start: Instant::now(), + duration: Duration::ZERO, + counter: 0, + } + } +} + +impl Default for TimerWrapper { + fn default() -> Self { + Self::new() + } +} + +impl pn532::CountDown for TimerWrapper { + type Time = Duration; + + fn start(&mut self, timeout: T) + where + T: Into, + { + self.start = Instant::now(); + self.duration = timeout.into(); + self.counter = 0; + } + + fn wait(&mut self) -> nb::Result<(), Infallible> { + let elapsed = self.start.elapsed(); + self.counter += 1; + if elapsed >= self.duration { + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } +} + +pub type Pn532Device<'a> = Pn532>, TimerWrapper, 32>; + +/// PN532 NFC reader driver. +pub struct NfcPn532Driver<'a> { + pn532: Pn532Device<'a>, +} + +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); + + let pn532 = Pn532::new(I2CInterface { i2c }, TimerWrapper::new()); + Self { pn532 } + } + + pub fn configure_sam(&mut self, timeout: Duration) -> Result<(), ()> { + if let Err(e) = self.pn532.process( + &Request::sam_configuration(SAMMode::Normal, false), + 0, + timeout, + ) { + error!("Could not initialize PN532: {e:?}"); + Err(()) + } else { + info!("Successfully init'ed PN532"); + Ok(()) + } + } + + pub fn poll_target(&mut self, timeout: Duration) -> Result<(), ()> { + match self.pn532.process(&Request::INLIST_ONE_ISO_A_TARGET, 23, timeout) { + Ok(uid) => { + info!("uid = {uid:?}"); + Ok(()) + } + Err(e) => { + info!("No NFC card found: {e:?}"); + Err(()) + } + } + } + + pub fn parse_nfc_page(iteration: u8, bytes: &[u8]) -> Result<[u8; 4], ()> { + let success = bytes.first().is_some_and(|x| *x == 0); + if success { + let mut arr = [0; 4]; + arr.copy_from_slice(&bytes[1..5]); + Ok(arr) + } else { + info!("err {iteration}: {:02x?}", bytes); + Err(()) + } + } + + pub fn read_card_data(&mut self) -> Result, ()> { + let ms_1000 = Duration::from_millis(1000); + self.poll_target(Duration::from_secs(10))?; + + let mut data: Vec = Vec::with_capacity(858); + + for i in 0x04..=230 { + let mut parse_result = Err(()); + while parse_result.is_err() { + let Ok(result) = self.pn532.process(&Request::ntag_read(i), 17, ms_1000) else { + continue; + }; + parse_result = Self::parse_nfc_page(i, result); + } + let bytes = parse_result.unwrap(); + data.extend(bytes); + } + + info!("Full data: {data:02x?}"); + Ok(data) + } + + pub fn device_mut(&mut self) -> &mut Pn532Device<'a> { + &mut self.pn532 + } +} +