nrf: Add NFCT driver.

Co-Authored-By: turbocool3r <turbocool3r@gmail.com>
Co-Authored-By: ferris <ferris@devdroplets.com>
This commit is contained in:
Dario Nieuwenhuis 2024-11-20 23:19:37 +01:00
parent 227e073fca
commit 0740b235ac
7 changed files with 508 additions and 0 deletions

View File

@ -161,6 +161,9 @@ embassy_hal_internal::peripherals! {
EGU3,
EGU4,
EGU5,
// NFC
NFCT,
}
impl_uarte!(UARTE0, UARTE0, UARTE0);

View File

@ -181,6 +181,9 @@ embassy_hal_internal::peripherals! {
EGU3,
EGU4,
EGU5,
// NFC
NFCT,
}
impl_usb!(USBD, USBD, USBD);

View File

@ -184,6 +184,9 @@ embassy_hal_internal::peripherals! {
EGU3,
EGU4,
EGU5,
// NFC
NFCT,
}
impl_usb!(USBD, USBD, USBD);

View File

@ -176,6 +176,9 @@ embassy_hal_internal::peripherals! {
// NVMC
NVMC,
// NFC
NFCT,
// UARTE, TWI & SPI
SERIAL0,
SERIAL1,

View File

@ -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(

409
embassy-nrf/src/nfct.rs Normal file
View File

@ -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<interrupt::typelevel::NFCT> 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<P = NFCT> + 'd,
_irq: impl interrupt::typelevel::Binding<interrupt::typelevel::NFCT, InterruptHandler> + '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<usize, Error> {
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)
}
}

View File

@ -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;
}
}
}
}