diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 7ff58d63e..2d9346229 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -161,6 +161,9 @@ embassy_hal_internal::peripherals! { EGU3, EGU4, EGU5, + + // NFC + NFCT, } impl_uarte!(UARTE0, UARTE0, UARTE0); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index a6273452a..4e4915c32 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -181,6 +181,9 @@ embassy_hal_internal::peripherals! { EGU3, EGU4, EGU5, + + // NFC + NFCT, } impl_usb!(USBD, USBD, USBD); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index fb341afd5..70a56971b 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -184,6 +184,9 @@ embassy_hal_internal::peripherals! { EGU3, EGU4, EGU5, + + // NFC + NFCT, } impl_usb!(USBD, USBD, USBD); diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index 6dc64fb4f..bc613e351 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -176,6 +176,9 @@ embassy_hal_internal::peripherals! { // NVMC NVMC, + // NFC + NFCT, + // UARTE, TWI & SPI SERIAL0, SERIAL1, diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 33111e1bd..7c5dd7c83 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -86,6 +86,14 @@ pub mod gpiote; #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; #[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any( + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-app" +))] +pub mod nfct; +#[cfg(not(feature = "_nrf54l"))] // TODO pub mod nvmc; #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any( diff --git a/embassy-nrf/src/nfct.rs b/embassy-nrf/src/nfct.rs new file mode 100644 index 000000000..2756c7952 --- /dev/null +++ b/embassy-nrf/src/nfct.rs @@ -0,0 +1,409 @@ +//! NFC tag emulator driver. +//! +//! This driver implements support for emulating an ISO14443-3 card. Anticollision and selection +//! are handled automatically in hardware, then the driver lets you receive and reply to +//! raw ISO14443-3 frames in software. +//! +//! Higher layers such as ISO14443-4 aka ISO-DEP and ISO7816 must be handled on top +//! in software. + +#![macro_use] + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +pub use vals::{Bitframesdd as SddPat, Discardmode as DiscardMode}; + +use crate::interrupt::InterruptExt; +use crate::pac::nfct::vals; +use crate::peripherals::NFCT; +use crate::util::slice_in_ram; +use crate::{interrupt, pac, Peripheral}; + +/// NFCID1 (aka UID) of different sizes. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum NfcId { + /// 4-byte UID. + SingleSize([u8; 4]), + /// 7-byte UID. + DoubleSize([u8; 7]), + /// 10-byte UID. + TripleSize([u8; 10]), +} + +/// The protocol field to be sent in the `SEL_RES` response byte (b6-b7). +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum SelResProtocol { + /// Configured for Type 2 Tag platform. + #[default] + Type2 = 0, + /// Configured for Type 4A Tag platform, compliant with ISO/IEC_14443. + Type4A = 1, + /// Configured for the NFC-DEP Protocol. + NfcDep = 2, + /// Configured for the NFC-DEP Protocol and Type 4A Tag platform. + NfcDepAndType4A = 3, +} + +/// Config for the `NFCT` peripheral driver. +#[derive(Clone)] +pub struct Config { + /// NFCID1 to use during autocollision. + pub nfcid1: NfcId, + /// SDD pattern to be sent in `SENS_RES`. + pub sdd_pat: SddPat, + /// Platform config to be sent in `SEL_RES`. + pub plat_conf: u8, + /// Protocol to be sent in the `SEL_RES` response. + pub protocol: SelResProtocol, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + trace!("irq"); + pac::NFCT.inten().write(|w| w.0 = 0); + WAKER.wake(); + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// NFC error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Rx Error received while waiting for frame + RxError, + /// Rx buffer was overrun, increase your buffer size to resolve this + RxOverrun, + /// Lost field. + Deactivated, + /// Collision + Collision, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, +} + +/// NFC tag emulator driver. +pub struct NfcT<'d> { + _p: PeripheralRef<'d, NFCT>, + rx_buf: [u8; 256], + tx_buf: [u8; 256], +} + +impl<'d> NfcT<'d> { + /// Create an Nfc Tag driver + pub fn new( + _p: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + config: &Config, + ) -> Self { + into_ref!(_p); + + let r = pac::NFCT; + + unsafe { + let reset = (r.as_ptr() as *mut u32).add(0xFFC / 4); + reset.write_volatile(0); + reset.read_volatile(); + reset.write_volatile(1); + } + + let nfcid_size = match &config.nfcid1 { + NfcId::SingleSize(bytes) => { + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*bytes)); + + vals::Nfcidsize::NFCID1SINGLE + } + NfcId::DoubleSize(bytes) => { + let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap(); + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk)); + + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(bytes); + r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + vals::Nfcidsize::NFCID1DOUBLE + } + NfcId::TripleSize(bytes) => { + let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap(); + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk)); + + let (bytes, chunk2) = bytes.split_last_chunk::<3>().unwrap(); + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(chunk2); + r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(bytes); + r.nfcid1_3rd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + vals::Nfcidsize::NFCID1TRIPLE + } + }; + + r.sensres().write(|w| { + w.set_nfcidsize(nfcid_size); + w.set_bitframesdd(config.sdd_pat); + w.set_platfconfig(config.plat_conf & 0xF); + }); + + r.selres().write(|w| { + w.set_protocol(config.protocol as u8); + }); + + // errata + #[cfg(feature = "nrf52832")] + unsafe { + // Errata 57 nrf52832 only + //(0x40005610 as *mut u32).write_volatile(0x00000005); + //(0x40005688 as *mut u32).write_volatile(0x00000001); + //(0x40005618 as *mut u32).write_volatile(0x00000000); + //(0x40005614 as *mut u32).write_volatile(0x0000003F); + + // Errata 98 + (0x4000568C as *mut u32).write_volatile(0x00038148); + } + + r.inten().write(|w| w.0 = 0); + + interrupt::NFCT.unpend(); + unsafe { interrupt::NFCT.enable() }; + + // clear all shorts + r.shorts().write(|_| {}); + + let res = Self { + _p, + tx_buf: [0u8; 256], + rx_buf: [0u8; 256], + }; + + assert!(slice_in_ram(&res.tx_buf), "TX Buf not in ram"); + assert!(slice_in_ram(&res.rx_buf), "RX Buf not in ram"); + + res + } + + /// Wait for field on and select. + /// + /// This waits for the field to become on, and then for a reader to select us. The ISO14443-3 + /// sense, anticollision and select procedure is handled entirely in hardware. + /// + /// When this returns, we have successfully been selected as a card. You must then + /// loop calling [`receive`](Self::receive) and responding with [`transmit`](Self::transmit). + pub async fn activate(&mut self) { + let r = pac::NFCT; + loop { + r.events_fieldlost().write_value(0); + r.events_fielddetected().write_value(0); + r.tasks_sense().write_value(1); + + // enable autocoll + #[cfg(not(feature = "nrf52832"))] + r.autocolresconfig().write(|w| w.0 = 0b10); + + r.framedelaymode().write(|w| { + w.set_framedelaymode(vals::Framedelaymode::WINDOW_GRID); + }); + + info!("waiting for field"); + poll_fn(|cx| { + WAKER.register(cx.waker()); + + if r.events_fielddetected().read() != 0 { + r.events_fielddetected().write_value(0); + return Poll::Ready(()); + } + + r.inten().write(|w| { + w.set_fielddetected(true); + }); + Poll::Pending + }) + .await; + + #[cfg(feature = "time")] + embassy_time::Timer::after_millis(1).await; // workaround errata 190 + + r.events_selected().write_value(0); + r.tasks_activate().write_value(1); + + trace!("Waiting to be selected"); + poll_fn(|cx| { + let r = pac::NFCT; + + WAKER.register(cx.waker()); + + if r.events_selected().read() != 0 || r.events_fieldlost().read() != 0 { + return Poll::Ready(()); + } + + r.inten().write(|w| { + w.set_selected(true); + w.set_fieldlost(true); + }); + Poll::Pending + }) + .await; + if r.events_fieldlost().read() != 0 { + continue; + } + + // TODO: add support for "window" frame delay, which is technically + // needed to be compliant with iso14443-4 + r.framedelaymode().write(|w| { + w.set_framedelaymode(vals::Framedelaymode::FREE_RUN); + }); + + // disable autocoll + #[cfg(not(feature = "nrf52832"))] + r.autocolresconfig().write(|w| w.0 = 0b11u32); + + return; + } + } + + /// Transmit an ISO14443-3 frame to the reader. + /// + /// You must call this only after receiving a frame with [`receive`](Self::receive), + /// and only once. Higher-layer protocols usually define timeouts, so calling this + /// too late can cause things to fail. + /// + /// This will fail with [`Error::Deactivated`] if we have been deselected due to either + /// the field being switched off or due to the ISO14443 state machine. When this happens, + /// you must stop calling [`receive`](Self::receive) and [`transmit`](Self::transmit), reset + /// all protocol state, and go back to calling [`activate`](Self::activate). + pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), Error> { + let r = pac::NFCT; + + //Setup DMA + self.tx_buf[..buf.len()].copy_from_slice(buf); + r.packetptr().write_value(self.tx_buf.as_ptr() as u32); + r.maxlen().write(|w| w.0 = buf.len() as _); + + // Set packet length + r.txd().amount().write(|w| { + w.set_txdatabits(0); + w.set_txdatabytes(buf.len() as _); + }); + + r.txd().frameconfig().write(|w| { + w.set_crcmodetx(true); + w.set_discardmode(DiscardMode::DISCARD_END); + w.set_parity(true); + w.set_sof(true); + }); + + r.events_error().write_value(0); + r.events_txframeend().write_value(0); + r.errorstatus().write(|w| w.0 = 0xffff_ffff); + + // Start starttx task + compiler_fence(Ordering::SeqCst); + r.tasks_starttx().write_value(1); + + poll_fn(move |cx| { + trace!("polling tx"); + let r = pac::NFCT; + WAKER.register(cx.waker()); + + if r.events_fieldlost().read() != 0 { + return Poll::Ready(Err(Error::Deactivated)); + } + + if r.events_txframeend().read() != 0 { + trace!("Txframend hit, should be finished trasmitting"); + return Poll::Ready(Ok(())); + } + + if r.events_error().read() != 0 { + trace!("Got error?"); + warn!("errors: {:08x}", r.errorstatus().read().0); + r.events_error().write_value(0); + return Poll::Ready(Err(Error::RxError)); + } + + r.inten().write(|w| { + w.set_txframeend(true); + w.set_error(true); + w.set_fieldlost(true); + }); + + Poll::Pending + }) + .await + } + + /// Receive an ISO14443-3 frame from the reader. + /// + /// After calling this, you must send back a response with [`transmit`](Self::transmit), + /// and only once. Higher-layer protocols usually define timeouts, so calling this + /// too late can cause things to fail. + pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + let r = pac::NFCT; + + r.rxd().frameconfig().write(|w| { + w.set_crcmoderx(true); + w.set_parity(true); + w.set_sof(true); + }); + + //Setup DMA + r.packetptr().write_value(self.rx_buf.as_mut_ptr() as u32); + r.maxlen().write(|w| w.0 = self.rx_buf.len() as _); + + // Reset and enable the end event + r.events_rxframeend().write_value(0); + r.events_rxerror().write_value(0); + + // Start enablerxdata only after configs are finished writing + compiler_fence(Ordering::SeqCst); + r.tasks_enablerxdata().write_value(1); + + poll_fn(move |cx| { + trace!("polling rx"); + let r = pac::NFCT; + WAKER.register(cx.waker()); + + if r.events_fieldlost().read() != 0 { + return Poll::Ready(Err(Error::Deactivated)); + } + + if r.events_rxerror().read() != 0 { + trace!("RXerror got in recv frame, should be back in idle state"); + r.events_rxerror().write_value(0); + warn!("errors: {:08x}", r.errorstatus().read().0); + return Poll::Ready(Err(Error::RxError)); + } + + if r.events_rxframeend().read() != 0 { + trace!("RX Frameend got in recv frame, should have data"); + r.events_rxframeend().write_value(0); + return Poll::Ready(Ok(())); + } + + r.inten().write(|w| { + w.set_rxframeend(true); + w.set_rxerror(true); + w.set_fieldlost(true); + }); + + Poll::Pending + }) + .await?; + + let n = r.rxd().amount().read().rxdatabytes() as usize - 2; + buf[..n].copy_from_slice(&self.rx_buf[..n]); + Ok(n) + } +} diff --git a/examples/nrf52840/src/bin/nfct.rs b/examples/nrf52840/src/bin/nfct.rs new file mode 100644 index 000000000..d559d006a --- /dev/null +++ b/examples/nrf52840/src/bin/nfct.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::config::HfclkSource; +use embassy_nrf::nfct::{Config as NfcConfig, NfcId, NfcT}; +use embassy_nrf::{bind_interrupts, nfct}; +use {defmt_rtt as _, embassy_nrf as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + NFCT => nfct::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_nrf::config::Config::default(); + config.hfclk_source = HfclkSource::ExternalXtal; + let p = embassy_nrf::init(config); + + dbg!("Setting up..."); + let config = NfcConfig { + nfcid1: NfcId::DoubleSize([0x04, 0x68, 0x95, 0x71, 0xFA, 0x5C, 0x64]), + sdd_pat: nfct::SddPat::SDD00100, + plat_conf: 0b0000, + protocol: nfct::SelResProtocol::Type4A, + }; + + let mut nfc = NfcT::new(p.NFCT, Irqs, &config); + + let mut buf = [0u8; 256]; + + loop { + info!("activating"); + nfc.activate().await; + + loop { + info!("rxing"); + let n = match nfc.receive(&mut buf).await { + Ok(n) => n, + Err(e) => { + error!("rx error {}", e); + break; + } + }; + let req = &buf[..n]; + info!("received frame {:02x}", req); + + let mut deselect = false; + let resp = match req { + [0xe0, ..] => { + info!("Got RATS, tx'ing ATS"); + &[0x06, 0x77, 0x77, 0x81, 0x02, 0x80][..] + } + [0xc2] => { + info!("Got deselect!"); + deselect = true; + &[0xc2] + } + _ => { + info!("Got unknown command!"); + &[0xFF] + } + }; + + match nfc.transmit(resp).await { + Ok(()) => {} + Err(e) => { + error!("tx error {}", e); + break; + } + } + + if deselect { + break; + } + } + } +}