mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-12-03 12:12:29 +00:00
nrf: Add NFCT driver.
Co-Authored-By: turbocool3r <turbocool3r@gmail.com> Co-Authored-By: ferris <ferris@devdroplets.com>
This commit is contained in:
parent
227e073fca
commit
0740b235ac
@ -161,6 +161,9 @@ embassy_hal_internal::peripherals! {
|
||||
EGU3,
|
||||
EGU4,
|
||||
EGU5,
|
||||
|
||||
// NFC
|
||||
NFCT,
|
||||
}
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0);
|
||||
|
@ -181,6 +181,9 @@ embassy_hal_internal::peripherals! {
|
||||
EGU3,
|
||||
EGU4,
|
||||
EGU5,
|
||||
|
||||
// NFC
|
||||
NFCT,
|
||||
}
|
||||
|
||||
impl_usb!(USBD, USBD, USBD);
|
||||
|
@ -184,6 +184,9 @@ embassy_hal_internal::peripherals! {
|
||||
EGU3,
|
||||
EGU4,
|
||||
EGU5,
|
||||
|
||||
// NFC
|
||||
NFCT,
|
||||
}
|
||||
|
||||
impl_usb!(USBD, USBD, USBD);
|
||||
|
@ -176,6 +176,9 @@ embassy_hal_internal::peripherals! {
|
||||
// NVMC
|
||||
NVMC,
|
||||
|
||||
// NFC
|
||||
NFCT,
|
||||
|
||||
// UARTE, TWI & SPI
|
||||
SERIAL0,
|
||||
SERIAL1,
|
||||
|
@ -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
409
embassy-nrf/src/nfct.rs
Normal 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)
|
||||
}
|
||||
}
|
79
examples/nrf52840/src/bin/nfct.rs
Normal file
79
examples/nrf52840/src/bin/nfct.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user