środa, 8 marca 2017

[7] Atmega328P - Karty Unique, układ EM4095

Ten post chciałbym poświęcić na opisanie sposobu podłączenia układu EM4095 do mikrokontrolera Atmega328p zamontowane na płytce Arduino Nano.

Standard Unique:


Poniżej przedstawię tylko skrótowo najważniejsze informacje. Większą ilość wiedzy można znaleźć np. na tej stronie.

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.

Mapa pamięci karty UNIQUE
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.

  1. #ifndef EM4095_LIB_H_
  2. #define EM4095_LIB_H_
  3. #ifndef F_CPU
  4. #define F_CPU 8000000UL
  5. #endif
  6. //-------------------------------
  7. #include <util/delay.h>
  8. #include <avr/interrupt.h>
  9. //-------------------------------
  10. #define OUT_PORT    PORTB
  11. #define OUT_DDR     DDRB
  12. #define OUT_PIN     PB0
  13. #define SHD_PORT    PORTD
  14. #define SHD_DDR     DDRD
  15. #define SHD_PIN     PD4
  16. //-------------------------------
  17. #define T_PRESCALER 8
  18. //-------------------------------
  19. //taktowanie z sygnału CLK układu EM4095
  20. #define T_TOLERANCE     18
  21. #define T_SHORT_PULSE   45
  22. #define T_LONG_PULSE    90
  23. #define T_MIN_HALF_BIT  T_SHORT_PULSE - T_TOLERANCE
  24. #define T_MAX_HALF_BIT  T_SHORT_PULSE + T_TOLERANCE
  25. #define T_MAX_BIT       T_LONG_PULSE + T_TOLERANCE
  26. #define TOLERANCE       ((F_CPU*(T_TOLERANCE   ))/T_PRESCALER)/125000
  27. #define MIN_HALF_BIT    ((F_CPU*(T_MIN_HALF_BIT))/T_PRESCALER)/125000
  28. #define MAX_HALF_BIT    ((F_CPU*(T_MAX_HALF_BIT))/T_PRESCALER)/125000
  29. #define MAX_BIT         ((F_CPU*(T_MAX_BIT     ))/T_PRESCALER)/125000
  30. //--------------------------------
  31. //maski 64 bit do obsługi tagu
  32. #define FRAME_HEADER_MASK  (uint64_t)0xFF80000000000000                    
  33. #define FRAME_MSB_MASK     (uint64_t)0x8000000000000000                    
  34. #define FRAME_LSB_MASK     (uint64_t)0x0000000000000001                
  35. //-------------------------------
  36. #define D_MASK   (uint64_t)0x00000000000003C0                      
  37. #define D_SHIFT  6
  38. #define P_MASK   (uint64_t)0x0000000000000020                      
  39. #define P_SHIFT  5
  40. #define C_MASK   (uint64_t)0x0000000000000002                      
  41. #define C_SHIFT  1
  42. //------------------------------
  43. typedef struct card_data_struct
  44. {
  45.     volatile uint8_t RFID_flag_decode;
  46.     uint8_t RFID_card_number[5];
  47.     uint8_t RFID_old_card_number[5];
  48.     volatile uint64_t RFID_data;
  49.     volatile uint64_t RFID_temp;
  50. } card_data_t;
  51. //--------------------------------
  52. typedef struct transmit_data_struct
  53. {
  54.     volatile uint16_t gv_last_edge;         //ostatnia wartość przechwycenia
  55.     volatile uint8_t  gv_edge_count;        //licznik zbocz
  56.     volatile uint8_t  gv_bit_count;         //licznik bitów odebranych
  57.     volatile uint8_t  gv_bit_value;         //wartość aktualnie przetwarzanego bitu
  58. } transmit_data_t;
  59. //--------------------------------
  60. void    RFID_init(void);
  61. uint8_t header_align(void);
  62. uint8_t parity_check(uint8_t value);
  63. uint8_t horizontal_parity_bit_check(void);
  64. uint8_t vertical_parity(void);
  65. #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:

  1. //----------------------------------------------------------
  2. #include "em4095_lib.h"
  3. //========================================================================================
  4. transmit_data_t transmit_data =
  5. {
  6.     .gv_last_edge = 0,          //ostatnia wartość przechwycenia
  7.     .gv_edge_count = 0,         //licznik zbocz
  8.     .gv_bit_count = 0,          //licznik bitów odebranych
  9.     .gv_bit_value = 0           //wartość aktualnie przetwarzanego bitu
  10. };
  11. //----------------------------------------------------------
  12. card_data_t card_data =
  13. {
  14.     .RFID_flag_decode       = 0,                        //Flaga odebranych danych
  15.     .RFID_card_number       = {0, 0, 0, 0, 0},          //Numer odebrany
  16.     .RFID_old_card_number   = {0, 0, 0, 0, 0},          //Wczesniejszy numer odebrany
  17.     .RFID_data              = 0,                        //Pełny numer karty
  18.     .RFID_temp              = 0                         //Przechowuje odebrane bity
  19. };
  20. //======================================================================================

Procedura inicjalizacji wygląda następująco:

  1. void RFID_init()
  2. {
  3.     OUT_DDR &= ~(1<<OUT_PIN);           //pin OUT jako wejście z podciąganiem
  4.     OUT_PORT |= (1<<OUT_PIN);
  5.     //-------------------------------------
  6.     SHD_DDR |= (1<<SHD_PIN);            //piny SHD jako wyjścia
  7.     SHD_PORT &= ~(1<<SHD_PIN);
  8.     //-------------------------------------
  9.     _delay_ms(200);
  10.     SHD_PORT |= (1<<SHD_PIN);
  11.     _delay_ms(200);
  12.     SHD_PORT &= ~(1<<SHD_PIN);
  13.     //-------------------------------------
  14.     //Ustawienie prescalera na 8, czyli 8Mhz/8 = 1MHz
  15.     //Konfiguracja timera 1
  16.     TCCR1B |=(1<<CS11);                 //preskaler 8
  17.     TCCR1B &= ~(1<<ICES1);              //zbocze opadające do pierwszego wyzwolenia
  18.     TIMSK1 |= (1<<ICIE1);               //aktywowanie przerwania
  19.     card_data.RFID_flag_decode = 0;     //zerowanie flagi
  20. }

Funkcja odpowiedzialna za sprawdzenie bitów startu:

  1. uint8_t header_set(void)
  2. {
  3.     uint8_t data_bit;                               //bit tymczasowy
  4.     uint8_t i = 0;
  5.    
  6.     while(i<64)                        
  7.     {                      
  8.         if((card_data.RFID_data & FRAME_HEADER_MASK) == FRAME_HEADER_MASK) {    return 1;   }
  9.        
  10.         if(card_data.RFID_data & FRAME_MSB_MASK)                 {  data_bit=1; }
  11.         else                                                     {  data_bit=0; }
  12.            
  13.         card_data.RFID_data=card_data.RFID_data<<1;        
  14.        
  15.         if(data_bit)                                             {  card_data.RFID_data|=FRAME_LSB_MASK;    }
  16.        
  17.         i++;
  18.     }
  19.    
  20.     if((card_data.RFID_data & FRAME_HEADER_MASK) == FRAME_HEADER_MASK)  { return 1; }
  21.     else                                                                { return 0; }
  22.     return 0;                          
  23. }

Funkcja odpowiedzialna za sprawdzenie bitów parzystości:

  1. uint8_t parity_check(uint8_t value)
  2. {  
  3.     if(value == 0)          { return 0; }
  4.     else if(value == 1)     { return 1; }
  5.     else if(value == 2)     { return 1; }
  6.     else if(value == 3)     { return 0; }
  7.     else if(value == 4)     { return 1; }
  8.     else if(value == 5)     { return 0; }
  9.     else if(value == 6)     { return 0; }
  10.     else if(value == 7)     { return 1; }
  11.     else if(value == 8)     { return 1; }
  12.     else if(value == 9)     { return 0; }
  13.     else if(value == 10)    { return 0; }
  14.     else if(value == 11)    { return 1; }
  15.     else if(value == 12)    { return 0; }
  16.     else if(value == 13)    { return 1; }
  17.     else if(value == 14)    { return 1; }
  18.     else if(value == 15)    { return 0; }
  19.     else
  20.     {
  21.         return 0xFF;  
  22.     }  
  23. }

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:

  1. uint8_t horizontal_parity_bit_check(void)
  2. {
  3.     uint8_t check_state=1;
  4.     uint8_t hbyte=0;
  5.    
  6.     for(uint8_t i=0;i<10;i++)
  7.     {
  8.         uint8_t par_c,par_r;
  9.        
  10.         hbyte=(card_data.RFID_data&(D_MASK)<<(i*5))>>(D_SHIFT+(i*5));  
  11.        
  12.         par_c=parity_check(hbyte);                 
  13.        
  14.         par_r=(card_data.RFID_data&(P_MASK)<<(i*5))>>(P_SHIFT+(i*5));      
  15.        
  16.         if(par_c!=par_r) { check_state=0;}
  17.        
  18.         if(i%2==0)  {   card_data.RFID_card_number[i/2]=hbyte;  }
  19.         else        {   card_data.RFID_card_number[i/2]|=hbyte<<4;  }
  20.     }
  21.    
  22.     return check_state;
  23. }

Kolejna funkcja ma za zadanie sprawdzenie bitów parzystości poziomej:

  1. uint8_t vertical_parity_bit_check(void)                 //obliczamy bit parzystości pionowej
  2. {
  3.     uint8_t i = 0;
  4.     uint8_t j = 0;
  5.    
  6.     uint8_t vertical_paritity_cal = 0;
  7.     uint8_t vertical_parity_res;
  8.    
  9.     uint8_t check[4] = {
  10.         (card_data.RFID_data & (C_MASK) << (3)) >> (4),
  11.         (card_data.RFID_data & (C_MASK) << (2)) >> (3),
  12.         (card_data.RFID_data & (C_MASK) << (1)) >> (2),
  13.         (card_data.RFID_data & (C_MASK)) >> (1)
  14.     };
  15.    
  16.     for(i=0;i<4;i++)
  17.     {
  18.         vertical_paritity_cal = 0;
  19.         vertical_parity_res = 0;
  20.        
  21.         for(j=0;j<5;j++)
  22.         {
  23.             if(card_data.RFID_card_number[j] & (128 >> i))  {   vertical_paritity_cal ^= 0x01;  }
  24.             if(card_data.RFID_card_number[j] & (8 >> i))    {   vertical_paritity_cal ^= 0x01;  }
  25.         }
  26.        
  27.         vertical_parity_res = check[i];
  28.        
  29.         //Jeśli dane się nie zgadzają to zakończ działanie
  30.         if(vertical_parity_res != vertical_paritity_cal) { return 0; }
  31.     }
  32.     return 1;
  33. }

Teraz ostatnia funkcja w której została przedstawiona obsługa przerwania:

  1. ISR(TIMER1_CAPT_vect)
  2. {
  3.     uint16_t pulse_width;
  4.     uint8_t  check_parity; 
  5.     pulse_width = ICR1 - transmit_data.gv_last_edge;        //szerokość impulsu
  6.     transmit_data.gv_last_edge=ICR1;                        //zapisujemy dane tego zbocza
  7.     TCCR1B ^= (1<<ICES1);                       //zmiana zbocza wyzwalającego na przeciwne
  8.     //Jeśli dane zostały wyzerowane
  9.     if(transmit_data.gv_edge_count == 0)
  10.     {
  11.         card_data.RFID_temp = 0;       
  12.         transmit_data.gv_bit_count = 0;
  13.         transmit_data.gv_bit_value=1;
  14.     }
  15.     if(pulse_width < MIN_HALF_BIT || pulse_width > MAX_BIT) { transmit_data.gv_edge_count=0; }
  16.     //Krótki impuls
  17.     else if(pulse_width >= MIN_HALF_BIT && pulse_width <= MAX_HALF_BIT)
  18.     {
  19.         //sprawdzenie czy wystąpiło zbocze parzyste
  20.         if(transmit_data.gv_edge_count % 2  == 0)  
  21.         {
  22.             card_data.RFID_temp<<=1;                                //to zapisujemy bit
  23.             card_data.RFID_temp |= (uint64_t)transmit_data.gv_bit_value;
  24.             transmit_data.gv_bit_count++;  
  25.         }
  26.         transmit_data.gv_edge_count++;
  27.     }
  28.     //Długi impuls
  29.     else                                          
  30.     {                          
  31.         transmit_data.gv_bit_value^=0x01;                          
  32.         card_data.RFID_temp<<=1;                           
  33.         card_data.RFID_temp |= (uint64_t)transmit_data.gv_bit_value;
  34.         transmit_data.gv_bit_count++;
  35.         transmit_data.gv_edge_count += 2;                          
  36.     }
  37.     if(transmit_data.gv_bit_count>64)                      
  38.     {
  39.         if (card_data.RFID_flag_decode == 0)                   
  40.         {
  41.             card_data.RFID_data=card_data.RFID_temp;
  42.                                    
  43.             check_parity=header_set();                             
  44.            
  45.             if(check_parity == 1) { check_parity = horizontal_parity_bit_check(); }
  46.             if(check_parity == 1) { check_parity = vertical_parity_bit_check(); }
  47.            
  48.             if(!check_parity)                          
  49.             {
  50.                 card_data.RFID_data=~card_data.RFID_data;                      
  51.                 check_parity=header_set(); 
  52.                    
  53.                 if(check_parity) { check_parity=horizontal_parity_bit_check(); }
  54.                 if(check_parity) { check_parity=vertical_parity_bit_check(); }
  55.             }
  56.             card_data.RFID_flag_decode = check_parity;
  57.             transmit_data.gv_edge_count=0;
  58.         }
  59.     }
  60. }

W pętli głównej należy sprawdzać czy flaga już wystąpiła jeśli tak wyświetl dane:

  1.     uint8_t first_card_number = 0;
  2.     uint8_t first_regist = 0;
  3.     uint8_t i = 0;
  4.     WDT_init();                                             //Init watchdog
  5.     RFID_init();                                            //RFID init
  6.     USART_INIT(COM0,BaudRate_9600);                         //Usart init
  7.     USART_SEND(COM0,"EM4095 Decoder",END_CRLF_ASCII);       //Przesłanie danych   
  8.     while(1)
  9.     {
  10.         wdt_reset();                                        //Reset watchdog
  11.         if(card_data->RFID_flag_decode == 1)
  12.         {
  13.             if(0 == first_card_number)
  14.             {
  15.                 for(i=0;i<4;i++)
  16.                 {
  17.                     card_data->RFID_old_card_number[i] = card_data->RFID_card_number[i];
  18.                 }
  19.                 first_card_number = 1;
  20.             }
  21.             for(i=0;i<4;i++)
  22.             {
  23.                 if(card_data->RFID_card_number[i] != card_data->RFID_old_card_number[i])
  24.                 {
  25.                     new_card_number = 1;
  26.                 }
  27.                 else
  28.                 {
  29.                     new_card_number = 0;
  30.                 }
  31.             }
  32.             if(1 == new_card_number &&)
  33.             {
  34.                 sprintf(UsartSendData, "Nr. Karty: %X %X %X %X %X", card_data->RFID_card_number[4], card_data->RFID_card_number[3],
  35.                 card_data->RFID_card_number[2], card_data->RFID_card_number[1], card_data->RFID_card_number[0]);
  36.                 USART_SEND(COM0,UsartSendData,END_CRLF_ASCII);
  37.                 for(i=0;i<4;i++)
  38.                 {
  39.                     card_data->RFID_old_card_number[i] = card_data->RFID_card_number[i];
  40.                 }
  41.                                
  42.                 card_data->RFID_flag_decode = 0;
  43.             }
  44.         }
  45.     }

Pod tym linkiem znajduje się program wraz z dokumentacją.