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:
#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); } }
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.
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
Antonio Marcos, coloque perguntas específicas no nosso grupo na Comunidade Maker MakerHero no Facebook (link no rodapé da página) que o pessoal ajuda.
Parabéns Daniel, gostei muito do Projeto, vou usar na Escola Pública para ajudá-los.