Add card detection
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
//! Transport abstraction and the I2C implementation.
|
||||
//!
|
||||
//! The PN532 communicates over a framed protocol regardless of the physical
|
||||
//! transport (I2C, SPI or HSU). The [`Interface`] trait mirrors that boundary:
|
||||
//! the driver builds logical commands, the interface handles framing and the
|
||||
//! acknowledgement/response handshake.
|
||||
|
||||
use core::convert::Infallible;
|
||||
use embedded_hal::delay::DelayNs;
|
||||
use embedded_hal::digital::{ErrorType, InputPin, OutputPin};
|
||||
use embedded_hal::i2c::{I2c, Operation};
|
||||
|
||||
use crate::commands::{
|
||||
PN532_ACK_WAIT_TIME_MS, PN532_HOST_TO_PN532, PN532_I2C_ADDRESS, PN532_PN532_TO_HOST,
|
||||
PN532_POSTAMBLE, PN532_PREAMBLE, PN532_STARTCODE1, PN532_STARTCODE2,
|
||||
};
|
||||
use crate::error::Error;
|
||||
|
||||
const ACK: [u8; 6] = [0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00];
|
||||
|
||||
/// Maximum response data length supported (matches the PN532's 64-byte packet buffer).
|
||||
const MAX_DATA_LEN: usize = 64;
|
||||
/// Maximum length of an outbound command frame.
|
||||
const WRITE_FRAME_CAPACITY: usize = 64;
|
||||
|
||||
/// Marker type for when no reset pin is connected.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoReset;
|
||||
|
||||
/// A PN532 reset pin (`RSTPD_N`, active-low).
|
||||
///
|
||||
/// Implemented for any [`OutputPin`] as well as for [`NoReset`] (which does
|
||||
/// nothing). A reset pin is optional but strongly recommended: without a reset
|
||||
/// pulse the PN532 can end up in an uninitialised state and NACK subsequent
|
||||
/// commands (surfacing as `Error::Transport(AcknowledgeCheckFailed(Data))`).
|
||||
pub trait ResetPin {
|
||||
/// Assert reset (drive the pin low).
|
||||
fn assert(&mut self);
|
||||
/// Deassert reset (drive the pin high).
|
||||
fn deassert(&mut self);
|
||||
}
|
||||
|
||||
impl ResetPin for NoReset {
|
||||
fn assert(&mut self) {}
|
||||
fn deassert(&mut self) {}
|
||||
}
|
||||
|
||||
impl<P: OutputPin> ResetPin for P {
|
||||
fn assert(&mut self) {
|
||||
let _ = self.set_low();
|
||||
}
|
||||
|
||||
fn deassert(&mut self) {
|
||||
let _ = self.set_high();
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker type for when no IRQ pin is connected.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct NoIrq;
|
||||
|
||||
impl ErrorType for NoIrq { type Error = Infallible; }
|
||||
|
||||
impl InputPin for NoIrq {
|
||||
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||
panic!("NoIrq should not be used");
|
||||
}
|
||||
|
||||
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||
panic!("NoIrq should not be used");
|
||||
}
|
||||
}
|
||||
|
||||
/// A framed transport to the PN532.
|
||||
///
|
||||
/// Implementations are responsible for the frame/ack handshake described in
|
||||
/// the PN532 user manual (UM0701-02).
|
||||
pub trait Interface {
|
||||
/// The error type produced by the underlying physical transport.
|
||||
type TransportError;
|
||||
|
||||
/// Initialise the hardware: pulse the reset pin (if provided) and wait for
|
||||
/// the PN532 to become ready.
|
||||
fn begin(&mut self) -> Result<(), Error<Self::TransportError>>;
|
||||
|
||||
/// Write a command frame (`header` + optional `body`) and wait for the ACK.
|
||||
fn write_command(
|
||||
&mut self,
|
||||
header: &[u8],
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<Self::TransportError>>;
|
||||
|
||||
/// Read a response frame, returning the length of the payload (excluding
|
||||
/// the `TFI` and command bytes) placed in `buf`.
|
||||
fn read_response(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
timeout_ms: u16,
|
||||
) -> Result<usize, Error<Self::TransportError>>;
|
||||
}
|
||||
|
||||
/// [`Interface`] implementation over a blocking `embedded_hal::i2c::I2c` bus.
|
||||
///
|
||||
/// `RST` and `IRQ` are the optional reset and IRQ pins; use [`NoReset`] /
|
||||
/// [`NoIrq`] (the defaults) or the relevant `embedded_hal` pins via the
|
||||
/// [`I2cInterface::with_reset`] / [`I2cInterface::with_reset_irq`] constructors.
|
||||
pub struct I2cInterface<I2C, D, RST = NoReset, IRQ = NoIrq> {
|
||||
i2c: I2C,
|
||||
delay: D,
|
||||
reset: RST,
|
||||
irq: IRQ,
|
||||
command: u8,
|
||||
}
|
||||
|
||||
impl<I2C, D> I2cInterface<I2C, D, NoReset, NoIrq> {
|
||||
/// Create an interface from an already-configured I2C bus and a delay
|
||||
/// source, without a reset or IRQ pin.
|
||||
pub fn new(i2c: I2C, delay: D) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset: NoReset,
|
||||
irq: NoIrq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST: ResetPin> I2cInterface<I2C, D, RST, NoIrq> {
|
||||
/// Create an interface with a reset pin (`RSTPD_N`).
|
||||
pub fn with_reset(i2c: I2C, delay: D, reset: RST) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset,
|
||||
irq: NoIrq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, IRQ: InputPin> I2cInterface<I2C, D, NoReset, IRQ> {
|
||||
/// Create an interface with an IRQ pin but no reset pin.
|
||||
pub fn with_irq(i2c: I2C, delay: D, irq: IRQ) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset: NoReset,
|
||||
irq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST: ResetPin, IRQ: InputPin> I2cInterface<I2C, D, RST, IRQ> {
|
||||
/// Create an interface with both a reset pin and an IRQ pin.
|
||||
pub fn with_reset_irq(i2c: I2C, delay: D, reset: RST, irq: IRQ) -> Self {
|
||||
Self {
|
||||
i2c,
|
||||
delay,
|
||||
reset,
|
||||
irq,
|
||||
command: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST, IRQ> I2cInterface<I2C, D, RST, IRQ> {
|
||||
/// Consume the interface and return the underlying I2C bus.
|
||||
pub fn release(self) -> I2C {
|
||||
self.i2c
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST, IRQ> Interface for I2cInterface<I2C, D, RST, IRQ>
|
||||
where
|
||||
I2C: I2c,
|
||||
D: DelayNs,
|
||||
RST: ResetPin,
|
||||
IRQ: InputPin,
|
||||
{
|
||||
type TransportError = I2C::Error;
|
||||
|
||||
fn begin(&mut self) -> Result<(), Error<Self::TransportError>> {
|
||||
// Pulse RSTPD_N: high -> low -> wait -> high -> wait. This mirrors the
|
||||
// Adafruit library's begin() reset sequence.
|
||||
self.reset.deassert();
|
||||
self.reset.assert();
|
||||
self.delay.delay_ms(400);
|
||||
self.reset.deassert();
|
||||
// Let the PN532 boot after the reset is released. The Adafruit library
|
||||
// waits ~10 ms + a 500 ms wakeup here; give it a full 500 ms.
|
||||
self.delay.delay_ms(500);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_command(
|
||||
&mut self,
|
||||
header: &[u8],
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<Self::TransportError>> {
|
||||
self.command = header[0];
|
||||
|
||||
let data_len = header.len() + body.len() + 1; // TFI + payload
|
||||
if data_len > 0xFF {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
|
||||
// PREAMBLE + STARTCODE1 + STARTCODE2 + LEN + LCS + TFI + payload + DCS + POSTAMBLE
|
||||
let frame_len = 6 + header.len() + body.len() + 2;
|
||||
let mut frame = [0u8; WRITE_FRAME_CAPACITY];
|
||||
if frame_len > frame.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
|
||||
frame[0] = PN532_PREAMBLE;
|
||||
frame[1] = PN532_STARTCODE1;
|
||||
frame[2] = PN532_STARTCODE2;
|
||||
frame[3] = data_len as u8;
|
||||
frame[4] = (!(data_len as u8)).wrapping_add(1);
|
||||
frame[5] = PN532_HOST_TO_PN532;
|
||||
|
||||
let mut sum = PN532_HOST_TO_PN532;
|
||||
let mut idx = 6;
|
||||
for &b in header.iter().chain(body.iter()) {
|
||||
frame[idx] = b;
|
||||
idx += 1;
|
||||
sum = sum.wrapping_add(b);
|
||||
}
|
||||
frame[idx] = (!sum).wrapping_add(1);
|
||||
frame[idx + 1] = PN532_POSTAMBLE;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::debug!("pn532: write cmd=0x{:02X} len={}", header[0], frame_len);
|
||||
self.i2c
|
||||
.write(PN532_I2C_ADDRESS, &frame[..frame_len])
|
||||
.map_err(Error::Transport)?;
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::debug!("pn532: write ACKed, reading ACK frame");
|
||||
self.read_ack_frame()
|
||||
}
|
||||
|
||||
fn read_response(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
timeout_ms: u16,
|
||||
) -> Result<usize, Error<Self::TransportError>> {
|
||||
// Wait for the PN532 to signal data is ready.
|
||||
self.wait_ready(timeout_ms)?;
|
||||
|
||||
// Read the full response frame in a single transaction, stripping the
|
||||
// RDY byte. Frame layout: PREAMBLE(3) + LEN + LCS + TFI + CMD + payload + DCS + POSTAMBLE.
|
||||
let frame_len = buf.len() + 9;
|
||||
let mut frame = [0u8; MAX_DATA_LEN + 10];
|
||||
if frame_len > frame.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
self.i2c
|
||||
.transaction(
|
||||
PN532_I2C_ADDRESS,
|
||||
&mut [
|
||||
Operation::Read(&mut [0]),
|
||||
Operation::Read(&mut frame[..frame_len]),
|
||||
],
|
||||
)
|
||||
.map_err(Error::Transport)?;
|
||||
|
||||
if frame[0] != PN532_PREAMBLE
|
||||
|| frame[1] != PN532_STARTCODE1
|
||||
|| frame[2] != PN532_STARTCODE2
|
||||
{
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
let length = frame[3] as usize;
|
||||
if frame[3].wrapping_add(frame[4]) != 0 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
let cmd = self.command.wrapping_add(1);
|
||||
if frame[5] != PN532_PN532_TO_HOST || frame[6] != cmd {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
let data_len = length.saturating_sub(2);
|
||||
if data_len > buf.len() {
|
||||
return Err(Error::NoSpace);
|
||||
}
|
||||
let mut sum = frame[5].wrapping_add(frame[6]);
|
||||
for i in 0..data_len {
|
||||
buf[i] = frame[7 + i];
|
||||
sum = sum.wrapping_add(frame[7 + i]);
|
||||
}
|
||||
if sum.wrapping_add(frame[7 + data_len]) != 0 {
|
||||
return Err(Error::InvalidFrame);
|
||||
}
|
||||
|
||||
Ok(data_len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST, IRQ> I2cInterface<I2C, D, RST, IRQ>
|
||||
where
|
||||
I2C: I2c,
|
||||
D: DelayNs,
|
||||
IRQ: InputPin,
|
||||
{
|
||||
/// Wait until the PN532 signals data is ready.
|
||||
///
|
||||
/// With an IRQ pin this blocks until the pin goes low; without one it polls
|
||||
/// the I2C status byte. Either way the status byte is consumed, so the next
|
||||
/// read returns the frame itself.
|
||||
fn wait_ready(&mut self, timeout_ms: u16) -> Result<(), Error<I2C::Error>> {
|
||||
let mut elapsed = 0u16;
|
||||
loop {
|
||||
match self.irq.is_low() {
|
||||
Ok(true) => {
|
||||
// IRQ asserted (active-low): data is ready. Poll the status
|
||||
// byte to confirm and consume it so the next read gets the
|
||||
// frame itself.
|
||||
let mut status = [0u8; 1];
|
||||
match self.i2c.read(PN532_I2C_ADDRESS, &mut status) {
|
||||
Ok(()) if status[0] & 1 == 1 => return Ok(()),
|
||||
_ => {
|
||||
self.delay.delay_ms(1);
|
||||
elapsed += 1;
|
||||
if timeout_ms != 0 && elapsed >= timeout_ms {
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false) | Err(_) => {
|
||||
// IRQ connected but not asserted yet (or a read error):
|
||||
// keep waiting.
|
||||
self.delay.delay_ms(1);
|
||||
elapsed += 1;
|
||||
if timeout_ms != 0 && elapsed >= timeout_ms {
|
||||
return Err(Error::Timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_ack_frame(&mut self) -> Result<(), Error<I2C::Error>> {
|
||||
self.wait_ready(PN532_ACK_WAIT_TIME_MS)?;
|
||||
let mut ack = [0u8; 6];
|
||||
self.i2c
|
||||
.transaction(
|
||||
PN532_I2C_ADDRESS,
|
||||
&mut [
|
||||
Operation::Read(&mut [0]),
|
||||
Operation::Read(&mut ack),
|
||||
],
|
||||
)
|
||||
.map_err(Error::Transport)?;
|
||||
if ack == ACK {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidAck)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user