Interfacing Arduino UNO with a PN512 based NFC module
Source code: arduino_pn512_i2c_test.zip
1. Reverse engineering
To start the project, I need first to identify the chip in the module and
determine how it is wired to the connector. The components of the module are
protected by a metal shield that, however, is not soldered to the board and
can be easily removed, revealing a PN512 chip (Fig. 2). This is a broadly adopted NFC frontend
[1] and its datasheet is publicly
available [2]. This chip is available in
different package types and this NFC module mounts the 32-pin HVQFN32 version
(SOT617-1). From Sec. 9 of the datasheet we find that this chip supports
direct interfacing of hosts using SPI, I2C-bus, or serial UART interfaces and
that the specific interface can be selected by configuring the
connections of a fiex combination of pins. If you look at Table 142 (Fig. 3) you can note that if pin A1 is set to HIGH state then the I2C communication
protocol is selected, otherwise the state of pin A0 switches between the SPI
and UART mode.
So, let's identify first the ground (GND) and the power supply (VDD) pins on
the connector. Pin 2 is clearly connected to the ground (Fig. 5) while the capacitor near the main connector (the one on the top of the
board in Fig. 1) can be used to identify
the pin connected to the VDD. A multimeter in continuity testing mode shows
that one of the pins of the capacitor is connected to the ground and the other
one is connected to pin 1 of the connector, therefore pin 1 = VDD and pin 2 =
GND.
|
| Fig. 5 - PN512 wiring diagram. |
Looking again at Table 142 (Fig. 3), we see
that the I2C interface uses ALE and D7, which act as SDA and SCL and are
connected to the two test points labeled TP113 and TP55, respectively
(see Fig. 5). A continuity test with the
multimeter tells us that pin 5 = SDA and pin 6 = SCL. Finally, poking around
with the multimeter I find that pin 3 and pin 4 are connected to an unlabeled
test point and to test point TP 114, respectively, which in turn are connected
to pint NRSTPD and IQR on the chip.
2. Writing the firmware
Since the PN512 is widely used, there is already a library designed to
interface with the PN512 and it can be imported directly into the
Arduino IDE, the MFRC522_PN512 [4][5]. The only issue is that this
library works only with the SPI interface, so we have to implement the
I2C communication handling ourselves.
The base class to handle PN512 communication in MFRC522_PN512 is the
class PN512, which is a subclass of the MFRC522. The class MFRC522
is defined in the files MFRC522.h and MFRC522.cpp. In particular, all
the communication with the chip is handled by the
functions PCD_WriteRegister and PCD_ReadRegister. So, we can
create a new file, let's call it PN512_I2C.h in which we define our new
class PN512_I2C as a sub-class of PN512:
#ifndef PN512_I2C_H
#define PN512_I2C_H
#include <Arduino.h>
#include "PN512.h"
#include <Wire.h>
class PN512_I2C: public PN512
{
using PN512::PN512;
public:
void setI2CAddress(byte addr) { i2c_addr = addr; }
byte getI2CAddress(byte addr) { return addr; }
void PCD_WriteRegister(PCD_Register reg, byte value) override;
void PCD_WriteRegister(PCD_Register reg, byte count, byte *values) override;
byte PCD_ReadRegister(PCD_Register reg) override;
void PCD_ReadRegister(PCD_Register reg, byte count, byte *values, byte rxAlign) override;
private:
byte i2c_addr = 0x28;
};
#endifPN512_I2C.h
#include <Arduino.h>
#include <MFRC522.h>
#include "PN512_I2C.h"
void PN512_I2C::PCD_WriteRegister(PCD_Register reg, byte value) {
reg = reg >> 1;
Wire.beginTransmission(this->i2c_addr);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
}
/**
* Writes a number of bytes to the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void PN512_I2C::PCD_WriteRegister(PCD_Register reg, byte count, byte *values) {
reg = reg >> 1;
Wire.beginTransmission(this->i2c_addr);
Wire.write(reg);
for (byte index = 0; index < count; index++) {
Wire.write(values[index]);
}
Wire.endTransmission();
}
/**
* Reads a byte from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
byte PN512_I2C::PCD_ReadRegister(PCD_Register reg) {
reg = reg >> 1;
Wire.beginTransmission(this->i2c_addr);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(this->i2c_addr, 1, true);
return Wire.read(); // Read the value back. Send 0 to stop reading.
}
/**
* Reads a number of bytes from the specified register in the MFRC522 chip.
* The interface is described in the datasheet section 8.1.2.
*/
void PN512_I2C::PCD_ReadRegister(PCD_Register reg, byte count, byte *values, byte rxAlign) {
if (count == 0) {
return;
}
reg = reg >> 1;
byte index = 0;
Wire.beginTransmission(this->i2c_addr);
// MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3.
Wire.write(0x80 | reg);
Wire.endTransmission();
count--;
// Only update bit positions rxAlign..7 in values[0]
if (rxAlign) {
// Create bit mask for bit positions rxAlign..7
byte mask = (0xFF << rxAlign) & 0xFF;
// Read value
Wire.requestFrom(this->i2c_addr, 1, true);
byte value = value = Wire.read();
// Apply mask to both current value of values[0] and the new data in value.
values[0] = (values[0] & ~mask) | (value & mask);
index++;
}
while (index < count) {
Wire.requestFrom(this->i2c_addr, 1, true);
values[index] = Wire.read();
index++;
}
Wire.requestFrom(this->i2c_addr, 1);
values[index] = Wire.read();
}PN512_I2C.cpp
Finally, we can write the main sketch pn512_i2c_test.ino:
#include "PN512_I2C.h"
#include <Wire.h>
#define RST 4 // set pin 4 as reset
#define IRQ 3 // set pin 3 as IRQ
//create reader instace setting also the reset pin
PN512_I2C reader(RST);
//init counter for first boot
byte serialCounter;
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
Wire.begin();
reader.setI2CAddress(0x28);
reader.PCD_Init();
reader.PCD_AntennaOn();
pinMode(LED_BUILTIN, OUTPUT);
pinMode(IRQ, INPUT);
attachInterrupt(digitalPinToInterrupt(IRQ), PN512_iqr, RISING);
if (reader.PCD_PerformSelfTest()) {
Serial.println("Self test OK!");
} else {
Serial.println("ERROR: self test FAILED!");
}
}
volatile bool new_interrupt = false;
void loop() {
if(Serial && serialCounter == 0) {
serialCounter = 1;
reader.PCD_DumpVersionToSerial();
Serial.println("Reader is ready, scan card or tag");
}
if (new_interrupt) {
new_interrupt = false;
}
if(!reader.PICC_IsNewCardPresent()) return; //wait for new card to be present
if(!reader.PICC_ReadCardSerial()) return; //read that new card
reader.ledBlink(50, LED_BUILTIN); //indicator when the card is read
byte cardUID[reader.uid.size];
for(byte i = 0; i < reader.uid.size; i++){
cardUID[i] = reader.uid.uidByte[i];
}
Serial.println("===========");
reader.PICC_DumpToSerial(&(reader.uid));
Serial.println("-----------");
reader.PICC_DumpDetailsToSerial(&(reader.uid));
Serial.println("-----------\n\n");
delay(50); //optional delay
}
void PN512_iqr() {
// Here we can handle the interrupts...
new_interrupt = true;
}pn512_i2c_test.ino
However, if we try to compile the sketch as it is we will encounter an
error because the functions PCD_WriteRegister and PCD_WriteRegister were
not declared as virtual methods in the library. We need to manually edit
the file MFRC522.h that is located in $arduino_lib_dir/MFRC522_PN512/src, where $arduino_lib_dir is the path where all the libraries are downloaded. We
need to find where the aforementioned functions are defined and we need
to prepend the attribute virtual to each definition. In the end, they
should look like
This modification will not affect the functionality of the library but allows us to easly override the functions used to communicate with the PN512 chip. Unfortunately, this is not persistent and you need to remake this edit every time you update the library. If you want to try it yourself, you can dowload the source code from this link: arduino_pn512_i2c_test.zip
virtual void PCD_WriteRegister(PCD_Register reg, byte value);
virtual void PCD_WriteRegister(PCD_Register reg, byte count, byte *values);
virtual byte PCD_ReadRegister(PCD_Register reg);
virtual void PCD_ReadRegister(PCD_Register reg, byte count, byte *values, byte rxAlign = 0);
3. Testing and conclusions
Now we can compile the sketch and upload it to the UNO. In the video below, I test the NFC reader using an old expired VISA and a NFC tag of a vendor machine. It detects the card and is able to read their UID and some other information but, obviously, it cannot read protected memory of the cards. I expect that a normal NFC tag should work as expected.





Comments
Post a Comment