środa, 25 lipca 2018

[8] ATxmega - EM4095

Ten post chciałbym poświęcić na przygotowanie projektu odczytu karty Unique za pomocą układu EM4095.

[Źródło: http://www.atmel.com/images/Atmel-8135-8-and-16-bit-AVR-microcontroller-ATxmega16D4-32D4-64D4-128D4_datasheet.pdf]

Program:


Poniżej przejdę przez wszystkie funkcje dekodujące.

Cały kod został oparty na projekcie udostępniony na stronie mikrokontrolery.blogspot.com. Tam też znajduje się opisany sposób kodowania Manchester, jak i opis działania funkcji obliczających numer karty i sprawdzających parzystość.

Funkcja inicjalizująca piny oraz timer:

  1. static void configSHDPin(void)
  2. {
  3.     ioport_configure_pin(EM_SHD_PIN, IOPORT_DIR_OUTPUT);
  4.        
  5.     gpio_set_pin_low(EM_SHD_PIN);
  6.     _delay_ms(5000);
  7.     gpio_set_pin_high(EM_SHD_PIN);
  8. }
  9. static void configDemodWithTimer(void)
  10. {
  11.     ioport_configure_pin(EM_DEMOD_PIN, IOPORT_DIR_INPUT);
  12.     EM_DEMOD_PORT_CTRL = PORT_ISC_BOTHEDGES_gc;
  13.     EVSYS_CH0MUX = EVSYS_CHMUX_PORTE_PIN2_gc;
  14.        
  15.     TC0_ConfigInputCapture( &TCE0, TC_EVSEL_CH0_gc );
  16.     TC0_EnableCCChannels( &TCE0, TC0_CCAEN_bm );
  17.     TC0_ConfigClockSource( &TCE0, TC_CLKSEL_DIV64_gc );
  18.    
  19.     TC0_SetCCAIntLevel( &TCE0, TC_CCAINTLVL_MED_gc );
  20.     PMIC.CTRL |= PMIC_HILVLEN_bm | PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm;
  21. }
  22. void EM_Init(void)
  23. {
  24.     configSHDPin();
  25.     configDemodWithTimer();
  26. }

Na samym początki należy zdefiniować pin SHD jako wyjście po czym ustawić na nim stan niski a następnie wysoki. Następnie konfiguruje pin DEMOD tak aby przerwanie było wywoływane po przyjściu zbocza opadającego bądź rosnącego, co spowoduje wyzwalanie przerwań dla Timera TCE0.

Obsługa przerwania od Timera:

  1. static inline uint8_t checkIfImpulseTooLongOrTooShort(uint16_t pulseLen){
  2.     if ((pulseLen < T_SHORT_PULSE_MIN) || (pulseLen > T_LONG_PULSE_MAX))
  3.     {
  4.         return 1;
  5.     }
  6.     return 0;
  7. }
  8. static inline uint8_t checkIfImpulseTooShort(uint16_t pulseLen){
  9.     if ((pulseLen >= T_SHORT_PULSE_MIN) && (pulseLen <= T_SHORT_PULSE_MAX))
  10.     {
  11.         return 1;
  12.     }
  13.     return 0;
  14. }
  15. ISR(TCE0_CCA_vect)
  16. {  
  17.     static uint8_t edgeCount;
  18.     static uint8_t bitValue;
  19.     static uint8_t receiveBitCount;
  20.     uint16_t pulseLen;
  21.    
  22.     /* Check if card data is still procesing */
  23.     if(!uniqueCardData.dataProced) { return; }
  24.    
  25.     if (!edgeCount) {
  26.         /* reset all variables */
  27.         receiveBitCount = 0;
  28.         bitValue = 1;
  29.         uniqueCardData.result = 0;
  30.     }
  31.    
  32.     pulseLen = TC_GetCaptureA( &TCE0 );
  33.    
  34.     if (checkIfImpulseTooLongOrTooShort(pulseLen))
  35.     {
  36.         edgeCount = 0;
  37.     }
  38.     else if (checkIfImpulseTooShort(pulseLen))
  39.     {
  40.         /* check if even */
  41.         if ((edgeCount % 2) == 0)
  42.         {
  43.             uniqueCardData.result <<= 1;
  44.             uniqueCardData.result |= (uint64_t)bitValue;
  45.             receiveBitCount++;
  46.         }
  47.         edgeCount++;
  48.     }
  49.     else
  50.     {
  51.         bitValue ^= 0x01;
  52.         uniqueCardData.result <<= 1;
  53.         uniqueCardData.result |= (uint64_t)bitValue;
  54.         receiveBitCount++;
  55.         edgeCount += 2;
  56.     }
  57.    
  58.     if (receiveBitCount > 64) /* whole frame received */
  59.     {
  60.         edgeCount = 0;
  61.         uniqueCardData.frameReady = true;
  62.         if (!uniqueCardData.frame)
  63.         {
  64.             PORTE_PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
  65.             uniqueCardData.frame = uniqueCardData.result;
  66.             uniqueCardData.dataProced = false;
  67.         }
  68.     }
  69.     TC_Restart( &TCE0 );
  70.  }

Gdy przychodzi przerwanie na pinie, to wartość czasu zostaje przechowywana w rejestrze CCA. Dzięki tej wartości można stwierdzić jakiej długości jest odczytany impuls.

Każda ramka składa się 9 bitów startu, które w każdym przypadku są zdefiniowane jako jedynki. Wobec tego należy wyszukać sekwencję startową za pomocą następującej funkcji:

  1. static bool FindHeader(uniqueCardData_t * card)
  2. {
  3.     uint8_t temp_bit;
  4.    
  5.     for (uint8_t i = 0; i < 64; i++)
  6.     {
  7.         if ((card->frame & FRAME_HEADER_MASK) == FRAME_HEADER_MASK) {
  8.             return true;
  9.         }
  10.         else
  11.         {
  12.             temp_bit = card->frame & FRAME_MSB_MASK ? 1 : 0;
  13.             card->frame <<= 1;
  14.             if (temp_bit)
  15.             {
  16.                 card->frame |= FRAME_LSB_MASK;
  17.             }
  18.         }
  19.     }
  20.    
  21.     if ((card->frame & FRAME_HEADER_MASK) == FRAME_HEADER_MASK)
  22.     {
  23.         return true;
  24.     }
  25.    
  26.     return false;
  27. }

Po przejściu modyfikowana jest ramka danych, tak aby na jej początku znajdowała się ramka danych.

Obliczanie parzystości pionowej oraz poziomej:

  1. static bool HorizontalParityCheck(uint64_t frame)
  2. {
  3.     /* clear read card number */
  4.     for(uint8_t i=0; i<5; i++)
  5.     {
  6.         uniqueCardData.card_number[i] = 0x00;
  7.     }
  8.     uint8_t status=1;
  9.     uint8_t hbyte=0;
  10.    
  11.     for(uint8_t i=0;i<10;i++)
  12.     {
  13.         uint8_t par_c;
  14.         uint8_t par_r;
  15.        
  16.         hbyte=(frame&((uint64_t)D10_MASK)<<(i*5))>>(D10_SHIFT+(i*5));
  17.        
  18.         par_c=parity_cal(hbyte);
  19.        
  20.         par_r=(frame&((uint64_t)P10_MASK)<<(i*5))>>(P10_SHIFT+(i*5));
  21.        
  22.         if(par_c!=par_r)
  23.         {
  24.             status=0;
  25.             return status;
  26.         }
  27.        
  28.         /* get card number */
  29.         if(i%2==0)
  30.         {
  31.             uniqueCardData.cardNumber[i/2]=hbyte;
  32.         }
  33.         else
  34.         {
  35.             uniqueCardData.cardNumber[i/2]|=hbyte<<4;
  36.         }
  37.     }
  38.     return status; //OK
  39. }
  40. static bool VerticalParityCheck(uint64_t frame) {
  41.     for(uint8_t i=0;i<4;i++)
  42.     {
  43.         uint8_t vpar_c=0;
  44.         uint8_t vpar_r;
  45.         for(uint8_t j=0;j<5;j++)
  46.         {
  47.             if(uniqueCardData.cardNumber[j]&(0b10000000>>i))
  48.             {
  49.                 vpar_c^=0x01;
  50.             }
  51.             if(uniqueCardData.cardNumber[j]&(0b00001000>>i))
  52.             {
  53.                 vpar_c^=0x01;
  54.             }
  55.         }
  56.         vpar_r=(frame&((uint64_t)CP4_MASK)<<(3-i))>>(CP4_SHIFT+(3-i));
  57.         if(vpar_r!=vpar_c)
  58.         {
  59.             return 0;
  60.         }
  61.     }
  62.     return 1;
  63. }

Funkcja pomocnicza obliczająca parzystość dla czterech bitów:

  1. static uint8_t parity_cal(uint8_t value){
  2.     switch(value){
  3.         case 0b0000: return 0;
  4.         case 0b0001: return 1;
  5.         case 0b0010: return 1;
  6.         case 0b0011: return 0;
  7.         case 0b0100: return 1;
  8.         case 0b0101: return 0;
  9.         case 0b0110: return 0;
  10.         case 0b0111: return 1;
  11.         case 0b1000: return 1;
  12.         case 0b1001: return 0;
  13.         case 0b1010: return 0;
  14.         case 0b1011: return 1;
  15.         case 0b1100: return 0;
  16.         case 0b1101: return 1;
  17.         case 0b1110: return 1;
  18.         case 0b1111: return 0;
  19.     }
  20.     return 2;
  21. }

Dekodowanie numeru karty:

  1.     if (uniqueCardData.frameReady)
  2.     {
  3.         /* refresh counter */
  4.         uniqueCardData.cardResetTimer = CARD_RESET_TIMER;
  5.         counter_started = true;
  6.        
  7.         if (FindHeader(&uniqueCardData))
  8.         {
  9.             uint8_t decodeStatus = 0;
  10.             decodeStatus = HorizontalParityCheck(uniqueCardData.frame);
  11.             if(decodeStatus){
  12.                 decodeStatus = VerticalParityCheck(uniqueCardData.frame);
  13.             }
  14.                        
  15.             if(!decodeStatus)
  16.             {
  17.                 uniqueCardData.frame = ~uniqueCardData.frame;
  18.                 decodeStatus = FindHeader(&uniqueCardData);
  19.                 if(decodeStatus)decodeStatus=HorizontalParityCheck(uniqueCardData.frame);
  20.                 if(decodeStatus)decodeStatus=VerticalParityCheck(uniqueCardData.frame);
  21.             }
  22.                        
  23.             if(decodeStatus)
  24.             {
  25.                 /* card number receive ok */
  26.             }
  27.             else
  28.             {
  29.                 uniqueCardData.frame = 0;
  30.                 uniqueCardData.frameReady = false;
  31.             }
  32.         }

Dane przechowywane są w strukturze:

  1. typedef struct uniqueCardDataStructure{
  2.     volatile uint64_t frame;           
  3.     volatile uint64_t result;          
  4.     uint8_t cardNumber[5];            
  5.     uint8_t oldCardNumber[5];        
  6.     volatile bool frameReady;      
  7.     volatile bool dataProced;      
  8.     volatile uint16_t cardResetTimer;
  9. } uniqueCardData_t;

Na samym początku sprawdzamy czy ramka z danymi została odebrana. Jeśli tak to czyścimy timer i sprawdzamy czy jest początek ramki. Następnie sprawdzamy parzystość, jeśli się nie zgadza odwracamy ramkę i sprawdzamy jeszcze raz. Po tych operacja dane z numerem karty są przechowywane w zmiennej.