Émulateur RFID sur Arduino



Beaucoup ont lu mon article " RFID Emulator ", où j'ai parlé en détail du dispositif EM Marine, comment enrouler une antenne et comment fabriquer un émulateur RFID à partir de trois parties. Mais soyons honnêtes, malgré l'ingénieuse simplicité de cet appareil, il est assez difficile de le répéter. Tout le monde n'a pas d'oscilloscope à la maison, afin de capturer la résonance, et un programmeur séparé est nécessaire pour le firmware ATtiny85.



Par conséquent, j'ai décidé de créer un tel émulateur que même un enfant puisse répéter. Tous les composants sont vendus dans presque tous les villages. De plus, sa fonctionnalité peut même être étendue. Par exemple, vous pouvez y enregistrer plusieurs cartes, ou vous pouvez ajouter un autre lecteur et enregistrer toutes les cartes dans un seul appareil, ou l'utiliser pour ... Alors, allons-y.



Matériel



Comme je l'ai dit, l'émulateur doit être construit sur des composants disponibles qui peuvent être facilement obtenus. Tout d'abord, regardons le circuit de l'émulateur.







Nous avons un circuit oscillatoire, que nous fermerons à un certain moment avec un transistor, et donc le courant dans le lecteur changera et il recevra les données transmises.

Le plus difficile pour nous à cet égard est le circuit oscillatoire accordé à une fréquence de 125 kHz. Et il existe une solution très simple d'où vous pouvez l'obtenir. Il existe un lecteur d'étiquettes RFID pour l'Arduino RDM6300 en vente . Le lecteur ne coûte que quelques centimes, et il est déjà livré avec une antenne, et le condensateur résonnant est déjà soudé sur la carte. Ainsi, en fait, nous n'avons besoin d'un lecteur que pour deux parties: la bobine et le condensateur résonnant.





Lecteur RDM6300 et emplacement du condensateur résonnant.



J'ai acheté ce lecteur pour un sou, ce qui est sans commune mesure avec le travail d'enroulement et de réglage d'une antenne. L'opération la plus difficile pour nous est de dessouder ce condensateur et de le souder au circuit imprimé. Je crois que même un élève du primaire peut y faire face.

En conséquence, nous collectons tout sur une maquette. J'ai deux résistances en parallèle uniquement parce que je n'avais pas de résistances de 10 kOhm sous la main, mais seulement 20 kOhm.





Circuit assemblé.



Eh bien, voyons en gros plan à quoi tout cela ressemble. J'ai spécialement alloué un foulard séparé pour le condensateur, où il est soudé directement sur les aiguilles de montage qui sont insérées dans ce matelas.





Afin de vérifier l'émulateur, j'ai d'abord pensé utiliser le même RDM6300 (j'en ai acheté deux). Et même au début, il l'a fait, mais ensuite il a décidé que ce n'était pas sérieux, de déboguer un Arduina avec un autre, et il a fait faillite sur un lecteur d'usine.





Lecteur d'usine.



Armer la minuterie



J'ai expliqué plus en détail toute la physique du processus et le principe de fonctionnement dans mon article précédent , je vous recommande donc fortement de vous familiariser avec celui-ci. Cependant, pour comprendre ce que je fais, je vais rafraîchir un peu certains points.



Permettez-moi de vous rappeler que l'EM4102 utilise le schéma d'encodage Manchester. Lorsque le protocole EM4102 est modulé, le temps de transmission d'un bit peut être de 64, 32 ou 16 périodes de la fréquence porteuse (125 kHz).







En termes simples, lors de la transmission d'un bit, nous changeons la valeur de un à zéro (lors de la transmission de zéro), ou de zéro à un (lors de la transmission d'un). En conséquence, si nous choisissons de transmettre un bit d'information à 64 périodes de la fréquence porteuse, alors pour la transmission du "demi-bit", nous aurons besoin de 32 périodes de la fréquence porteuse. Ainsi, chaque quartet devrait changer à un rythme:



f=125000/32 = 3906,25 
      
      





La période de ce "demi-bit" sera égale à 256 ms.



Maintenant, nous devons calculer la minuterie pour qu'elle secoue notre jambe avec une fréquence donnée. Mais je suis devenu si paresseux que lorsque j'ai ouvert la fiche technique et que j'ai commencé à bâiller, j'ai décidé de trouver une solution toute faite. Et il s'est avéré qu'il existe des calculs de minuterie prêts à l'emploi, il suffit de saisir vos données. Meet: calculateur de minuterie pour Arduino .



Il suffit de marteler la fréquence de minuterie de 3906 Hz, et nous allons immédiatement générer un code prêt à l'emploi. Eh bien, n'est-ce pas un miracle!





Veuillez noter que j'ai entré la fréquence dans son ensemble, et il l'a comptée comme fractionnaire et exactement celle dont nous avons besoin. J'ai reçu le code d'initialisation du minuteur suivant:



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();
}
      
      





Brillant, simple, concis.



Le vecteur d'interruption pour la sortie est également très simple. Permettez-moi de vous rappeler que nous devons faire une transition de un à zéro dans le cas du transfert de zéro, et de zéro à un, dans le cas du transfert d'un (voir la figure pour une compréhension). Par conséquent, nous regardons ce que nous transmettons maintenant et où nous en sommes dans le «demi-bit», en lisant progressivement toutes les données du tableau de données.



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;
		}
	}
}
      
      





Traduire les données pour la transmission



Ici aussi, vous devez actualiser la mémoire des formats de données stockés sur la carte. La façon dont ils sont écrits. Prenons un exemple en direct.



Supposons que nous ayons une carte, mais pas de lecteur. Le numéro 010.48351 est inscrit sur la carte .





Une vraie carte avec le numéro 010, 48351.



Comment traduire ce numéro en numéro de série inscrit sur la carte? Assez simple. Rappelez-vous la formule: nous traduisons les deux parties du nombre séparément:



010d = 0xA
48351d = 0xBCDF
      
      





Donc, nous obtenons le numéro de série: 0xABCDF. Vérifions-le, lisons la carte avec un lecteur (elle lit au format décimal), on obtient un nombre:



0000703711
      
      





Nous le traduisons au format hexadécimal avec n'importe quelle calculatrice et obtenons à nouveau: 0xABCDF.

Cela semble si simple, attendez, maintenant vous devez vous fatiguer la cervelle. Permettez-moi de vous rappeler le format des données présentes sur la carte elle-même.





Je vais le mettre en mots:



  1. Il y a neuf unités d'en-tête au début.
  2. ID client d'un demi-octet le plus bas.
  3. À la fin du bit de parité.
  4. La seconde moitié de l'octet est l'ID client.
  5. Bit de parité.
  6. Le demi-octet le moins significatif de l'octet zéro du numéro de série.
  7. Bit de parité
  8. .
  9. ,
  10. . 10 ( ).
  11. , .


Au total, nous obtenons 64 bits de données (soit cinq octets!). En remarque, mon lecteur ne lit pas l'ID client et je l'accepte comme zéro.



Qu'est-ce qu'un bit de parité? C'est le nombre de uns dans le package: s'il est pair, alors le bit de parité est zéro, sinon, alors un. Le moyen le plus simple de le calculer est simplement un XOR régulier.



En fait, j'ai longtemps réfléchi à comment rendre la conversion du numéro de série en un package plus élégante, afin qu'elle prenne moins de place dans le microcontrôleur. Par conséquent, j'ai esquissé un petit programme qui fait cela. Le programme peut être consulté sous le spoiler.



Programme de test pour traduire le numéro de série en données
#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;
}

      
      





Le plus important pour nous est de savoir à quoi ressembleront les bits de parité. Pour plus de commodité, j'ai fait la sortie à l'écran exactement de la même manière que dans cette plaque. Le résultat est comme ça.





card_id est le numéro de série de la carte (dont nous avons parlé ci-dessus).



La première colonne est les nibls, la seconde est leur représentation binaire, la troisième est le bit de parité. La troisième ligne à partir du bas correspond aux bits de parité de tous les nibl. Comme je l'ai dit, ils sont calculés simplement par XOR.



Après avoir testé les calculs, les avoir vérifiés visuellement, j'ai vérifié les données résultantes dans le programme sur Arduino (la dernière ligne est spécialement pour l'insertion dans le code). Tout a bien fonctionné. À la suite de l'esquisse de ce programme, j'ai obtenu une fonction de recalcul prête à l'emploi. Auparavant, les calculs de battements étaient des programmes de quelqu'un d'autre sur un ordinateur et je n'aimais pas leur implémentation monstrueuse. Ainsi, la fonction de conversion du numéro de série dans le format de transmission ressemble à ceci:




#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));
  }
}
      
      





Tout, vous pouvez procéder à des tests sur le terrain. Le code source du projet vit ici .



Des tests



Comme on dit, il vaut mieux voir une fois que lire mille fois. Surtout pour vous, j'ai enregistré un film sur le travail de cet émulateur. Je voulais le tester sur du vrai matériel et essayer d'entrer dans le bureau en utilisant Arduino, mais avec la fichue pandémie, ils n'y sont pas autorisés. Par conséquent, les tests à grande échelle devront être examinés sur la table, dans des conditions de laboratoire.





conclusions



J'espère vraiment que de tels articles inciteront les débutants à apprendre la programmation et l'électronique. Et ils contribueront également au départ du marché de ce type de cartes, car les plus non protégées et les moins sûres, puisque maintenant même un enfant peut les copier et les imiter.



J'exprime ma gratitude à Michal Krumnikl pour sa patience il y a de nombreuses années, lorsqu'il m'a expliqué sur icq le fonctionnement d'un tel émulateur, ainsi que son aide au développement du code. En un sens, ce sont ses idées et ses développements il y a 13 ans.



Liens












All Articles