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}