From 8e4fc9cc193f4802bae92e84cd4db256be956a2b Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Tue, 24 Nov 2020 20:01:55 +0300 Subject: [PATCH] Fixing PN532 to work with SPI (#1306) * Making SPI working for PN532 * Fixes bug into TryDecode106kbpsTypeA function introduced during nullability migration * Improving sample with example for each interface * Adjusting documentation --- src/devices/Pn532/Pn532.cs | 56 +++++++++++++++++------ src/devices/Pn532/README.md | 8 ++-- src/devices/Pn532/samples/Program.cs | 67 ++++++++++++++++++++++++---- 3 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/devices/Pn532/Pn532.cs b/src/devices/Pn532/Pn532.cs index ce18f97dfe..3e4bcddfa2 100644 --- a/src/devices/Pn532/Pn532.cs +++ b/src/devices/Pn532/Pn532.cs @@ -55,6 +55,7 @@ public class Pn532 : CardTransceiver, IDisposable private GpioController? _controller = null; private SerialPort? _serialPort = null; private int _pin = 18; + private bool _shouldDispose; private SecurityAccessModuleMode _securityAccessModuleMode = SecurityAccessModuleMode.Normal; private uint _virtualCardTimeout = 0x17; @@ -152,12 +153,17 @@ public Pn532(string portName, LogLevel logLevel = LogLevel.None) /// Create a PN532 using SPI /// /// The SPI Device + /// The GPIO pin number for the chip select + /// A GPIO controller /// The log level - public Pn532(SpiDevice spiDevice, LogLevel logLevel = LogLevel.None) + /// Dispose the GPIO Controller at the end + public Pn532(SpiDevice spiDevice, int pinChipSelect, GpioController? controller = null, LogLevel logLevel = LogLevel.None, bool shouldDispose = false) { LogLevel = logLevel; _spiDevice = spiDevice ?? throw new ArgumentNullException(nameof(spiDevice)); - _controller = new GpioController(); + _shouldDispose = shouldDispose || controller == null; + _controller = controller ?? new GpioController(); + _pin = pinChipSelect >= 0 ? pinChipSelect : throw new ArgumentException($"Pin ChipSelect can only be positive"); _controller.OpenPin(_pin, PinMode.Output); _controller.Write(_pin, PinValue.High); Thread.Sleep(2); @@ -426,7 +432,9 @@ private bool SetSecurityAccessModule() // Pass the SAM, the virtual card timeout and remove IRQ Span toSend = stackalloc byte[3] { - (byte)_securityAccessModuleMode, (byte)(_virtualCardTimeout), 0x00 + (byte)_securityAccessModuleMode, + (byte)(_virtualCardTimeout), + 0x00 }; var ret = WriteCommand(CommandSet.SAMConfiguration, toSend); LogInfo.Log($"{nameof(SetSecurityAccessModule)} Write: {ret}", LogLevel.Debug); @@ -564,7 +572,7 @@ private bool SetParameters(ParametersFlags parametersFlags) try { byte[] nfcId = new byte[toDecode[4]]; - byte[] ats = new byte[toDecode[5 + nfcId.Length]]; + byte[] ats = new byte[0]; for (int i = 0; i < nfcId.Length; i++) { @@ -573,6 +581,7 @@ private bool SetParameters(ParametersFlags parametersFlags) if ((5 + nfcId.Length) > toDecode.Length) { + ats = new byte[toDecode[5 + nfcId.Length]]; for (int i = 0; i < ats.Length; i++) { ats[i] = toDecode[6 + nfcId.Length + i]; @@ -808,7 +817,7 @@ public override int Transceive(byte targetNumber, ReadOnlySpan dataToSend, /// A raw byte array containing the number of cards, the card type and the raw data. Null if nothing has been polled public byte[]? AutoPoll(byte numberPolling, ushort periodMilliSecond, PollingType[] pollingType) { - if (pollingType is null or { Length: >15 }) + if (pollingType is null or { Length: > 15 }) { return null; } @@ -1207,7 +1216,8 @@ public bool WriteGpio(Port7 p7, Port3 p3) { Span toWrite = stackalloc byte[2] { - (byte)p7, (byte)p3 + (byte)p7, + (byte)p3 }; var ret = WriteCommand(CommandSet.WriteGPIO, toWrite); if (ret < 0) @@ -2018,13 +2028,20 @@ private bool CheckAckFrame() { Span ackReceived = stackalloc byte[6] { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 }; if (_spiDevice is object && _controller is object) { _controller.Write(_pin, PinValue.Low); - _spiDevice.WriteByte(ReadData); + _spiDevice.WriteByte(ReverseByte(ReadData)); _spiDevice.Read(ackReceived); + ReverseByte(ackReceived); + _controller.Write(_pin, PinValue.High); } else if (_i2cDevice is object) @@ -2058,8 +2075,8 @@ private bool IsReady() if (_spiDevice is object && _controller is object) { _controller.Write(_pin, PinValue.Low); - _spiDevice.WriteByte(ReadStatus); - ret = _spiDevice.ReadByte(); + _spiDevice.WriteByte(ReverseByte(ReadStatus)); + ret = ReverseByte(_spiDevice.ReadByte()); _controller.Write(_pin, PinValue.High); } else if (_i2cDevice is object) @@ -2102,10 +2119,7 @@ private bool IsReady() 0xff }; - private static byte ReverseByte(byte toReverse) - { - return BitReverseTable[toReverse]; - } + private static byte ReverseByte(byte toReverse) => BitReverseTable[toReverse]; private static void ReverseByte(Span span) { @@ -2127,6 +2141,20 @@ public void Dispose() _i2cDevice?.Dispose(); _i2cDevice = null!; + if (_shouldDispose) + { + if (_controller is object) + { + if (_controller.IsPinOpen(_pin)) + { + _controller.ClosePin(_pin); + } + + _controller.Dispose(); + _controller = null; + } + } + if (_serialPort is { IsOpen: true }) { _serialPort.Close(); diff --git a/src/devices/Pn532/README.md b/src/devices/Pn532/README.md index 44c38e3009..43908026ac 100644 --- a/src/devices/Pn532/README.md +++ b/src/devices/Pn532/README.md @@ -1,6 +1,6 @@ # PN532 - RFID and NFC reader -PN532 is a RFID and NFC reader. It does supports various standards: IsoIec14443TypeA, IsoIec14443TypeB, Iso18092. This implementation should support as well PN533 which is a full ASB serial only implementation and have few more registers and functions but looks retro compatible with this implentation. +PN532 is a RFID and NFC reader. It does supports various standards: IsoIec14443TypeA, IsoIec14443TypeB, Iso18092. This implementation should support as well PN533 which is a full ASB serial only implementation and have few more registers and functions but looks retro compatible with this implementation. ## Documentation @@ -18,7 +18,7 @@ string device = "/dev/ttyS0"; pn532 = new Pn532(device); ``` -To act as a crad reader, the PN532 has to be in listening mode. 2 options are available, either thru using the ```ListPassiveTarget``` either the ```AutoPoll``` functions. +To act as a card reader, the PN532 has to be in listening mode. 2 options are available, either thru using the ```ListPassiveTarget``` either the ```AutoPoll``` functions. Example with polling a simple passive 14443 type A card like a Mifare: @@ -182,8 +182,8 @@ It is possible to emulate any Type A, Type B and Felica cards. Communication support: - [X] HSU serial port: fully supported - [X] I2C: supported -- [ ] SPI: experimental, using a specific chip select pin as well as using LSB with reverse bytes function rather than built in function. This is due to current limitation in the SPI implementations -- [ ] Hardware reset pin +- [X] SPI: supported but using a specific chip select pin as well as using LSB with reverse bytes function rather than built in function. This is due to current limitation in the SPI implementations for Raspberry Pi. **Important**: when using Chip Select, it must be a pin which is different from the SPI defined one. +- [ ] Hardware reset pin: This can be done with the user code. Miscellaneous commands: - [X] Diagnose. Note: partial implementation, basics tests implemented only diff --git a/src/devices/Pn532/samples/Program.cs b/src/devices/Pn532/samples/Program.cs index 2458178a5b..052b2f81b0 100644 --- a/src/devices/Pn532/samples/Program.cs +++ b/src/devices/Pn532/samples/Program.cs @@ -3,6 +3,9 @@ using System; using System.Collections.Generic; +using System.Device.Gpio; +using System.Device.I2c; +using System.Device.Spi; using System.Linq; using System.Threading; using Iot.Device.Card; @@ -11,15 +14,53 @@ using Iot.Device.Pn532; using Iot.Device.Pn532.ListPassive; -string device = "/dev/ttyS0"; -using Pn532 pn532 = new Pn532(device); -if (args.Length > 0) +Pn532 pn532; + +Console.WriteLine("Welcome to Pn532 example."); +Console.WriteLine("Which interface do you want to use with your Pn532?"); +Console.WriteLine("1. HSU: Hight Speed UART (high speed serial port)"); +Console.WriteLine("2. I2C"); +Console.WriteLine("3. SPI"); +var choiceInterface = Console.ReadKey(); +Console.WriteLine(); +if (choiceInterface is not { KeyChar: '1' or '2' or '3' }) { - pn532.LogLevel = LogLevel.Debug; + Console.WriteLine($"You can only select 1, 2 or 3"); + return; +} + +Console.WriteLine("Do you want log level to Debug? Y/N"); +var debugLevelConsole = Console.ReadKey(); +Console.WriteLine(); +LogLevel debugLevel = debugLevelConsole is { KeyChar: 'Y' or 'y' } ? LogLevel.Debug : LogLevel.None; + +if (choiceInterface is { KeyChar: '3' }) +{ + Console.WriteLine("Which pin number do you want as Chip Select?"); + var pinSelectConsole = Console.ReadLine(); + int pinSelect; + try + { + pinSelect = Convert.ToInt32(pinSelectConsole); + } + catch (Exception ex) when (ex is FormatException || ex is OverflowException) + { + Console.WriteLine("Impossible to convert the pin number."); + return; + } + + pn532 = new Pn532(SpiDevice.Create(new SpiConnectionSettings(0)), pinSelect, logLevel: debugLevel); +} +else if (choiceInterface is { KeyChar: '2' }) +{ + pn532 = new Pn532(I2cDevice.Create(new I2cConnectionSettings(1, Pn532.I2cDefaultAddress)), debugLevel); } else { - pn532.LogLevel = LogLevel.None; + Console.WriteLine("Please enter the serial port to use. ex: COM3 on Windows or /dev/ttyS0 on Linux"); + + var device = Console.ReadLine(); + pn532 = new Pn532(device!, debugLevel); } if (pn532.FirmwareVersion is FirmwareVersion version) @@ -34,16 +75,18 @@ // To run tests, uncomment the next line // RunTests(pn532); - ReadMiFare(pn532); + // ReadMiFare(pn532); // To read Credit Cards, uncomment the next line - // ReadCreditCard(pn532); + ReadCreditCard(pn532); } else { Console.WriteLine($"Error"); } +pn532?.Dispose(); + void DumpAllRegisters(Pn532 pn532) { const int MaxRead = 16; @@ -90,6 +133,13 @@ void ReadMiFare(Pn532 pn532) return; } + for (int i = 0; i < retData.Length; i++) + { + Console.Write($"{retData[i]:X} "); + } + + Console.WriteLine(); + var decrypted = pn532.TryDecode106kbpsTypeA(retData.AsSpan().Slice(1)); if (decrypted is object) { @@ -102,7 +152,8 @@ void ReadMiFare(Pn532 pn532) MifareCard mifareCard = new(pn532, decrypted.TargetNumber) { - BlockNumber = 0, Command = MifareCardCommand.AuthenticationA + BlockNumber = 0, + Command = MifareCardCommand.AuthenticationA }; mifareCard.SetCapacity(decrypted.Atqa, decrypted.Sak);