Skip to content

Commit

Permalink
Fixing PN532 to work with SPI (dotnet#1306)
Browse files Browse the repository at this point in the history
* Making SPI working for PN532
* Fixes bug into TryDecode106kbpsTypeA function introduced during nullability migration
* Improving sample with example for each interface
* Adjusting documentation
  • Loading branch information
Ellerbach authored Nov 24, 2020
1 parent 32ce212 commit 8e4fc9c
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 26 deletions.
56 changes: 42 additions & 14 deletions src/devices/Pn532/Pn532.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -152,12 +153,17 @@ public Pn532(string portName, LogLevel logLevel = LogLevel.None)
/// Create a PN532 using SPI
/// </summary>
/// <param name="spiDevice">The SPI Device</param>
/// <param name="pinChipSelect">The GPIO pin number for the chip select</param>
/// <param name="controller">A GPIO controller</param>
/// <param name="logLevel">The log level</param>
public Pn532(SpiDevice spiDevice, LogLevel logLevel = LogLevel.None)
/// <param name="shouldDispose">Dispose the GPIO Controller at the end</param>
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);
Expand Down Expand Up @@ -426,7 +432,9 @@ private bool SetSecurityAccessModule()
// Pass the SAM, the virtual card timeout and remove IRQ
Span<byte> 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);
Expand Down Expand Up @@ -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++)
{
Expand All @@ -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];
Expand Down Expand Up @@ -808,7 +817,7 @@ public override int Transceive(byte targetNumber, ReadOnlySpan<byte> dataToSend,
/// <returns>A raw byte array containing the number of cards, the card type and the raw data. Null if nothing has been polled</returns>
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;
}
Expand Down Expand Up @@ -1207,7 +1216,8 @@ public bool WriteGpio(Port7 p7, Port3 p3)
{
Span<byte> toWrite = stackalloc byte[2]
{
(byte)p7, (byte)p3
(byte)p7,
(byte)p3
};
var ret = WriteCommand(CommandSet.WriteGPIO, toWrite);
if (ret < 0)
Expand Down Expand Up @@ -2018,13 +2028,20 @@ private bool CheckAckFrame()
{
Span<byte> 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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<byte> span)
{
Expand All @@ -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();
Expand Down
8 changes: 4 additions & 4 deletions src/devices/Pn532/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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:

Expand Down Expand Up @@ -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
Expand Down
67 changes: 59 additions & 8 deletions src/devices/Pn532/samples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
Expand Down

0 comments on commit 8e4fc9c

Please sign in to comment.