Standard Unique:
Jest to najprostszy oraz najbardziej podstawowy standard RFID. Pozwala na odczyt kart z odległości kilku cm do 0,5 metra. Karty mają możliwość wyłącznie odczytu ich przez użytkownika,
Standard UNIQUE charakteryzuje się:
- Częstotliwością pracy wynoszącą 125kHz.
- Zapis 64 bitów.
- Modulacja ASK, czyli poprzez zmianę amplitudy fali nośnej.
- Kodowanie Manchester, o którym będzie mowa w dalszej części.
- 40 Bitowy numer seryjny.
Karty zapisywane są na etapie produkcji. Są one pasywne co oznacza, że nie mają własnego źródła zasilania. Jego zasilenie odbywa się poprzez sprzężenie indukcyjne w polu elektromagnetycznym wytwarzanym wokół anteny czytnika. W karcie znajdują się kondensatory, które po naładowaniu zasilą znajdujący się w niej układ sekwencyjny. Wysłanie pojedynczego bitu danych wynosi 64 okresy fali nośnej, czas trwania to 512 us.
Układ sterujący na podstawie częstotliwości sygnału generuje sygnał taktujący. Zmiana prądu odbywa się poprzez dodanie rezystancji. Powoduje to zmianę pola magnetycznego.
Kodowanie
Dane na karcie zapisywane są w następujący sposób. W pierwszej kolejności przesyłany jest nagłówek który stanowi same jedynki (9 bitów). Po nim nadawany jest numer karty, zgodnie z rysunkiem, od D00 do D93. W skład tego numeru wchodzi identyfikator nadawany klientowi przez producenta układów tj. od D00 do D23. Dodatkowo nadawane są bity parzystości dla kolumn oraz wierszy, co stanowi 14 bitów (10 dla wierszy oraz 4 dla kolumn). Wartość 1 w tych bitach informuje, że w ciągu znajduje się nieparzysta liczba jedynek. Gdy parzysta to wartości w polach PR wynosi 0.Bity parzystości poziomej oraz pionowej działają tak samo.
Pamięć kart Unique [link]
Jak już wspomniałem wcześniej w układzie wykorzystywane jest kodowanie Manchester. Każdy okres sygnału, czyli pojedynczy bit zawiera w sobie sygnał wysoki oraz niski. Żeby stosować to kodowanie należy znać okres szukanego sygnału.
Na samym początku należy oczekiwać na pojawienie się jakiegoś zbocza. Najwygodniej wykonywaćto w przerwaniu, przez co spokojnie program będzie mógł wykonywać inne operacje. W momencie otrzymania pierwszego bitu, czyli wyzwolenia przerwania, należy odczekać około 3/4 okresu po czym przez około połowy czasu trwania wykresu należy szukać kolejnego zbocza. Jeśli wystąpi to następuje powtórzenie całej procedury. Jeśli nastąpi nadawanie tych samych bitów to czas potrzebny na wypuszczenie kolejnego bitu będzie równy połowie okresu. Dla różnych bitów ten czas będzie wynosił pełny czas trwania okresu.
Cała procedura odczytu zależy wobec tego od odczekania odpowiedniej ilości czasu czy to będzie połowa czy pełny okres.
Dekodowanie danych odbywa się poprzez odebranie pełnej ramki danych czyli 64 bitów. W nich wyszukiwane są bity startu, czyli 9 jedynek. Tylko na początku transmisji może wystąpić taka kombinacja.
Jako dodatkowe zabezpieczanie trzeba się upewnić, że dekodowanie jest przeprowadzane od odpowiedniego zbocza, drugi rodzaj błędu polega na wystąpieniu błędów w transmisji. Będzie się to objawiać nie poprawnymi danymi w bitach parzystości, odnośnie linii danych, które one dotyczą.
Wobec tego procedurę obsługi można sprowadzić do takich kroków jak, sprawdzanie danych do momentu wystąpienia bitu startu. Jak już się uda znaleźć początek ramki to należy sprawdzić bity parzystości. Jeśli wszystko się zgadza to następuje złożenie danych w całość. Natomiast gdy wystąpił błąd w którymś z kroków, to może to oznaczać, że rozpoczęto zliczanie nie od tego zbocza. W takim przypadku należy odwrócić dane, czyli zanegować i powtórzyć całą procedurę. Jeśli to nie przyniosło efektów to odebrana została błędna ramka danych. Teraz należy odrzucić stare dane i ponownie pobrać informacje od karty.
Układ EM4095:
Układ zawiera następujące wyprowadzenia:
SHD - pozwala na włączenie, wyłączenie bądź reset układu. Wykonuje się to poprzez podanie sygnału niskiego.
DEMOD_OUT - na nim znajdować się będzie sygnał jaki zostanie odebrany z anteny.
MOD - pozwala na modulację sygnału jaki jest nadawany przez antenę.
RDY/CLK - może zostać wykorzystane jako generator sygnału odniesienia.
ANT - złącze anteny.
PCB:
Przykładową płytkę PCB można znaleźć na tej stronie. Znajduje się tam przykładowe rozwiązanie projektowe dla PCB.
Można także skorzystać z noty opublikowanej przez producenta an404 (EM4095 Application Note), która zawiera przykładowe sposoby podłączenia oraz rozmieszczenia elementów na płytce.
Jak widać wszystkie niezbędne elementy w skład których wchodzą głównie kondensatory. Dla tak zaprojektowanej płytki producent przewidział zakres odczytu wynoszący 11cm. W przypadku układów RFID zwłaszcza jeśli chodzi o obwód anteny, to bardzo ważne jest jej kształt powierzchnia oraz dobór odpowiednich komponentów zwłaszcza rezystor Rser. Wszystkie inne przykładowe schematy podłączenia można znaleźć w dokumentacji producenta w internecie, bądź w linku na dole strony.
Program:
Poniżej przejdę przez wszystkie elementy programu. Zaczynając od pliku nagłówkowego.
W przygotowaniu programu opierałem się na bibliotece udostępnionej na stronie mikrokontrolery.blogspot.com.
Do układu, jak już wspomniałem, podłączone zostaną dwa wyprowadzenia, OUT oraz SHD. Zegar mikrokontrolera został ustawiony jako 8MHz, taktowane ze źródła wewnętrznego.
- #ifndef EM4095_LIB_H_
- #define EM4095_LIB_H_
- #ifndef F_CPU
- #define F_CPU 8000000UL
- #endif
- //-------------------------------
- #include <util/delay.h>
- #include <avr/interrupt.h>
- //-------------------------------
- #define OUT_PORT PORTB
- #define OUT_DDR DDRB
- #define OUT_PIN PB0
- #define SHD_PORT PORTD
- #define SHD_DDR DDRD
- #define SHD_PIN PD4
- //-------------------------------
- #define T_PRESCALER 8
- //-------------------------------
- //taktowanie z sygnału CLK układu EM4095
- #define T_TOLERANCE 18
- #define T_SHORT_PULSE 45
- #define T_LONG_PULSE 90
- #define T_MIN_HALF_BIT T_SHORT_PULSE - T_TOLERANCE
- #define T_MAX_HALF_BIT T_SHORT_PULSE + T_TOLERANCE
- #define T_MAX_BIT T_LONG_PULSE + T_TOLERANCE
- #define TOLERANCE ((F_CPU*(T_TOLERANCE ))/T_PRESCALER)/125000
- #define MIN_HALF_BIT ((F_CPU*(T_MIN_HALF_BIT))/T_PRESCALER)/125000
- #define MAX_HALF_BIT ((F_CPU*(T_MAX_HALF_BIT))/T_PRESCALER)/125000
- #define MAX_BIT ((F_CPU*(T_MAX_BIT ))/T_PRESCALER)/125000
- //--------------------------------
- //maski 64 bit do obsługi tagu
- #define FRAME_HEADER_MASK (uint64_t)0xFF80000000000000
- #define FRAME_MSB_MASK (uint64_t)0x8000000000000000
- #define FRAME_LSB_MASK (uint64_t)0x0000000000000001
- //-------------------------------
- #define D_MASK (uint64_t)0x00000000000003C0
- #define D_SHIFT 6
- #define P_MASK (uint64_t)0x0000000000000020
- #define P_SHIFT 5
- #define C_MASK (uint64_t)0x0000000000000002
- #define C_SHIFT 1
- //------------------------------
- typedef struct card_data_struct
- {
- volatile uint8_t RFID_flag_decode;
- uint8_t RFID_card_number[5];
- uint8_t RFID_old_card_number[5];
- volatile uint64_t RFID_data;
- volatile uint64_t RFID_temp;
- } card_data_t;
- //--------------------------------
- typedef struct transmit_data_struct
- {
- volatile uint16_t gv_last_edge; //ostatnia wartość przechwycenia
- volatile uint8_t gv_edge_count; //licznik zbocz
- volatile uint8_t gv_bit_count; //licznik bitów odebranych
- volatile uint8_t gv_bit_value; //wartość aktualnie przetwarzanego bitu
- } transmit_data_t;
- //--------------------------------
- void RFID_init(void);
- uint8_t header_align(void);
- uint8_t parity_check(uint8_t value);
- uint8_t horizontal_parity_bit_check(void);
- uint8_t vertical_parity(void);
- #endif /* EM4095_LIB_H_ */
W strukturze przechowywane będą dane odpowiedzialne za flagę poprawnego zdekodowania danych. Będzie ona sprawdzana w pętli głównej programu, gdy zostanie ustawiona, to będzie oznaczać, że dane zostały odebrane oraz poprawnie zdekodowane. Kolejne dwie zmienna tablicowe będzie przechowywały numer karty sformatowany po jednym bajcie w każdej komórce. Jedna z nich przechowuje świeżo odebrany numer, druga z nich zawiera numer poprzednio odebrany. Będę to wykorzystywał do zabezpieczenia możliwości ponownego odczytu tej samej karty ponownie. Można to użyć na dwa sposoby. Jeden z nich polega na tym, że dane nie będą zmieniane aż uda się otrzymać inny numer karty. Powoduje to zabezpieczenie przed otwarciem przez różne osoby jednym numerem karty. Drugi sposób polega na tym, że odczekuje się pewną ilość czasu za nim karta będzie mogła być ponownie odczytana np. 10 sekund. Po tym czasie zmienna z danymi wcześniejszymi zostanie wyczyszczona.
Dane zostają wstępnie zainicjalizowane w wywołaniu struktury w pliku c:
- //----------------------------------------------------------
- #include "em4095_lib.h"
- //========================================================================================
- transmit_data_t transmit_data =
- {
- .gv_last_edge = 0, //ostatnia wartość przechwycenia
- .gv_edge_count = 0, //licznik zbocz
- .gv_bit_count = 0, //licznik bitów odebranych
- .gv_bit_value = 0 //wartość aktualnie przetwarzanego bitu
- };
- //----------------------------------------------------------
- card_data_t card_data =
- {
- .RFID_flag_decode = 0, //Flaga odebranych danych
- .RFID_card_number = {0, 0, 0, 0, 0}, //Numer odebrany
- .RFID_old_card_number = {0, 0, 0, 0, 0}, //Wczesniejszy numer odebrany
- .RFID_data = 0, //Pełny numer karty
- .RFID_temp = 0 //Przechowuje odebrane bity
- };
- //======================================================================================
- void RFID_init()
- {
- OUT_DDR &= ~(1<<OUT_PIN); //pin OUT jako wejście z podciąganiem
- OUT_PORT |= (1<<OUT_PIN);
- //-------------------------------------
- SHD_DDR |= (1<<SHD_PIN); //piny SHD jako wyjścia
- SHD_PORT &= ~(1<<SHD_PIN);
- //-------------------------------------
- _delay_ms(200);
- SHD_PORT |= (1<<SHD_PIN);
- _delay_ms(200);
- SHD_PORT &= ~(1<<SHD_PIN);
- //-------------------------------------
- //Ustawienie prescalera na 8, czyli 8Mhz/8 = 1MHz
- //Konfiguracja timera 1
- TCCR1B |=(1<<CS11); //preskaler 8
- TCCR1B &= ~(1<<ICES1); //zbocze opadające do pierwszego wyzwolenia
- TIMSK1 |= (1<<ICIE1); //aktywowanie przerwania
- card_data.RFID_flag_decode = 0; //zerowanie flagi
- }
Funkcja odpowiedzialna za sprawdzenie bitów startu:
- uint8_t header_set(void)
- {
- uint8_t data_bit; //bit tymczasowy
- uint8_t i = 0;
- while(i<64)
- {
- if((card_data.RFID_data & FRAME_HEADER_MASK) == FRAME_HEADER_MASK) { return 1; }
- if(card_data.RFID_data & FRAME_MSB_MASK) { data_bit=1; }
- else { data_bit=0; }
- card_data.RFID_data=card_data.RFID_data<<1;
- if(data_bit) { card_data.RFID_data|=FRAME_LSB_MASK; }
- i++;
- }
- if((card_data.RFID_data & FRAME_HEADER_MASK) == FRAME_HEADER_MASK) { return 1; }
- else { return 0; }
- return 0;
- }
Funkcja odpowiedzialna za sprawdzenie bitów parzystości:
- uint8_t parity_check(uint8_t value)
- {
- if(value == 0) { return 0; }
- else if(value == 1) { return 1; }
- else if(value == 2) { return 1; }
- else if(value == 3) { return 0; }
- else if(value == 4) { return 1; }
- else if(value == 5) { return 0; }
- else if(value == 6) { return 0; }
- else if(value == 7) { return 1; }
- else if(value == 8) { return 1; }
- else if(value == 9) { return 0; }
- else if(value == 10) { return 0; }
- else if(value == 11) { return 1; }
- else if(value == 12) { return 0; }
- else if(value == 13) { return 1; }
- else if(value == 14) { return 1; }
- else if(value == 15) { return 0; }
- else
- {
- return 0xFF;
- }
- }
Jak wspomniałem wcześniej bity z parzystą liczbą jedynek mają bit parzystości równy 0, z nieparzystą natomiast wartość ta wynosi 1. Sprawdzane są dane po 4 bity w rzędzie zgodnie z protokołem transmisji.
Sprawdzenie bitów parzystości:
Sprawdzenie bitów parzystości:
- uint8_t horizontal_parity_bit_check(void)
- {
- uint8_t check_state=1;
- uint8_t hbyte=0;
- for(uint8_t i=0;i<10;i++)
- {
- uint8_t par_c,par_r;
- hbyte=(card_data.RFID_data&(D_MASK)<<(i*5))>>(D_SHIFT+(i*5));
- par_c=parity_check(hbyte);
- par_r=(card_data.RFID_data&(P_MASK)<<(i*5))>>(P_SHIFT+(i*5));
- if(par_c!=par_r) { check_state=0;}
- if(i%2==0) { card_data.RFID_card_number[i/2]=hbyte; }
- else { card_data.RFID_card_number[i/2]|=hbyte<<4; }
- }
- return check_state;
- }
Kolejna funkcja ma za zadanie sprawdzenie bitów parzystości poziomej:
- uint8_t vertical_parity_bit_check(void) //obliczamy bit parzystości pionowej
- {
- uint8_t i = 0;
- uint8_t j = 0;
- uint8_t vertical_paritity_cal = 0;
- uint8_t vertical_parity_res;
- uint8_t check[4] = {
- (card_data.RFID_data & (C_MASK) << (3)) >> (4),
- (card_data.RFID_data & (C_MASK) << (2)) >> (3),
- (card_data.RFID_data & (C_MASK) << (1)) >> (2),
- (card_data.RFID_data & (C_MASK)) >> (1)
- };
- for(i=0;i<4;i++)
- {
- vertical_paritity_cal = 0;
- vertical_parity_res = 0;
- for(j=0;j<5;j++)
- {
- if(card_data.RFID_card_number[j] & (128 >> i)) { vertical_paritity_cal ^= 0x01; }
- if(card_data.RFID_card_number[j] & (8 >> i)) { vertical_paritity_cal ^= 0x01; }
- }
- vertical_parity_res = check[i];
- //Jeśli dane się nie zgadzają to zakończ działanie
- if(vertical_parity_res != vertical_paritity_cal) { return 0; }
- }
- return 1;
- }
Teraz ostatnia funkcja w której została przedstawiona obsługa przerwania:
- ISR(TIMER1_CAPT_vect)
- {
- uint16_t pulse_width;
- uint8_t check_parity;
- pulse_width = ICR1 - transmit_data.gv_last_edge; //szerokość impulsu
- transmit_data.gv_last_edge=ICR1; //zapisujemy dane tego zbocza
- TCCR1B ^= (1<<ICES1); //zmiana zbocza wyzwalającego na przeciwne
- //Jeśli dane zostały wyzerowane
- if(transmit_data.gv_edge_count == 0)
- {
- card_data.RFID_temp = 0;
- transmit_data.gv_bit_count = 0;
- transmit_data.gv_bit_value=1;
- }
- if(pulse_width < MIN_HALF_BIT || pulse_width > MAX_BIT) { transmit_data.gv_edge_count=0; }
- //Krótki impuls
- else if(pulse_width >= MIN_HALF_BIT && pulse_width <= MAX_HALF_BIT)
- {
- //sprawdzenie czy wystąpiło zbocze parzyste
- if(transmit_data.gv_edge_count % 2 == 0)
- {
- card_data.RFID_temp<<=1; //to zapisujemy bit
- card_data.RFID_temp |= (uint64_t)transmit_data.gv_bit_value;
- transmit_data.gv_bit_count++;
- }
- transmit_data.gv_edge_count++;
- }
- //Długi impuls
- else
- {
- transmit_data.gv_bit_value^=0x01;
- card_data.RFID_temp<<=1;
- card_data.RFID_temp |= (uint64_t)transmit_data.gv_bit_value;
- transmit_data.gv_bit_count++;
- transmit_data.gv_edge_count += 2;
- }
- if(transmit_data.gv_bit_count>64)
- {
- if (card_data.RFID_flag_decode == 0)
- {
- card_data.RFID_data=card_data.RFID_temp;
- check_parity=header_set();
- if(check_parity == 1) { check_parity = horizontal_parity_bit_check(); }
- if(check_parity == 1) { check_parity = vertical_parity_bit_check(); }
- if(!check_parity)
- {
- card_data.RFID_data=~card_data.RFID_data;
- check_parity=header_set();
- if(check_parity) { check_parity=horizontal_parity_bit_check(); }
- if(check_parity) { check_parity=vertical_parity_bit_check(); }
- }
- card_data.RFID_flag_decode = check_parity;
- transmit_data.gv_edge_count=0;
- }
- }
- }
W pętli głównej należy sprawdzać czy flaga już wystąpiła jeśli tak wyświetl dane:
- uint8_t first_card_number = 0;
- uint8_t first_regist = 0;
- uint8_t i = 0;
- WDT_init(); //Init watchdog
- RFID_init(); //RFID init
- USART_INIT(COM0,BaudRate_9600); //Usart init
- USART_SEND(COM0,"EM4095 Decoder",END_CRLF_ASCII); //Przesłanie danych
- while(1)
- {
- wdt_reset(); //Reset watchdog
- if(card_data->RFID_flag_decode == 1)
- {
- if(0 == first_card_number)
- {
- for(i=0;i<4;i++)
- {
- card_data->RFID_old_card_number[i] = card_data->RFID_card_number[i];
- }
- first_card_number = 1;
- }
- for(i=0;i<4;i++)
- {
- if(card_data->RFID_card_number[i] != card_data->RFID_old_card_number[i])
- {
- new_card_number = 1;
- }
- else
- {
- new_card_number = 0;
- }
- }
- if(1 == new_card_number &&)
- {
- sprintf(UsartSendData, "Nr. Karty: %X %X %X %X %X", card_data->RFID_card_number[4], card_data->RFID_card_number[3],
- card_data->RFID_card_number[2], card_data->RFID_card_number[1], card_data->RFID_card_number[0]);
- USART_SEND(COM0,UsartSendData,END_CRLF_ASCII);
- for(i=0;i<4;i++)
- {
- card_data->RFID_old_card_number[i] = card_data->RFID_card_number[i];
- }
- card_data->RFID_flag_decode = 0;
- }
- }
- }
Pod tym linkiem znajduje się program wraz z dokumentacją.