aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-11-20 23:19:37 +0100
committerDario Nieuwenhuis <[email protected]>2024-11-20 23:29:22 +0100
commit0740b235ac546081d54d103b0cdd018e7ef2581c (patch)
tree9b35bcf531a98ead9f01f51b19e39050e94aa9d0
parent227e073fca97bcbcec42d9705e0a8ef19fc433b5 (diff)
nrf: Add NFCT driver.
Co-Authored-By: turbocool3r <[email protected]> Co-Authored-By: ferris <[email protected]>
-rw-r--r--embassy-nrf/src/chips/nrf52832.rs3
-rw-r--r--embassy-nrf/src/chips/nrf52833.rs3
-rw-r--r--embassy-nrf/src/chips/nrf52840.rs3
-rw-r--r--embassy-nrf/src/chips/nrf5340_app.rs3
-rw-r--r--embassy-nrf/src/lib.rs8
-rw-r--r--embassy-nrf/src/nfct.rs409
-rw-r--r--examples/nrf52840/src/bin/nfct.rs79
7 files changed, 508 insertions, 0 deletions
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! {
161 EGU3, 161 EGU3,
162 EGU4, 162 EGU4,
163 EGU5, 163 EGU5,
164
165 // NFC
166 NFCT,
164} 167}
165 168
166impl_uarte!(UARTE0, UARTE0, UARTE0); 169impl_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! {
181 EGU3, 181 EGU3,
182 EGU4, 182 EGU4,
183 EGU5, 183 EGU5,
184
185 // NFC
186 NFCT,
184} 187}
185 188
186impl_usb!(USBD, USBD, USBD); 189impl_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! {
184 EGU3, 184 EGU3,
185 EGU4, 185 EGU4,
186 EGU5, 186 EGU5,
187
188 // NFC
189 NFCT,
187} 190}
188 191
189impl_usb!(USBD, USBD, USBD); 192impl_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! {
176 // NVMC 176 // NVMC
177 NVMC, 177 NVMC,
178 178
179 // NFC
180 NFCT,
181
179 // UARTE, TWI & SPI 182 // UARTE, TWI & SPI
180 SERIAL0, 183 SERIAL0,
181 SERIAL1, 184 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;
86#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] 86#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))]
87pub mod i2s; 87pub mod i2s;
88#[cfg(not(feature = "_nrf54l"))] // TODO 88#[cfg(not(feature = "_nrf54l"))] // TODO
89#[cfg(any(
90 feature = "nrf52832",
91 feature = "nrf52833",
92 feature = "nrf52840",
93 feature = "_nrf5340-app"
94))]
95pub mod nfct;
96#[cfg(not(feature = "_nrf54l"))] // TODO
89pub mod nvmc; 97pub mod nvmc;
90#[cfg(not(feature = "_nrf54l"))] // TODO 98#[cfg(not(feature = "_nrf54l"))] // TODO
91#[cfg(any( 99#[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 @@
1//! NFC tag emulator driver.
2//!
3//! This driver implements support for emulating an ISO14443-3 card. Anticollision and selection
4//! are handled automatically in hardware, then the driver lets you receive and reply to
5//! raw ISO14443-3 frames in software.
6//!
7//! Higher layers such as ISO14443-4 aka ISO-DEP and ISO7816 must be handled on top
8//! in software.
9
10#![macro_use]
11
12use core::future::poll_fn;
13use core::sync::atomic::{compiler_fence, Ordering};
14use core::task::Poll;
15
16use embassy_hal_internal::{into_ref, PeripheralRef};
17use embassy_sync::waitqueue::AtomicWaker;
18pub use vals::{Bitframesdd as SddPat, Discardmode as DiscardMode};
19
20use crate::interrupt::InterruptExt;
21use crate::pac::nfct::vals;
22use crate::peripherals::NFCT;
23use crate::util::slice_in_ram;
24use crate::{interrupt, pac, Peripheral};
25
26/// NFCID1 (aka UID) of different sizes.
27#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
28pub enum NfcId {
29 /// 4-byte UID.
30 SingleSize([u8; 4]),
31 /// 7-byte UID.
32 DoubleSize([u8; 7]),
33 /// 10-byte UID.
34 TripleSize([u8; 10]),
35}
36
37/// The protocol field to be sent in the `SEL_RES` response byte (b6-b7).
38#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
39pub enum SelResProtocol {
40 /// Configured for Type 2 Tag platform.
41 #[default]
42 Type2 = 0,
43 /// Configured for Type 4A Tag platform, compliant with ISO/IEC_14443.
44 Type4A = 1,
45 /// Configured for the NFC-DEP Protocol.
46 NfcDep = 2,
47 /// Configured for the NFC-DEP Protocol and Type 4A Tag platform.
48 NfcDepAndType4A = 3,
49}
50
51/// Config for the `NFCT` peripheral driver.
52#[derive(Clone)]
53pub struct Config {
54 /// NFCID1 to use during autocollision.
55 pub nfcid1: NfcId,
56 /// SDD pattern to be sent in `SENS_RES`.
57 pub sdd_pat: SddPat,
58 /// Platform config to be sent in `SEL_RES`.
59 pub plat_conf: u8,
60 /// Protocol to be sent in the `SEL_RES` response.
61 pub protocol: SelResProtocol,
62}
63
64/// Interrupt handler.
65pub struct InterruptHandler {
66 _private: (),
67}
68
69impl interrupt::typelevel::Handler<interrupt::typelevel::NFCT> for InterruptHandler {
70 unsafe fn on_interrupt() {
71 trace!("irq");
72 pac::NFCT.inten().write(|w| w.0 = 0);
73 WAKER.wake();
74 }
75}
76
77static WAKER: AtomicWaker = AtomicWaker::new();
78
79/// NFC error.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82#[non_exhaustive]
83pub enum Error {
84 /// Rx Error received while waiting for frame
85 RxError,
86 /// Rx buffer was overrun, increase your buffer size to resolve this
87 RxOverrun,
88 /// Lost field.
89 Deactivated,
90 /// Collision
91 Collision,
92 /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
93 BufferNotInRAM,
94}
95
96/// NFC tag emulator driver.
97pub struct NfcT<'d> {
98 _p: PeripheralRef<'d, NFCT>,
99 rx_buf: [u8; 256],
100 tx_buf: [u8; 256],
101}
102
103impl<'d> NfcT<'d> {
104 /// Create an Nfc Tag driver
105 pub fn new(
106 _p: impl Peripheral<P = NFCT> + 'd,
107 _irq: impl interrupt::typelevel::Binding<interrupt::typelevel::NFCT, InterruptHandler> + 'd,
108 config: &Config,
109 ) -> Self {
110 into_ref!(_p);
111
112 let r = pac::NFCT;
113
114 unsafe {
115 let reset = (r.as_ptr() as *mut u32).add(0xFFC / 4);
116 reset.write_volatile(0);
117 reset.read_volatile();
118 reset.write_volatile(1);
119 }
120
121 let nfcid_size = match &config.nfcid1 {
122 NfcId::SingleSize(bytes) => {
123 r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*bytes));
124
125 vals::Nfcidsize::NFCID1SINGLE
126 }
127 NfcId::DoubleSize(bytes) => {
128 let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap();
129 r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk));
130
131 let mut chunk = [0u8; 4];
132 chunk[1..].copy_from_slice(bytes);
133 r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk));
134
135 vals::Nfcidsize::NFCID1DOUBLE
136 }
137 NfcId::TripleSize(bytes) => {
138 let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap();
139 r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk));
140
141 let (bytes, chunk2) = bytes.split_last_chunk::<3>().unwrap();
142 let mut chunk = [0u8; 4];
143 chunk[1..].copy_from_slice(chunk2);
144 r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk));
145
146 let mut chunk = [0u8; 4];
147 chunk[1..].copy_from_slice(bytes);
148 r.nfcid1_3rd_last().write(|w| w.0 = u32::from_be_bytes(chunk));
149
150 vals::Nfcidsize::NFCID1TRIPLE
151 }
152 };
153
154 r.sensres().write(|w| {
155 w.set_nfcidsize(nfcid_size);
156 w.set_bitframesdd(config.sdd_pat);
157 w.set_platfconfig(config.plat_conf & 0xF);
158 });
159
160 r.selres().write(|w| {
161 w.set_protocol(config.protocol as u8);
162 });
163
164 // errata
165 #[cfg(feature = "nrf52832")]
166 unsafe {
167 // Errata 57 nrf52832 only
168 //(0x40005610 as *mut u32).write_volatile(0x00000005);
169 //(0x40005688 as *mut u32).write_volatile(0x00000001);
170 //(0x40005618 as *mut u32).write_volatile(0x00000000);
171 //(0x40005614 as *mut u32).write_volatile(0x0000003F);
172
173 // Errata 98
174 (0x4000568C as *mut u32).write_volatile(0x00038148);
175 }
176
177 r.inten().write(|w| w.0 = 0);
178
179 interrupt::NFCT.unpend();
180 unsafe { interrupt::NFCT.enable() };
181
182 // clear all shorts
183 r.shorts().write(|_| {});
184
185 let res = Self {
186 _p,
187 tx_buf: [0u8; 256],
188 rx_buf: [0u8; 256],
189 };
190
191 assert!(slice_in_ram(&res.tx_buf), "TX Buf not in ram");
192 assert!(slice_in_ram(&res.rx_buf), "RX Buf not in ram");
193
194 res
195 }
196
197 /// Wait for field on and select.
198 ///
199 /// This waits for the field to become on, and then for a reader to select us. The ISO14443-3
200 /// sense, anticollision and select procedure is handled entirely in hardware.
201 ///
202 /// When this returns, we have successfully been selected as a card. You must then
203 /// loop calling [`receive`](Self::receive) and responding with [`transmit`](Self::transmit).
204 pub async fn activate(&mut self) {
205 let r = pac::NFCT;
206 loop {
207 r.events_fieldlost().write_value(0);
208 r.events_fielddetected().write_value(0);
209 r.tasks_sense().write_value(1);
210
211 // enable autocoll
212 #[cfg(not(feature = "nrf52832"))]
213 r.autocolresconfig().write(|w| w.0 = 0b10);
214
215 r.framedelaymode().write(|w| {
216 w.set_framedelaymode(vals::Framedelaymode::WINDOW_GRID);
217 });
218
219 info!("waiting for field");
220 poll_fn(|cx| {
221 WAKER.register(cx.waker());
222
223 if r.events_fielddetected().read() != 0 {
224 r.events_fielddetected().write_value(0);
225 return Poll::Ready(());
226 }
227
228 r.inten().write(|w| {
229 w.set_fielddetected(true);
230 });
231 Poll::Pending
232 })
233 .await;
234
235 #[cfg(feature = "time")]
236 embassy_time::Timer::after_millis(1).await; // workaround errata 190
237
238 r.events_selected().write_value(0);
239 r.tasks_activate().write_value(1);
240
241 trace!("Waiting to be selected");
242 poll_fn(|cx| {
243 let r = pac::NFCT;
244
245 WAKER.register(cx.waker());
246
247 if r.events_selected().read() != 0 || r.events_fieldlost().read() != 0 {
248 return Poll::Ready(());
249 }
250
251 r.inten().write(|w| {
252 w.set_selected(true);
253 w.set_fieldlost(true);
254 });
255 Poll::Pending
256 })
257 .await;
258 if r.events_fieldlost().read() != 0 {
259 continue;
260 }
261
262 // TODO: add support for "window" frame delay, which is technically
263 // needed to be compliant with iso14443-4
264 r.framedelaymode().write(|w| {
265 w.set_framedelaymode(vals::Framedelaymode::FREE_RUN);
266 });
267
268 // disable autocoll
269 #[cfg(not(feature = "nrf52832"))]
270 r.autocolresconfig().write(|w| w.0 = 0b11u32);
271
272 return;
273 }
274 }
275
276 /// Transmit an ISO14443-3 frame to the reader.
277 ///
278 /// You must call this only after receiving a frame with [`receive`](Self::receive),
279 /// and only once. Higher-layer protocols usually define timeouts, so calling this
280 /// too late can cause things to fail.
281 ///
282 /// This will fail with [`Error::Deactivated`] if we have been deselected due to either
283 /// the field being switched off or due to the ISO14443 state machine. When this happens,
284 /// you must stop calling [`receive`](Self::receive) and [`transmit`](Self::transmit), reset
285 /// all protocol state, and go back to calling [`activate`](Self::activate).
286 pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), Error> {
287 let r = pac::NFCT;
288
289 //Setup DMA
290 self.tx_buf[..buf.len()].copy_from_slice(buf);
291 r.packetptr().write_value(self.tx_buf.as_ptr() as u32);
292 r.maxlen().write(|w| w.0 = buf.len() as _);
293
294 // Set packet length
295 r.txd().amount().write(|w| {
296 w.set_txdatabits(0);
297 w.set_txdatabytes(buf.len() as _);
298 });
299
300 r.txd().frameconfig().write(|w| {
301 w.set_crcmodetx(true);
302 w.set_discardmode(DiscardMode::DISCARD_END);
303 w.set_parity(true);
304 w.set_sof(true);
305 });
306
307 r.events_error().write_value(0);
308 r.events_txframeend().write_value(0);
309 r.errorstatus().write(|w| w.0 = 0xffff_ffff);
310
311 // Start starttx task
312 compiler_fence(Ordering::SeqCst);
313 r.tasks_starttx().write_value(1);
314
315 poll_fn(move |cx| {
316 trace!("polling tx");
317 let r = pac::NFCT;
318 WAKER.register(cx.waker());
319
320 if r.events_fieldlost().read() != 0 {
321 return Poll::Ready(Err(Error::Deactivated));
322 }
323
324 if r.events_txframeend().read() != 0 {
325 trace!("Txframend hit, should be finished trasmitting");
326 return Poll::Ready(Ok(()));
327 }
328
329 if r.events_error().read() != 0 {
330 trace!("Got error?");
331 warn!("errors: {:08x}", r.errorstatus().read().0);
332 r.events_error().write_value(0);
333 return Poll::Ready(Err(Error::RxError));
334 }
335
336 r.inten().write(|w| {
337 w.set_txframeend(true);
338 w.set_error(true);
339 w.set_fieldlost(true);
340 });
341
342 Poll::Pending
343 })
344 .await
345 }
346
347 /// Receive an ISO14443-3 frame from the reader.
348 ///
349 /// After calling this, you must send back a response with [`transmit`](Self::transmit),
350 /// and only once. Higher-layer protocols usually define timeouts, so calling this
351 /// too late can cause things to fail.
352 pub async fn receive(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
353 let r = pac::NFCT;
354
355 r.rxd().frameconfig().write(|w| {
356 w.set_crcmoderx(true);
357 w.set_parity(true);
358 w.set_sof(true);
359 });
360
361 //Setup DMA
362 r.packetptr().write_value(self.rx_buf.as_mut_ptr() as u32);
363 r.maxlen().write(|w| w.0 = self.rx_buf.len() as _);
364
365 // Reset and enable the end event
366 r.events_rxframeend().write_value(0);
367 r.events_rxerror().write_value(0);
368
369 // Start enablerxdata only after configs are finished writing
370 compiler_fence(Ordering::SeqCst);
371 r.tasks_enablerxdata().write_value(1);
372
373 poll_fn(move |cx| {
374 trace!("polling rx");
375 let r = pac::NFCT;
376 WAKER.register(cx.waker());
377
378 if r.events_fieldlost().read() != 0 {
379 return Poll::Ready(Err(Error::Deactivated));
380 }
381
382 if r.events_rxerror().read() != 0 {
383 trace!("RXerror got in recv frame, should be back in idle state");
384 r.events_rxerror().write_value(0);
385 warn!("errors: {:08x}", r.errorstatus().read().0);
386 return Poll::Ready(Err(Error::RxError));
387 }
388
389 if r.events_rxframeend().read() != 0 {
390 trace!("RX Frameend got in recv frame, should have data");
391 r.events_rxframeend().write_value(0);
392 return Poll::Ready(Ok(()));
393 }
394
395 r.inten().write(|w| {
396 w.set_rxframeend(true);
397 w.set_rxerror(true);
398 w.set_fieldlost(true);
399 });
400
401 Poll::Pending
402 })
403 .await?;
404
405 let n = r.rxd().amount().read().rxdatabytes() as usize - 2;
406 buf[..n].copy_from_slice(&self.rx_buf[..n]);
407 Ok(n)
408 }
409}
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 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_nrf::config::HfclkSource;
7use embassy_nrf::nfct::{Config as NfcConfig, NfcId, NfcT};
8use embassy_nrf::{bind_interrupts, nfct};
9use {defmt_rtt as _, embassy_nrf as _, panic_probe as _};
10
11bind_interrupts!(struct Irqs {
12 NFCT => nfct::InterruptHandler;
13});
14
15#[embassy_executor::main]
16async fn main(_spawner: Spawner) {
17 let mut config = embassy_nrf::config::Config::default();
18 config.hfclk_source = HfclkSource::ExternalXtal;
19 let p = embassy_nrf::init(config);
20
21 dbg!("Setting up...");
22 let config = NfcConfig {
23 nfcid1: NfcId::DoubleSize([0x04, 0x68, 0x95, 0x71, 0xFA, 0x5C, 0x64]),
24 sdd_pat: nfct::SddPat::SDD00100,
25 plat_conf: 0b0000,
26 protocol: nfct::SelResProtocol::Type4A,
27 };
28
29 let mut nfc = NfcT::new(p.NFCT, Irqs, &config);
30
31 let mut buf = [0u8; 256];
32
33 loop {
34 info!("activating");
35 nfc.activate().await;
36
37 loop {
38 info!("rxing");
39 let n = match nfc.receive(&mut buf).await {
40 Ok(n) => n,
41 Err(e) => {
42 error!("rx error {}", e);
43 break;
44 }
45 };
46 let req = &buf[..n];
47 info!("received frame {:02x}", req);
48
49 let mut deselect = false;
50 let resp = match req {
51 [0xe0, ..] => {
52 info!("Got RATS, tx'ing ATS");
53 &[0x06, 0x77, 0x77, 0x81, 0x02, 0x80][..]
54 }
55 [0xc2] => {
56 info!("Got deselect!");
57 deselect = true;
58 &[0xc2]
59 }
60 _ => {
61 info!("Got unknown command!");
62 &[0xFF]
63 }
64 };
65
66 match nfc.transmit(resp).await {
67 Ok(()) => {}
68 Err(e) => {
69 error!("tx error {}", e);
70 break;
71 }
72 }
73
74 if deselect {
75 break;
76 }
77 }
78 }
79}