Skip to content

Improve performance and add ability to sleep #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 36 additions & 25 deletions adafruit_pn532/adafruit_pn532.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,39 +164,21 @@
_FRAME_START = b"\x00\x00\xFF"


def _reset(pin):
"""Perform a hardware reset toggle"""
pin.direction = Direction.OUTPUT
pin.value = True
time.sleep(0.1)
pin.value = False
time.sleep(0.5)
pin.value = True
time.sleep(0.1)


class BusyError(Exception):
"""Base class for exceptions in this module."""


class PN532:
"""PN532 driver base, must be extended for I2C/SPI/UART interfacing"""

def __init__(self, *, debug=False, reset=None):
def __init__(self, *, debug=False, irq=None, reset=None):
"""Create an instance of the PN532 class
"""
self.low_power = True
self.debug = debug
if reset:
if debug:
print("Resetting")
_reset(reset)

try:
self._wakeup()
_ = self.firmware_version # first time often fails, try 2ce
return
except (BusyError, RuntimeError):
pass
self._irq = irq
self._reset_pin = reset
self.reset()
_ = self.firmware_version

def _read_data(self, count):
Expand All @@ -218,6 +200,18 @@ def _wakeup(self):
# Send special command to wake up
raise NotImplementedError

def reset(self):
"""Perform a hardware reset toggle and then wake up the PN532"""
if self._reset_pin:
if self.debug:
print("Resetting")
self._reset_pin.direction = Direction.OUTPUT
self._reset_pin.value = False
time.sleep(0.1)
self._reset_pin.value = True
time.sleep(0.1)
self._wakeup()

def _write_frame(self, data):
"""Write a frame to the PN532 with the specified data bytearray."""
assert (
Expand Down Expand Up @@ -255,7 +249,7 @@ def _read_frame(self, length):
might be returned!
"""
# Read frame with expected length of data.
response = self._read_data(length + 8)
response = self._read_data(length + 7)
if self.debug:
print("Read frame:", [hex(i) for i in response])

Expand Down Expand Up @@ -306,6 +300,9 @@ def send_command(
Will wait up to timeout seconds for the acknowlegment and return True.
If no acknowlegment is received, False is returned.
"""
if self.low_power:
self._wakeup()

# Build frame data with command and parameters.
data = bytearray(2 + len(params))
data[0] = _HOSTTOPN532
Expand All @@ -316,7 +313,6 @@ def send_command(
try:
self._write_frame(data)
except OSError:
self._wakeup()
return False
if not self._wait_ready(timeout):
return False
Expand All @@ -342,6 +338,21 @@ def process_response(self, command, response_length=0, timeout=1):
# Return response data.
return response[2:]

def power_down(self):
"""Put the PN532 into a low power state. If the reset pin is connected a
hard power down is performed, if not, a soft power down is performed
instead. Returns True if the PN532 was powered down successfully or
False if not."""
if self._reset_pin: # Hard Power Down if the reset pin is connected
self._reset_pin.value = False
self.low_power = True
else:
# Soft Power Down otherwise. Enable wakeup on I2C, SPI, UART
response = self.call_function(_COMMAND_POWERDOWN, params=[0xB0, 0x00])
self.low_power = response[0] == 0x00
time.sleep(0.005)
return self.low_power

@property
def firmware_version(self):
"""Call PN532 GetFirmwareVersion function and return a tuple with the IC,
Expand Down
23 changes: 10 additions & 13 deletions adafruit_pn532/i2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import adafruit_bus_device.i2c_device as i2c_device
from digitalio import Direction
from micropython import const
from adafruit_pn532.adafruit_pn532 import PN532, BusyError, _reset
from adafruit_pn532.adafruit_pn532 import PN532, BusyError

_I2C_ADDRESS = const(0x24)

Expand All @@ -55,23 +55,23 @@ def __init__(self, i2c, *, irq=None, reset=None, req=None, debug=False):
reset pin and debugging output.
"""
self.debug = debug
self._irq = irq
self._req = req
if reset:
_reset(reset)
self._i2c = i2c_device.I2CDevice(i2c, _I2C_ADDRESS)
super().__init__(debug=debug, reset=reset)
super().__init__(debug=debug, irq=irq, reset=reset)

def _wakeup(self): # pylint: disable=no-self-use
"""Send any special commands/data to wake up PN532"""
if self._reset_pin:
self._reset_pin.value = True
time.sleep(0.01)
if self._req:
self._req.direction = Direction.OUTPUT
self._req.value = True
time.sleep(0.1)
self._req.value = False
time.sleep(0.1)
time.sleep(0.01)
self._req.value = True
time.sleep(0.5)
time.sleep(0.01)
self.low_power = False
self.SAM_configuration() # Put the PN532 back in normal mode

def _wait_ready(self, timeout=1):
"""Poll PN532 if status byte is ready, up to `timeout` seconds"""
Expand All @@ -82,11 +82,10 @@ def _wait_ready(self, timeout=1):
with self._i2c:
self._i2c.readinto(status)
except OSError:
self._wakeup()
continue
if status == b"\x01":
return True # No longer busy
time.sleep(0.05) # lets ask again soon!
time.sleep(0.01) # lets ask again soon!
# Timed out!
return False

Expand All @@ -101,8 +100,6 @@ def _read_data(self, count):
i2c.readinto(frame) # ok get the data, plus statusbyte
if self.debug:
print("Reading: ", [hex(i) for i in frame[1:]])
else:
time.sleep(0.1)
return frame[1:] # don't return the status byte

def _write_data(self, framebytes):
Expand Down
14 changes: 7 additions & 7 deletions adafruit_pn532/spi.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,19 @@ class PN532_SPI(PN532):
def __init__(self, spi, cs_pin, *, irq=None, reset=None, debug=False):
"""Create an instance of the PN532 class using SPI"""
self.debug = debug
self._irq = irq
self._spi = spi_device.SPIDevice(spi, cs_pin)
super().__init__(debug=debug, reset=reset)
super().__init__(debug=debug, irq=irq, reset=reset)

def _wakeup(self):
"""Send any special commands/data to wake up PN532"""
if self._reset_pin:
self._reset_pin.value = True
time.sleep(0.01)
with self._spi as spi:
time.sleep(1)
spi.write(bytearray([0x00])) # pylint: disable=no-member
time.sleep(1)
time.sleep(0.01)
self.low_power = False
self.SAM_configuration() # Put the PN532 back in normal mode

def _wait_ready(self, timeout=1):
"""Poll PN532 if status byte is ready, up to `timeout` seconds"""
Expand All @@ -85,7 +88,6 @@ def _wait_ready(self, timeout=1):
timestamp = time.monotonic()
with self._spi as spi:
while (time.monotonic() - timestamp) < timeout:
time.sleep(0.02) # required
spi.write_readinto(
status_cmd, status_response
) # pylint: disable=no-member
Expand All @@ -103,7 +105,6 @@ def _read_data(self, count):
frame[0] = reverse_bit(_SPI_DATAREAD)

with self._spi as spi:
time.sleep(0.02) # required
spi.write_readinto(frame, frame) # pylint: disable=no-member
for i, val in enumerate(frame):
frame[i] = reverse_bit(val) # turn LSB data to MSB
Expand All @@ -119,5 +120,4 @@ def _write_data(self, framebytes):
if self.debug:
print("Writing: ", [hex(i) for i in rev_frame])
with self._spi as spi:
time.sleep(0.02) # required
spi.write(bytes(rev_frame)) # pylint: disable=no-member
32 changes: 17 additions & 15 deletions adafruit_pn532/uart.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,34 @@
class PN532_UART(PN532):
"""Driver for the PN532 connected over Serial UART"""

def __init__(self, uart, *, irq=None, reset=None, debug=False):
def __init__(self, uart, *, reset=None, debug=False):
"""Create an instance of the PN532 class using Serial connection.
Optional IRQ pin (not used), reset pin and debugging output.
Optional reset pin and debugging output.
"""
self.debug = debug
self._irq = irq
self._uart = uart
super().__init__(debug=debug, reset=reset)

def _wakeup(self):
"""Send any special commands/data to wake up PN532"""
# self._write_frame([_HOSTTOPN532, _COMMAND_SAMCONFIGURATION, 0x01])
if self._reset_pin:
self._reset_pin.value = True
time.sleep(0.01)
self.low_power = False
self._uart.write(
b"\x55\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
) # wake up!
self.SAM_configuration()

def _wait_ready(self, timeout=1):
"""Wait `timeout` seconds"""
time.sleep(timeout)
return True
timestamp = time.monotonic()
while (time.monotonic() - timestamp) < timeout:
if self._uart.in_waiting > 0:
return True # No Longer Busy
time.sleep(0.01) # lets ask again soon!
# Timed out!
return False

def _read_data(self, count):
"""Read a specified count of bytes from the PN532."""
Expand All @@ -71,17 +81,9 @@ def _read_data(self, count):
raise BusyError("No data read from PN532")
if self.debug:
print("Reading: ", [hex(i) for i in frame])
else:
time.sleep(0.1)
return frame

def _write_data(self, framebytes):
"""Write a specified count of bytes to the PN532"""
while self._uart.read(
1
): # this would be a lot nicer if we could query the # of bytes
pass
self._uart.write(
"\x55\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
) # wake up!
self._uart.reset_input_buffer()
self._uart.write(framebytes)
59 changes: 59 additions & 0 deletions examples/pn532_low_power.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
This example shows connecting to the PN532 with I2C (requires clock
stretching support), SPI, or UART. SPI is best, it uses the most pins but
is the most reliable and universally supported. In this example we put the PN532
into low power mode and sleep for 1 second in-between trying to read tags.
After initialization, try waving various 13.56MHz RFID cards over it!
"""

import time
import board
import busio
from digitalio import DigitalInOut

#
# NOTE: pick the import that matches the interface being used
#
from adafruit_pn532.i2c import PN532_I2C

# from adafruit_pn532.spi import PN532_SPI
# from adafruit_pn532.uart import PN532_UART

# I2C connection:
i2c = busio.I2C(board.SCL, board.SDA)

# Non-hardware
# pn532 = PN532_I2C(i2c, debug=False)

# With I2C, we recommend connecting RSTPD_N (reset) to a digital pin for manual
# harware reset
reset_pin = DigitalInOut(board.D6)
# On Raspberry Pi, you must also connect a pin to P32 "H_Request" for hardware
# wakeup! this means we don't need to do the I2C clock-stretch thing
req_pin = DigitalInOut(board.D12)
pn532 = PN532_I2C(i2c, debug=False, reset=reset_pin, req=req_pin)

# SPI connection:
# spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
# cs_pin = DigitalInOut(board.D5)
# pn532 = PN532_SPI(spi, cs_pin, debug=False)

# UART connection
# uart = busio.UART(board.TX, board.RX, baudrate=115200, timeout=0.1)
# pn532 = PN532_UART(uart, debug=False)

ic, ver, rev, support = pn532.firmware_version
print("Found PN532 with firmware version: {0}.{1}".format(ver, rev))

# Configure PN532 to communicate with MiFare cards
pn532.SAM_configuration()

print("Waiting for RFID/NFC card...")
while True:
# Check if a card is available to read
uid = pn532.read_passive_target(timeout=0.5)
print(".", end="")
if uid is not None:
print("Found card with UID:", [hex(i) for i in uid])
pn532.power_down()
time.sleep(1.0)