W tym poście chciałbym przedstawić sposób odczytu karty Mifare za pomocą modułu MF RC522.
Dokumentacje do układu można pobrać pod tym linkiem.
Projekt przygotowałem na płytce STM32F429I, natomiast można go bardzo łatwo przenieść na inny mikrokontroler z tej serii. Należy tylko odpowiednio zmodyfikować wyprowadzenia czy prędkości poszczególnych linii.
Podłączenie do STM32:
Do podłączenia układu wykorzystałem następujące wyprowadzenia:
- CS(SDA) - PG2
- SCK - PE2
- MISO - PE6
- MOSI - PE5
- GND - GND
- VCC - 3.3V
- RST - 3.3V
Inicjalizacja SPI:
Tutaj wykorzystywane będą dwie funkcje jedna uruchamiająca SPI, druga odpowiedzialna za przesyłanie danych.
Przy przygotowywaniu kodu opierałem się na materiałach udostępnionych na serwisie GitHub dla Arduino.
Zacznę od uruchomienia układu SPI. Wykorzystałem SPI4 na pinach PE6, PE5 oraz PE2. Linia CS jest jednym z wyprowadzeń.
- void enableSPI4ForMfrc522()
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- SPI_InitTypeDef SPI_InitStructure;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI4, ENABLE);
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
- GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_SPI4);
- GPIO_PinAFConfig(GPIOE, GPIO_PinSource5, GPIO_AF_SPI4);
- GPIO_PinAFConfig(GPIOE, GPIO_PinSource2, GPIO_AF_SPI4);
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
- /* SCK pin */
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
- GPIO_Init(GPIOE, &GPIO_InitStructure);
- /* MOSI pin */
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
- GPIO_Init(GPIOE, &GPIO_InitStructure);
- /* MISO pin */
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
- GPIO_Init(GPIOE, &GPIO_InitStructure);
- /* Configurate SPI */
- SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
- SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
- SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
- SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
- SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
- SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
- SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
- SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
- SPI_InitStructure.SPI_CRCPolynomial = 7;
- SPI4->CR1 &= ~SPI_CR1_SPE;
- SPI_Init(SPI4, &SPI_InitStructure);
- SPI4->CR1 |= SPI_CR1_SPE;
- }
Funkcja przesyłająca dane:
- #define CHECK_SPI_FREE(SPIx) (((SPIx)->SR & (SPI_SR_TXE | SPI_SR_RXNE)) == 0 || ((SPIx)->SR & SPI_SR_BSY))
- uint8_t spiSendData(SPI_TypeDef* SPIx, uint8_t dataToSend)
- {
- /* Check if SPI is enabled */
- if (!((SPIx)->CR1 & SPI_CR1_SPE))
- {
- return 0;
- }
- /* wait for previus transmission to finish */
- while (CHECK_SPI_FREE(SPIx));
- SPIx->DR = dataToSend; /* Write data to buffer */
- /* Wait for transmission to complete */
- while(CHECK_SPI_FREE(SPIx))
- /* Return receove data */
- return SPIx->DR;
- }
Programowanie modułu:
Najpierw procedura uruchamiająca układ:
Tutaj na początku ustawiane są potrzebne piny w tym CS, następnie wpisywane są odpowiednie wartości do rejestrów. Na końcu uruchamiana jest antena.
Ustawienie pinu CS:
Reset układu wygląda następująco:
Tutaj należy posłużyć się pewnym opóźnieniem. Ponieważ nie jest zdefiniowane ile czasu będzie trwało czyszczenie danych. Sprawdzanie następuje poprzez odczytanie bitu PowerDown z rejestru Command. Jeśli bit jest wyczyszczony to można przejść do dalszych operacji.
Teraz dwie funkcje odpowiedzialne za uruchomienie oraz wyłączenie pinów od anteny.
Kolejne dwie funkcje odpowiadają za programowe wyłączenie oraz uruchomienie układu:
- void mfrc522_Initialization(void)
- {
- mfrc522_InitCSPin(); /* Enable CS pin */
- enableSPI4ForMfrc522(); /* Enable SPI4 */
- mfrc522_Reset(); /* Reset device */
- mfrc522_WriteRegister(MFRC522_TX_MODE_REGISTER, 0x00); /* Define internal timer settings */
- mfrc522_WriteRegister(MFRC522_RX_MODE_REGISTER, 0x00); /* Lower 8 bits of TPrescaler value, 4 high bits are in tModeReg */
- mfrc522_WriteRegister(MFRC522_MOD_WIDTH_REGISTER, 0x26);
- mfrc522_WriteRegister(MFRC522_T_MODE_REGISTER, 0x80); /* Define internal timer settings */
- mfrc522_WriteRegister(MFRC522_T_PRESCALER_REGISTER, 0xA9); /* Lower 8 bits of TPrescaler value, 4 high bits are in tModeReg */
- mfrc522_WriteRegister(MFRC522_T_RELOAD_H_REGISTER, 0x03); /* Define timer reload value */
- mfrc522_WriteRegister(MFRC522_T_RELOAD_L_REGISTER, 0xE8);
- mfrc522_WriteRegister(MFRC522_RF_CFG_REGISTER, RF_CFG_GAIN_48dB); /* Set maximum gain 48db */
- mfrc522_WriteRegister(MFRC522_TX_AUTO_REGISTER, 0x40); /* ASK modulation */
- mfrc522_WriteRegister(MFRC522_MODE_REGISTER, 0x3D); /* Set the preset value for the CRC coprocessor */
- mfrc522_AntenaOn(); /* Enable antenna pins */
- }
Tutaj na początku ustawiane są potrzebne piny w tym CS, następnie wpisywane są odpowiednie wartości do rejestrów. Na końcu uruchamiana jest antena.
Ustawienie pinu CS:
- static void mrfc522_InitCSPin(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- RCC_AHB1PeriphClockCmd(MFRC522_CS_RCC, ENABLE);
- GPIO_InitStruct.GPIO_Pin = MFRC522_CS_PIN;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(MFRC522_CS_PORT, &GPIO_InitStruct);
- MFRC522_CS_HIGH; /* Set pin to High */
- }
Reset układu wygląda następująco:
- static void mrfc522_ClearRegisterBitMask(void)
- {
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER1, PCD_SOFTRESET);
- while(mfrc522_ReadRegister(MFRC522_COMMAND_REGISTER) & (1<<4))
- {}
- }
Tutaj należy posłużyć się pewnym opóźnieniem. Ponieważ nie jest zdefiniowane ile czasu będzie trwało czyszczenie danych. Sprawdzanie następuje poprzez odczytanie bitu PowerDown z rejestru Command. Jeśli bit jest wyczyszczony to można przejść do dalszych operacji.
Teraz dwie funkcje odpowiedzialne za uruchomienie oraz wyłączenie pinów od anteny.
- static void mrfc522_AntenaOff(void)
- {
- mfrc522_ClearRegisterBitMask(MFRC522_TX_CONTROL_REGISTER, 0x03);
- }
- static void mrfc522_AntenaOn(void)
- {
- uint8_t val = mfrc522_ReadRegister(MFRC522_TX_CONTROL_REGISTER);
- if (!(val & 0x03))
- {
- mfrc522_SetRegisterBitMask(MFRC522_TX_CONTROL_REGISTER, 0x03);
- }
- }
Kolejne dwie funkcje odpowiadają za programowe wyłączenie oraz uruchomienie układu:
- static void mrfc522_SoftPowerDown(void)
- {
- uint8_t value = mfrc522_ReadRegister(MFRC522_COMMAND_REGISTER);
- value |= (1<<4); /* Set power down bit to 1 */
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER, value);
- }
- static void mrfc522_SoftPowerUp(void)
- {
- uint8_t value = mrfc522_ReadRegister(MFRC522_COMMAND_REGISTER);
- value &= ~(1<<4); /* clear power down bit */
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER, value);
- uint16_t timeout = 0;
- while(timeout <= 1000)
- {
- value = mfrc522_ReadRegister(MFRC522_COMMAND_REGISTER);
- if(!(value & (1<<4))) /* Check if wake up finished */
- {
- break;
- }
- timeout++;
- }
- }
Teraz czas na dwie funkcje odpowiedzialne za komunikację. Jedna wysyła dane do układu, druga je odbiera.
Ustawienie oraz wyczyszczenie poszczególnych bitów w rejestrze:
Ustawienie układu w stan wstrzymania, uśpienia:
Najpierw przygotowywana jest komenda, obliczenie CRC oraz sprawdzenie statusu operacji, następnie przesłanie danych.
Funkcja odpowiedzialna za przesyłanie komendy Request. Przygotowuje ona układ do przejście w stan gotowości. Przygotowuje go do odbierania ramek anty kolizji (anticollision) czy wyboru (select). Ta ostatnia wybiera rodzaj karty pomiędzy mifare classic a ultralight.
Teraz procedura przesłania danych do procedury antykolizji:
Najpierw czyszczone są rejestry statusu oraz bit informujący o wystąpieniu kolizji. Dalej przesyłane są dane do układu z kodem antykolizji. Do wskaźnika podawanego do funkcji zostanie wprowadzony odczytany numer z karty Mifare. Po tym następuje sprawdzenie czy dane sprawdzane są różne od ostatniego bajtu danych. Jeśli tak to zgłaszany jest błąd.
Obliczenie CRC:
Najpierw przesyłana jest komenda startu oraz aktywacji. Następnie czyszczony jest bit CRCIrq, który informuje o zakończeniu obliczania CRC. Kolejna komenda inicjalizuje stos FIFO. Dalej w pętli for wprowadzane są dane wejściowe, po niej następuje rozpoczęcie obliczania oraz oczekiwanie na wynik. Gdy bit CRCIrq zostanie wyczyszczony to następuje odczytanie danych i wpisanie ich do wskaźnika na dane wyjściowe.
Odczytanie danych z bloku:
Zapis danych do bloku:
Zapis danych do bloku odbywa się poprzez wybranie komendy do wprowadzenia danych oraz adres bloku. Następnie obliczane jest CRC po którym następuje transmisja danych. Dalej sprawdzany jest status operacji oraz przesłanie całego bloku do zapisu.
Proces transmisji musi zakończyć się zwróceniem czterech bajtów ACK, dlatego sprawdzana jest ilość bajtów przed ustawieniem statusu operacji.
Kolejna funkcja służy do uwierzytelnienia transmisji przez podanie odpowiedniego klucza dostępu do określonego bloku danych:
Najpierw przygotowanie komendy. Można podawać jako parametr dwie komendy jedna to PICC_AUTHENTICATION_KEY_A druga to PICC_AUTHENTICATION_KEY_B. Następnie do bufora wprowadzany jest klucz dostępu (6 bajtów), oraz UID 4 bajty. Dalej przygotowana ramka zostaje przesłana i sprawdzany jest status odbioru.
Następna funkcja dokonuje sprawdzenia czy została przyłożona karta do układu:
W funkcji powyżej najpierw następuje włączenie anteny, dalej przesyłana jest komenda REQUEST typ A. Ma ona za zadanie zmianę stanu z IDLE na READY oraz przygotowanie układu na komendę ANTICOLLISION. Po sprawdzeniu czy karta została odczytana przesyłana jest komenda HALT. Ostatnim krokiem jest wyłączenie pinów od anteny.
Funkcja przesłania danych do układu:
Funkcja powyżej ma za zadanie dodanie przesyłanych danych do kolejki FIFO układu. Najpierw rozpoczyna się od uruchomienia przerwań, oraz iniciajlizacji FIFO. Następnie wpisuje się dane do rejestru. Dalsza część dotyczy sprawdzenia przeprowadzonych operacji.
Przygotowałem dwa programy testowe. Pierwsza z nich odczytuje numer zapisany na karcie:
Druga funkcja testowa odczytuje parametry zapisane na karcie, następnie programuje jeden z sektorów po czym odczytuje zaprogramowane dane:
Najpierw wysyłana jest komenda Request. W przypadku powodzenia zwraca parametry karty. Dla Mifare Classic 1k wartość ta wynosi 0x04. Dalej przesyłana jest komenda Anticollision, która przesyła odczytany numer seryjny karty. Następnie wybierana jest karta i odczytywany jest jej rozmiar. Kolejnym krokiem jest proces uwierzytelniania przez podanie hasła, po czym rozpoczyna się zapis danych do określonego bloku. Przed odczytem zapisanych danych należy przeprowadzić ponownie proces uwierzytelniania. Po nim następuje odczytanie danych. Gdy ta operacja się powiedzie to nastąpi wyświetlenie danych na ekranie.
Główna pętla programu wygląda następująco:
Cały projekt można pobrać z dysku Google pod tym linkiem.
- static void mrfc522_WriteRegister(uint8_t devAddress, uint8_t valueToWrite)
- {
- MFRC522_CS_LOW;
- spiSendData(MFRC522_SPI, (devAddress << 1) & 0x7E);
- spiSendData(MFRC522_SPI, valueToWrite);
- MFRC522_CS_HIGH;
- }
- static uint8_t mfrc522_ReadRegister(uint8_t devAddress)
- {
- uint8_t value = 0;
- MFRC522_CS_LOW;
- spiSendData(MFRC522_SPI, ((devAddress << 1) & 0x7E) | 0x80);
- value = spiSendData(MFRC522_SPI, MFRC522_DUMMY);
- MFRC522_CS_HIGH;
- return value;
- }
Ustawienie oraz wyczyszczenie poszczególnych bitów w rejestrze:
- static void mrfc522_SetRegisterBitMask(uint8_t regToSet, uint8_t bitMask)
- {
- uint8_t tmpDat = mfrc522_ReadRegister(regToSet);
- mfrc522_WriteRegister(regToSet, tmpDat | bitMask);
- }
- static void mfrc522_ClearRegisterBitMask(uint8_t regToClear, uint8_t bitMask)
- {
- uint8_t tmpDat = mfrc522_ReadRegister(regToClear);
- mfrc522_WriteRegister(regToClear, tmpDat & (~bitMask));
- }
Ustawienie układu w stan wstrzymania, uśpienia:
- static void mfrc522_HaltA(void)
- {
- uint16_t numBytesToWrite= 0;
- uint8_t buff[4] = {0};
- uint8_t operationStatus = 0;
- buff[0] = PICC_HALTA;
- buff[1] = 0;
- operationStatus = mfrc522_CRC(buff, 2, &buff[2]);
- if(operationStatus != 0)
- {
- return; /* Error */
- }
- operationStatus = mfrc522_TransmitData(PCD_TRANSCEIVE, buff, 4, buff, &numBytesToWrite);
- if(operationStatus != 0)
- {
- return; /* Error */
- }
- }
Najpierw przygotowywana jest komenda, obliczenie CRC oraz sprawdzenie statusu operacji, następnie przesłanie danych.
Funkcja odpowiedzialna za przesyłanie komendy Request. Przygotowuje ona układ do przejście w stan gotowości. Przygotowuje go do odbierania ramek anty kolizji (anticollision) czy wyboru (select). Ta ostatnia wybiera rodzaj karty pomiędzy mifare classic a ultralight.
- static uint8_t mfrc522_Request(uint8_t requestPICC, uint8_t* cardNumberPoint)
- {
- uint8_t operationStatus = 2;
- uint16_t retBits = 0;
- mfrc522_WriteRegister(MFRC522_BIT_FRAMING_REGISTER, 0x07);
- cardNumberPoint[0] = requestPICC;
- operationStatus = mfrc522_TransmitData(PCD_TRANSCEIVE, cardNumberPoint, 1, cardNumberPoint, &retBits);
- if ((operationStatus != 0) || (retBits != 0x10))
- {
- operationStatus = 2;
- }
- return operationStatus;
- }
Teraz procedura przesłania danych do procedury antykolizji:
- static uint8_t mfrc522_Anticollision(uint8_t* serNum)
- {
- uint8_t operationStatus = 2;
- uint8_t loop = 0;
- uint8_t serNumCheck = 0;
- uint16_t unLen;
- mfrc522_ClearRegisterBitMask(MFRC522_STATUS2_REGISTER, 0x08);
- mfrc522_ClearRegisterBitMask(MFRC522_COLL_REGISTER,0x80);
- mfrc522_WriteRegister(MFRC522_BIT_FRAMING_REGISTER, 0x00);
- serNum[0] = PICC_ANTICOLLISION;
- serNum[1] = 0x20;
- operationStatus = mfrc522_TransmitData(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);
- if (operationStatus == 0)
- {
- serNumCheck = serNum[0] ^ serNum[1] ^ serNum[2] ^ serNum[3];
- }
- if (serNumCheck != serNum[4])
- {
- operationStatus = 2;
- }
- return operationStatus;
- }
Najpierw czyszczone są rejestry statusu oraz bit informujący o wystąpieniu kolizji. Dalej przesyłane są dane do układu z kodem antykolizji. Do wskaźnika podawanego do funkcji zostanie wprowadzony odczytany numer z karty Mifare. Po tym następuje sprawdzenie czy dane sprawdzane są różne od ostatniego bajtu danych. Jeśli tak to zgłaszany jest błąd.
Obliczenie CRC:
- static uint8_t mfrc522_CRC(uint8_t* pointIntputData, uint8_t dataLength, uint8_t* pointOutputData)
- {
- uint8_t count = 0;
- uint8_t readedData = 0;
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER1, PCD_IDLE); //Stop then active command
- mfrc522_ClearRegisterBitMask(MFRC522_DIV_IRQ_REGISTER, 0x04); //Clear the CRC Interrupt tequest bit
- mfrc522_SetRegisterBitMask(MFRC522_FIFO_LEVEL_REGISTER, 0x80); //Fifo initialization
- //Write data to the Fifo
- for (count = 0; count < dataLength; count++) {
- mfrc522_WriteRegister(MFRC522_FIFO_DATA_REGISTER, *(pointIntputData+count));
- }
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER1, PCD_CALCCRC); //Start calculation
- //Wait for CRC calculation to complete
- count = 0x11FF;
- do {
- readedData = mfrc522_ReadRegister(MFRC522_DIV_IRQ_REGISTER);
- count--;
- if(!(readedData&0x04)) //Check if bit CRCIRq is clear.
- {
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER, PCD_IDLE); //Stop calculationg CRC
- //Transfer result to buffer
- pointOutputData[0] = mfrc522_ReadRegister(MFRC522_CRC_RESULT_L_REGISTER);
- pointOutputData[1] = mfrc522_ReadRegister(MFRC522_CRC_RESULT_M_REGISTER);
- return 0;
- }
- } while(count!=0); //Works when CRCIRQ = 1
- return 2;
- }
Najpierw przesyłana jest komenda startu oraz aktywacji. Następnie czyszczony jest bit CRCIrq, który informuje o zakończeniu obliczania CRC. Kolejna komenda inicjalizuje stos FIFO. Dalej w pętli for wprowadzane są dane wejściowe, po niej następuje rozpoczęcie obliczania oraz oczekiwanie na wynik. Gdy bit CRCIrq zostanie wyczyszczony to następuje odczytanie danych i wpisanie ich do wskaźnika na dane wyjściowe.
Odczytanie danych z bloku:
- uint8_t mfrc522_Read(uint8_t blockAddr, uint8_t* receiveData, uint8_t bufferSize)
- {
- uint8_t operationStatus = 2;
- uint16_t retLength;
- //Check pointer data
- if(receiveData == NULL || bufferSize < 18)
- {
- return 1;
- }
- //Build buffer
- receiveData[0] = PICC_READ;
- receiveData[1] = blockAddr;
- mfrc522_CRC(receiveData, 2, &receiveData[2]);
- //Send data
- operationStatus = mfrc522_TransmitData(PCD_TRANSCEIVE, receiveData, 4, receiveData, &retLength);
- //Check operation status
- if ((operationStatus != 0) || (retLength != 0x90))
- {
- operationStatus = 2;
- }
- return operationStatus;
- }
Zapis danych do bloku:
- uint8_t mfrc522_Write(uint8_t blockAddr, uint8_t* writeData, uint8_t writeDataSize)
- {
- uint8_t operationStatus = 2; //Default error
- uint16_t receiveBits = 0;
- uint8_t loop = 0;
- uint8_t dataBuffer[18];
- dataBuffer[0] = PICC_WRITE;
- dataBuffer[1] = blockAddr;
- mfrc522_CRC(dataBuffer, 2, &dataBuffer[2]);
- operationStatus = mfrc522_TransmitData(PCD_TRANSCEIVE, dataBuffer, 4, dataBuffer, &receiveBits);
- if ((operationStatus != 0) ||
- (receiveBits != 4) ||
- ((dataBuffer[0] & 0x0F) != 0x0A))
- {
- operationStatus = 2;
- }
- if (operationStatus == 0)
- {
- //Data to the FIFO write 16Byte
- for (loop = 0; loop < 16; loop++)
- {
- dataBuffer[loop] = *(writeData+loop);
- }
- mfrc522_CRC(dataBuffer, 16, &dataBuffer[16]);
- operationStatus = mfrc522_TransmitData(PCD_TRANSCEIVE, dataBuffer, 18, dataBuffer, &receiveBits);
- if ((operationStatus != 0) ||
- (receiveBits != 4) ||
- ((dataBuffer[0] & 0x0F) != 0x0A))
- {
- operationStatus = 2;
- }
- }
- return operationStatus;
- }
Zapis danych do bloku odbywa się poprzez wybranie komendy do wprowadzenia danych oraz adres bloku. Następnie obliczane jest CRC po którym następuje transmisja danych. Dalej sprawdzany jest status operacji oraz przesłanie całego bloku do zapisu.
Proces transmisji musi zakończyć się zwróceniem czterech bajtów ACK, dlatego sprawdzana jest ilość bajtów przed ustawieniem statusu operacji.
Kolejna funkcja służy do uwierzytelnienia transmisji przez podanie odpowiedniego klucza dostępu do określonego bloku danych:
- uint8_t mfrc522_Authentication( uint8_t command, uint8_t blockAddr, uint8_t* sectorKey, uint8_t* serialNumber)
- {
- uint8_t operationStatus = 0;
- uint16_t receiveData = 0;
- uint8_t loop = 0;
- uint8_t dataBuffer[12] = {0};
- dataBuffer[0] = command;
- dataBuffer[1] = blockAddr;
- //6 bytes key
- for (loop = 0; loop < 6; loop++)
- {
- dataBuffer[loop+2] = *(sectorKey+loop);
- }
- //4 bytes uid
- for (loop=0; loop<4; loop++)
- {
- dataBuffer[loop+8] = *(serialNumber+loop);
- }
- operationStatus = mfrc522_TransmitData(PCD_AUTHENT, dataBuffer, 12, dataBuffer, &receiveData);
- //Check status
- if ((operationStatus != 0) ||
- (!(mfrc522_ReadRegister(MFRC522_STATUS2_REGISTER) & 0x08)))
- {
- operationStatus = 2;
- }
- return operationStatus;
- }
Najpierw przygotowanie komendy. Można podawać jako parametr dwie komendy jedna to PICC_AUTHENTICATION_KEY_A druga to PICC_AUTHENTICATION_KEY_B. Następnie do bufora wprowadzany jest klucz dostępu (6 bajtów), oraz UID 4 bajty. Dalej przygotowana ramka zostaje przesłana i sprawdzany jest status odbioru.
Następna funkcja dokonuje sprawdzenia czy została przyłożona karta do układu:
- uint8_t mfrc522_Check(uint8_t* cardNumberPointer)
- {
- uint8_t operationStatus = 2; //By default write 2 as error
- mfrc522_AntenaOn(); //Turn on antenna pins
- operationStatus = mfrc522_Request(PICC_REQ_IDLE_STATE_REQA, cardNumberPointer); //Find cards, return card type
- if (operationStatus == 0) //If there is a card
- {
- operationStatus = mfrc522_Anticollision(cardNumberPointer);//Get card number
- }
- mfrc522_HaltA(); //Command card into hibernation
- mfrc522_AntenaOff();//Turn off antena
- return operationStatus;
- }
W funkcji powyżej najpierw następuje włączenie anteny, dalej przesyłana jest komenda REQUEST typ A. Ma ona za zadanie zmianę stanu z IDLE na READY oraz przygotowanie układu na komendę ANTICOLLISION. Po sprawdzeniu czy karta została odczytana przesyłana jest komenda HALT. Ostatnim krokiem jest wyłączenie pinów od anteny.
Funkcja przesłania danych do układu:
- static uint8_t mfrc522_TransmitDataToFifo(uint8_t commandToExecute, uint8_t* dataToSendPoint, uint8_t sendDataLength,
- uint8_t* returnDataPoint, uint16_t* returnDataLenPoint)
- {
- uint8_t operationStatus = 2;
- uint8_t interruptStatus = 0x00;
- uint8_t interruptWait = 0x00;
- uint8_t transmitLastBits = 0;
- uint8_t readedData = 0;
- uint16_t loop = 0;
- switch (commandToExecute)
- {
- case PCD_AUTHENT:
- {
- interruptStatus = 0x12;
- interruptWait = 0x10;
- break;
- }
- case PCD_TRANSCEIVE:
- {
- interruptStatus = 0x77;
- interruptWait = 0x30;
- break;
- }
- default:
- {
- operationStatus = 2;
- return operationStatus;
- }
- }
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER, interruptStatus | 0x80); //Allow interrupt request
- mfrc522_ClearRegisterBitMask(MFRC522_COMM_IRQ_REGISTER, 0x80); //Clear interrupt request bit
- mfrc522_SetRegisterBitMask(MFRC522_FIFO_LEVEL_REGISTER, 0x80); //Initialize fifo
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER1, PCD_IDLE);
- for (loop = 0; loop < sendDataLength; loop++) //Write DataToFifo
- {
- mfrc522_WriteRegister(MFRC522_FIFO_DATA_REGISTER, dataToSendPoint[loop]);
- }
- mfrc522_WriteRegister(MFRC522_COMMAND_REGISTER1, commandToExecute);
- if (commandToExecute == PCD_TRANSCEIVE)
- {
- mfrc522_SetRegisterBitMask(MFRC522_BIT_FRAMING_REGISTER, 0x80); //StartSend = 1,transmission of data starts
- }
- loop = 4000;
- do
- {
- readedData = mfrc522_ReadRegister(MFRC522_COMM_IRQ_REGISTER);
- loop--;
- } while ((loop != 0) && !(readedData & 0x01) && !(readedData & interruptWait));
- if (loop != 0)
- {
- uint8_t readStatus = mfrc522_ReadRegister(MFRC522_ERROR_REGISTER); //Check if there is any error
- if (!(readStatus & 0x1B))
- {
- operationStatus = 0;
- if (commandToExecute == PCD_TRANSCEIVE)
- {
- readedData = mfrc522_ReadRegister(MFRC522_FIFO_LEVEL_REGISTER);
- transmitLastBits = mfrc522_ReadRegister(MFRC522_CONTROL_REGISTER) & 0x07;
- if (transmitLastBits)
- {
- *returnDataLenPoint = transmitLastBits + ((readedData - 1) * 8);
- }
- else
- {
- *returnDataLenPoint = (readedData * 8);
- }
- if (readedData == 0)
- {
- readedData = 1;
- }
- if (readedData > MFRC522_MAX_LEN)
- {
- readedData = MFRC522_MAX_LEN;
- }
- //Reading the received data in FIFO
- for (loop = 0; loop < readedData; loop++)
- {
- returnDataPoint[loop] = mfrc522_ReadRegister(MFRC522_FIFO_DATA_REGISTER);
- }
- }
- }
- else
- {
- operationStatus = 2;
- }
- }
- return operationStatus;
- }
Funkcja powyżej ma za zadanie dodanie przesyłanych danych do kolejki FIFO układu. Najpierw rozpoczyna się od uruchomienia przerwań, oraz iniciajlizacji FIFO. Następnie wpisuje się dane do rejestru. Dalsza część dotyczy sprawdzenia przeprowadzonych operacji.
Przygotowałem dwa programy testowe. Pierwsza z nich odczytuje numer zapisany na karcie:
- void mfrc522_Process_CardNumberOnly(void)
- {
- /* Recognized card ID */
- uint8_t cardNumber[5];
- char buffer[30] = {0};
- if (mfrc522_CheckIfCardPresent(cardNumber) == 0)
- {
- Prep_Font_DrawString(10,10,(uint8_t*)"Card detected", &Arial_7x10,RGB_COL_WHITE,RGB_COL_RED);
- sprintf(buffer, "0x%02x\n0x%02x\n0x%02x\n0x%02x\n0x%02x", cardNumber[0], cardNumber[1], cardNumber[2], cardNumber[3], cardNumber[4]);
- Prep_Font_DrawString(10,20,(uint8_t*)buffer, &Arial_7x10, RGB_COL_WHITE,RGB_COL_RED);
- }
- }
Druga funkcja testowa odczytuje parametry zapisane na karcie, następnie programuje jeden z sektorów po czym odczytuje zaprogramowane dane:
- void mfrc522_Process(void)
- {
- uint8_t operationStatus;
- uint8_t receiveDataBuffer[50] = {0};
- uint8_t cardSize = 0;
- uint8_t blockAddr = 0;
- char displayBuffer[50] = {0};
- uint8_t serialNumber[5] = {0};
- //Search for a card in a field
- operationStatus = mfrc522_Request(PICC_REQ_IDLE_STATE_REQA, receiveDataBuffer);
- if (operationStatus == 0) /* Success */
- {
- sprintf(displayBuffer, "Card found: 0x%x, 0x%x", receiveDataBuffer[0], receiveDataBuffer[1]);
- Prep_Font_DrawString(10,10,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- }
- else
- {
- return;
- }
- // Anti-collision, return the card's 4-byte serial number
- operationStatus = mfrc522_Anticollision(receiveDataBuffer);
- memcpy(serialNumber, receiveDataBuffer, 5);
- if (operationStatus == 0) /* Success */
- {
- sprintf(displayBuffer, "Card Number: %x%x%x%x", serialNumber[0],serialNumber[1],serialNumber[2],serialNumber[3]);
- Prep_Font_DrawString(10,20,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- }
- else
- {
- Prep_Font_DrawString(10,20,(uint8_t*)"Card size Error",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- return;
- }
- // Election card, return capacity
- cardSize = mfrc522_SelectTag_CascadeLevel1(serialNumber);
- if (cardSize != 0)
- {
- sprintf(displayBuffer, "Card size: %d Kbits", cardSize);
- Prep_Font_DrawString(10,30,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- }
- else
- {
- Prep_Font_DrawString(10,30,(uint8_t*)"Card size Error",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- return;
- }
- blockAddr = 6;
- operationStatus = mfrc522_Authentication(PICC_AUTHENTICATION_KEY_A, blockAddr, key, serialNumber);// Authentication
- if (operationStatus == 0) /* Success */
- {
- // Write data
- operationStatus = mfrc522_Write(blockAddr, writeData);
- if(operationStatus != 0)
- {
- Prep_Font_DrawString(10,110,(uint8_t*)"Write 1 Error",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- return;
- }
- else
- {
- Prep_Font_DrawString(10,120,(uint8_t*)"Write 1 OK",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- }
- }
- else
- {
- Prep_Font_DrawString(10,130,(uint8_t*)"Authentication Error",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- return;
- }
- blockAddr = 6;
- operationStatus = mfrc522_Authentication(PICC_AUTHENTICATION_KEY_A, blockAddr, key, serialNumber);
- if (operationStatus == 0) /* Success */
- {
- // Read data
- blockAddr = 6;
- operationStatus = mfrc522_Read(blockAddr, receiveDataBuffer, sizeof(receiveDataBuffer)/sizeof(receiveDataBuffer[0]));
- if (operationStatus == 0)
- {
- Prep_Font_DrawString(10,140,(uint8_t*)"Read Ok",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- sprintf(displayBuffer, "[0]%d; [1]%d; [2]%d", receiveDataBuffer[0], receiveDataBuffer[1], receiveDataBuffer[2]);
- Prep_Font_DrawString(10,155,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- sprintf(displayBuffer, "[3]%d; [4]%d;", receiveDataBuffer[3], receiveDataBuffer[4]);
- Prep_Font_DrawString(10,170,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- sprintf(displayBuffer, "[5]%d; [6]%d; [7]%d", receiveDataBuffer[5], receiveDataBuffer[6], receiveDataBuffer[7]);
- Prep_Font_DrawString(10,185, (uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- sprintf(displayBuffer, "[8]%d; [9]%d; [10]%d;", receiveDataBuffer[8], receiveDataBuffer[9], receiveDataBuffer[10]);
- Prep_Font_DrawString(10,200,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- sprintf(displayBuffer, "[11]%d; [12]%d; [13]%d;", receiveDataBuffer[11], receiveDataBuffer[12], receiveDataBuffer[13]);
- Prep_Font_DrawString(10,215,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- sprintf(displayBuffer, "[14]%d; [15]%d;", receiveDataBuffer[14], receiveDataBuffer[15]);
- Prep_Font_DrawString(10,230,(uint8_t*)displayBuffer,&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- }
- else
- {
- Prep_Font_DrawString(10,150,(uint8_t*)"Read Error",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- return;
- }
- }
- else
- {
- Prep_Font_DrawString(10,140,(uint8_t*)"Authentication 2 Error",&Arial_7x10,RGB_COL_BLACK,RGB_COL_GREEN);
- return;
- }
- mfrc522_HaltA();
- }
Najpierw wysyłana jest komenda Request. W przypadku powodzenia zwraca parametry karty. Dla Mifare Classic 1k wartość ta wynosi 0x04. Dalej przesyłana jest komenda Anticollision, która przesyła odczytany numer seryjny karty. Następnie wybierana jest karta i odczytywany jest jej rozmiar. Kolejnym krokiem jest proces uwierzytelniania przez podanie hasła, po czym rozpoczyna się zapis danych do określonego bloku. Przed odczytem zapisanych danych należy przeprowadzić ponownie proces uwierzytelniania. Po nim następuje odczytanie danych. Gdy ta operacja się powiedzie to nastąpi wyświetlenie danych na ekranie.
Główna pętla programu wygląda następująco:
- int main(void)
- {
- SystemInit();
- Tft_Display_Initialization();
- Tft_Layer_FullScreenMode();
- Tft_SetLayer_1();
- Tft_FillLayer(RGB_COL_WHITE);
- Tft_SetLayer_2();
- Tft_FillLayer(RGB_COL_GREEN);
- mfrc522_Initialization();
- while(1)
- {
- //mfrc522_Process_CardNumberOnly();
- mfrc522_Process();
- }
- }
Cały projekt można pobrać z dysku Google pod tym linkiem.