Uma das aplicações mais comuns de Rfid no dia-a-dia é o controle de passagens (bilhetagem). Neste post vamos ver como isto pode ser feito com os cartões MIFARE e simular esta operação usando o Módulo Leitor RfId Mfrc522 ligado a um Arduino.

Material Necessário
Para a parte prática você precisará:
- Kit Módulo Leitor Rfid Mfrc522 Mifare
- Arduino Uno
- Cabos macho-fêmea para ligação do módulo ao Arduino
IMPORTANTE: Como vamos escrever no cartão e reconfigurá-lo, use somente um cartão vazio e que você possa descartar caso algo ocorra errado.
Conhecendo um pouco o cartão RFID Mifare
MIFARE é a marca da NXP (que já foi uma divisão da Philips) para uma série de integrados desenvolvidos para uso em cartões de proximidade ou smartcards sem contato.
O Mifare Classic (no qual vamos nos focar) é basicamente um dispositivo de memória no qual podem ser feitas leituras e escritas sem contato a curta distância, condicionadas a recursos de segurança. A memória disponível é dividida em setores, numerados a partir de zero. Cada setor pode ser protegido de uma forma independente dos demais. O setor, por sua vez, é dividido em blocos de 16 bytes. O último bloco de cada setor é usado para configurar a proteção do setor. O primeiro bloco do primeiro setor é o “bloco do fabricante” e (em teoria) não pode ser alterado. Ele contem a identificação única do cartão ou tag. O cartão e o tag que vem no kit possuem 1K de memória, o que corresponde a 16 setores cada um com 4 blocos.
O acesso a cada setor é controlado por uma configuração e duas chaves de 48 bits (chamadas A e B), armazenadas no último bloco do setor. A forma como a configuração é armazenada neste bloco é um pouco confusa. Temos três bits para cada bloco e, para maior segurança, estes bits são gravados duas vezes (o valor normal e o invertido):

A interpretação dos três bits é diferente entre os blocos de dados e o bloco de configuração:


Antes de qualquer operação de leitura ou escrita, é necessário fazer uma autenticação, usando uma das chaves. Uma pegadinha é que uma chave que possa ser lida não pode ser usada para autenticação. A chave A nunca pode ser lida, mas a leitura da chave B pode ser permitida (três primeiras linhas da primeira tabela acima), neste caso a chave B passa a ser um dado ao invés de chave. Em todo acesso o cartão verifica se o bloco de configuração é valido, se não for todo o setor é bloqueado de forma definitiva (uma forma fácil de perder um setor).
Os cartões/tags novos vem com o acesso configurado para permitir ler e escrever nos blocos de dados com a chave A ou B. Porém, o bloco de configuração só pode ser lido ou escrito usando a chave A e a chave B pode ser lida. Portanto, apenas a chave A pode ser usada para autenticação; tipicamente vem de fábrica como FFFFFFFFFFFF.
Usando um cartão RFID Mifare para um sistema de passagens
Na aplicação de bilhetagem (ou de “carteira eletrônica”), o cartão (ou tag) é carregado com uma certa quantidade de “créditos” que vão sendo consumidos em troca de serviços ou bens. Olhando novamente a tabela de controle de blocos de dados, repare que existem dois usos possíveis: dados e valor. Você poderia implementar a bilhetagem usando o modo dados, mas o modo valor existe exatamente para que as operações de carga e consumo sejam feitas de forma mais segura.
No uso como dados o cartão não se preocupa com o que você escreve nos blocos, para uso como valor o conteúdo do bloco precisa estar em um formato específico:

Na figura acima value é o valor (saldo); para maior segurança ele é repetido três vezes no bloco (uma delas negado). O valor é um número com sinal de quatro bytes (no formato complemento de 2); ou seja de -2.147.483.648 a 2.147.483.647. adr é o endereço de um outro bloco do setor para “gerenciamento de backup” (segundo o datasheet, não encontrei explicações ou exemplo de uso).
Uma vez configurado um bloco para uso como valor, o seu acesso fica condicionado a possuir o formato correto e existem quatro operações básicas que podem ser feitas com ele:
- incremento, onde um “crédito” é somado ao valor; o resultado fica em um registrador temporário interno ao cartão
- decremento, onde um “débito” é subtraído do valor; o resultado fica em um registrador temporário interno ao cartão
- restauração, onde o valor atual é copiado para o registrador temporário
- transferência, onde o valor no registrador temporário é gravado no bloco
Portanto um carga é feita combinando um incremento com transferência e um consumo é feito combinando um decremento com transferência.
Olhando novamente a tabela do controle de acesso, percebemos que no modo “001” qualquer uma das chaves pode ser usada para fazer um consumo, mas não há como fazer uma carga (isto é útil para cartões pré-carregados e descartáveis). No modo “110” qualquer chave pode ser usada para fazer um consumo mas apenas a B pode fazer uma carga (os pontos onde o cartão é usado conhecem apenas a chave A; a chave B fica somente nos poucos pontos de recarga).
Montagem do Sistema de Passagem com leitor RFID e Arduino
A conexão do módulo Rfid ao Arduino deve ser feita conforme a figura abaixo:

Código para o Sistema de Passagem com leitor RFID e Arduino
Para o nosso programa exemplo vamos usar a biblioteca MFRC522 que pode ser instalada diretamente da IDE:

Neste exemplo vamos usar o Serial Monitor da IDE para interagir com o programa. Para isto certifique-se que ele está configurado como abaixo:

O programa permite as seguintes operações, cada uma disparada por uma letra:
- (I)niciar o cartão: reconfigura o bloco x do setor y para o modo dados (usando a chave de fábrica ou a nossa chave B), grava um valor inicial de 20 e o reconfigura para o modo valor com as nossas chaves A e B
- (R)ecarga: acrescenta 20 “créditos” ao saldo no cartão
- (C)onsumo: tenta fazer uma “compra” com o custo de 3 “créditos”
- (S)aldo: apresenta o saldo no cartão
- (F)abrica: volta o setor para a configuração de fábrica
Abaixo o código:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
#include <SPI.h> #include <MFRC522.h> // Acesso ao módulo Rfid const byte RST_PIN = 9; // pino de reset const byte SS_PIN = 10; // pino de seleção MFRC522 mfrc522(SS_PIN, RST_PIN); // Chaves typedef enum { FABRICA, NOSSA, OUTRA } CHAVE; MFRC522::MIFARE_Key chaveFabrica; MFRC522::MIFARE_Key chaveA; MFRC522::MIFARE_Key chaveB; CHAVE chave; // Vamos usar o setor #4, que contem os blocos 16 a 19 const byte SETOR = 4; const byte BOLOCO_VAL = 17; const byte BLOCO_CFG = 19; // Valores de recarga e consumo const int32_t VALOR_RECARGA = 10; const int32_t VALOR_CONSUMO = 3; // Iniciação void setup() { // Inicia a porta serial Serial.begin (115200); // Inicia o acesso ao módulo SPI.begin(); mfrc522.PCD_Init(); // Inicia as chaves for (byte i = 0; i < 6; i++) { chaveFabrica.keyByte[i] = 0xFF; } chaveA.keyByte[0] = 'D'; chaveA.keyByte[1] = 'Q'; chaveA.keyByte[2] = 'S'; chaveA.keyByte[3] = 'o'; chaveA.keyByte[4] = 'f'; chaveA.keyByte[5] = 't'; chaveB.keyByte[0] = '1'; chaveB.keyByte[1] = '2'; chaveB.keyByte[2] = '3'; chaveB.keyByte[3] = '4'; chaveB.keyByte[4] = '5'; chaveB.keyByte[5] = '6'; } // Laço principal void loop() { // Solicita uma opção Serial.print ("(I)nicia, (R)ecarrega, (C)onsome, (S)aldo ou (F)abrica?"); int c; while (Serial.read() != -1) { delay(10); } do { c = Serial.read(); c = toupper(c); } while (strchr("IRCSF", c) == NULL); char cmd[2] = ""; cmd[0] = (char) c; Serial.println (cmd); // Pede para aproximar um cartão if (aguardaCartao()) { // Trata a opção if (c == 'I') { inicia(); } else if (c == 'R') { recarrega(); } else if (c == 'C') { consome(); } else if (c == 'S') { saldo(); } else if (c == 'F') { fabrica(); } mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); } Serial.println(); } // Inicia o cartão para uso void inicia() { MFRC522::StatusCode status; // Se autentica com a chave B status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, BLOCO_CFG, (chave == FABRICA)? &chaveFabrica : &chaveB, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na autenticacao!"); return; } // Grava o setor de valor com o formato correto if (!iniciaValor(BOLOCO_VAL)) { return; } // Reconfigura o setor para usar nossas chaves e as condições de acesso desejadas // 1o bloco 000 configuração padrão, pode acessar com chaves A ou B // 2o bloco 110 valor, leitura/decremento com chave A ou B, escrita/incremento só com B // 3o bloco 000 configuração padrão, pode acessar com chaves A ou B // 4o bloco 011 escrita só com B, chave A só lê bits de acesso byte cfgBuffer[16]; memset (cfgBuffer, 0, 16); memcpy (cfgBuffer, &chaveA, 6); memcpy (cfgBuffer+10, &chaveB, 6); mfrc522.MIFARE_SetAccessBits(&cfgBuffer[6], 0, 6, 0, 3); status = mfrc522.MIFARE_Write(BLOCO_CFG, cfgBuffer, 16); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro ao configurar!"); } Serial.println ("Cartao iniciado"); } // Recarrega o cartão, acrescentando VALOR_RECARGA "créditos" void recarrega() { MFRC522::StatusCode status; if (chave != NOSSA) { Serial.println ("Cartao nao iniciado!"); return; } // Se autentica com a chave B status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, BLOCO_CFG, &chaveB, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na autenticacao!"); return; } status = mfrc522.MIFARE_Increment(BOLOCO_VAL, VALOR_RECARGA); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro ao recarregar!"); return; } status = mfrc522.MIFARE_Transfer(BOLOCO_VAL); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na salvar novo saldo!"); return; } // Mostra o novo saldo saldo(); } // Consome VALOR_CONSUMO "créditos" void consome() { MFRC522::StatusCode status; if (chave != NOSSA) { Serial.println ("Cartao nao iniciado!"); return; } // Se autentica com a chave A status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, BLOCO_CFG, &chaveA, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na autenticacao!"); return; } // Vê se tem saldo suficente int32_t valor; status = mfrc522.MIFARE_GetValue(BOLOCO_VAL, &valor); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na leitura!"); return; } if (valor < VALOR_CONSUMO) { Serial.println ("Saldo insuficiente!"); return; } // Atualiza o saldo status = mfrc522.MIFARE_Decrement(BOLOCO_VAL, VALOR_CONSUMO); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro ao debitar!"); return; } status = mfrc522.MIFARE_Transfer(BOLOCO_VAL); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na salvar novo saldo!"); return; } // Mostra o novo saldo saldo(); } // Informa o saldo void saldo() { MFRC522::StatusCode status; if (chave != NOSSA) { Serial.println ("Cartao nao iniciado!"); return; } // Se autentica com a chave A status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, BLOCO_CFG, &chaveA, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na autenticacao!"); return; } // Le o Valor int32_t valor; status = mfrc522.MIFARE_GetValue(BOLOCO_VAL, &valor); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na leitura!"); return; } Serial.print ("Saldo: "); Serial.println (valor); } // Recoloca o cartão na condição de fábrica void fabrica() { MFRC522::StatusCode status; // Se autentica com a chave B status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, BLOCO_CFG, (chave == FABRICA)? &chaveFabrica : &chaveB, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro na autenticacao!"); return; } // Reconfigura o setor para a configuração padrão byte cfgBuffer[16]; memset (cfgBuffer, 0, 16); memcpy (cfgBuffer, &chaveFabrica, 6); memcpy (cfgBuffer+10, &chaveFabrica, 6); mfrc522.MIFARE_SetAccessBits(&cfgBuffer[6], 0, 0, 0, 1); status = mfrc522.MIFARE_Write(BLOCO_CFG, cfgBuffer, 16); if (status != MFRC522::STATUS_OK) { Serial.println ("Erro ao configurar!"); } Serial.println ("Cartao de volta a condicao de fabrica"); } // Aguarda apresentar um cartão ou digitar algo bool aguardaCartao() { Serial.println ("Aproxime um cartao (digite qualquer tecla para desistir)"); while (true) { if (Serial.read() != -1) { return false; } if (!mfrc522.PICC_IsNewCardPresent()) continue; if (mfrc522.PICC_ReadCardSerial()) break; } Serial.print ("ID: "); dump_buffer (mfrc522.uid.uidByte, mfrc522.uid.size); Serial.println(); Serial.print(F("Tipo: ")); MFRC522::PICC_Type tipo = mfrc522.PICC_GetType(mfrc522.uid.sak); Serial.println(mfrc522.PICC_GetTypeName(tipo)); // Verifica se tipo aceito if ( (tipo == MFRC522::PICC_TYPE_MIFARE_MINI) || (tipo == MFRC522::PICC_TYPE_MIFARE_1K) || (tipo == MFRC522::PICC_TYPE_MIFARE_4K) ) { chave = testaChave(); return chave != OUTRA; } Serial.println ("Cartao incompativel!"); return false; } // Verifica qual a chave usada no nosso setor CHAVE testaChave() { MFRC522::StatusCode status; status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, BLOCO_CFG, &chaveFabrica, &(mfrc522.uid)); if (status == MFRC522::STATUS_OK) { Serial.println ("Usa chave da fábrica"); return FABRICA; } // Reinicia a comunicação, para poder testar outra chave mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); mfrc522.PICC_IsNewCardPresent(); mfrc522.PICC_ReadCardSerial(); status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, BLOCO_CFG, &chaveA, &(mfrc522.uid)); if (status == MFRC522::STATUS_OK) { Serial.println ("Usa a nossa chave"); return NOSSA; } Serial.println ("Chave desconhecida!"); return OUTRA; } // Inicia um bloco no formato apropriado para valor bool iniciaValor(byte bloco) { MFRC522::StatusCode status; byte valueBlock[] = { 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, bloco, ~bloco, bloco, ~bloco }; status = mfrc522.MIFARE_Write(bloco, valueBlock, 16); if (status != MFRC522::STATUS_OK) { Serial.print("Erro ao gravar bloco!"); return false; } return true; } // Mostra o conteudo de um buffer em hexa void dump_buffer(byte *buffer, byte tam) { for (byte i = 0; i < tam; i++) { Serial.print(buffer[i] < 0x10 ? " 0" : " "); Serial.print(buffer[i], HEX); } } |
|
1 |
Resultado do Sistema de Passagem com leitor RFID e Arduino
A imagem abaixo mostra um exemplo de funcionamento.
Você pode experimentar com outros valores de recarga e consumo alterando as linhas que definem as constantes VALOR_RECARGA e VALOR_CONSUMO. Você pode também mudar as chaves em setup(), porém tome o cuidado de retornar o cartão à condição de fábrica antes de iniciá-lo com uma nova chave.
Sugestões para Projetos
Neste post vimos o básico de como usar o cartão com bilhetagem, disparando as operações através do monitor serial. Algumas ideias de projeto usando estes conhecimentos:
- Conectar um display e botões ao Arduino para operar sem conexão a um micro.
- Após um consumo bem sucedido, acionar um servo motor para liberar uma recompensa
- Montar uma mini “vending machine“, onde um produto é selecionado e, após registrar o consumo, um motor de passo ou servo é usado para entregar o produto.
Gostou do post Sistema de passagens com Módulo Leitor Rfid e Arduino? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial.

Ola Daniel
Gostaria de saber se existe uma antena de Longo Alcance Rfid para indicar para usar com Arduíno para projetos de controle de movimentação de patrimônio?? Vi esse Leitor De Cartão Rfid Impermeável De Longo Alcance R16-7db P em https://produto.mercadolivre.com.br/MLB-3745712489-leitor-de-carto-rfid-impermeavel-de-longo-alcance-r16-7db-p-_JM#polycard_client=bookmarks será que daria certo.
Não tenho experiência com isso, mas existem alguns leitores Mifare que falam em até 10cm de alcance. Este leitor que você menciona é para 125kHz, não funciona com cartões Mifare.
Boa noite,
É possível integrar vários RFID com o arduino, por exemplo com ligação em série. Se sim, terias como elaborar um artigo sobre este tema?
Você pode ligar vários leitores sim. No artigo eu falei mais sobre o cartão, mas o leitor utilize comunicação SPI. Os sinais SCK, MISO e MOSI podem ser ligados a vários leitores (em paralelo), porém cada leitor precisa de duas saídas digitais (não compartilhadas) no Arduino para os sinais SS e RST.
Muito bom este sistema, pretendo utilizar com as “crianças”. Fazem a atividade, recebem créditos, depois no final, usam para “comprar” alguns brindes. Show.
Olá, Boa Tarde, Me chamo Antonio Marcos , sou discente do curso de agrocomputação Fatec/Senai MT , gostária de ver a possibilidade de me ajudar estou com o projeto de acesso usando um micro controlador esp32 e um kiit modulo rfid mfrc 522e não encontrei nada neste sentido só que um projeto é normal por usb e outro por bluetooth deixo meu e-mail para contato [email protected]
sem mais elevo minhas estimas e considerações
Parabéns Daniel, gostei muito do Projeto, vou usar na Escola Pública para ajudá-los.