arduino-nfc-pn532

Leer, grabar, o emular tags NFC con Arduino y PN532

El PN532 es un chip NFC que podemos conectar a un procesador como Arduino para leer y escribir tarjetas NFC, comunicarse con móviles, o incluso actuar como tag NFC. Es un integrado ampliamente utilizado en todo tipo de dispositivos comerciales que implementan NDC.

Recordar que el NFC, es un superset del RFID, que es un sistema de comunicación inalámbrica. El NFC añade funciones de seguridad, especialmente la limitación del rango de lectura de escritura al rango de 50-100mm.

Para más información sobre el RFID podéis consultar la entrada Lectura de tarjetas RFID con Arduino y lector MIFARERC522.

El NFC se ha popularizado como sistema de comunicación debido a su mayor seguridad y a la posibilidad de actuar como Peer-to-Peer, es decir, que un dispositivo NFC puede actuar tanto como receptor. Esto ha propiciado que dispositivos como los smartphone incorporen un chip NFC, tanto para leer tarjetas, como para actuar como tag en servicios, por ejemplo, de autentificación o pago.

La comunicación es muy sencilla, ya que podemos comunicarnos a través de SPI, I2C o HSU (High Speed UART). El PN532 opera 3.3V, pero dispone de conversión de nivel por lo que es posible conectarlo con un procesador de 5V.

Precio

El PN532 es un poco más caro que el MIFARE RC522, pero también es un dispositivo muy superior a este. Aun así, es un dispositivo asequible. Podemos encontrarlo por unos 3.5€ en vendedores internacionales de AliExpress o Ebay.

arduino-nfc-pn532-componente

¿Cómo funciona el PN532?

El PN532 incluye un procesador 80C51 con 40 KB ROM y 1 KB RAM, junto con los elementos necesarios para hacer funcionar correctamente la comunicación NFC. Los módulos Maker disponibles incluyen una antena en la propia PCB.

arduino-nfc-pn532-i2c-funcionamiento

Soporta 6 modos de operación:

  • ISO/IEC 14443A/MIFARE Lector/Grabador.
  • ISO/IEC 14443A/MIFARE Card MIFARE Classic 1K y MIFARE Classic 4K Card.
  • ISO/IEC 14443B Lector/Grabador.
  • FeliCa Lector/Grabador.
  • FeliCa Card emulación.
  • ISO/IEC 18092, ECMA 340 Peer-to-Peer

Las distancias típicas de actuación son de 50mm para lectura y escritura, y 100mm para emulación. La velocidad de lectura es de hasta 212 kbits/s y la de escritura de hasta 424kbts/s. Estos valores dependen, entre otros, de la antena integrada en el módulo. En los módulos Maker el alcance típico es 30mm a 50mm.

El PN532 opera a 3.3V pero admite tensiones de alimentación de 2.7V a 5.4V. Las líneas de comunicación I2C/UART funcionan a 3.3V a 24V TTL. Sin embargo, el interface SPI funciona a 3.3, pero incorpora resistencias de 100 Ohm en serie de forma que también puede ser conectado a 5V.

El consumo es de 100mA en modo Stand-By y 120mA durante la lectura y escritura. Adicionalmente dispone de dos modos de baja energía, uno modo Soft-Power-Down con un consumo de 22uA, y un modo Hard-Power-Down con un consumo de 1uA.

El PN532 también puede ser usado con ordenadores y micro ordenadores como Rasperry Pi con la librería libnfc.

Formato NDEF (NFC Data Exchange Format)

El NFC Data Echange Format (NDEF) es una forma estandarizada de intercambiar información entre dispositivos NFC compatibles. El formato NDEF es usado para almacenar información de intercambio, como por ejemplo URIs, texto plano, etc.

Un mensaje NDEF está formado por dos componentes, messages y records. Un mensaje está formado por uno o varios records. Cada uno de los records consta de un tipo de records, un ID y los propios datos en si (payload), junto con los campos de longitud de cada uno de los anteriores.

Las tags NFC como las tarjetas Mifare Classic pueden ser configuradas como tags NDEF. Los mensajes NDEF también pueden ser usados para intercambiar datos entre dos dispositivos NFC activos (modo Peer-to-Peer)

El estándar es mantenido por el NFC Forum, y está disponible para su consulta de forma gratuita, previa aceptación de las condiciones de licencia.

Esquema de montaje

EL PN532 puede conectarse tanto por I2C, por SPI o UART. El interface de comunicación se elige mediante unos Jumper ubicados en la placa.

arduino-nfc-pn532-configuracion

Conexión por I2C

La conexión por I2C sería la siguiente

arduino-nfc-pn532-i2c-esquema

Que vista desde Arduino quedaría así.

arduino-nfc-pn532-i2c-conexion

Conexión por SPI

Por su parte, la conexión mediante SPI es la siguiente

arduino-nfc-pn532-spi-esquema

Que vista desde Arduino queda así,

arduino-nfc-pn532-spi-conexion

Conexión por SPI

Finalmente, la conexión mediante HSU (High Speed UART) es la siguiente

arduino-nfc-pn532-uart-esquema

Que vista desde Arduino quedaría así,

arduino-nfc-pn532-uart-conexion

Los pines I2C y SPI indicados son válidos para los modelos de Arduino Uno, Nano y Mini Pro. Para otros modelos de Arduino consultar el esquema patillaje correspondiente.

Ejemplos de código

Para programar el PN532 usaremos la librería desarrollada por Adafruit disponible en este enlace. Esta librería únicamente permite trabajar con comunicación I2C y SPI, pero no UART.

La librería proporciona ejemplos de código, que resulta aconsejable revisar. Los siguientes ejemplos son modificaciones a partir de los disponibles en la librería.

Mostrar datos y UID

En este ejemplo mostramos los datos del lector PN532, y de los tag que acerquemos.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>


#define PN532_IRQ   (2)

#define PN532_RESET (3)  // Not connected by default on the NFC Shield

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup(void) {
  Serial.begin(115200);
 
  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("PN53x no encontrado");
    while (1); // halt
  }

  // Mostrar datos del sensor
  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); 
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); 
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
  
  // Configurar para leer etiquetas RFID
  nfc.setPassiveActivationRetries(0xFF);
  nfc.SAMConfig();
  
  Serial.println("Esperando tarjeta ISO14443A");
}

// Funcion auxiliar para mostrar el buffer
void printArray(byte *buffer, byte bufferSize) {
   for (byte i = 0; i < bufferSize; i++) {
      Serial.print(buffer[i] < 0x10 ? " 0" : " ");
      Serial.print(buffer[i], HEX);
   }
}
 

void loop(void) {
  boolean success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
  uint8_t uidLength;

  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength);
  
  if (success) {
    Serial.println("Tarjeta encontrada");
    Serial.print("UID Longitud: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
    Serial.print("UID: "); printArray(uid, uidLength);
    Serial.println("");
  
    delay(1000);
  }
  else
  {    
    Serial.println("Tarjeta no encontrada");
  }
}

arduino-nfc-pn532-0-detectar-tarjeta

Grabar datos

En este ejemplo vemos cómo grabar datos en la memoria de un tag.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>


#define PN532_IRQ   (2)

#define PN532_RESET (3)

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup(void) 
{
  Serial.begin(115200);

  // Configurar para leer etiquetas RFID
  nfc.begin();
  nfc.setPassiveActivationRetries(0xFF);
  nfc.SAMConfig();
  
  Serial.println("Esperando tarjeta");
}

void loop(void) 
{
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
  uint8_t uidLength;
    
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);  
  if (success) {
      Serial.println("Intentando autentificar bloque 4 con clave KEYA");
      uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

      success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);   
      if (success)
      {
        Serial.println("Sector 1 (Bloques 4 a 7) autentificados");
        uint8_t data[16];
 
        memcpy(data, (const uint8_t[]){ 'l', 'u', 'i', 's', 'l', 'l', 'a', 'm', 'a', 's', '.', 'e', 's', 0, 0, 0 }, sizeof data);
        success = nfc.mifareclassic_WriteDataBlock (4, data);
    
        if (success)
        {          
          Serial.println("Datos escritos en bloque 4");
          delay(10000);           
        }
        else
        {
          Serial.println("Fallo al escribir tarjeta");
          delay(1000);   
        }
      }
      else
      {
        Serial.println("Fallo autentificar tarjeta");
        delay(1000); 
      }
    }
}

arduino-nfc-pn532-1-escribir-tarjeta

Leer datos

En este ejemplo vemos cómo leer datos previamente grabados en la memoria de un tag.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>


#define PN532_IRQ   (2)

#define PN532_RESET (3)

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup(void) 
{
  Serial.begin(115200);

  // Configurar para leer etiquetas RFID
  nfc.begin();
  nfc.setPassiveActivationRetries(0xFF);
  nfc.SAMConfig();
  
  Serial.println("Esperando tarjeta");
}

void loop(void) 
{
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
  uint8_t uidLength;
    
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);  
  if (success) 
  {
      Serial.println("Intentando autentificar bloque 4 con clave KEYA");
      uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

      success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);   
      if (success)
      {
        Serial.println("Sector 1 (Bloques 4 a 7) autentificados");
        uint8_t data[16];
          
        success = nfc.mifareclassic_ReadDataBlock(4, data);    
        if (success)
        {          
      Serial.println("Datos leidos de sector 4:");
          nfc.PrintHexChar(data, 16);
          Serial.println("");
          delay(5000);               
        }
        else
        {
          Serial.println("Fallo al leer tarjeta");
        }
      }
      else
      {
        Serial.println("Fallo autentificar tarjeta");
      }
    }
}

arduino-nfc-pn532-2-leer-tarjeta

Comprobar UID

En este ejemplo validamos si una tarjeta es válida comprobando su UID.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>


#define PN532_IRQ   (2)

#define PN532_RESET (3)

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup(void) {
  Serial.begin(115200);

  // Configurar para leer etiquetas RFID
  nfc.begin();
  nfc.setPassiveActivationRetries(0xFF);
  nfc.SAMConfig();

  Serial.println("Esperando tarjeta");
}

const uint8_t validUID[4] = { 0xC8, 0x3E, 0xE7, 0x59 };  // Ejemplo de UID valido

//Función para comparar dos vectores
bool isEqualArray(uint8_t* arrayA, uint8_t* arrayB, uint8_t length)
{
  for (uint8_t index = 0; index < length; index++)
  {
    if (arrayA[index] != arrayB[index]) return false;
  }
  return true;
}

void loop(void)
{
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
  uint8_t uidLength;

  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  if (success)
  {
    uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

    if (isEqualArray(uid, validUID, uidLength))
    {
      Serial.println("Tarjeta valida");
      delay(5000);
    }
    else
    {
      Serial.println("Tarjeta invalida");
      delay(5000);
    }
  }
}

arduino-nfc-pn532-3-validar-tarjeta

Comprobar datos

En este ejemplo validamos si una tarjeta es válida comprobando datos que hemos grabado previamente.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>


#define PN532_IRQ   (2)

#define PN532_RESET (3)

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup(void) {
  Serial.begin(115200);

  // Configurar para leer etiquetas RFID
  nfc.begin();
  nfc.setPassiveActivationRetries(0xFF);
  nfc.SAMConfig();

  Serial.println("Esperando tarjeta");
}

const uint8_t validData[16] = { 'l', 'u', 'i', 's', 'l', 'l', 'a', 'm', 'a', 's', '.', 'e', 's', 0, 0, 0 };  // Ejemplo de clave valida

//Función para comparar dos vectores
bool isEqualArray(uint8_t* arrayA, uint8_t* arrayB, uint8_t length)
{
  for (uint8_t index = 0; index < length; index++)
  {
    if (arrayA[index] != arrayB[index]) return false;
  }
  return true;
}

void loop(void) {
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
  uint8_t uidLength;

  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  if (success)
  {
    uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

    success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);
    if (success)
    {
      uint8_t data[16];

      success = nfc.mifareclassic_ReadDataBlock(4, data);
      if (success)
      {
        if (isEqualArray(data, validData, sizeof validData))
        {
          Serial.println("Tarjeta valida");
          delay(5000);
        }
        else
        {
          Serial.println("Tarjeta invalida");
          delay(5000);
        }
      }
    }
    else
    {
      Serial.println("Fallo autentificar tarjeta");
      delay(5000);
    }
  }
}

Emular un tag

Para emular un tag (NFC Type 4) podemos usar la librería de Seed-Studio disponible en este enlace. La librería dispone de un ejemplo que muestra la emulación de un tag. No obstante, la funcionalidad real es limitada.

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github. github-full