Ten post chciałbym poświęcić na przygotowanie programu odpowiedzialnego za czytanie kart mifare. Takim układem będzie PN532. Komunikacja z nim zostanie przeprowadzona za pomocą UART-u.
Układ PN532:
Pozwala na odczytywanie i zapisywanie kart typu Mifare. Komunikacja możliwa jest poprzez interfejs i2c, spi bądź opisywany w tym poście UART. Ostatni z nich jest dosyć prosty w obsłudze, jednak trzeba pamiętać o odpowiedniej kolejności komend. Zła kolejność bądź niedokładność w przesyłaniu danych może powodować brak komunikacji bądź zawieszenie układu PN532.
Ten post będzie zawierał opcje dotyczącą odczytu kart Mifare. Zapis kart polega na wysłaniu odpowiedniej komendy z blokiem.
Ten post będzie zawierał opcje dotyczącą odczytu kart Mifare. Zapis kart polega na wysłaniu odpowiedniej komendy z blokiem.
Struktura ramki danych, podstawowa, wygląda następująco:
0x00 0x00 0xFF LEN LCS TFI PD0 PD1 .... PDn DCS 0x00
Idąc od początku najpierw wysyłana jest 1 bajt zwany PREAMBLE (0x00) po czym przesyłane są dwa bajty oznaczające kod startu (0x00 0xFF). Następnie podana jest ilość przesyłanych bajtów (LEN) od TFI do PDn. Dalej wysyłana jest suma kontrolna liczona z zależności LEN + LCS = 0x00. Kolejny bajt czyli (TFI) oznacza kod komendy bądź kod odpowiedzi. Ten drugi jest kodem komendy powiększonym o jeden. Po nim przesyłane są informacje o długości LEN - 1. Przedostatni bajt jest to druga suma kontrolna liczona z zależności TFI + PD0 + ... + PDn = 0x00. Ostatnim bajtem jest informacja o zakończeniu transmisji czyli POSTAMBLE (0x00).
Maksymalnie można wysłać 265 bajtów wliczając w to kod komendy.
Funkcje do obsługi UARTu zostały opisane w jednym z poprzednich postów.
Programowanie:
Główna funkcja komunikująca się z modułem PN532 została przygotowana w oparciu o zadanie z USARTu drugiego.
- void uart1Task(void)
- {
- const int uart1_num = UART_NUM_1;
- Uart1_Init(uart1_num, 115200);
- uint8_t *uart1Buffer = (uint8_t*)malloc(BUFFER_SIZE);
- while(1){
- int len1 = uart_read_bytes(uart1_num, uart1Buffer, BUFFER_SIZE, 80 / portTICK_RATE_MS);
- uart_write_bytes(1, (const char*)uart1Buffer, len1);
- }
- }
- void uart2Task(void)
- {
- static uint8_t PN532_Status = 1;
- const int uart2_num = UART_NUM_2;
- uint8_t checkStat = 0;
- Uart2_Init(uart2_num, 115200);
- uint8_t *uart2Buffer = (uint8_t*)malloc(BUFFER_SIZE);
- Uart_Send_Data(UART_NUM_1, (uint8_t*)"PN532 Program\r\n");
- /* PN532 Initialization */
- PN532Initialization();
- vTaskDelay(500);
- while(1)
- {
- int len2 = uart_read_bytes(uart2_num, uart2Buffer, BUFFER_SIZE, 80 / portTICK_RATE_MS);
- /* Put received buffer to uart 1 */
- //uart_write_bytes(1, (const char*)uart2Buffer, len2);
- /* Check status, implementation needed when device hang */
- checkStat = PN532CheckReceiveBuffer(uart2Buffer, len2, 1000, PN532_Status);
- if(checkStat == PN532_STAT_ERROR)
- {
- PN532_Status = 1;
- PN532Initialization();
- vTaskDelay(500);
- }
- if(checkStat == PN532_STAT_OK)
- {
- if(PN532_Status == 1)
- {
- Uart_Send_Data(UART_NUM_1, (uint8_t*)"Initialization OK\r\n");
- /* If initialization OK then change status to read mifare cards */
- PN532_Status = 2;
- }
- }
- vTaskDelay(100);
- if(PN532_Status == 2)
- {
- PN532ReadCardPoll();
- vTaskDelay(100);
- }
- /* Clear buffer */
- for(uint16_t loop = 0; loop<BUFFER_SIZE; loop++)
- {
- uart2Buffer[loop] = 0;
- }
- }
- }
- void app_main(void)
- {
- nvs_flash_init();
- xTaskCreate(uart1Task, "uart_uart1Task", 1024, NULL, 10, NULL);
- xTaskCreate(uart2Task, "uart_uart2Task", 1024, NULL, 10, NULL);
- }
Na samym początku funkcja uruchamia UART po czym wysyłane są rozkazy inicjalizujące do modułu PN532 w celu sprawdzenia połączenia. Jeśli się ono powiodło to następuje wysłanie danych do odbioru numeru karty. W przeciwnym przypadku wysyłane są podstawowe komendy do układu do momentu uzyskania połączenia.
Część instrukcji sprawdza czy wystąpił błąd podczas odbierania ramki danych. W takim wypadku układ ulega zawieszeniu i nie otrzymuje się poprawnych ramek. Nie ma możliwości odczytu karty bez ponownego resetu. Dlatego dodałem jedną pętlę sprawdzającą tą sytuacje. Jeśli ona wystąpi następuje ponowna inicjalizacja układu a po niej ponowne sprawdzenie otrzymanego bufora.
Część instrukcji sprawdza czy wystąpił błąd podczas odbierania ramki danych. W takim wypadku układ ulega zawieszeniu i nie otrzymuje się poprawnych ramek. Nie ma możliwości odczytu karty bez ponownego resetu. Dlatego dodałem jedną pętlę sprawdzającą tą sytuacje. Jeśli ona wystąpi następuje ponowna inicjalizacja układu a po niej ponowne sprawdzenie otrzymanego bufora.
Procedura uruchomienia układu wygląda następująco:
- void PN532Initialization(void)
- {
- uint8_t sendFrame[] = { 0x02, 0x00 };
- PN532_ClearDataToSendBuffer(); /* Clear buffer with data */
- PN532_ACKCommand(); /* Send ACK */
- PN532_WakeCommand(); /* Send wake frame */
- vTaskDelay(20);
- PN532_ACKCommand(); /* Send ACK */
- PN532_CommandFrame(pn532_DataToSendBuffer, sendFrame, 2); /* Send Command frame */
- }
Ważne jest wysyłanie dodatkowych komend ACK. Układ PN532 może wysyłać ramki danych zawierające wcześniejsze informacje z komend. Przesłanie ACK powoduje wyczyszczenie bufora z danymi znajdującego się w układzie. Dzięki temu ramka danych będzie wysyłana bez przesunięcia.
Sprawdzenie poprawności odebranych danych:
- PN532_Status_t PN532CheckReceiveBuffer(uint8_t * rxPassBuffer, int bufferLength, uint8_t status)
- {
- uint8_t responseFrame[] = {PN532_PN532TOHOST, 0x02 + 1};
- uint8_t checkStat = 0;
- if(status == 1)
- {
- if(PN532_CmdAcknowledge(rxPassBuffer) != PN532_STAT_OK)
- {
- return PN532_STAT_ERROR;
- }
- if(PN532_SearchForFrame(rxPassBuffer, responseFrame, 2) == 0xFF)
- {
- return PN532_STAT_ERROR;
- }
- }
- /* Initialization went ok, check card number data */
- else if(status == 2)
- {
- checkStat = PN532_CheckReadMifare(rxPassBuffer, bufferLength);
- if(checkStat == MIFARE_STAT_OK)
- {
- PN532_GetReceiveCardNumber(rxPassBuffer, bufferLength);
- }
- else if(checkStat == MIFARE_STAT_ERROR)
- {
- return PN532_STAT_ERROR;
- }
- }
- return PN532_STAT_OK;
- }
Pierwsza część dotyczy sprawdzenia danych dla procesu uruchomienia układu. Druga część bloku sprawdzenia odebranych danych z numeru karty.
Wyciągnięcie numeru karty:
Numer karty jest wyciągany z bufora po czym jest on zamieniany na zmienną 32 bitową. Następuje sprawdzenie czy jest on inny od poprzednio odczytanego numeru. Jeśli tak to porównywany jest numer z poprzednio przyłożoną kartą. W tym przypadku karty należy przykładać na przemian. Nie może wystąpić ciągłe przyłożenie. Aby było możliwe przykładanie jednej karty należy uruchomić licznik i co określony interwał czasowy np 1-2 sekundy czyści bufor z poprzedniego odczytu. Pozwoli to na blokowanie spamowania odczytanym numer oraz będzie można jedną kartę odczytać ciągle po przyłożeniu.
Kolejna z wykorzystywanych funkcji odpowiada za przeszukiwanie odebranego bufora danych w poszukiwaniu ramki danych:
Do funkcji podawana jest odebrana ramka danych, szukana ramka oraz ilość danych jakie należy sprawdzić.
Komenda ACK wysyłana jest po przez użycie następującej komendy:
- static Mifare_Status_t PN532_CheckReadMifare(uint8_t * rxDataBuffer, uint16_t bufferLength)
- {
- volatile uint8_t len = 0;
- uint8_t dataTable[255] = {0};
- if(PN532_InAutoPoll(dataTable, rxDataBuffer, bufferLength) != PN532_STAT_OK)
- {
- return MIFARE_STAT_ERROR; /* */
- }
- if(*dataTable == 0x00)
- {
- return MIFARE_STAT_NOCARD; /* No card in field */
- }
- else if(*dataTable == 0x01)
- {
- /* Check whether Mifare card is detected */
- if( *(dataTable+1) != 0x10 )
- {
- return MIFARE_STAT_OTHER_CARD;
- }
- len = *(dataTable+7) + 1;
- if(len > bufferLength)
- {
- len = bufferLength;
- }
- for(uint8_t i=0; i<len; i++)
- {
- *(rxDataBuffer + i) = *(dataTable + 7 + i);
- }
- }
- else if(*dataTable == 0x02)
- {
- if(*(dataTable+1) == 0x10)
- {
- len = *(dataTable+7)+1;
- if(len > bufferLength)
- {
- len = bufferLength;
- }
- for(uint8_t i=0; i<len; i++)
- {
- *(rxDataBuffer + i) = *(dataTable + 7 + i);
- if((*(dataTable+7) + 1) > bufferLength)
- {
- return MIFARE_STAT_OK; /* Stat Ok */
- }
- }
- if(*(dataTable + 3 + *(dataTable+2)) == 0x10)
- {
- uint8_t lenExt = *(dataTable + 7 + *(dataTable+2)) + 1;
- if(len > (bufferLength - len))
- {
- lenExt=(bufferLength - len);
- }
- for(uint8_t i=len; i<(len+lenExt); i++)
- {
- *(rxDataBuffer + i) = *(dataTable + 9 + *(dataTable+2) + i - 5);
- }
- }
- }
- }
- return MIFARE_STAT_OK; /* Stat Ok */
- }
Wyciągnięcie numeru karty:
- static uint8_t PN532_GetReceiveCardNumber(uint8_t *buffer, uint16_t length)
- {
- uint32_t decodeCardNumber = 0;
- static uint32_t prevCardNumber = 0;
- char dataBuffer[40] = {0};
- if(*(buffer) == 0x04)
- {
- decodeCardNumber = *(buffer + 3);
- decodeCardNumber = (decodeCardNumber << 8) + *(buffer + 2);
- decodeCardNumber = (decodeCardNumber << 8) + *(buffer + 1);
- if(prevCardNumber != decodeCardNumber)
- {
- prevCardNumber = decodeCardNumber;
- sprintf(dataBuffer, "Nr K:%u\r\n", decodeCardNumber);
- Uart_Send_Data(1, (uint8_t*)&dataBuffer);
- }
- }
- return 1;
- }
Numer karty jest wyciągany z bufora po czym jest on zamieniany na zmienną 32 bitową. Następuje sprawdzenie czy jest on inny od poprzednio odczytanego numeru. Jeśli tak to porównywany jest numer z poprzednio przyłożoną kartą. W tym przypadku karty należy przykładać na przemian. Nie może wystąpić ciągłe przyłożenie. Aby było możliwe przykładanie jednej karty należy uruchomić licznik i co określony interwał czasowy np 1-2 sekundy czyści bufor z poprzedniego odczytu. Pozwoli to na blokowanie spamowania odczytanym numer oraz będzie można jedną kartę odczytać ciągle po przyłożeniu.
Kolejna z wykorzystywanych funkcji odpowiada za przeszukiwanie odebranego bufora danych w poszukiwaniu ramki danych:
- static uint8_t PN532_SearchForFrame(uint8_t * dataBuffer, uint8_t *dataFrame, uint16_t dataFrameLength)
- {
- uint8_t *ptr = dataBuffer;
- for(int i = 0; i<(PN532_MAX_FRAME_LENGTH - dataFrameLength); i++)
- {
- if(memcmp(ptr,dataFrame,dataFrameLength * sizeof(uint8_t)) == 0){
- return i;
- }
- p++;
- }
- return 0xFF;
- }
Do funkcji podawana jest odebrana ramka danych, szukana ramka oraz ilość danych jakie należy sprawdzić.
Komenda ACK wysyłana jest po przez użycie następującej komendy:
- static void PN532_ACKCommand(void)
- {
- Uart_SendArray(PN532_UART_PORT, (uint8_t*)&pn532_frame_ACK, 6);
- }
Wybudzenie układu dokonuje się poprzez przesłanie komendy wybudzającej:
- static void PN532_WakeCommand(void)
- {
- uint8_t pn532_frame_WAKE[] = { 0x55, 0x55, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0xff, 0x03, 0xfd,
- 0xd4, 0x14, 0x01, 0x17, 0x00 };
- Uart_SendArray(PN532_UART_PORT, (uint8_t*)&pn532_frame_WAKE, 24);
- }
Wysłanie komendy przygotowanej wcześniej wygląda następująco:
- static void PN532_CommandFrame(uint8_t *buffer, uint8_t * cmd, uint8_t cmdlen)
- {
- uint16_t checksum = 0;
- uint8_t len = 8;
- PN532_ClearDataToSendBuffer();
- pn532_DataToSendBuffer[0] = PN532_PREAMBLE;
- pn532_DataToSendBuffer[1] = PN532_STARTCODE1;
- pn532_DataToSendBuffer[2] = PN532_STARTCODE2;
- pn532_DataToSendBuffer[3] = cmdlen + 1;
- pn532_DataToSendBuffer[4] = ~cmdlen;
- pn532_DataToSendBuffer[5] = PN532_TOPN532;
- checksum += PN532_TOPN532;
- for(uint8_t i = 6; i < (6 + cmdlen); i++){
- pn532_DataToSendBuffer[i] = (*cmd);
- checksum += (*cmd);
- cmd++;
- }
- len += cmdlen;
- pn532_DataToSendBuffer[6+cmdlen] = (~checksum+1);
- pn532_DataToSendBuffer[6+cmdlen+1] = PN532_POSTAMBLE;
- Uart_SendArray(PN532_UART_PORT, (uint8_t*)&pn532_DataToSendBuffer, len);
- }
W funkcji powyżej przygotowywana jest ramka danych do przesłania. Wprowadzana jest ona do podanego bufora. Ostania linijka przekazuje wskaźnik do tablicy do przesłania.
Sprawdzenie wersji oprogramowania odpowiedzialne jest poprzez funkcje:
- static void PN532_GetFirmware(void)
- {
- uint8_t pn532_frame_FIRMWARE[] = { 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD4, 0x02, 0x2A, 0x00 };
- Uart_SendArray(PN532_UART_PORT, pn532_frame_FIRMWARE, 9);
- }
Przesyła ona odpowiedni rozkaz do układu.
Odczytanie karty może być wykonane w oparciu o dwa rozkazy. Pierwszy z nich pozwala na proste odczytanie. Czyli przesyłany jest rozkaz podania numeru karty, jeśli karta jest w polu to przesyłana jest odpowiedź:
- static void PN532SimpleCardRead(void)
- {
- uint8_t pn532_frame_GETTAG[11] = { 0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD4,
- 0x4A, 0x01, 0x00, 0xE1, 0x00};
- PN532_ACKCommand();
- Uart_SendArray(PN532_UART_PORT, pn532_frame_GETTAG, 11);
- }
Przesyłana komenda (InListPassiveTarget) odczytuje znajdujące się w polu tagi. Format komendy wygląda następująco:
- [00]: 0x00
- [01]: 0x00
- [02]: 0xFF
- [03]: 0x04
- [04]: 0xFC
- [05]: 0xD4
- [06]: 0x4A - kod komendy
- [07]: 0x01 - maksymalna liczba tagów znajdujących się w polu. PN532 może obsłużyć maksymalnie dwa układy
- [08]: 0x00 - określa prędkość transmisji oraz typ modulacji. Przy wpisaniu 0x00 wybrany jest 106 kbps type A (ISO/IEC14443 Type A);
- [09]: 0xE1 - są to dane odpowiedzialne za inicjalizacje czytnika do odczytu. Wartość tego pola jest dostosowana do podanych wartości prędkości transmisji, trybu odczytu itp.
- [10]: 0x00 - zakończenie transmisji
Jeśli znajdują się dane w polu to układ odeśle następujące informacje:
- [00]: 0x00
- [01]: 0x00
- [02]: 0xFF
- [03]: 0x0C
- [04]: 0xF4
- [05]: 0xD5
- [06]: 0x4B - kod komendy powiększony o 1;
- [07]: 0x01 - numer odczytanego układu;
- [08]: 0x01 - SENS_RES;
- [09]: 0x00 - SENS_RES;
- [10]: 0x04 - SEL_RES;
- [11]: 0x08 - NFCIDLength;
- [12]: 0x04 - ilość danych dla numery karty;
- [13]: 0xXX - numer karty;
- [14]: 0xXX - numer karty;
- [15]: 0xXX - numer karty;
- [16]: 0xXX - numer karty;
- [17]: 0x00 - zakończenie transmisji;
Gdy nie ma danych zostanie odesłany kod ACK (0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00) W przypadku błędnego odczytu zostanie przesłana ramka NACK (0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00);
Druga funkcja zawiera obsługę komendy InAutoPoll. Służy ona do odczytu kart o podanym formacie. Jest ona głównie wykorzystywana w programie w pętli:
- static PN532_Status_t PN532_InAutoPoll(uint8_t *writeDataBuffer, uint8_t *receiveBuffer, uint16_t bufferLen)
- {
- uint8_t Frame[2] = { PN532_PN532TOHOST, PN532_COMMAND_INAUTOPOLL + 1 };
- uint8_t endFrame[5] = {0x00, 0xFF, 0x00, 0xFF, 0x00};
- /* wait for ack frame */
- if(PN532_CmdAcknowledge(receiveBuffer) != PN532_STAT_OK)
- {
- return PN532_STAT_ERROR;
- }
- /* Check if there is proper response frame */
- if(PN532_SearchForFrame(receiveBuffer, Frame, 2) == 0xFF)
- {
- return PN532_STAT_ERROR;
- }
- /* wait for end of transmission */
- if(PN532_SearchForFrame(receiveBuffer, endFrame, 5) == 0xFF)
- {
- return PN532_STAT_ERROR;
- }
- PN532_ACKCommand();
- uint8_t pos = PN532_SearchForFrame(receiveBuffer, Frame, 2);
- uint16_t len = *(buffer + pos - 2);
- for(uint8_t i = 2; i<len; i++)
- {
- *(writeDataBuffer + i - 2) = *(receiveBuffer + pos + i);
- }
- return PN532_STAT_OK;
- }
Bibliografia: