st25r95/command.rs
1// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <hello@foundationdevices.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! ST25R95 Command System
5//!
6//! This module defines the complete command set for the ST25R95 NFC transceiver.
7//! Each command corresponds to a specific operation that can be performed by the
8//! chip, from basic identification to complex protocol operations.
9//!
10//! ## Command Categories
11//!
12//! ### Configuration Commands
13//! - **ProtocolSelect**: Select RF protocol and configure parameters
14//! - **RdReg/WrReg**: Read/write internal registers for fine-tuning
15//! - **Idle**: Enter low-power mode with configurable wake-up sources
16//!
17//! ### Communication Commands
18//! - **SendRecv**: Send command to tag and receive response (reader mode)
19//! - **Send**: Send response to reader (card emulation mode)
20//! - **Listen**: Enter listening mode for card emulation
21//!
22//! ### Diagnostic Commands
23//! - **Idn**: Read chip identification and version information
24//! - **Echo**: Test communication link with the chip
25//! - **PollField**: Detect presence of external RF fields
26//!
27//! ### Special Commands
28//! - **ACFilter**: Configure anti-collision filtering for card emulation
29//!
30//! ## Command Flow
31//!
32//! Commands follow a specific sequence:
33//!
34//! 1. **Send command** via SPI with parameters
35//! 2. **Wait for IRQ_OUT** indicating completion
36//! 3. **Read response** containing status and optional data
37//!
38//! All commands are sent through the SPI interface using the `St25r95Spi` trait.
39//! The numeric values correspond to the command bytes sent to the ST25R95.
40
41/// ST25R95 Command Enumeration
42///
43/// Represents all supported commands for the ST25R95 NFC transceiver.
44/// Each variant corresponds to a specific operation and has an associated
45/// command byte value used in SPI communication.
46///
47/// See ST25R95 datasheet Section 4.4 for complete command specifications.
48#[derive(Debug, Copy, Clone, PartialEq)]
49pub enum Command {
50 /// Read device identification
51 ///
52 /// Returns chip identification string including:
53 /// - Product name ("NFC" prefix)
54 /// - Device type and version
55 /// - ROM CRC for verification
56 ///
57 /// Used for chip detection and version checking during initialization.
58 Idn = 0x01,
59
60 /// Select RF communication protocol
61 ///
62 /// Configures the ST25R95 for a specific NFC protocol with associated
63 /// parameters. This command must be called before any RF communication.
64 ///
65 /// Parameters vary by protocol and include:
66 /// - Data rate configuration
67 /// - Timing parameters (FDT)
68 /// - Modulation settings
69 /// - Protocol-specific options
70 ProtocolSelect = 0x02,
71
72 /// Poll for RF field presence
73 ///
74 /// Detects whether an external RF field is active. Can also wait for
75 /// field appearance/disappearance with configurable timeout.
76 ///
77 /// Useful for:
78 /// - Detecting nearby readers
79 /// - Multi-reader collision avoidance
80 /// - Power-saving field detection
81 PollField = 0x03,
82
83 /// Send command to tag and receive response
84 ///
85 /// Primary communication command for reader mode. Sends data to an
86 /// NFC tag and automatically receives the tag's response.
87 ///
88 /// Features:
89 /// - Automatic CRC generation/checking
90 /// - Protocol-specific framing
91 /// - Configurable timeouts
92 /// - Automatic retransmission if needed
93 SendRecv = 0x04,
94
95 /// Enter listening mode (card emulation)
96 ///
97 /// Places the ST25R95 in card emulation mode where it waits for
98 /// commands from external NFC readers. The chip remains in this
99 /// state until a command is received or the mode is cancelled.
100 ///
101 /// Used for:
102 /// - Payment card emulation
103 /// - Access token simulation
104 /// - Peer-to-peer communication
105 Listen = 0x05,
106
107 /// Send data to external reader (card emulation)
108 ///
109 /// Sends response data to an external NFC reader while in card
110 /// emulation mode. This command is used after receiving a reader
111 /// command via the `Listen` mode.
112 ///
113 /// Uses load modulation to respond to reader queries.
114 Send = 0x06,
115
116 /// Enter low-power idle mode
117 ///
118 /// Places the ST25R95 in a low-power state with configurable
119 /// wake-up sources. This is the primary power-saving command.
120 ///
121 /// Wake-up sources include:
122 /// - IRQ_IN pulse
123 /// - External RF field detection
124 /// - Tag detection (with calibration)
125 /// - Timeout expiration
126 /// - SPI communication attempt
127 Idle = 0x07,
128
129 /// Read internal register
130 ///
131 /// Reads a single byte from an internal ST25R95 register.
132 /// Used for status checking and configuration verification.
133 ///
134 /// Supports both direct and indexed register access.
135 RdReg = 0x08,
136
137 /// Write internal register
138 ///
139 /// Writes a byte to an internal ST25R95 register.
140 /// Used for configuration and runtime parameter adjustment.
141 ///
142 /// Supports both direct and indexed register access.
143 WrReg = 0x09,
144
145 /// Anti-collision filter command (Type A card emulation)
146 ///
147 /// Configures and manages the anti-collision filter for ISO14443-A
148 /// card emulation. This allows selective card presence emulation.
149 ///
150 /// Functions:
151 /// - Activate/deactivate filter
152 /// - Set filter parameters (ATQA, SAK, UID)
153 /// - Read filter state
154 ACFilter = 0x0D,
155
156 /// Echo test command
157 ///
158 /// Simple diagnostic command that tests the SPI communication
159 /// link with the ST25R95. The chip responds with a simple
160 /// acknowledgement when the command is received.
161 ///
162 /// Used for:
163 /// - Communication verification
164 /// - SPI interface testing
165 /// - Basic connectivity checks
166 Echo = 0x55,
167}
168
169impl TryFrom<u8> for Command {
170 type Error = u8;
171
172 fn try_from(value: u8) -> Result<Self, Self::Error> {
173 match value {
174 0x01 => Ok(Self::Idn),
175 0x02 => Ok(Self::ProtocolSelect),
176 0x03 => Ok(Self::PollField),
177 0x04 => Ok(Self::SendRecv),
178 0x05 => Ok(Self::Listen),
179 0x06 => Ok(Self::Send),
180 0x07 => Ok(Self::Idle),
181 0x08 => Ok(Self::RdReg),
182 0x09 => Ok(Self::WrReg),
183 // 0x0B => Ok(Self::SubFreqRes),
184 0x0D => Ok(Self::ACFilter),
185 0x55 => Ok(Self::Echo),
186 c => Err(c),
187 }
188 }
189}
190
191/// Field detection timing parameters for PollField command
192///
193/// This structure configures the timing for field detection operations
194/// when using the PollField command with a timeout. It allows waiting
195/// for RF field appearance or disappearance with precise timing control.
196///
197/// ## Parameters
198///
199/// - **apparance**: `true` to wait for field appearance, `false` for disappearance
200/// - **presc**: Prescaler value for timing calculation (0-255)
201/// - **timer**: Timer value for timing calculation (0-255)
202///
203/// ## Timing Formula
204///
205/// The timeout is calculated as:
206/// ```text
207/// timeout_us = ((presc + 1) * (timer + 1)) / 13.56
208/// ```
209///
210/// This provides a range from approximately 74μs to 3.6 seconds.
211///
212/// ## Usage Examples
213///
214/// ```rust,ignore
215/// // Wait 100ms for field appearance
216/// let wff = WaitForField {
217/// apparance: true,
218/// presc: 15, // (15 + 1) * (13 + 1) / 13.56 ≈ 16.5ms
219/// timer: 13,
220/// };
221/// nfc.poll_field(Some(wff))?;
222///
223/// // Wait 1 second for field disappearance
224/// let wff = WaitForField {
225/// apparance: false,
226/// presc: 200, // (200 + 1) * (67 + 1) / 13.56 ≈ 1.0s
227/// timer: 67,
228/// };
229/// nfc.poll_field(Some(wff))?;
230/// ```
231#[derive(Debug, Copy, Clone, Default)]
232pub struct WaitForField {
233 /// Wait for field appearance (true) or disappearance (false)
234 pub apparance: bool,
235 /// Prescaler value for timing calculation
236 pub presc: u8,
237 /// Timer value for timing calculation
238 pub timer: u8,
239}
240
241impl WaitForField {
242 /// Calculate the timeout value in microseconds
243 ///
244 /// Returns the configured timeout duration in microseconds based on
245 /// the prescaler and timer values.
246 ///
247 /// ## Formula
248 /// ```text
249 /// timeout_us = ((presc + 1) × (timer + 1)) / 13.56
250 /// ```
251 ///
252 /// ## Examples
253 /// ```rust,ignore
254 /// let wff = WaitForField { apparance: true, presc: 15, timer: 13 };
255 /// assert_eq!(wff.us(), 16523.0); // ~16.5ms
256 /// ```
257 pub fn us(self) -> f32 {
258 (((self.presc) as f32 + 1f32) * ((self.timer as f32) + 1f32)) / 13.56f32
259 }
260}
261
262/// Low-Frequency Oscillator (LFO) frequency selection
263///
264/// The LFO is used during low-power idle mode to generate periodic
265/// wake-up intervals. The frequency determines how often the ST25R95
266/// checks for wake-up conditions when in sleep mode.
267///
268/// ## Frequency Selection Trade-offs
269///
270/// - **Higher frequency** (32 kHz): Faster wake-up response, higher power consumption
271/// - **Lower frequency** (4 kHz): Lower power consumption, slower wake-up response
272///
273/// ## Usage Context
274///
275/// This setting affects:
276/// - Wake-up timing precision
277/// - Field detection responsiveness
278/// - Tag detection calibration intervals
279/// - Overall power consumption in idle mode
280///
281/// ## Typical Applications
282///
283/// - **32 kHz**: Applications requiring fast response (payment systems)
284/// - **16 kHz**: Balanced performance (access control)
285/// - **8 kHz**: Power-conscious applications (inventory tracking)
286/// - **4 kHz**: Maximum power saving (remote sensors)
287#[derive(Debug, Copy, Clone, Default, PartialEq)]
288pub enum LFOFreq {
289 /// 32 kHz LFO frequency
290 ///
291 /// Provides fastest wake-up response but highest power consumption.
292 /// Suitable for applications requiring quick response times.
293 #[default]
294 KHz32 = 0b00,
295
296 /// 16 kHz LFO frequency
297 ///
298 /// Balanced performance with moderate power consumption.
299 /// Good general-purpose choice for most applications.
300 KHz16 = 0b01,
301
302 /// 8 kHz LFO frequency
303 ///
304 /// Lower power consumption with acceptable response time.
305 /// Suitable for battery-powered applications.
306 KHz8 = 0b10,
307
308 /// 4 kHz LFO frequency
309 ///
310 /// Lowest power consumption but slowest wake-up response.
311 /// Ideal for ultra-low-power applications.
312 KHz4 = 0b11,
313}
314
315impl LFOFreq {
316 /// Calculate the LFO period in microseconds
317 ///
318 /// Returns the period of one LFO cycle in microseconds.
319 ///
320 /// ## Formula
321 /// ```text
322 /// period_us = 1 / frequency_kHz * 1000
323 /// ```
324 ///
325 /// ## Examples
326 /// ```rust,ignore
327 /// assert_eq!(LFOFreq::KHz32.period_us(), 31.25);
328 /// assert_eq!(LFOFreq::KHz4.period_us(), 250.0);
329 /// ```
330 pub fn period_us(self) -> f32 {
331 match self {
332 LFOFreq::KHz32 => 31.25,
333 LFOFreq::KHz16 => 62.5,
334 LFOFreq::KHz8 => 125.0,
335 LFOFreq::KHz4 => 250.0,
336 }
337 }
338
339 /// Calculate the reference period in milliseconds
340 ///
341 /// Returns the reference period (tREF) which equals 256 LFO cycles.
342 /// This value is used internally by the ST25R95 for timing calculations.
343 ///
344 /// ## Formula
345 /// ```text
346 /// t_ref_ms = 256 × period_us / 1000
347 /// ```
348 ///
349 /// ## Examples
350 /// ```rust,ignore
351 /// assert_eq!(LFOFreq::KHz32.t_ref_ms(), 8);
352 /// assert_eq!(LFOFreq::KHz4.t_ref_ms(), 64);
353 /// ```
354 pub fn t_ref_ms(self) -> u8 {
355 match self {
356 LFOFreq::KHz32 => 8,
357 LFOFreq::KHz16 => 16,
358 LFOFreq::KHz8 => 32,
359 LFOFreq::KHz4 => 64,
360 }
361 }
362}
363
364impl TryFrom<u8> for LFOFreq {
365 type Error = ();
366
367 fn try_from(value: u8) -> Result<Self, Self::Error> {
368 match value {
369 0b00 => Ok(LFOFreq::KHz32),
370 0b01 => Ok(LFOFreq::KHz16),
371 0b10 => Ok(LFOFreq::KHz8),
372 0b11 => Ok(LFOFreq::KHz4),
373 _ => Err(()),
374 }
375 }
376}
377
378/// Wake-up source configuration for idle mode
379///
380/// This structure defines which events can wake the ST25R95 from low-power
381/// idle mode. Multiple wake-up sources can be enabled simultaneously,
382/// providing flexible power management strategies.
383///
384/// ## Wake-up Source Types
385///
386/// ### Hardware Wake-up
387/// - **IRQ_IN pulse**: Host-controlled wake-up via GPIO pulse
388/// - **Field detection**: External RF field detected
389/// - **Tag detection**: Tag enters the RF field (requires calibration)
390///
391/// ### Timed Wake-up
392/// - **Timeout**: Automatic wake-up after specified period
393///
394/// ## Calibration Requirements
395///
396/// **Tag detection requires calibration** using `calibrate_tag_detector()`:
397///
398/// ```rust,ignore
399/// // Calibrate first
400/// let dac_ref = nfc.calibrate_tag_detector()?;
401///
402/// // Then use tag detection
403/// let params = IdleParams {
404/// wus: WakeUpSource {
405/// tag_detection: true,
406/// timeout: true, // Always enable timeout with tag detection
407/// ..Default::default()
408/// },
409/// ..Default::default()
410/// };
411/// ```
412///
413/// ## Power Consumption Impact
414///
415/// Each enabled wake-up source affects power consumption:
416/// - **IRQ_IN only**: Lowest power (host-controlled)
417/// - **Field detection**: Moderate power (continuous monitoring)
418/// - **Tag detection**: Higher power (periodic field generation)
419/// - **Timeout**: Low power (internal timer only)
420///
421/// ## Recommended Configurations
422///
423/// ```rust,ignore
424/// // Host-controlled wake-up (lowest power)
425/// WakeUpSource {
426/// irq_in_low_pulse: true,
427/// ..Default::default()
428/// }
429///
430/// // Field detection for reader collision avoidance
431/// WakeUpSource {
432/// field_detection: true,
433/// ..Default::default()
434/// }
435///
436/// // Tag detection with timeout failsafe
437/// WakeUpSource {
438/// tag_detection: true,
439/// timeout: true,
440/// ..Default::default()
441/// }
442/// ```
443#[derive(Debug, Copy, Clone, Default, PartialEq)]
444pub struct WakeUpSource {
445 /// Low-Frequency Oscillator frequency setting
446 ///
447 /// Determines the timing resolution for periodic wake-up checks
448 /// and affects overall power consumption. Higher frequencies provide
449 /// faster response but consume more power.
450 pub lfo_freq: LFOFreq,
451
452 /// Special sleep mode wake-up
453 ///
454 /// Reserved for special sleep mode operations. Typically left disabled
455 /// in normal applications.
456 pub ss_low_pulse: bool,
457
458 /// IRQ_IN pulse wake-up
459 ///
460 /// Wake up when the host sends a low pulse on the IRQ_IN GPIO pin.
461 /// This provides host-controlled wake-up with precise timing.
462 ///
463 /// **Use cases**:
464 /// - Host-initiated wake-up for scheduled operations
465 /// - Synchronization with other system events
466 /// - Emergency wake-up from external triggers
467 pub irq_in_low_pulse: bool,
468
469 /// External RF field detection wake-up
470 ///
471 /// Wake up when an external RF field is detected. This is useful for:
472 /// - Detecting nearby NFC readers
473 /// - Multi-reader collision avoidance
474 /// - Power-saving in reader-dense environments
475 ///
476 /// **Note**: Only works when the ST25R95 is not generating its own field.
477 pub field_detection: bool,
478
479 /// Tag presence detection wake-up
480 ///
481 /// Wake up when a tag enters the RF field during periodic field generation.
482 /// This requires **prior calibration** using `calibrate_tag_detector()`.
483 ///
484 /// **Use cases**:
485 /// - Automated inventory systems
486 /// - Tag presence monitoring
487 /// - Smart shelf applications
488 ///
489 /// **Warning**: Requires calibration and always enable timeout as failsafe.
490 pub tag_detection: bool,
491
492 /// Timeout wake-up
493 ///
494 /// Wake up after a specified time period. This provides a reliable
495 /// failsafe mechanism and is recommended when using other wake-up
496 /// sources to prevent infinite idle periods.
497 ///
498 /// **Configuration**: The timeout duration is set by the `max_sleep`
499 /// parameter in `IdleParams`.
500 pub timeout: bool,
501}
502
503impl From<WakeUpSource> for u8 {
504 fn from(wus: WakeUpSource) -> Self {
505 (wus.lfo_freq as u8) << 6
506 | (wus.ss_low_pulse as u8) << 4
507 | (wus.irq_in_low_pulse as u8) << 3
508 | (wus.field_detection as u8) << 2
509 | (wus.tag_detection as u8) << 1
510 | wus.timeout as u8
511 }
512}
513
514impl TryFrom<u8> for WakeUpSource {
515 type Error = ();
516
517 fn try_from(value: u8) -> Result<Self, Self::Error> {
518 Ok(WakeUpSource {
519 lfo_freq: LFOFreq::try_from((value >> 6) & 0b11)?,
520 ss_low_pulse: (value >> 4) & 1 == 1,
521 irq_in_low_pulse: (value >> 3) & 1 == 1,
522 field_detection: (value >> 2) & 1 == 1,
523 tag_detection: (value >> 1) & 1 == 1,
524 timeout: value & 1 == 1,
525 })
526 }
527}
528
529#[derive(Debug, Copy, Clone, PartialEq)]
530pub struct CtrlResConf {
531 pub field_detector_enabled: bool,
532 pub iref_enabled: bool, /* TODO: Must to be set to 1 in WUCtrl for tag detection
533 * operations, otherwise must be put to 0 */
534 pub dac_comp_high: bool,
535 pub lfo_enabled: bool, // TODO: Must be set to 1 in WUCtrl
536 pub hfo_enabled: bool, // TODO: Must be set to 1 in WUCtrl
537 pub vdda_enabled: bool,
538 pub hibernate_state_enabled: bool,
539 pub sleep_state_enabled: bool,
540}
541
542impl Default for CtrlResConf {
543 fn default() -> Self {
544 Self {
545 field_detector_enabled: false,
546 iref_enabled: false,
547 dac_comp_high: false,
548 lfo_enabled: false,
549 hfo_enabled: false,
550 vdda_enabled: false,
551 hibernate_state_enabled: true,
552 sleep_state_enabled: false,
553 }
554 }
555}
556
557impl From<CtrlResConf> for u16 {
558 fn from(ctrl: CtrlResConf) -> Self {
559 (ctrl.field_detector_enabled as u16) << 9
560 | (ctrl.iref_enabled as u16) << 8
561 | (ctrl.dac_comp_high as u16) << 7
562 | (ctrl.lfo_enabled as u16) << 5
563 | (ctrl.hfo_enabled as u16) << 4
564 | (ctrl.vdda_enabled as u16) << 3
565 | (ctrl.hibernate_state_enabled as u16) << 2
566 | ctrl.sleep_state_enabled as u16
567 }
568}
569
570#[derive(Debug, Copy, Clone)]
571pub struct DacData {
572 /// Lower compare value for tag detection.
573 /// This value must be set to 0x00 during tag detection calibration.
574 pub low: u8,
575 /// Higher compare value for tag detection.
576 /// This is a variable used during tag detection calibration.
577 pub high: u8,
578}
579
580/// Configuration parameters for the Idle command
581///
582/// This structure defines the complete configuration for entering low-power
583/// idle mode with the ST25R95. It controls wake-up sources, power management,
584/// timing parameters, and advanced features like tag detection.
585///
586/// ## Power Management States
587///
588/// The Idle command can place the ST25R95 in several low-power states:
589/// - **Sleep**: Moderate power savings, fast wake-up
590/// - **Deep Sleep**: Higher power savings, slower wake-up
591/// - **Hibernate**: Maximum power savings, reset required
592///
593/// ## Wake-up Sources
594///
595/// Multiple wake-up sources can be enabled simultaneously:
596/// - **IRQ_IN pulse**: Host-controlled wake-up
597/// - **Field detection**: External RF field detected
598/// - **Tag detection**: Tag enters RF field (requires calibration)
599/// - **Timeout**: Automatic wake-up after specified time
600/// - **SPI communication**: SPI activity detection
601///
602/// ## Tag Detection Calibration
603///
604/// For reliable tag detection, the DAC comparator thresholds must be
605/// calibrated using the `calibrate_tag_detector()` method. This establishes
606/// the optimal `dac_data.high` and `dac_data.low` values for the specific
607/// hardware environment.
608///
609/// ## Usage Examples
610///
611/// ```rust,ignore
612/// // Simple idle with IRQ_IN wake-up
613/// let params = IdleParams {
614/// wus: WakeUpSource {
615/// irq_in_low_pulse: true,
616/// ..Default::default()
617/// },
618/// ..Default::default()
619/// };
620/// let wake_source = nfc.idle(params)?;
621///
622/// // Tag detection mode (after calibration)
623/// let params = IdleParams {
624/// wus: WakeUpSource {
625/// tag_detection: true,
626/// timeout: true,
627/// ..Default::default()
628/// },
629/// wu_period: 0x20, // Detection period
630/// max_sleep: 10, // Max trials before timeout
631/// ..Default::default()
632/// };
633/// ```
634#[derive(Debug, Copy, Clone)]
635pub struct IdleParams {
636 /// Wake-up sources configuration and LFO frequency
637 ///
638 /// Defines which events can wake the ST25R95 from idle mode and
639 /// the LFO frequency used for periodic checking.
640 pub wus: WakeUpSource,
641
642 /// Power management settings for entering idle mode
643 ///
644 /// Configures which circuits remain active when entering idle mode.
645 /// This affects power consumption and wake-up capabilities.
646 pub enter_ctrl: CtrlResConf,
647
648 /// Power management settings for wake-up period
649 ///
650 /// Configures which circuits are activated during periodic wake-up
651 /// checks (e.g., for field or tag detection).
652 pub wu_ctrl: CtrlResConf,
653
654 /// Power management settings for leaving idle mode
655 ///
656 /// Configures which circuits are activated when leaving idle mode
657 /// and returning to normal operation.
658 pub leave_ctrl: CtrlResConf,
659
660 /// Wake-up period timing
661 ///
662 /// Specifies the time interval between periodic wake-up checks
663 /// when using timed wake-up or tag detection. Value is multiplied
664 /// by the LFO period to determine the actual timing.
665 ///
666 /// - **Range**: 0-255
667 /// - **Effect**: Higher values = longer intervals = lower power
668 pub wu_period: u8,
669
670 /// Oscillator startup delay
671 ///
672 /// Defines the wait time for the High-Frequency Oscillator (HFO)
673 /// to stabilize after wake-up before RF operations can begin.
674 ///
675 /// - **Range**: 0-255
676 /// - **Effect**: Must be long enough for stable oscillation
677 pub osc_start: u8,
678
679 /// DAC startup delay
680 ///
681 /// Defines the wait time for the Digital-to-Analog Converter (DAC)
682 /// to stabilize before tag detection measurements.
683 ///
684 /// - **Range**: 0-255
685 /// - **Effect**: Must be long enough for DAC settling
686 pub dac_start: u8,
687
688 /// Tag detection threshold values
689 ///
690 /// Contains the high and low comparator thresholds for tag detection.
691 /// These values must be calibrated for reliable operation using
692 /// `calibrate_tag_detector()`.
693 pub dac_data: DacData,
694
695 /// Number of RF field swings during tag detection
696 ///
697 /// Specifies how many cycles of the RF field are generated during
698 /// each tag detection attempt. More swings provide better detection
699 /// but consume more power.
700 ///
701 /// - **Range**: 0-255
702 /// - **Trade-off**: Power consumption vs. detection reliability
703 pub swing_count: u8,
704
705 /// Maximum number of detection attempts before timeout
706 ///
707 /// Specifies how many tag detection trials are performed before
708 /// giving up and reporting a timeout. During calibration, this
709 /// should be set to 0x01.
710 ///
711 /// - **Range**: 0-255
712 /// - **Calibration**: Must be 0x01 during `calibrate_tag_detector()`
713 /// - **Normal use**: Higher values for better reliability
714 pub max_sleep: u8,
715}
716
717impl Default for IdleParams {
718 fn default() -> Self {
719 Self {
720 wus: WakeUpSource::default(),
721 enter_ctrl: CtrlResConf::default(),
722 wu_ctrl: CtrlResConf::default(),
723 leave_ctrl: CtrlResConf {
724 field_detector_enabled: false,
725 iref_enabled: false,
726 dac_comp_high: false,
727 lfo_enabled: false,
728 hfo_enabled: true,
729 vdda_enabled: true,
730 hibernate_state_enabled: false,
731 sleep_state_enabled: false,
732 },
733 wu_period: 0x20,
734 osc_start: 0x60,
735 dac_start: 0x60,
736 dac_data: DacData {
737 low: 0x64,
738 high: 0x74,
739 },
740 swing_count: 0x3F,
741 max_sleep: 0x08,
742 }
743 }
744}
745
746impl IdleParams {
747 // TODO: impl a Builder that check max_sleep range
748
749 pub(crate) fn data(self) -> [u8; 14] {
750 let mut data = [0u8; 14];
751 data[0] = self.wus.into();
752 let enter_ctrl: u16 = self.enter_ctrl.into();
753 data[1..3].copy_from_slice(&enter_ctrl.to_le_bytes());
754 let wu_ctrl: u16 = self.wu_ctrl.into();
755 data[3..5].copy_from_slice(&wu_ctrl.to_le_bytes());
756 let leave_ctrl: u16 = self.leave_ctrl.into();
757 data[5..7].copy_from_slice(&leave_ctrl.to_le_bytes());
758 data[7] = self.wu_period;
759 data[8] = self.osc_start;
760 data[9] = self.dac_start;
761 data[10] = self.dac_data.low;
762 data[11] = self.dac_data.high;
763 data[12] = self.swing_count;
764 data[13] = self.max_sleep;
765 data
766 }
767
768 pub fn duration_before_timeout(self) -> f32 {
769 256.0
770 * self.wus.lfo_freq.period_us()
771 * (self.wu_period as f32 + 2.0)
772 * (self.max_sleep as f32 + 1.0)
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779
780 #[test]
781 pub fn test_wakeup_source() {
782 // Wake-up by Timeout
783 assert_eq!(
784 u8::from(WakeUpSource {
785 lfo_freq: LFOFreq::KHz32,
786 ss_low_pulse: false,
787 irq_in_low_pulse: false,
788 field_detection: false,
789 tag_detection: false,
790 timeout: true,
791 }),
792 0x01
793 );
794 // Wake-up by tag detect
795 assert_eq!(
796 u8::from(WakeUpSource {
797 lfo_freq: LFOFreq::KHz32,
798 ss_low_pulse: false,
799 irq_in_low_pulse: false,
800 field_detection: false,
801 tag_detection: true,
802 timeout: false,
803 }),
804 0x02
805 );
806 // Wake-up by low pulse on IRQ_IN pin
807 assert_eq!(
808 u8::from(WakeUpSource {
809 lfo_freq: LFOFreq::KHz32,
810 ss_low_pulse: false,
811 irq_in_low_pulse: true,
812 field_detection: false,
813 tag_detection: false,
814 timeout: false,
815 }),
816 0x08
817 );
818 }
819
820 #[test]
821 pub fn test_ctrl_res_conf() {
822 assert_eq!(
823 CtrlResConf {
824 field_detector_enabled: false,
825 iref_enabled: false,
826 dac_comp_high: false,
827 lfo_enabled: false,
828 hfo_enabled: false,
829 vdda_enabled: false,
830 hibernate_state_enabled: true,
831 sleep_state_enabled: false,
832 },
833 CtrlResConf::default() // Hibernate
834 );
835 assert_eq!(
836 u16::from(CtrlResConf {
837 field_detector_enabled: false,
838 iref_enabled: false,
839 dac_comp_high: false,
840 lfo_enabled: false,
841 hfo_enabled: false,
842 vdda_enabled: false,
843 hibernate_state_enabled: true,
844 sleep_state_enabled: false,
845 }),
846 0x0004 // Hibernate
847 );
848 assert_eq!(
849 u16::from(CtrlResConf {
850 field_detector_enabled: false,
851 iref_enabled: false,
852 dac_comp_high: false,
853 lfo_enabled: false,
854 hfo_enabled: true,
855 vdda_enabled: true,
856 hibernate_state_enabled: false,
857 sleep_state_enabled: false,
858 }),
859 0x0018 // default Leave control
860 );
861 }
862
863 #[test]
864 pub fn test_idle_self() {
865 // Example of switch from Active mode to Hibernate state
866 assert_eq!(
867 IdleParams {
868 wus: WakeUpSource {
869 lfo_freq: LFOFreq::KHz32,
870 ss_low_pulse: false,
871 irq_in_low_pulse: true,
872 field_detection: false,
873 tag_detection: false,
874 timeout: false,
875 },
876 wu_period: 0,
877 osc_start: 0,
878 dac_start: 0,
879 dac_data: DacData { low: 0, high: 0 },
880 swing_count: 0,
881 max_sleep: 0,
882 ..Default::default()
883 }
884 .data(),
885 [0x08, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
886 );
887 // Example of switch from Active to WFE mode (wake-up by low pulse on IRQ_IN pin)
888 assert_eq!(
889 IdleParams {
890 wus: WakeUpSource {
891 lfo_freq: LFOFreq::KHz32,
892 ss_low_pulse: false,
893 irq_in_low_pulse: true,
894 field_detection: false,
895 tag_detection: false,
896 timeout: false,
897 },
898 enter_ctrl: CtrlResConf {
899 field_detector_enabled: false,
900 iref_enabled: false,
901 dac_comp_high: false,
902 lfo_enabled: false,
903 hfo_enabled: false,
904 vdda_enabled: false,
905 hibernate_state_enabled: false,
906 sleep_state_enabled: true,
907 },
908 wu_ctrl: CtrlResConf {
909 field_detector_enabled: false,
910 iref_enabled: false,
911 dac_comp_high: false,
912 lfo_enabled: true,
913 hfo_enabled: true,
914 vdda_enabled: true,
915 hibernate_state_enabled: false,
916 sleep_state_enabled: false,
917 },
918 wu_period: 0,
919 dac_start: 0,
920 dac_data: DacData { low: 0, high: 0 },
921 swing_count: 0,
922 max_sleep: 0,
923 ..Default::default()
924 }
925 .data(),
926 [0x08, 0x01, 0x00, 0x38, 0x00, 0x18, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00]
927 );
928 // Example of switch from Active to WFE mode (wake-up by low pulse on SPI_SS pin)
929 assert_eq!(
930 IdleParams {
931 wus: WakeUpSource {
932 lfo_freq: LFOFreq::KHz32,
933 ss_low_pulse: true,
934 irq_in_low_pulse: false,
935 field_detection: false,
936 tag_detection: false,
937 timeout: false,
938 },
939 enter_ctrl: CtrlResConf {
940 field_detector_enabled: false,
941 iref_enabled: false,
942 dac_comp_high: false,
943 lfo_enabled: false,
944 hfo_enabled: false,
945 vdda_enabled: false,
946 hibernate_state_enabled: false,
947 sleep_state_enabled: true,
948 },
949 wu_ctrl: CtrlResConf {
950 field_detector_enabled: false,
951 iref_enabled: false,
952 dac_comp_high: false,
953 lfo_enabled: true,
954 hfo_enabled: true,
955 vdda_enabled: true,
956 hibernate_state_enabled: false,
957 sleep_state_enabled: false,
958 },
959 wu_period: 0,
960 dac_start: 0,
961 dac_data: DacData { low: 0, high: 0 },
962 swing_count: 0,
963 max_sleep: 0,
964 ..Default::default()
965 }
966 .data(),
967 [0x10, 0x01, 0x00, 0x38, 0x00, 0x18, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00]
968 );
969 // Example of wake-up by Timeout (7 seconds)
970 assert_eq!(
971 IdleParams {
972 wus: WakeUpSource {
973 lfo_freq: LFOFreq::KHz32,
974 ss_low_pulse: false,
975 irq_in_low_pulse: false,
976 field_detection: false,
977 tag_detection: false,
978 timeout: true,
979 },
980 enter_ctrl: CtrlResConf {
981 field_detector_enabled: false,
982 iref_enabled: false,
983 dac_comp_high: false,
984 lfo_enabled: true,
985 hfo_enabled: false,
986 vdda_enabled: false,
987 hibernate_state_enabled: false,
988 sleep_state_enabled: true,
989 },
990 wu_ctrl: CtrlResConf {
991 field_detector_enabled: false,
992 iref_enabled: false,
993 dac_comp_high: false,
994 lfo_enabled: true,
995 hfo_enabled: true,
996 vdda_enabled: true,
997 hibernate_state_enabled: false,
998 sleep_state_enabled: false,
999 },
1000 wu_period: 0,
1001 dac_data: DacData { low: 0, high: 0 },
1002 swing_count: 0,
1003 ..Default::default()
1004 }
1005 .data(),
1006 [0x01, 0x21, 0x00, 0x38, 0x00, 0x18, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x08]
1007 );
1008 // Example of switch from Active to Tag detector mode (wake-up by tag detection or low
1009 // pulse on IRQ_IN pin) (32 kHz, inactivity duration = 272 ms, DAC oscillator = 3 ms,
1010 // Swing = 63 pulses of 13.56 MHz)
1011 assert_eq!(
1012 IdleParams {
1013 wus: WakeUpSource {
1014 lfo_freq: LFOFreq::KHz32,
1015 ss_low_pulse: false,
1016 irq_in_low_pulse: true,
1017 field_detection: false,
1018 tag_detection: true,
1019 timeout: false,
1020 },
1021 enter_ctrl: CtrlResConf {
1022 field_detector_enabled: false,
1023 iref_enabled: false,
1024 dac_comp_high: false,
1025 lfo_enabled: true,
1026 hfo_enabled: false,
1027 vdda_enabled: false,
1028 hibernate_state_enabled: false,
1029 sleep_state_enabled: true,
1030 },
1031 wu_ctrl: CtrlResConf {
1032 field_detector_enabled: false,
1033 iref_enabled: true,
1034 dac_comp_high: false,
1035 lfo_enabled: true,
1036 hfo_enabled: true,
1037 vdda_enabled: true,
1038 hibernate_state_enabled: false,
1039 sleep_state_enabled: true,
1040 },
1041 ..Default::default()
1042 }
1043 .data(),
1044 [0x0A, 0x21, 0x00, 0x39, 0x01, 0x18, 0x00, 0x20, 0x60, 0x60, 0x64, 0x74, 0x3F, 0x08] /* Datasheet gives bytes[3] = 0x79 (with bit 6 set) */
1045 );
1046 // Example of a basic Idle command used during the Tag detection Calibration process
1047 assert_eq!(
1048 IdleParams {
1049 wus: WakeUpSource {
1050 lfo_freq: LFOFreq::KHz32,
1051 ss_low_pulse: false,
1052 irq_in_low_pulse: false,
1053 field_detection: false,
1054 tag_detection: true,
1055 timeout: true,
1056 },
1057 enter_ctrl: CtrlResConf {
1058 field_detector_enabled: false,
1059 iref_enabled: false,
1060 dac_comp_high: true,
1061 lfo_enabled: true,
1062 hfo_enabled: false,
1063 vdda_enabled: false,
1064 hibernate_state_enabled: false,
1065 sleep_state_enabled: true,
1066 },
1067 wu_ctrl: CtrlResConf {
1068 field_detector_enabled: false,
1069 iref_enabled: true,
1070 dac_comp_high: true,
1071 lfo_enabled: true,
1072 hfo_enabled: true,
1073 vdda_enabled: true,
1074 hibernate_state_enabled: false,
1075 sleep_state_enabled: false,
1076 },
1077 dac_data: DacData {
1078 low: 0x00,
1079 high: 0x74
1080 },
1081 max_sleep: 0x01,
1082 ..Default::default()
1083 }
1084 .data(),
1085 [0x03, 0xA1, 0x00, 0xB8, 0x01, 0x18, 0x00, 0x20, 0x60, 0x60, 0x00, 0x74, 0x3F, 0x01] /* Datasheet gives bytes[3] = 0xF8 (with bit 6 set) */
1086 );
1087 // RFAL Idle default value
1088 // RFAL can only modify wu_period and dac_data
1089 assert_eq!(
1090 IdleParams {
1091 wus: WakeUpSource {
1092 lfo_freq: LFOFreq::KHz32,
1093 ss_low_pulse: false,
1094 irq_in_low_pulse: true,
1095 field_detection: false,
1096 tag_detection: true,
1097 timeout: false,
1098 },
1099 enter_ctrl: CtrlResConf {
1100 field_detector_enabled: false,
1101 iref_enabled: false,
1102 dac_comp_high: false,
1103 lfo_enabled: true,
1104 hfo_enabled: false,
1105 vdda_enabled: false,
1106 hibernate_state_enabled: false,
1107 sleep_state_enabled: true,
1108 },
1109 wu_ctrl: CtrlResConf {
1110 field_detector_enabled: false,
1111 iref_enabled: true,
1112 dac_comp_high: false,
1113 lfo_enabled: true,
1114 hfo_enabled: true,
1115 vdda_enabled: true,
1116 hibernate_state_enabled: false,
1117 sleep_state_enabled: false,
1118 },
1119 dac_data: DacData {
1120 low: 0x74,
1121 high: 0x84
1122 },
1123 max_sleep: 0x00,
1124 ..Default::default()
1125 }
1126 .data(),
1127 [0x0A, 0x21, 0x00, 0x38, 0x01, 0x18, 0x00, 0x20, 0x60, 0x60, 0x74, 0x84, 0x3F, 0x00]
1128 );
1129 // RFAL Calibrate default value
1130 // RFAL can only modify wu_period and dac_data
1131 assert_eq!(
1132 IdleParams {
1133 wus: WakeUpSource {
1134 lfo_freq: LFOFreq::KHz32,
1135 ss_low_pulse: false,
1136 irq_in_low_pulse: false,
1137 field_detection: false,
1138 tag_detection: true,
1139 timeout: true,
1140 },
1141 enter_ctrl: CtrlResConf {
1142 field_detector_enabled: false,
1143 iref_enabled: false,
1144 dac_comp_high: true,
1145 lfo_enabled: true,
1146 hfo_enabled: false,
1147 vdda_enabled: false,
1148 hibernate_state_enabled: false,
1149 sleep_state_enabled: true,
1150 },
1151 wu_ctrl: CtrlResConf {
1152 field_detector_enabled: false,
1153 iref_enabled: true,
1154 dac_comp_high: true,
1155 lfo_enabled: true,
1156 hfo_enabled: true,
1157 vdda_enabled: true,
1158 hibernate_state_enabled: false,
1159 sleep_state_enabled: false,
1160 },
1161 wu_period: 0,
1162 dac_data: DacData {
1163 low: 0x00,
1164 high: 0x00
1165 },
1166 max_sleep: 0x01,
1167 ..Default::default()
1168 }
1169 .data(),
1170 [0x03, 0xA1, 0x00, 0xB8, 0x01, 0x18, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x3F, 0x01]
1171 );
1172 }
1173}