Fig1. Tarjetas y modulo RC522 rfID |
Fig2. Sistema rfID |
- Etiqueta(Transpondedor): formado por una antena(RF), la unidad de control digital(MCU) y una memoria(EEPROM).
- Lector(Interrogador): compuesto por la antena(RF), un decodificador y el puerto de comunicación serial UART/SPI/I2C.
La distancia de lectura depende de la potencia del módulo lector, en nuestro caso son aproximadamente 50mm(RC522), pero existen lectores con mayor rango de alcance.
- MIFARE Ultralight 512 bit Sin Seguridad
- MIFARE Classic 1K-4Kbytes Seguridad Crypto-1
- MIFARE Desfire 4Kbit Seguridad 3DES
Fig3. Pines modulo RC522 |
Fig4. Mapa de memoria EEPROM 1kbyte |
Fig5. Procedimiento de autenticación de tres pasos |
- Modificar la llave A, pero no es posible leer su valor.
- Modificar los bits de acceso y la llave B del bloque.
- Realizar operaciones de lectura, escritura, incremento y decremento.
- Se puede leer la llave B, siendo posible usar este campo para datos.
La figura 6 nos muestra el esquema del circuito utilizado para las pruebas de nuestro programa, y donde se puede observar la conexión entre el microcontrolador PIC16F887 y el modulo RC522. Parte del circuito esta alimentado por una fuente de 5V, y a través de un regulador de voltaje lineal MCP1702, se consiguen los 3.3V necesarios para el funcionamiento del modulo RC522, por esta razón para garantizar la compatibilidad de los niveles lógicos en las lineas de comunicación del modulo RFID, se utilizan las resistencias como divisores de tensión. Por otro lado la comunicación serial con la PC, utiliza un convertidor USB a UART, en nuestro caso es un modulo CP2102, pero puede ser cualquier otro modelo. Finalmente se dispone de tres diodos LED, uno conectado al pin RE2 que servirá para indicar la actividad del programa y dos conectados al pin RD0 y RD1 que nos indicaran mediante el color si el acceso esta autorizado(Verde) o denegado(Rojo), además un pulsador de entrada en RB0 permite realizar un procedimiento de carga de valor en la memoria de la tarjeta RFID.
Fig6. Esquema del circuito PIC16-RC522 |
Programa del Microcontrolador PIC
El objetivo de este programa es comprobar la conectividad PIC16F - RC522, para lo cual se realizara la identificación de una tarjeta MiFARE, para obtener la etiqueta del modelo de tarjeta y el numero de serie correspondiente, sin entrar en la fase de autenticación y operaciones en la memoria EEPROM, que veremos mas adelante con el programa final presente en el archivo de proyecto MPLABX cuyo enlace se encuentra al final.
En programa haremos usos de diversos procedimientos y funciones declarados en los archivos de cabecera de las librerías mrc522.h y spi.h, aclarar que el código de estas librerías se crearon a partir de otras fuentes que encontré en la red.
Ahora describiré las secciones que tiene el código principal main.c con una breve explicación de lo que se pretende realizar. Recuerda que al final de esta publicación esta el enlace para descarga del proyecto MPLABX que contiene todos lo archivos utilizados.
#pragma config FOSC=INTRC_NOCLKOUT, WDTE = OFF, BOREN = OFF, LVP = OFF
#include <xc.h>
#include <stdio.h>
#include "spi.h"
#include "mrc522.h"
#define LEDpin PORTEbits.RE2 //Led de la tarjeta
#define DS1pin PORTDbits.RD0 //Led Verde
#define DS2pin PORTDbits.RD1 //Led Rojo
#define SW1pin PORTBbits.RB0 //Entrada Selector
volatile __bit tickms;
uint8_t buff[16];
uint16_t tagtype; //2-byre para el tag_id
uint32_t tagserial; //4-byte para el tag_serial
uint8_t tagkey[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; //Default
void main(void)
{
setupMCU(); //Configura el PIC
MRCInit(); //Inicializa el modulo RC522
printf("\r\nLISTA RFID\r\n");
while(1)
{
if(tickms) //Ejecuta cada 1ms
{
tickms = 0;
taskLED(); //Destella LED
taskAPP(); //Lectura y cambio del RFID
}
}
}
En las lineas luego de la configuración e inicialización de los modulo, el programa ingresa en un bucle condicionado por la bandera tick1ms, que se activa cada milisegundo de acuerdo a la interrupción del temporizador TMR0, para este fin se utiliza la interrupción del temporizador TMR0. Por lo tanto podemos resumir que dentro del bucle del programa principal se realiza la llamada concurrente cada milisegundo de dos procedimientos:
taskLED: Es un procedimiento que se ejecuta en cada milisegundo, internamente utiliza una variable estática como contador para determinar la condición de encendido y apagado del LED conectado al pin RE2, el objetivo es destellar la luz cada segundo con un ciclo activo(encendido) de un 20%, para indicar la actividad del programa que corre el PIC. las siguientes lineas describen el código de este procedimiento.
void taskLED(void) //Destello de LED1 1Hz al 20%
{
static uint16_t cnt = 0;
if(cnt++ > 999)
{
cnt = 0;
LEDpin = 1; //Activa LED
}
if(cnt == 200) LEDpin = 0; //Apaga LED
}
taskAPP: Este procedimiento que internamente subdivide el trabajo en estados, se encarga de la comunicación con el modulo RFID llevando a cabo las siguientes operaciones:
- Efectuar las operaciones respectivas.
Las siguientes lineas muestran el código de este procedimiento que describiremos por etapas.
void taskAPP(void)
{
static uint8_t state = 0;
static uint16_t cnt = 0;
int32_t value = 0;
uint8_t res, i;
switch(state)
{
case 0: //Detecta tarjeta RFID, con intervalos de 250ms
if(MRCRequest(PICC_REQIDL, buff)== MI_OK) //step1
{
tagtype = buff[0];
tagtype <<= 8;
tagtype |= buff[1];
state = 1; //Estado de Tarjeta detectada
}
else
{
MRCHalt(); //Finaliza comunicación RFID
state = 2; //Estado de espera 250ms
}
break;
case 1: //Lectura del serial ID
if(MRCAntiColl(buff) == MI_OK) //step2
{
tagserial = buff[0];
tagserial <<= 8;
tagserial |= buff[1];
tagserial <<= 8;
tagserial |= buff[2];
tagserial <<= 8;
tagserial |= buff[3];
printf("\r\nID Card:%lX\r\n",tagserial);
if(MRCSelectTag(buff) == 0x08) state = 4;//step3 else
{
printf("SAK Incorrecto");
MRCHalt(); //Finaliza comunicación RFID
state = 3;
}
}
else
{
MRCHalt(); //Finaliza comunicación RFID
state = 2;
}
break;
case 2: //Espera de 250ms para detección
if(cnt++ > 249)
{
cnt = 0;
state = 0; //Reinicia estado
} else _nop();
break;
case 3: //Espera de 2000ms entre lecturas
if(cnt++ > 1999) //Espera dos segundos entre lecturas
{
DS2pin = 0;
DS1pin = 0;
cnt = 0;
state = 0;
} else _nop();
break;
case 4: //Autenticación y actualización del valor del bloque 2
if(MRCAuth(PICC_AUTHENT1A, 2, tagkey, buff) == MI_OK)
{
if(SW1pin == 0) //Si pulsador esta presionado
{
MRCEncodeValue(5, 2, buff);//Codifica Valor 5
MRCWrite(2, buff); //Escribe en el bloque 2
printf("Recargado");
}
else
{
MRCUpdateValue(2, PICC_DECREMENT, 1);//Decrementa
MRCRead(2, buff); //Lectura del bloque 2
value = MRCDecodeValue(buff); //Decodifica valor
if(value >=0)
{
printf("Credito:%ld", value);
DS1pin = 1;
}
else
{
printf("Sin credito");
DS2pin = 1;
}
}
}
MRCHalt();
state = 3;
break;
}
}
El primer estado (case 0) dentro del procedimiento, verifica la disponibilidad de un chip rfID valido con lector RC522, si no hay un chip identificado el procedimiento realizara una espera de 200ms para repetir el proceso. La función utilizada es MRCRequest(PICC_REQIDL, buff), que retornara 0 (MI_OK) si existe una tarjeta como la MiFARE dentro de la zona de proximidad del lector RC522, y guardara en el vector buff dos bytes de identificación cuyos valores corresponden con la siguiente tabla:
- 0x4400 = Mifare_UltraLight
- 0x0400 = Mifare_One(S50)
- 0x0200 = Mifare_One(S70)
- 0x0800 = Mifare_Pro(X)
- 0x4403 = Mifare_DESFire
Si la identificación es correcta, el procedimiento pasara el segundo estado (case 1) para realizar la lectura del numero serial de la tarjeta y preparar la etapa de autenticacion.
El numero de serie se obtiene con la función MRCAntiColl(buff), esta función recupera los primeros cuatro bytes del bloque 3, sector 0 que es de solo lectura y contiene el numero serial único establecido por el fabricante conforme a la figura 7.
Fig7. Datos del fabricante, solo para lectura |
El ultimo estado del procedimiento es para la autenticacion de la tarjeta en el bloque 2 con la función MRCAuth(PICC_AUTHENT1A, 2, tagkey, buff), utilizando la llave A establecida por defecto, entonces dependiendo del resultado y el estado del pulsador SW1, se llevaran a cabo las siguientes operaciones:
Si el pulsador SW1 se encuentra presionado, entonces se realizara la escritura del bloque 2 con la función MRCWrite(2, buff), donde los 16 bytes del bloque estarán codificados en un formato de Valor(ValueBlock) que se lleva a cabo utilizando el procedimiento MRCEncodeValue(5, 2, buff), es valor que se escribe es un numero 5 que representa en nuestro ejemplo la recarga de un valor de crédito a una tarjeta.
Si el pulsado SW1 no esta presionado, entonces se decrementa un uno al valor del bloque 2, con la función MRCUpdateValue(2, PICC_DECREMENT, 1) y luego se realizara la lectura del bloque con MRCRead(2, buff), para luego recuperar el Valor con el procedimiento MRCDecodeValue(buff).
El propósito de este ultimo estado es recargar una tarjeta con crédito y usar la operación para decremento unitario de forma automática cada vez que se realiza la lectura de la tarjeta, entonces una luz verde se activara siempre que la tarjeta tenga crédito y una luz roja cuando ya no cuente con saldo.
Seguidamente se describe el procedimiento de configuracion del PIC y la rutina de interrupción ISR.
void setupMCU(void) //Procedimiento para configurar el PIC
{
OSCCONbits.IRCF = 0b111; //Oscilador Interno 8MHz
while(OSCCONbits.HTS == 0);
ANSEL = 0; //Desactiva pines ADC AN0 al AN7
ANSELH = 0;//Desactiva pines ADC AN8 al AN13
TRISEbits.TRISE2 = 0; //Salida Pin LED
PORTEbits.RE2 = 0; //Apaga el LED
TRISDbits.TRISD0 = 0; //Salida DS2 Verde
TRISDbits.TRISD1 = 0; //Sal;da DS1 Rojo
PORTD = 0; //Salidas en nivel bajo
OPTION_REGbits.nRBPU = 0; //Activa las pull-ups PORTB
/* CONFIGURACION TIMER0 1MS a Fosc=8MHz*/
OPTION_REGbits.T0CS = 0;//Modo Termporizador
OPTION_REGbits.PSA = 0; //Con prescala
OPTION_REGbits.PS = 0b011; //Prescala 1:16
TMR0 = 131; //256-(time/((pre)*(4/Fosc))) time=0.001 seg
INTCONbits.T0IF = 0; //Limpia bandera
INTCONbits.T0IE = 1; //Activa interrupción del TMR0
/* CONFIGURACION UART 9600 BPS*/
BAUDCTLbits.BRG16 = 0; //8-bit BaudGen
TXSTAbits.BRGH = 1; //Modo High Speed
TXSTAbits.TXEN = 1; //Habilita el transmisor UART
//RCSTAbits.CREN = 1; //Habilita el receptor UART
RCSTAbits.SPEN = 1; //Activa el modulo UART
SPBRG = 51; //Formula [8M/(16 * 9600)] - 1
/* CONFIGURACION MSSP MODO SPI 500KHz 8MHz*/
TRISCbits.TRISC5 = 0; //SDO Salida en modo master
TRISCbits.TRISC3 = 0; //SCK Salida en modo master
SSPCONbits.SSPM = 0b0001; //Master SPI(Fosc/16)=500KHz
SSPSTATbits.SMP = 1; //Data sample at end SMP = 1
SSPCONbits.CKP = 0; //Clock Idle low level SCK=0
SSPSTATbits.CKE = 1;//Positive edge clock enable CKE = 1;
SSPCONbits.SSPEN = 1;//Enable SSP Module
INTCONbits.GIE = 1; //Habilitador global ISR
}
void __interrupt() isr(void)
{
uint8_t res;
if(INTCONbits.T0IF)
{
INTCONbits.T0IF = 0;
TMR0 += 131;
tickms = 1;
}
}
Pruebas de funcionamiento
Antes de abrir el proyecto en MPLABX, asegúrate de tener instalado la versión del compilador XC8, una vez confirmado procedemos a abrir el proyecto, y luego de llevar a cabo la exploración de los archivos, procedemos a compilar el programa, si no hay errores procedemos a cargar el código al microcontrolador y llevar a cabo las pruebas de funcionamiento.
En primer lugar debe notar que el led de indicación destella una vez por segundo, esto nos indica la correcta ejecución del programa, restando únicamente acercar una tarjeta RFID para que se lleve a cabo la identificación y se muestre un mensaje a través del puerto serie. Para poder visualizar este mensaje utilizaremos un complemento disponible para MPLABX, accediendo a menu->tools->plugins, en esta ventana nos aseguramos de activar la instalación de Simple Serial Port Terminal
Fig8. Instalación de Simple Serial Port Terminal |
Una vez instalado Simple Serial Port Terminal se debe configurar el puerto COM asignado al convertidor UART-USB, en windows la asignación tiene nombre COMxx, mientras que en linux es un acceso a ruta /dev/ttyUSBx.
Finalizada la configuracion del terminal, cada vez que se aproxime una tarjeta MiFARE, veremos un mensaje con el numero serial de la tarjeta y la cantidad de crédito que dispone como se observa en la Figura 9. Recuerde que para hacer una recarga es necesario aproximar la tarjeta manteniendo el pulsador SW1 presionado.
Fig9. Mensajes recibidos en el Puerto Serie |
Fig10. Circuito PIC utilizado para el programa |
Verificado el funcionamiento adecuado de la comunicación entre el PIC16F y el modulo RC522 como parte del objetivo de esta publicación, si bien las características de las tarjetas MiFARE posibilitan mayores prestaciones de seguridad, tales como la Autenticación por bloque, y el almacenamiento de variables en memoria con operaciones de auto incremento o decremento. En muchas situaciones solo basta con llevar a cabo la lectura del numero serial, puesto que la identificación de la tarjeta posibilita asociarlo con un elemento u objeto sujeto al control, por ejemplo se puede consultar un base de datos del personal autorizado en base al numero de serie en su tarjeta de control.
Atentamente, Pablo Zárate Arancibia email: pablinza@me.com / pablinzte@gmail.com, @pablinzar