Emulador RFID no Arduino



Muitos leram meu post " Emulador de RFID ", onde falei em detalhes sobre o dispositivo EM Marine, como enrolar uma antena e como fazer um emulador de RFID de três partes. Mas, sejamos honestos, apesar da simplicidade engenhosa desse dispositivo, é difícil repetir. Nem todo mundo tem um osciloscópio em casa, a fim de capturar a ressonância, e um programador separado é necessário para o firmware ATtiny85.



Portanto, decidi fazer um emulador que até uma criança pode repetir. Todos os componentes são vendidos em quase todas as aldeias. Além disso, sua funcionalidade pode até ser expandida. Por exemplo, você pode salvar vários cartões nele, ou você pode adicionar outro leitor e salvar todos os cartões em um dispositivo, ou usá-lo para ... Então, vamos lá.



Hardware



Como eu disse, o emulador deve ser construído usando os componentes disponíveis que podem ser facilmente obtidos. Primeiro, vamos dar uma olhada no circuito do emulador.







Temos um circuito oscilatório, que iremos fechar em um determinado momento com um transistor, e assim a corrente no leitor mudará, e ele receberá os dados transmitidos.

A coisa mais difícil para nós neste pacote é o circuito oscilatório sintonizado em uma frequência de 125 kHz. E há uma solução muito simples de onde você pode obtê-lo. Há um leitor de tag RFID para o Arduino RDM6300 à venda . O leitor custa apenas alguns centavos, e já vem com uma antena, e o capacitor ressonante já está soldado na placa. Assim, na verdade, precisamos apenas de um leitor para duas partes: a bobina e o capacitor ressonante.





Leitor RDM6300 e localização do capacitor ressonante.



Comprei este leitor por alguns centavos, o que é incomensurável com o trabalho de enrolar e sintonizar uma antena. A operação mais difícil para nós é dessoldar este capacitor e soldá-lo à placa de circuito. Acredito que até mesmo um aluno da escola primária pode lidar com isso.

Como resultado, coletamos tudo em uma placa de ensaio. Tenho dois resistores em paralelo apenas porque não tinha resistores de 10 kOhm disponíveis, mas apenas 20 kOhm.





Circuito montado.



Bem, vamos ver em close-up como tudo parece. Aloquei especialmente um lenço separado para o capacitor, onde é soldado diretamente nas agulhas de montagem que são inseridas neste colchão.





Para verificar o emulador, inicialmente pensei em usar o mesmo RDM6300 (comprei dois). E mesmo no começo ele fez isso, mas depois decidiu que de alguma forma não era sério depurar um Arduina com outro e faliu com um leitor de fábrica.





Leitor de fábrica.



Ativando o cronômetro



Eu contei mais detalhadamente toda a física do processo e o princípio de operação em meu artigo anterior , então recomendo fortemente que você se familiarize com ele. Porém, para entender o que estou fazendo, vou relembrar um pouco alguns pontos.



Deixe-me lembrá-lo de que o EM4102 usa o esquema de codificação Manchester. Quando o protocolo EM4102 é modulado, o tempo de transmissão de um bit pode ser 64, 32 ou 16 períodos da frequência da portadora (125 kHz).







Simplificando, ao transmitir um bit, alteramos o valor de um para zero (ao transmitir zero), ou de zero para um (ao transmitir um). Conseqüentemente, se escolhermos transmitir um bit de informação a 64 períodos da frequência da portadora, então para a transmissão de "meio bit" precisaremos de 32 períodos da frequência da portadora. Assim, cada nibble deve mudar em uma taxa:



f=125000/32 = 3906,25 
      
      





O período deste "meio bit" será igual a 256 ms.



Agora precisamos calcular o cronômetro para que ele sacuda nossa perna com uma determinada frequência. Mas fiquei com tanta preguiça que, abrindo a folha de dados e começando a bocejar, decidi encontrar algum tipo de solução pronta. E descobriu-se que existem cálculos de cronômetro prontos, basta inserir seus dados. Conheça: calculadora do cronômetro para Arduino .



Precisamos apenas martelar a frequência do temporizador de 3.906 Hz e geraremos imediatamente um código pronto para uso. Bem, não é um milagre!





Observe que eu inseri a frequência como um todo, e ele a contou como fracionária e exatamente a que precisamos. Recebi o seguinte código de inicialização do temporizador:



void setupTimer1() {
  noInterrupts();
  // Clear registers
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;

  // 3906.25 Hz (16000000/((4095+1)*1))
  OCR1A = 4095;
  // Prescaler 1
  TCCR1B |= (1 << CS10);
  // Output Compare Match A Interrupt Enable
  TIMSK1 |= (1 << OCIE1A);
  interrupts();
}
      
      





Brilhante, simples, lacônico.



O vetor de interrupção para saída também é muito simples. Devo lembrar que precisamos fazer a transição de um para zero no caso de transferência de zero, e de zero para um, no caso de transferência de um (veja a figura para um entendimento). Portanto, olhamos o que estamos passando agora e onde estamos no "meio-bit", lendo gradualmente todos os dados do array de dados.



ISR(TIMER1_COMPA_vect) {
        TCNT1=0;
	if (((data[byte_counter] << bit_counter)&0x80)==0x00) {
	    if (half==0) digitalWrite(ANTENNA, LOW);
	    if (half==1) digitalWrite(ANTENNA, HIGH);
	}
	else {
	    if (half==0) digitalWrite(ANTENNA, HIGH);
	    if (half==1) digitalWrite(ANTENNA, LOW);
	}
    
	half++;
	if (half==2) {
	    half=0;
	    bit_counter++;
	    if (bit_counter==8) {
	        bit_counter=0;
	        byte_counter=(byte_counter+1)%8;
		}
	}
}
      
      





Tradução de dados para transmissão



Também aqui você deve atualizar a memória dos formatos de dados armazenados no cartão. A maneira como são escritos. Vamos dar um exemplo ao vivo.



Suponha que temos um cartão, mas nenhum leitor. O número 010.48351 está escrito no cartão .





Um cartão real com o número 010, 48351.



Como podemos traduzir esse número para o número de série que está escrito no cartão? Simples o suficiente. Lembre-se da fórmula: traduzimos as duas partes do número separadamente:



010d = 0xA
48351d = 0xBCDF
      
      





Portanto, obtemos o número de série: 0xABCDF. Vamos verificar, ler o cartão com um leitor (lê em formato decimal), temos um número:



0000703711
      
      





Nós traduzimos para o formato hexadecimal com qualquer calculadora e obtemos novamente: 0xABCDF.

Parece tão simples, espere, agora você tem que forçar seus cérebros. Deixe-me lembrá-lo do formato dos dados que estão no próprio cartão.





Vou colocar em palavras:



  1. Existem nove unidades de título no início.
  2. ID de cliente do meio byte mais baixo.
  3. No final do bit de paridade.
  4. A segunda metade do byte é o ID do cliente.
  5. Bit de paridade.
  6. O meio byte menos significativo do byte zero do número de série.
  7. Bit de paridade
  8. .
  9. ,
  10. . 10 ( ).
  11. , .


No total, obtemos 64 bits de dados (cinco bytes!). Como observação lateral, meu leitor não lê o ID do cliente e eu o aceito como zero.



O que é um bit de paridade? Este é o número de unidades no pacote: se for par, o bit de paridade é zero, se não, então um. A maneira mais fácil de calcular é apenas um XOR regular.



Na verdade, pensei muito tempo em como tornar a conversão do número de série em uma embalagem mais elegante, para que ocupasse menos espaço no microcontrolador. Portanto, esbocei um pequeno programa que faz isso. O programa pode ser visto no spoiler.



Programa de teste para tradução serial em dados
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)  \
  (byte & 0x80 ? '1' : '0'), \
  (byte & 0x40 ? '1' : '0'), \
  (byte & 0x20 ? '1' : '0'), \
  (byte & 0x10 ? '1' : '0'), \
  (byte & 0x08 ? '1' : '0'), \
  (byte & 0x04 ? '1' : '0'), \
  (byte & 0x02 ? '1' : '0'), \
  (byte & 0x01 ? '1' : '0') 

#define NYBBLE_TO_BINARY_PATTERN "%c%c%c%c"
#define NYBBLE_TO_BINARY(byte)  \
	(byte & 0x08 ? '1' : '0'), \
	(byte & 0x04 ? '1' : '0'), \
	(byte & 0x02 ? '1' : '0'), \
	(byte & 0x01 ? '1' : '0') 


int main() {
	//unsigned long long card_id = 0x00000ABCDF;
	//uint64_t card_id = 0x00000ABCDF;
	uint64_t card_id = (uint64_t)3604000;
	uint64_t data_card_ul = 0x1FFF; //first 9 bit as 1
	int32_t i;
	uint8_t tmp_nybble;
	uint8_t column_parity_bits = 0;
	printf("card_id = 0x%lX\n", card_id);
	for (i = 9; i >= 0; i--) { //5 bytes = 10 nybbles
		tmp_nybble = (uint8_t) (0x0f & (card_id >> i*4));
		data_card_ul = (data_card_ul << 4) | tmp_nybble;
		printf("0x%02X", (int) tmp_nybble);
		printf("\t"NYBBLE_TO_BINARY_PATTERN, NYBBLE_TO_BINARY(tmp_nybble));
		printf("\t %d\n", (tmp_nybble >> 3 & 0x01) ^ (tmp_nybble >> 2 & 0x01) ^\
			(tmp_nybble >> 1 & 0x01) ^ (tmp_nybble  & 0x01));
		data_card_ul = (data_card_ul << 1) | ((tmp_nybble >> 3 & 0x01) ^ (tmp_nybble >> 2 & 0x01) ^\
			(tmp_nybble >> 1 & 0x01) ^ (tmp_nybble  & 0x01));
		column_parity_bits ^= tmp_nybble;
	}
	data_card_ul = (data_card_ul << 4) | column_parity_bits;
	data_card_ul = (data_card_ul << 1); //1 stop bit = 0
	printf("\t"NYBBLE_TO_BINARY_PATTERN"\n", NYBBLE_TO_BINARY(column_parity_bits));
	printf("data_card_ul = 0x%lX\n", data_card_ul);
	
	for (i = 7; i >= 0; i--) {
		printf("0x%02X,", (int) (0xFF & (data_card_ul >> i * 8)));
	}
	printf("\n");
	return 0;
}

      
      





O mais importante para nós é a aparência dos bits de paridade. Por conveniência, fiz a saída para a tela exatamente da mesma maneira que nesta placa. O resultado é assim.





card_id é o número de série do cartão (sobre o qual falamos acima).



A primeira coluna são os nibls, a segunda é sua representação de bits, a terceira é o bit de paridade. A terceira linha da parte inferior são os bits de paridade de todos os irmãos. Como eu disse, eles são calculados simplesmente pelo XOR.



Tendo testado os cálculos, tendo-os verificado visualmente, verifiquei os dados resultantes no programa do Arduino (a última linha é especialmente para inserção no código). Tudo funcionou perfeitamente. Como resultado do esboço deste programa, obtive uma função de recálculo pronta. Anteriormente, os cálculos de batida eram programas de outra pessoa em um computador e eu não gostava de sua implementação monstruosa. Assim, a função de converter o número de série no formato de transmissão é assim:




#define CARD_ID 0xABCDF

uint8_t data[8];

void data_card_ul() {
  uint64_t card_id = (uint64_t)CARD_ID;
  uint64_t data_card_ul = (uint64_t)0x1FFF; //first 9 bit as 1
  int32_t i;
  uint8_t tmp_nybble;
  uint8_t column_parity_bits = 0;
  for (i = 9; i >= 0; i--) { //5 bytes = 10 nybbles
    tmp_nybble = (uint8_t) (0x0f & (card_id >> i*4));
    data_card_ul = (data_card_ul << 4) | tmp_nybble;
    data_card_ul = (data_card_ul << 1) | ((tmp_nybble >> 3 & 0x01) ^ (tmp_nybble >> 2 & 0x01) ^\
      (tmp_nybble >> 1 & 0x01) ^ (tmp_nybble  & 0x01));
    column_parity_bits ^= tmp_nybble;
  }
  data_card_ul = (data_card_ul << 4) | column_parity_bits;
  data_card_ul = (data_card_ul << 1); //1 stop bit = 0
  for (i = 0; i < 8; i++) {
    data[i] = (uint8_t)(0xFF & (data_card_ul >> (7 - i) * 8));
  }
}
      
      





Tudo, você pode prosseguir para os testes de campo. O código-fonte do projeto está aqui .



Testes



Como se costuma dizer, é melhor ver uma vez do que ler mil vezes. Principalmente para você, gravei um filme sobre o trabalho desse emulador. Eu queria testá-lo em hardware real e tentar entrar no escritório usando o Arduino, mas com a maldita pandemia, eles não são permitidos lá. Portanto, os testes em escala real terão que ser vistos na mesa, em condições de laboratório.





conclusões



Eu realmente espero que esses artigos estimulem os novatos a aprender programação e eletrônica. E contribuirão também para o afastamento do mercado deste tipo de cartões, por serem os mais desprotegidos e inseguros, já que agora até uma criança pode copiá-los e emulá-los.



Expresso minha gratidão a Michal Krumnikl por sua paciência há muitos, muitos anos, quando me explicou no icq a operação de tal emulador, bem como a ajuda no desenvolvimento do código. Em certo sentido, essas são suas ideias e desenvolvimentos 13 anos atrás.



Links












All Articles