[Źródło: http://www.atmel.com/images/Atmel-8135-8-and-16-bit-AVR-microcontroller-ATxmega16D4-32D4-64D4-128D4_datasheet.pdf]
Po przejściu modyfikowana jest ramka danych, tak aby na jej początku znajdowała się ramka danych.
Obliczanie parzystości pionowej oraz poziomej:
Funkcja pomocnicza obliczająca parzystość dla czterech bitów:
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:
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:
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:
Funkcja inicjalizująca piny oraz timer:
- static void configSHDPin(void)
- {
- ioport_configure_pin(EM_SHD_PIN, IOPORT_DIR_OUTPUT);
- gpio_set_pin_low(EM_SHD_PIN);
- _delay_ms(5000);
- gpio_set_pin_high(EM_SHD_PIN);
- }
- static void configDemodWithTimer(void)
- {
- ioport_configure_pin(EM_DEMOD_PIN, IOPORT_DIR_INPUT);
- EM_DEMOD_PORT_CTRL = PORT_ISC_BOTHEDGES_gc;
- EVSYS_CH0MUX = EVSYS_CHMUX_PORTE_PIN2_gc;
- TC0_ConfigInputCapture( &TCE0, TC_EVSEL_CH0_gc );
- TC0_EnableCCChannels( &TCE0, TC0_CCAEN_bm );
- TC0_ConfigClockSource( &TCE0, TC_CLKSEL_DIV64_gc );
- TC0_SetCCAIntLevel( &TCE0, TC_CCAINTLVL_MED_gc );
- PMIC.CTRL |= PMIC_HILVLEN_bm | PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm;
- }
- void EM_Init(void)
- {
- configSHDPin();
- configDemodWithTimer();
- }
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:
- static inline uint8_t checkIfImpulseTooLongOrTooShort(uint16_t pulseLen){
- if ((pulseLen < T_SHORT_PULSE_MIN) || (pulseLen > T_LONG_PULSE_MAX))
- {
- return 1;
- }
- return 0;
- }
- static inline uint8_t checkIfImpulseTooShort(uint16_t pulseLen){
- if ((pulseLen >= T_SHORT_PULSE_MIN) && (pulseLen <= T_SHORT_PULSE_MAX))
- {
- return 1;
- }
- return 0;
- }
- ISR(TCE0_CCA_vect)
- {
- static uint8_t edgeCount;
- static uint8_t bitValue;
- static uint8_t receiveBitCount;
- uint16_t pulseLen;
- /* Check if card data is still procesing */
- if(!uniqueCardData.dataProced) { return; }
- if (!edgeCount) {
- /* reset all variables */
- receiveBitCount = 0;
- bitValue = 1;
- uniqueCardData.result = 0;
- }
- pulseLen = TC_GetCaptureA( &TCE0 );
- if (checkIfImpulseTooLongOrTooShort(pulseLen))
- {
- edgeCount = 0;
- }
- else if (checkIfImpulseTooShort(pulseLen))
- {
- /* check if even */
- if ((edgeCount % 2) == 0)
- {
- uniqueCardData.result <<= 1;
- uniqueCardData.result |= (uint64_t)bitValue;
- receiveBitCount++;
- }
- edgeCount++;
- }
- else
- {
- bitValue ^= 0x01;
- uniqueCardData.result <<= 1;
- uniqueCardData.result |= (uint64_t)bitValue;
- receiveBitCount++;
- edgeCount += 2;
- }
- if (receiveBitCount > 64) /* whole frame received */
- {
- edgeCount = 0;
- uniqueCardData.frameReady = true;
- if (!uniqueCardData.frame)
- {
- PORTE_PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
- uniqueCardData.frame = uniqueCardData.result;
- uniqueCardData.dataProced = false;
- }
- }
- TC_Restart( &TCE0 );
- }
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:
- static bool FindHeader(uniqueCardData_t * card)
- {
- uint8_t temp_bit;
- for (uint8_t i = 0; i < 64; i++)
- {
- if ((card->frame & FRAME_HEADER_MASK) == FRAME_HEADER_MASK) {
- return true;
- }
- else
- {
- temp_bit = card->frame & FRAME_MSB_MASK ? 1 : 0;
- card->frame <<= 1;
- if (temp_bit)
- {
- card->frame |= FRAME_LSB_MASK;
- }
- }
- }
- if ((card->frame & FRAME_HEADER_MASK) == FRAME_HEADER_MASK)
- {
- return true;
- }
- return false;
- }
Po przejściu modyfikowana jest ramka danych, tak aby na jej początku znajdowała się ramka danych.
Obliczanie parzystości pionowej oraz poziomej:
- static bool HorizontalParityCheck(uint64_t frame)
- {
- /* clear read card number */
- for(uint8_t i=0; i<5; i++)
- {
- uniqueCardData.card_number[i] = 0x00;
- }
- uint8_t status=1;
- uint8_t hbyte=0;
- for(uint8_t i=0;i<10;i++)
- {
- uint8_t par_c;
- uint8_t par_r;
- hbyte=(frame&((uint64_t)D10_MASK)<<(i*5))>>(D10_SHIFT+(i*5));
- par_c=parity_cal(hbyte);
- par_r=(frame&((uint64_t)P10_MASK)<<(i*5))>>(P10_SHIFT+(i*5));
- if(par_c!=par_r)
- {
- status=0;
- return status;
- }
- /* get card number */
- if(i%2==0)
- {
- uniqueCardData.cardNumber[i/2]=hbyte;
- }
- else
- {
- uniqueCardData.cardNumber[i/2]|=hbyte<<4;
- }
- }
- return status; //OK
- }
- static bool VerticalParityCheck(uint64_t frame) {
- for(uint8_t i=0;i<4;i++)
- {
- uint8_t vpar_c=0;
- uint8_t vpar_r;
- for(uint8_t j=0;j<5;j++)
- {
- if(uniqueCardData.cardNumber[j]&(0b10000000>>i))
- {
- vpar_c^=0x01;
- }
- if(uniqueCardData.cardNumber[j]&(0b00001000>>i))
- {
- vpar_c^=0x01;
- }
- }
- vpar_r=(frame&((uint64_t)CP4_MASK)<<(3-i))>>(CP4_SHIFT+(3-i));
- if(vpar_r!=vpar_c)
- {
- return 0;
- }
- }
- return 1;
- }
Funkcja pomocnicza obliczająca parzystość dla czterech bitów:
- static uint8_t parity_cal(uint8_t value){
- switch(value){
- case 0b0000: return 0;
- case 0b0001: return 1;
- case 0b0010: return 1;
- case 0b0011: return 0;
- case 0b0100: return 1;
- case 0b0101: return 0;
- case 0b0110: return 0;
- case 0b0111: return 1;
- case 0b1000: return 1;
- case 0b1001: return 0;
- case 0b1010: return 0;
- case 0b1011: return 1;
- case 0b1100: return 0;
- case 0b1101: return 1;
- case 0b1110: return 1;
- case 0b1111: return 0;
- }
- return 2;
- }
Dekodowanie numeru karty:
Dane przechowywane są w strukturze:
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.
- if (uniqueCardData.frameReady)
- {
- /* refresh counter */
- uniqueCardData.cardResetTimer = CARD_RESET_TIMER;
- counter_started = true;
- if (FindHeader(&uniqueCardData))
- {
- uint8_t decodeStatus = 0;
- decodeStatus = HorizontalParityCheck(uniqueCardData.frame);
- if(decodeStatus){
- decodeStatus = VerticalParityCheck(uniqueCardData.frame);
- }
- if(!decodeStatus)
- {
- uniqueCardData.frame = ~uniqueCardData.frame;
- decodeStatus = FindHeader(&uniqueCardData);
- if(decodeStatus)decodeStatus=HorizontalParityCheck(uniqueCardData.frame);
- if(decodeStatus)decodeStatus=VerticalParityCheck(uniqueCardData.frame);
- }
- if(decodeStatus)
- {
- /* card number receive ok */
- }
- else
- {
- uniqueCardData.frame = 0;
- uniqueCardData.frameReady = false;
- }
- }
Dane przechowywane są w strukturze:
- typedef struct uniqueCardDataStructure{
- volatile uint64_t frame;
- volatile uint64_t result;
- uint8_t cardNumber[5];
- uint8_t oldCardNumber[5];
- volatile bool frameReady;
- volatile bool dataProced;
- volatile uint16_t cardResetTimer;
- } 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.