From ae7421a5b3b3e898dc5ffb4e19ced45e02fe1098 Mon Sep 17 00:00:00 2001 From: Willem Oldemans Date: Sat, 2 Oct 2021 11:41:29 +0200 Subject: [PATCH] initial commit --- PN532.cpp | 900 ++++++++++++++++++ PN532.h | 181 ++++ PN532Interface.h | 56 ++ PN532_debug.h | 20 + README.md | 30 + emulatetag.cpp | 261 +++++ emulatetag.h | 71 ++ examples/android_hce/android_hce.ino | 135 +++ .../emulate_tag_ndef/emulate_tag_ndef.ino | 69 ++ examples/iso14443a_uid/iso14443a_uid.pde | 78 ++ .../mifareclassic_formatndef.pde | 170 ++++ .../mifareclassic_memdump.pde | 157 +++ .../mifareclassic_ndeftoclassic.pde | 172 ++++ .../mifareclassic_updatendef.pde | 145 +++ examples/ntag21x_protect/ntag21x_protect.ino | 91 ++ examples/ntag21x_rw/ntag21x_rw.ino | 72 ++ examples/p2p_raw/p2p_raw.ino | 51 + .../p2p_with_ndef_library.ino | 55 ++ examples/readMifare/readMifare.pde | 145 +++ license.txt | 27 + llcp.cpp | 309 ++++++ llcp.h | 75 ++ mac_link.cpp | 20 + mac_link.h | 51 + snep.cpp | 122 +++ snep.h | 49 + 26 files changed, 3512 insertions(+) create mode 100644 PN532.cpp create mode 100644 PN532.h create mode 100644 PN532Interface.h create mode 100644 PN532_debug.h create mode 100644 README.md create mode 100644 emulatetag.cpp create mode 100644 emulatetag.h create mode 100644 examples/android_hce/android_hce.ino create mode 100644 examples/emulate_tag_ndef/emulate_tag_ndef.ino create mode 100644 examples/iso14443a_uid/iso14443a_uid.pde create mode 100644 examples/mifareclassic_formatndef/mifareclassic_formatndef.pde create mode 100644 examples/mifareclassic_memdump/mifareclassic_memdump.pde create mode 100644 examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde create mode 100644 examples/mifareclassic_updatendef/mifareclassic_updatendef.pde create mode 100644 examples/ntag21x_protect/ntag21x_protect.ino create mode 100644 examples/ntag21x_rw/ntag21x_rw.ino create mode 100644 examples/p2p_raw/p2p_raw.ino create mode 100644 examples/p2p_with_ndef_library/p2p_with_ndef_library.ino create mode 100644 examples/readMifare/readMifare.pde create mode 100644 license.txt create mode 100644 llcp.cpp create mode 100644 llcp.h create mode 100644 mac_link.cpp create mode 100644 mac_link.h create mode 100644 snep.cpp create mode 100644 snep.h diff --git a/PN532.cpp b/PN532.cpp new file mode 100644 index 0000000..122f414 --- /dev/null +++ b/PN532.cpp @@ -0,0 +1,900 @@ +/**************************************************************************/ +/*! + @file PN532.cpp + @author Adafruit Industries & Seeed Studio + @license BSD +*/ +/**************************************************************************/ + +#include "Arduino.h" +#include "PN532.h" +#include "PN532_debug.h" +#include + +#define HAL(func) (_interface->func) + +PN532::PN532(PN532Interface &interface) +{ + _interface = &interface; +} + +/**************************************************************************/ +/*! + @brief Setups the HW +*/ +/**************************************************************************/ +void PN532::begin() +{ + HAL(begin)(); + HAL(wakeup)(); +} + +/**************************************************************************/ +/*! + @brief Prints a hexadecimal value in plain characters + + @param data Pointer to the uint8_t data + @param numBytes Data length in bytes +*/ +/**************************************************************************/ +void PN532::PrintHex(const uint8_t *data, const uint32_t numBytes) +{ +#ifdef ARDUINO + for (uint8_t i = 0; i < numBytes; i++) { + if (data[i] < 0x10) { + Serial.print(" 0"); + } else { + Serial.print(' '); + } + Serial.print(data[i], HEX); + } + Serial.println(""); +#else + for (uint8_t i = 0; i < numBytes; i++) { + printf(" %2X", data[i]); + } + printf("\n"); +#endif +} + +/**************************************************************************/ +/*! + @brief Prints a hexadecimal value in plain characters, along with + the char equivalents in the following format + + 00 00 00 00 00 00 ...... + + @param data Pointer to the data + @param numBytes Data length in bytes +*/ +/**************************************************************************/ +void PN532::PrintHexChar(const uint8_t *data, const uint32_t numBytes) +{ +#ifdef ARDUINO + for (uint8_t i = 0; i < numBytes; i++) { + if (data[i] < 0x10) { + Serial.print(" 0"); + } else { + Serial.print(' '); + } + Serial.print(data[i], HEX); + } + Serial.print(" "); + for (uint8_t i = 0; i < numBytes; i++) { + char c = data[i]; + if (c <= 0x1f || c > 0x7f) { + Serial.print('.'); + } else { + Serial.print(c); + } + } + Serial.println(""); +#else + for (uint8_t i = 0; i < numBytes; i++) { + printf(" %2X", data[i]); + } + printf(" "); + for (uint8_t i = 0; i < numBytes; i++) { + char c = data[i]; + if (c <= 0x1f || c > 0x7f) { + printf("."); + } else { + printf("%c", c); + } + printf("\n"); + } +#endif +} + +/**************************************************************************/ +/*! + @brief Checks the firmware version of the PN5xx chip + + @returns The chip's firmware version and ID +*/ +/**************************************************************************/ +uint32_t PN532::getFirmwareVersion(void) +{ + uint32_t response; + + pn532_packetbuffer[0] = PN532_COMMAND_GETFIRMWAREVERSION; + + if (HAL(writeCommand)(pn532_packetbuffer, 1)) { + return 0; + } + + // read data packet + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + if (0 > status) { + return 0; + } + + response = pn532_packetbuffer[0]; + response <<= 8; + response |= pn532_packetbuffer[1]; + response <<= 8; + response |= pn532_packetbuffer[2]; + response <<= 8; + response |= pn532_packetbuffer[3]; + + return response; +} + + +/**************************************************************************/ +/*! + Writes an 8-bit value that sets the state of the PN532's GPIO pins + + @warning This function is provided exclusively for board testing and + is dangerous since it will throw an error if any pin other + than the ones marked "Can be used as GPIO" are modified! All + pins that can not be used as GPIO should ALWAYS be left high + (value = 1) or the system will become unstable and a HW reset + will be required to recover the PN532. + + pinState[0] = P30 Can be used as GPIO + pinState[1] = P31 Can be used as GPIO + pinState[2] = P32 *** RESERVED (Must be 1!) *** + pinState[3] = P33 Can be used as GPIO + pinState[4] = P34 *** RESERVED (Must be 1!) *** + pinState[5] = P35 Can be used as GPIO + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::writeGPIO(uint8_t pinstate) +{ + // Make sure pinstate does not try to toggle P32 or P34 + pinstate |= (1 << PN532_GPIO_P32) | (1 << PN532_GPIO_P34); + + // Fill command buffer + pn532_packetbuffer[0] = PN532_COMMAND_WRITEGPIO; + pn532_packetbuffer[1] = PN532_GPIO_VALIDATIONBIT | pinstate; // P3 Pins + pn532_packetbuffer[2] = 0x00; // P7 GPIO Pins (not used ... taken by I2C) + + DMSG("Writing P3 GPIO: "); + DMSG_HEX(pn532_packetbuffer[1]); + DMSG("\n"); + + // Send the WRITEGPIO command (0x0E) + if (HAL(writeCommand)(pn532_packetbuffer, 3)) + return 0; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Reads the state of the PN532's GPIO pins + + @returns An 8-bit value containing the pin state where: + + pinState[0] = P30 + pinState[1] = P31 + pinState[2] = P32 + pinState[3] = P33 + pinState[4] = P34 + pinState[5] = P35 +*/ +/**************************************************************************/ +uint8_t PN532::readGPIO(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_READGPIO; + + // Send the READGPIO command (0x0C) + if (HAL(writeCommand)(pn532_packetbuffer, 1)) + return 0x0; + + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* READGPIO response without prefix and suffix should be in the following format: + + byte Description + ------------- ------------------------------------------ + b0 P3 GPIO Pins + b1 P7 GPIO Pins (not used ... taken by I2C) + b2 Interface Mode Pins (not used ... bus select pins) + */ + + + DMSG("P3 GPIO: "); DMSG_HEX(pn532_packetbuffer[7]); + DMSG("P7 GPIO: "); DMSG_HEX(pn532_packetbuffer[8]); + DMSG("I0I1 GPIO: "); DMSG_HEX(pn532_packetbuffer[9]); + DMSG("\n"); + + return pn532_packetbuffer[0]; +} + +/**************************************************************************/ +/*! + @brief Configures the SAM (Secure Access Module) +*/ +/**************************************************************************/ +bool PN532::SAMConfig(void) +{ + pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION; + pn532_packetbuffer[1] = 0x01; // normal mode; + pn532_packetbuffer[2] = 0x14; // timeout 50ms * 20 = 1 second + pn532_packetbuffer[3] = 0x01; // use IRQ pin! + + DMSG("SAMConfig\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 4)) + return false; + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Sets the MxRtyPassiveActivation uint8_t of the RFConfiguration register + + @param maxRetries 0xFF to wait forever, 0x00..0xFE to timeout + after mxRetries + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::setPassiveActivationRetries(uint8_t maxRetries) +{ + pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION; + pn532_packetbuffer[1] = 5; // Config item 5 (MaxRetries) + pn532_packetbuffer[2] = 0xFF; // MxRtyATR (default = 0xFF) + pn532_packetbuffer[3] = 0x01; // MxRtyPSL (default = 0x01) + pn532_packetbuffer[4] = maxRetries; + + if (HAL(writeCommand)(pn532_packetbuffer, 5)) + return 0x0; // no ACK + + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/***** ISO14443A Commands ******/ + +/**************************************************************************/ +/*! + Waits for an ISO14443A target to enter the field + + @param cardBaudRate Baud rate of the card + @param uid Pointer to the array that will be populated + with the card's UID (up to 7 bytes) + @param uidLength Pointer to the variable that will hold the + length of the card's UID. + @param timeout The number of tries before timing out + @param inlist If set to true, the card will be inlisted + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +bool PN532::readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout, bool inlist) +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; // max 1 cards at once (we can set this to 2 later) + pn532_packetbuffer[2] = cardbaudrate; + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return 0x0; // command failed + } + + // read data packet + if (HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout) < 0) { + return 0x0; + } + + // check some basic stuff + /* ISO14443A card response should be in the following format: + + byte Description + ------------- ------------------------------------------ + b0 Tags Found + b1 Tag Number (only one used in this example) + b2..3 SENS_RES + b4 SEL_RES + b5 NFCID Length + b6..NFCIDLen NFCID + */ + + if (pn532_packetbuffer[0] != 1) + return 0; + + uint16_t sens_res = pn532_packetbuffer[2]; + sens_res <<= 8; + sens_res |= pn532_packetbuffer[3]; + + DMSG("ATQA: 0x"); DMSG_HEX(sens_res); + DMSG("SAK: 0x"); DMSG_HEX(pn532_packetbuffer[4]); + DMSG("\n"); + + /* Card appears to be Mifare Classic */ + *uidLength = pn532_packetbuffer[5]; + + for (uint8_t i = 0; i < pn532_packetbuffer[5]; i++) { + uid[i] = pn532_packetbuffer[6 + i]; + } + + if (inlist) { + inListedTag = pn532_packetbuffer[1]; + } + + return 1; +} + + +/***** Mifare Classic Functions ******/ + +/**************************************************************************/ +/*! + Indicates whether the specified block number is the first block + in the sector (block 0 relative to the current sector) +*/ +/**************************************************************************/ +bool PN532::mifareclassic_IsFirstBlock (uint32_t uiBlock) +{ + // Test if we are in the small or big sectors + if (uiBlock < 128) + return ((uiBlock) % 4 == 0); + else + return ((uiBlock) % 16 == 0); +} + +/**************************************************************************/ +/*! + Indicates whether the specified block number is the sector trailer +*/ +/**************************************************************************/ +bool PN532::mifareclassic_IsTrailerBlock (uint32_t uiBlock) +{ + // Test if we are in the small or big sectors + if (uiBlock < 128) + return ((uiBlock + 1) % 4 == 0); + else + return ((uiBlock + 1) % 16 == 0); +} + +/**************************************************************************/ +/*! + Tries to authenticate a block of memory on a MIFARE card using the + INDATAEXCHANGE command. See section 7.3.8 of the PN532 User Manual + for more information on sending MIFARE and other commands. + + @param uid Pointer to a byte array containing the card UID + @param uidLen The length (in bytes) of the card's UID (Should + be 4 for MIFARE Classic) + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param keyNumber Which key type to use during authentication + (0 = MIFARE_CMD_AUTH_A, 1 = MIFARE_CMD_AUTH_B) + @param keyData Pointer to a byte array containing the 6 bytes + key value + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData) +{ + uint8_t i; + + // Hang on to the key and uid data + memcpy (_key, keyData, 6); + memcpy (_uid, uid, uidLen); + _uidLen = uidLen; + + // Prepare the authentication command // + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; /* Data Exchange Header */ + pn532_packetbuffer[1] = 1; /* Max card numbers */ + pn532_packetbuffer[2] = (keyNumber) ? MIFARE_CMD_AUTH_B : MIFARE_CMD_AUTH_A; + pn532_packetbuffer[3] = blockNumber; /* Block Number (1K = 0..63, 4K = 0..255 */ + memcpy (pn532_packetbuffer + 4, _key, 6); + for (i = 0; i < _uidLen; i++) { + pn532_packetbuffer[10 + i] = _uid[i]; /* 4 bytes card ID */ + } + + if (HAL(writeCommand)(pn532_packetbuffer, 10 + _uidLen)) + return 0; + + // Read the response packet + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + // Check if the response is valid and we are authenticated??? + // for an auth success it should be bytes 5-7: 0xD5 0x41 0x00 + // Mifare auth error is technically byte 7: 0x14 but anything other and 0x00 is not good + if (pn532_packetbuffer[0] != 0x00) { + DMSG("Authentification failed\n"); + return 0; + } + + return 1; +} + +/**************************************************************************/ +/*! + Tries to read an entire 16-bytes data block at the specified block + address. + + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param data Pointer to the byte array that will hold the + retrieved data (if any) + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data) +{ + DMSG("Trying to read 16 bytes from block "); + DMSG_INT(blockNumber); + + /* Prepare the command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ + pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 4)) { + return 0; + } + + /* Read the response packet */ + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* If byte 8 isn't 0x00 we probably have an error */ + if (pn532_packetbuffer[0] != 0x00) { + return 0; + } + + /* Copy the 16 data bytes to the output buffer */ + /* Block content starts at byte 9 of a valid response */ + memcpy (data, pn532_packetbuffer + 1, 16); + + return 1; +} + +/**************************************************************************/ +/*! + Tries to write an entire 16-bytes data block at the specified block + address. + + @param blockNumber The block number to authenticate. (0..63 for + 1KB cards, and 0..255 for 4KB cards). + @param data The byte array that contains the data to write. + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data) +{ + /* Prepare the first command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_WRITE; /* Mifare Write command = 0xA0 */ + pn532_packetbuffer[3] = blockNumber; /* Block Number (0..63 for 1K, 0..255 for 4K) */ + memcpy (pn532_packetbuffer + 4, data, 16); /* Data Payload */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 20)) { + return 0; + } + + /* Read the response packet */ + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + Formats a Mifare Classic card to store NDEF Records + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_FormatNDEF (void) +{ + uint8_t sectorbuffer1[16] = {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + uint8_t sectorbuffer2[16] = {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + uint8_t sectorbuffer3[16] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + // Note 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 must be used for key A + // for the MAD sector in NDEF records (sector 0) + + // Write block 1 and 2 to the card + if (!(mifareclassic_WriteDataBlock (1, sectorbuffer1))) + return 0; + if (!(mifareclassic_WriteDataBlock (2, sectorbuffer2))) + return 0; + // Write key A and access rights card + if (!(mifareclassic_WriteDataBlock (3, sectorbuffer3))) + return 0; + + // Seems that everything was OK (?!) + return 1; +} + +/**************************************************************************/ +/*! + Writes an NDEF URI Record to the specified sector (1..15) + + Note that this function assumes that the Mifare Classic card is + already formatted to work as an "NFC Forum Tag" and uses a MAD1 + file system. You can use the NXP TagWriter app on Android to + properly format cards for this. + + @param sectorNumber The sector that the URI record should be written + to (can be 1..15 for a 1K card) + @param uriIdentifier The uri identifier code (0 = none, 0x01 = + "http://www.", etc.) + @param url The uri text to write (max 38 characters). + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareclassic_WriteNDEFURI (uint8_t sectorNumber, uint8_t uriIdentifier, const char *url) +{ + // Figure out how long the string is + uint8_t len = strlen(url); + + // Make sure we're within a 1K limit for the sector number + if ((sectorNumber < 1) || (sectorNumber > 15)) + return 0; + + // Make sure the URI payload is between 1 and 38 chars + if ((len < 1) || (len > 38)) + return 0; + + // Note 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 must be used for key A + // in NDEF records + + // Setup the sector buffer (w/pre-formatted TLV wrapper and NDEF message) + uint8_t sectorbuffer1[16] = {0x00, 0x00, 0x03, len + 5, 0xD1, 0x01, len + 1, 0x55, uriIdentifier, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer2[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer3[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t sectorbuffer4[16] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + if (len <= 6) { + // Unlikely we'll get a url this short, but why not ... + memcpy (sectorbuffer1 + 9, url, len); + sectorbuffer1[len + 9] = 0xFE; + } else if (len == 7) { + // 0xFE needs to be wrapped around to next block + memcpy (sectorbuffer1 + 9, url, len); + sectorbuffer2[0] = 0xFE; + } else if ((len > 7) && (len <= 22)) { + // Url fits in two blocks + memcpy (sectorbuffer1 + 9, url, 7); + memcpy (sectorbuffer2, url + 7, len - 7); + sectorbuffer2[len - 7] = 0xFE; + } else if (len == 23) { + // 0xFE needs to be wrapped around to final block + memcpy (sectorbuffer1 + 9, url, 7); + memcpy (sectorbuffer2, url + 7, len - 7); + sectorbuffer3[0] = 0xFE; + } else { + // Url fits in three blocks + memcpy (sectorbuffer1 + 9, url, 7); + memcpy (sectorbuffer2, url + 7, 16); + memcpy (sectorbuffer3, url + 23, len - 23); + sectorbuffer3[len - 23] = 0xFE; + } + + // Now write all three blocks back to the card + if (!(mifareclassic_WriteDataBlock (sectorNumber * 4, sectorbuffer1))) + return 0; + if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 1, sectorbuffer2))) + return 0; + if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 2, sectorbuffer3))) + return 0; + if (!(mifareclassic_WriteDataBlock ((sectorNumber * 4) + 3, sectorbuffer4))) + return 0; + + // Seems that everything was OK (?!) + return 1; +} + +/***** Mifare Ultralight Functions ******/ + +/**************************************************************************/ +/*! + Tries to read an entire 4-bytes page at the specified address. + + @param page The page number (0..63 in most cases) + @param buffer Pointer to the byte array that will hold the + retrieved data (if any) +*/ +/**************************************************************************/ +uint8_t PN532::mifareultralight_ReadPage (uint8_t page, uint8_t *buffer) +{ + /* Prepare the command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_READ; /* Mifare Read command = 0x30 */ + pn532_packetbuffer[3] = page; /* Page Number (0..63 in most cases) */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 4)) { + return 0; + } + + /* Read the response packet */ + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + /* If byte 8 isn't 0x00 we probably have an error */ + if (pn532_packetbuffer[0] == 0x00) { + /* Copy the 4 data bytes to the output buffer */ + /* Block content starts at byte 9 of a valid response */ + /* Note that the command actually reads 16 bytes or 4 */ + /* pages at a time ... we simply discard the last 12 */ + /* bytes */ + memcpy (buffer, pn532_packetbuffer + 1, 4); + } else { + return 0; + } + + // Return OK signal + return 1; +} + +/**************************************************************************/ +/*! + Tries to write an entire 4-bytes data buffer at the specified page + address. + + @param page The page number to write into. (0..63). + @param buffer The byte array that contains the data to write. + + @returns 1 if everything executed properly, 0 for an error +*/ +/**************************************************************************/ +uint8_t PN532::mifareultralight_WritePage (uint8_t page, uint8_t *buffer) +{ + /* Prepare the first command */ + pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = 1; /* Card number */ + pn532_packetbuffer[2] = MIFARE_CMD_WRITE_ULTRALIGHT; /* Mifare UL Write cmd = 0xA2 */ + pn532_packetbuffer[3] = page; /* page Number (0..63) */ + memcpy (pn532_packetbuffer + 4, buffer, 4); /* Data Payload */ + + /* Send the command */ + if (HAL(writeCommand)(pn532_packetbuffer, 8)) { + return 0; + } + + /* Read the response packet */ + return (0 < HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer))); +} + +/**************************************************************************/ +/*! + @brief Exchanges an APDU with the currently inlisted peer + + @param send Pointer to data to send + @param sendLength Length of the data to send + @param response Pointer to response data + @param responseLength Pointer to the response data length +*/ +/**************************************************************************/ +bool PN532::inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength) +{ + uint8_t i; + + pn532_packetbuffer[0] = 0x40; // PN532_COMMAND_INDATAEXCHANGE; + pn532_packetbuffer[1] = inListedTag; + + if (HAL(writeCommand)(pn532_packetbuffer, 2, send, sendLength)) { + return false; + } + + int16_t status = HAL(readResponse)(response, *responseLength, 1000); + if (status < 0) { + return false; + } + + if ((response[0] & 0x3f) != 0) { + DMSG("Status code indicates an error\n"); + return false; + } + + uint8_t length = status; + length -= 1; + + if (length > *responseLength) { + length = *responseLength; // silent truncation... + } + + for (uint8_t i = 0; i < length; i++) { + response[i] = response[i + 1]; + } + *responseLength = length; + + return true; +} + +/**************************************************************************/ +/*! + @brief 'InLists' a passive target. PN532 acting as reader/initiator, + peer acting as card/responder. +*/ +/**************************************************************************/ +bool PN532::inListPassiveTarget() +{ + pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; + pn532_packetbuffer[1] = 1; + pn532_packetbuffer[2] = 0; + + DMSG("inList passive target\n"); + + if (HAL(writeCommand)(pn532_packetbuffer, 3)) { + return false; + } + + int16_t status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 30000); + if (status < 0) { + return false; + } + + if (pn532_packetbuffer[0] != 1) { + return false; + } + + inListedTag = pn532_packetbuffer[1]; + + return true; +} + +int8_t PN532::tgInitAsTarget(const uint8_t* command, const uint8_t len, const uint16_t timeout){ + + int8_t status = HAL(writeCommand)(command, len); + if (status < 0) { + return -1; + } + + status = HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout); + if (status > 0) { + return 1; + } else if (PN532_TIMEOUT == status) { + return 0; + } else { + return -2; + } +} + +/** + * Peer to Peer + */ +int8_t PN532::tgInitAsTarget(uint16_t timeout) +{ + const uint8_t command[] = { + PN532_COMMAND_TGINITASTARGET, + 0, + 0x00, 0x00, //SENS_RES + 0x00, 0x00, 0x00, //NFCID1 + 0x40, //SEL_RES + + 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, // POL_RES + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, + + 0x01, 0xFE, 0x0F, 0xBB, 0xBA, 0xA6, 0xC9, 0x89, 0x00, 0x00, //NFCID3t: Change this to desired value + + 0x0a, 0x46, 0x66, 0x6D, 0x01, 0x01, 0x10, 0x02, 0x02, 0x00, 0x80, // LLCP magic number, version parameter and MIUX + 0x00 + }; + return tgInitAsTarget(command, sizeof(command), timeout); +} + +int16_t PN532::tgGetData(uint8_t *buf, uint8_t len) +{ + buf[0] = PN532_COMMAND_TGGETDATA; + + if (HAL(writeCommand)(buf, 1)) { + return -1; + } + + int16_t status = HAL(readResponse)(buf, len, 3000); + if (0 >= status) { + return status; + } + + uint16_t length = status - 1; + + + if (buf[0] != 0) { + DMSG("status is not ok\n"); + return -5; + } + + for (uint8_t i = 0; i < length; i++) { + buf[i] = buf[i + 1]; + } + + return length; +} + +bool PN532::tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + if (hlen > (sizeof(pn532_packetbuffer) - 1)) { + if ((body != 0) || (header == pn532_packetbuffer)) { + DMSG("tgSetData:buffer too small\n"); + return false; + } + + pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; + if (HAL(writeCommand)(pn532_packetbuffer, 1, header, hlen)) { + return false; + } + } else { + for (int8_t i = hlen - 1; i >= 0; i--){ + pn532_packetbuffer[i + 1] = header[i]; + } + pn532_packetbuffer[0] = PN532_COMMAND_TGSETDATA; + + if (HAL(writeCommand)(pn532_packetbuffer, hlen + 1, body, blen)) { + return false; + } + } + + if (0 > HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer), 3000)) { + return false; + } + + if (0 != pn532_packetbuffer[0]) { + return false; + } + + return true; +} + +int16_t PN532::inRelease(const uint8_t relevantTarget){ + + pn532_packetbuffer[0] = PN532_COMMAND_INRELEASE; + pn532_packetbuffer[1] = relevantTarget; + + if (HAL(writeCommand)(pn532_packetbuffer, 2)) { + return 0; + } + + // read data packet + return HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); +} + +uint8_t PN532::ntag21x_auth(const uint8_t *key) +{ + uint8_t i; + + // Prepare the authentication command // + pn532_packetbuffer[0] = 0x42; + pn532_packetbuffer[1] = 0x1B; + memcpy (pn532_packetbuffer + 2, key, 4); + + if (HAL(writeCommand)(pn532_packetbuffer, 6)) + return 0; + + // Read the response packet + HAL(readResponse)(pn532_packetbuffer, sizeof(pn532_packetbuffer)); + + // Check if the response is valid and we are authenticated??? + if (pn532_packetbuffer[0] != 0x00) { + DMSG("\nAuthentification failed\n"); + return 0; + } + + return 1; +} + diff --git a/PN532.h b/PN532.h new file mode 100644 index 0000000..97026a8 --- /dev/null +++ b/PN532.h @@ -0,0 +1,181 @@ +/**************************************************************************/ +/*! + @file PN532.h + @author Adafruit Industries & Seeed Studio + @license BSD +*/ +/**************************************************************************/ + +#ifndef __PN532_H__ +#define __PN532_H__ + +#include +#include "PN532Interface.h" + +// PN532 Commands +#define PN532_COMMAND_DIAGNOSE (0x00) +#define PN532_COMMAND_GETFIRMWAREVERSION (0x02) +#define PN532_COMMAND_GETGENERALSTATUS (0x04) +#define PN532_COMMAND_READREGISTER (0x06) +#define PN532_COMMAND_WRITEREGISTER (0x08) +#define PN532_COMMAND_READGPIO (0x0C) +#define PN532_COMMAND_WRITEGPIO (0x0E) +#define PN532_COMMAND_SETSERIALBAUDRATE (0x10) +#define PN532_COMMAND_SETPARAMETERS (0x12) +#define PN532_COMMAND_SAMCONFIGURATION (0x14) +#define PN532_COMMAND_POWERDOWN (0x16) +#define PN532_COMMAND_RFCONFIGURATION (0x32) +#define PN532_COMMAND_RFREGULATIONTEST (0x58) +#define PN532_COMMAND_INJUMPFORDEP (0x56) +#define PN532_COMMAND_INJUMPFORPSL (0x46) +#define PN532_COMMAND_INLISTPASSIVETARGET (0x4A) +#define PN532_COMMAND_INATR (0x50) +#define PN532_COMMAND_INPSL (0x4E) +#define PN532_COMMAND_INDATAEXCHANGE (0x40) +#define PN532_COMMAND_INCOMMUNICATETHRU (0x42) +#define PN532_COMMAND_INDESELECT (0x44) +#define PN532_COMMAND_INRELEASE (0x52) +#define PN532_COMMAND_INSELECT (0x54) +#define PN532_COMMAND_INAUTOPOLL (0x60) +#define PN532_COMMAND_TGINITASTARGET (0x8C) +#define PN532_COMMAND_TGSETGENERALBYTES (0x92) +#define PN532_COMMAND_TGGETDATA (0x86) +#define PN532_COMMAND_TGSETDATA (0x8E) +#define PN532_COMMAND_TGSETMETADATA (0x94) +#define PN532_COMMAND_TGGETINITIATORCOMMAND (0x88) +#define PN532_COMMAND_TGRESPONSETOINITIATOR (0x90) +#define PN532_COMMAND_TGGETTARGETSTATUS (0x8A) + +#define PN532_RESPONSE_INDATAEXCHANGE (0x41) +#define PN532_RESPONSE_INLISTPASSIVETARGET (0x4B) + + +#define PN532_MIFARE_ISO14443A (0x00) + +// Mifare Commands +#define MIFARE_CMD_AUTH_A (0x60) +#define MIFARE_CMD_AUTH_B (0x61) +#define MIFARE_CMD_READ (0x30) +#define MIFARE_CMD_WRITE (0xA0) +#define MIFARE_CMD_WRITE_ULTRALIGHT (0xA2) +#define MIFARE_CMD_TRANSFER (0xB0) +#define MIFARE_CMD_DECREMENT (0xC0) +#define MIFARE_CMD_INCREMENT (0xC1) +#define MIFARE_CMD_STORE (0xC2) + +// Prefixes for NDEF Records (to identify record type) +#define NDEF_URIPREFIX_NONE (0x00) +#define NDEF_URIPREFIX_HTTP_WWWDOT (0x01) +#define NDEF_URIPREFIX_HTTPS_WWWDOT (0x02) +#define NDEF_URIPREFIX_HTTP (0x03) +#define NDEF_URIPREFIX_HTTPS (0x04) +#define NDEF_URIPREFIX_TEL (0x05) +#define NDEF_URIPREFIX_MAILTO (0x06) +#define NDEF_URIPREFIX_FTP_ANONAT (0x07) +#define NDEF_URIPREFIX_FTP_FTPDOT (0x08) +#define NDEF_URIPREFIX_FTPS (0x09) +#define NDEF_URIPREFIX_SFTP (0x0A) +#define NDEF_URIPREFIX_SMB (0x0B) +#define NDEF_URIPREFIX_NFS (0x0C) +#define NDEF_URIPREFIX_FTP (0x0D) +#define NDEF_URIPREFIX_DAV (0x0E) +#define NDEF_URIPREFIX_NEWS (0x0F) +#define NDEF_URIPREFIX_TELNET (0x10) +#define NDEF_URIPREFIX_IMAP (0x11) +#define NDEF_URIPREFIX_RTSP (0x12) +#define NDEF_URIPREFIX_URN (0x13) +#define NDEF_URIPREFIX_POP (0x14) +#define NDEF_URIPREFIX_SIP (0x15) +#define NDEF_URIPREFIX_SIPS (0x16) +#define NDEF_URIPREFIX_TFTP (0x17) +#define NDEF_URIPREFIX_BTSPP (0x18) +#define NDEF_URIPREFIX_BTL2CAP (0x19) +#define NDEF_URIPREFIX_BTGOEP (0x1A) +#define NDEF_URIPREFIX_TCPOBEX (0x1B) +#define NDEF_URIPREFIX_IRDAOBEX (0x1C) +#define NDEF_URIPREFIX_FILE (0x1D) +#define NDEF_URIPREFIX_URN_EPC_ID (0x1E) +#define NDEF_URIPREFIX_URN_EPC_TAG (0x1F) +#define NDEF_URIPREFIX_URN_EPC_PAT (0x20) +#define NDEF_URIPREFIX_URN_EPC_RAW (0x21) +#define NDEF_URIPREFIX_URN_EPC (0x22) +#define NDEF_URIPREFIX_URN_NFC (0x23) + +#define PN532_GPIO_VALIDATIONBIT (0x80) +#define PN532_GPIO_P30 (0) +#define PN532_GPIO_P31 (1) +#define PN532_GPIO_P32 (2) +#define PN532_GPIO_P33 (3) +#define PN532_GPIO_P34 (4) +#define PN532_GPIO_P35 (5) + +class PN532 +{ +public: + PN532(PN532Interface &interface); + + void begin(void); + + // Generic PN532 functions + bool SAMConfig(void); + uint32_t getFirmwareVersion(void); + bool writeGPIO(uint8_t pinstate); + uint8_t readGPIO(void); + bool setPassiveActivationRetries(uint8_t maxRetries); + + /** + * @brief Init PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t tgInitAsTarget(uint16_t timeout = 0); + int8_t tgInitAsTarget(const uint8_t* command, const uint8_t len, const uint16_t timeout = 0); + + int16_t tgGetData(uint8_t *buf, uint8_t len); + bool tgSetData(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + int16_t inRelease(const uint8_t relevantTarget = 0); + + // ISO14443A functions + bool inListPassiveTarget(); + bool readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout = 1000, bool inlist = false); + bool inDataExchange(uint8_t *send, uint8_t sendLength, uint8_t *response, uint8_t *responseLength); + + // Mifare Classic functions + bool mifareclassic_IsFirstBlock (uint32_t uiBlock); + bool mifareclassic_IsTrailerBlock (uint32_t uiBlock); + uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData); + uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data); + uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data); + uint8_t mifareclassic_FormatNDEF (void); + uint8_t mifareclassic_WriteNDEFURI (uint8_t sectorNumber, uint8_t uriIdentifier, const char *url); + + // Mifare Ultralight functions + uint8_t mifareultralight_ReadPage (uint8_t page, uint8_t *buffer); + uint8_t mifareultralight_WritePage (uint8_t page, uint8_t *buffer); + + uint8_t ntag21x_auth(const uint8_t *key); + + // Help functions to display formatted text + static void PrintHex(const uint8_t *data, const uint32_t numBytes); + static void PrintHexChar(const uint8_t *pbtData, const uint32_t numBytes); + + uint8_t *getBuffer(uint8_t *len) { + *len = sizeof(pn532_packetbuffer) - 4; + return pn532_packetbuffer; + }; + +private: + uint8_t _uid[7]; // ISO14443A uid + uint8_t _uidLen; // uid len + uint8_t _key[6]; // Mifare Classic key + uint8_t inListedTag; // Tg number of inlisted tag. + + uint8_t pn532_packetbuffer[64]; + + PN532Interface *_interface; +}; + +#endif diff --git a/PN532Interface.h b/PN532Interface.h new file mode 100644 index 0000000..14f590a --- /dev/null +++ b/PN532Interface.h @@ -0,0 +1,56 @@ + + +#ifndef __PN532_INTERFACE_H__ +#define __PN532_INTERFACE_H__ + +#include + +#define PN532_PREAMBLE (0x00) +#define PN532_STARTCODE1 (0x00) +#define PN532_STARTCODE2 (0xFF) +#define PN532_POSTAMBLE (0x00) + +#define PN532_HOSTTOPN532 (0xD4) +#define PN532_PN532TOHOST (0xD5) + +#define PN532_ACK_WAIT_TIME (10) // ms, timeout of waiting for ACK + +#define PN532_INVALID_ACK (-1) +#define PN532_TIMEOUT (-2) +#define PN532_INVALID_FRAME (-3) +#define PN532_NO_SPACE (-4) + +#define REVERSE_BITS_ORDER(b) b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; \ + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; \ + b = (b & 0xAA) >> 1 | (b & 0x55) << 1 + +class PN532Interface +{ +public: + virtual void begin() = 0; + virtual void wakeup() = 0; + + /** + * @brief write a command and check ack + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return 0 success + * not 0 failed + */ + virtual int8_t writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0) = 0; + + /** + * @brief read the response of a command, strip prefix and suffix + * @param buf to contain the response data + * @param len lenght to read + * @param timeout max time to wait, 0 means no timeout + * @return >=0 length of response without prefix and suffix + * <0 failed to read response + */ + virtual int16_t readResponse(uint8_t buf[], uint8_t len, uint16_t timeout = 1000) = 0; +}; + +#endif + diff --git a/PN532_debug.h b/PN532_debug.h new file mode 100644 index 0000000..ba0b589 --- /dev/null +++ b/PN532_debug.h @@ -0,0 +1,20 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +//#define DEBUG + +#include "Arduino.h" + +#ifdef DEBUG +#define DMSG(args...) Serial.print(args) +#define DMSG_STR(str) Serial.println(str) +#define DMSG_HEX(num) Serial.print(' '); Serial.print(num, HEX) +#define DMSG_INT(num) Serial.print(' '); Serial.print(num) +#else +#define DMSG(args...) +#define DMSG_STR(str) +#define DMSG_HEX(num) +#define DMSG_INT(num) +#endif + +#endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d9d4f6 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +## NFC library for Arduino + +This is an Arduino library for PN532 to use NFC technology. +It works with + ++ [NFC Shield](http://goo.gl/Cac2OH) ++ [Xadow NFC](http://goo.gl/qBZMt0) ++ [PN532 NFC/RFID controller breakout board](http://goo.gl/tby9Sw) + +### Features ++ Support I2C, SPI and HSU of PN532 ++ Read/write Mifare Classic Card ++ Works with [Don's NDEF Library](http://goo.gl/jDjsXl) ++ Support Peer to Peer communication(exchange data with android 4.0+) ++ Support [mbed platform](http://goo.gl/kGPovZ) + +### Getting Started +1. Download [zip file](http://goo.gl/F6beRM) and +extract the 4 folders(PN532, PN532_SPI, PN532_I2C and PN532_HSU) into Arduino's libraries. +2. Downlaod [Don's NDEF library](http://goo.gl/ewxeAe) and extract it intro Arduino's libraries. +3. Follow the examples of the two libraries. + +### To do ++ Card emulation + +### Contribution +It's based on [Adafruit_NFCShield_I2C](http://goo.gl/pk3FdB). +[Seeed Studio](http://goo.gl/zh1iQh) adds SPI interface and peer to peer communication support. +@Don writes the [NDEF library](http://goo.gl/jDjsXl) to make it more easy to use. +@JiapengLi adds HSU interface. diff --git a/emulatetag.cpp b/emulatetag.cpp new file mode 100644 index 0000000..25a6e99 --- /dev/null +++ b/emulatetag.cpp @@ -0,0 +1,261 @@ +/**************************************************************************/ +/*! + @file emulatetag.cpp + @author Armin Wieser + @license BSD +*/ +/**************************************************************************/ + +#include "emulatetag.h" +#include "PN532_debug.h" + +#include + +#define MAX_TGREAD + + +// Command APDU +#define C_APDU_CLA 0 +#define C_APDU_INS 1 // instruction +#define C_APDU_P1 2 // parameter 1 +#define C_APDU_P2 3 // parameter 2 +#define C_APDU_LC 4 // length command +#define C_APDU_DATA 5 // data + +#define C_APDU_P1_SELECT_BY_ID 0x00 +#define C_APDU_P1_SELECT_BY_NAME 0x04 + +// Response APDU +#define R_APDU_SW1_COMMAND_COMPLETE 0x90 +#define R_APDU_SW2_COMMAND_COMPLETE 0x00 + +#define R_APDU_SW1_NDEF_TAG_NOT_FOUND 0x6a +#define R_APDU_SW2_NDEF_TAG_NOT_FOUND 0x82 + +#define R_APDU_SW1_FUNCTION_NOT_SUPPORTED 0x6A +#define R_APDU_SW2_FUNCTION_NOT_SUPPORTED 0x81 + +#define R_APDU_SW1_MEMORY_FAILURE 0x65 +#define R_APDU_SW2_MEMORY_FAILURE 0x81 + +#define R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x62 +#define R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES 0x82 + +// ISO7816-4 commands +#define ISO7816_SELECT_FILE 0xA4 +#define ISO7816_READ_BINARY 0xB0 +#define ISO7816_UPDATE_BINARY 0xD6 + +typedef enum { NONE, CC, NDEF } tag_file; // CC ... Compatibility Container + +bool EmulateTag::init(){ + pn532.begin(); + return pn532.SAMConfig(); +} + +void EmulateTag::setNdefFile(const uint8_t* ndef, const int16_t ndefLength){ + if(ndefLength > (NDEF_MAX_LENGTH -2)){ + DMSG("ndef file too large (> NDEF_MAX_LENGHT -2) - aborting"); + return; + } + + ndef_file[0] = ndefLength >> 8; + ndef_file[1] = ndefLength & 0xFF; + memcpy(ndef_file+2, ndef, ndefLength); +} + +void EmulateTag::setUid(uint8_t* uid){ + uidPtr = uid; +} + +bool EmulateTag::emulate(const uint16_t tgInitAsTargetTimeout){ + + // http://www.nxp.com/documents/application_note/AN133910.pdf + uint8_t command[] = { + PN532_COMMAND_TGINITASTARGET, + 0x05, // MODE: PICC only, Passive only + + 0x04, 0x00, // SENS_RES + 0x00, 0x00, 0x00, // NFCID1 + 0x20, // SEL_RES + + 0x01, 0xFE, // Parameters to build POL_RES + 0xA2, 0xA3, 0xA4, + 0xA5, 0xA6, 0xA7, + 0xC0, 0xC1, 0xC2, + 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0xFF, + 0xFF, + 0xAA, 0x99, 0x88, //NFCID3t (10 bytes) + 0x77, 0x66, 0x55, 0x44, + 0x33, 0x22, 0x11, + + 0, // length of general bytes + 0 // length of historical bytes + }; + + if(uidPtr != 0){ // if uid is set copy 3 bytes to nfcid1 + memcpy(command + 4, uidPtr, 3); + } + + if(1 != pn532.tgInitAsTarget(command,sizeof(command), tgInitAsTargetTimeout)){ + DMSG("tgInitAsTarget failed or timed out!"); + return false; + } + + uint8_t compatibility_container[] = { + 0, 0x0F, + 0x20, + 0, 0x54, + 0, 0xFF, + 0x04, // T + 0x06, // L + 0xE1, 0x04, // File identifier + ((NDEF_MAX_LENGTH & 0xFF00) >> 8), (NDEF_MAX_LENGTH & 0xFF), // maximum NDEF file size + 0x00, // read access 0x0 = granted + 0x00 // write access 0x0 = granted | 0xFF = deny + }; + + if(tagWriteable == false){ + compatibility_container[14] = 0xFF; + } + + tagWrittenByInitiator = false; + + uint8_t rwbuf[128]; + uint8_t sendlen; + int16_t status; + tag_file currentFile = NONE; + uint16_t cc_size = sizeof(compatibility_container); + bool runLoop = true; + + while(runLoop){ + status = pn532.tgGetData(rwbuf, sizeof(rwbuf)); + if(status < 0){ + DMSG("tgGetData failed!\n"); + pn532.inRelease(); + return true; + } + + uint8_t p1 = rwbuf[C_APDU_P1]; + uint8_t p2 = rwbuf[C_APDU_P2]; + uint8_t lc = rwbuf[C_APDU_LC]; + uint16_t p1p2_length = ((int16_t) p1 << 8) + p2; + + switch(rwbuf[C_APDU_INS]){ + case ISO7816_SELECT_FILE: + switch(p1){ + case C_APDU_P1_SELECT_BY_ID: + if(p2 != 0x0c){ + DMSG("C_APDU_P2 != 0x0c\n"); + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + } else if(lc == 2 && rwbuf[C_APDU_DATA] == 0xE1 && (rwbuf[C_APDU_DATA+1] == 0x03 || rwbuf[C_APDU_DATA+1] == 0x04)){ + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + if(rwbuf[C_APDU_DATA+1] == 0x03){ + currentFile = CC; + } else if(rwbuf[C_APDU_DATA+1] == 0x04){ + currentFile = NDEF; + } + } else { + setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); + } + break; + case C_APDU_P1_SELECT_BY_NAME: + const uint8_t ndef_tag_application_name_v2[] = {0, 0x7, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01 }; + if(0 == memcmp(ndef_tag_application_name_v2, rwbuf + C_APDU_P2, sizeof(ndef_tag_application_name_v2))){ + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + } else{ + DMSG("function not supported\n"); + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + break; + } + break; + case ISO7816_READ_BINARY: + switch(currentFile){ + case NONE: + setResponse(TAG_NOT_FOUND, rwbuf, &sendlen); + break; + case CC: + if( p1p2_length > NDEF_MAX_LENGTH){ + setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); + }else { + memcpy(rwbuf,compatibility_container + p1p2_length, lc); + setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); + } + break; + case NDEF: + if( p1p2_length > NDEF_MAX_LENGTH){ + setResponse(END_OF_FILE_BEFORE_REACHED_LE_BYTES, rwbuf, &sendlen); + }else { + memcpy(rwbuf, ndef_file + p1p2_length, lc); + setResponse(COMMAND_COMPLETE, rwbuf + lc, &sendlen, lc); + } + break; + } + break; + case ISO7816_UPDATE_BINARY: + if(!tagWriteable){ + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } else{ + if( p1p2_length > NDEF_MAX_LENGTH){ + setResponse(MEMORY_FAILURE, rwbuf, &sendlen); + } + else{ + memcpy(ndef_file + p1p2_length, rwbuf + C_APDU_DATA, lc); + setResponse(COMMAND_COMPLETE, rwbuf, &sendlen); + tagWrittenByInitiator = true; + + uint16_t ndef_length = (ndef_file[0] << 8) + ndef_file[1]; + if ((ndef_length > 0) && (updateNdefCallback != 0)) { + updateNdefCallback(ndef_file + 2, ndef_length); + } + } + } + break; + default: + DMSG("Command not supported!"); + DMSG_HEX(rwbuf[C_APDU_INS]); + DMSG("\n"); + setResponse(FUNCTION_NOT_SUPPORTED, rwbuf, &sendlen); + } + status = pn532.tgSetData(rwbuf, sendlen); + if(status < 0){ + DMSG("tgSetData failed\n!"); + pn532.inRelease(); + return true; + } + } + pn532.inRelease(); + return true; +} + +void EmulateTag::setResponse(responseCommand cmd, uint8_t* buf, uint8_t* sendlen, uint8_t sendlenOffset){ + switch(cmd){ + case COMMAND_COMPLETE: + buf[0] = R_APDU_SW1_COMMAND_COMPLETE; + buf[1] = R_APDU_SW2_COMMAND_COMPLETE; + *sendlen = 2 + sendlenOffset; + break; + case TAG_NOT_FOUND: + buf[0] = R_APDU_SW1_NDEF_TAG_NOT_FOUND; + buf[1] = R_APDU_SW2_NDEF_TAG_NOT_FOUND; + *sendlen = 2; + break; + case FUNCTION_NOT_SUPPORTED: + buf[0] = R_APDU_SW1_FUNCTION_NOT_SUPPORTED; + buf[1] = R_APDU_SW2_FUNCTION_NOT_SUPPORTED; + *sendlen = 2; + break; + case MEMORY_FAILURE: + buf[0] = R_APDU_SW1_MEMORY_FAILURE; + buf[1] = R_APDU_SW2_MEMORY_FAILURE; + *sendlen = 2; + break; + case END_OF_FILE_BEFORE_REACHED_LE_BYTES: + buf[0] = R_APDU_SW1_END_OF_FILE_BEFORE_REACHED_LE_BYTES; + buf[1] = R_APDU_SW2_END_OF_FILE_BEFORE_REACHED_LE_BYTES; + *sendlen= 2; + break; + } +} diff --git a/emulatetag.h b/emulatetag.h new file mode 100644 index 0000000..478e30f --- /dev/null +++ b/emulatetag.h @@ -0,0 +1,71 @@ +/**************************************************************************/ +/*! + @file emulatetag.h + @author Armin Wieser + @license BSD + + Implemented using NFC forum documents & library of libnfc +*/ +/**************************************************************************/ + +#ifndef __EMULATETAG_H__ +#define __EMULATETAG_H__ + +#include "PN532.h" + +#define NDEF_MAX_LENGTH 128 // altough ndef can handle up to 0xfffe in size, arduino cannot. +typedef enum {COMMAND_COMPLETE, TAG_NOT_FOUND, FUNCTION_NOT_SUPPORTED, MEMORY_FAILURE, END_OF_FILE_BEFORE_REACHED_LE_BYTES} responseCommand; + +class EmulateTag{ + +public: +EmulateTag(PN532Interface &interface) : pn532(interface), uidPtr(0), tagWrittenByInitiator(false), tagWriteable(true), updateNdefCallback(0) { } + + bool init(); + + bool emulate(const uint16_t tgInitAsTargetTimeout = 0); + + /* + * @param uid pointer to byte array of length 3 (uid is 4 bytes - first byte is fixed) or zero for uid + */ + void setUid(uint8_t* uid = 0); + + void setNdefFile(const uint8_t* ndef, const int16_t ndefLength); + + void getContent(uint8_t** buf, uint16_t* length){ + *buf = ndef_file + 2; // first 2 bytes = length + *length = (ndef_file[0] << 8) + ndef_file[1]; + } + + bool writeOccured(){ + return tagWrittenByInitiator; + } + + void setTagWriteable(bool setWriteable){ + tagWriteable = setWriteable; + } + + uint8_t* getNdefFilePtr(){ + return ndef_file; + } + + uint8_t getNdefMaxLength(){ + return NDEF_MAX_LENGTH; + } + + void attach(void (*func)(uint8_t *buf, uint16_t length)) { + updateNdefCallback = func; + }; + +private: + PN532 pn532; + uint8_t ndef_file[NDEF_MAX_LENGTH]; + uint8_t* uidPtr; + bool tagWrittenByInitiator; + bool tagWriteable; + void (*updateNdefCallback)(uint8_t *ndef, uint16_t length); + + void setResponse(responseCommand cmd, uint8_t* buf, uint8_t* sendlen, uint8_t sendlenOffset = 0); +}; + +#endif diff --git a/examples/android_hce/android_hce.ino b/examples/android_hce/android_hce.ino new file mode 100644 index 0000000..9ad3e4e --- /dev/null +++ b/examples/android_hce/android_hce.ino @@ -0,0 +1,135 @@ +#include +#include +#include +#include + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer HCE--------"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + //nfc.setPassiveActivationRetries(0xFF); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop() +{ + bool success; + + uint8_t responseLength = 32; + + Serial.println("Waiting for an ISO14443A card"); + + // set shield to inListPassiveTarget + success = nfc.inListPassiveTarget(); + + if(success) { + + Serial.println("Found something!"); + + uint8_t selectApdu[] = { 0x00, /* CLA */ + 0xA4, /* INS */ + 0x04, /* P1 */ + 0x00, /* P2 */ + 0x07, /* Length of AID */ + 0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* AID defined on Android App */ + 0x00 /* Le */ }; + + uint8_t response[32]; + + success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), response, &responseLength); + + if(success) { + + Serial.print("responseLength: "); Serial.println(responseLength); + + nfc.PrintHexChar(response, responseLength); + + do { + uint8_t apdu[] = "Hello from Arduino"; + uint8_t back[32]; + uint8_t length = 32; + + success = nfc.inDataExchange(apdu, sizeof(apdu), back, &length); + + if(success) { + + Serial.print("responseLength: "); Serial.println(length); + + nfc.PrintHexChar(back, length); + } + else { + + Serial.println("Broken connection?"); + } + } + while(success); + } + else { + + Serial.println("Failed sending SELECT AID"); + } + } + else { + + Serial.println("Didn't find anything!"); + } + + delay(1000); +} + +void printResponse(uint8_t *response, uint8_t responseLength) { + + String respBuffer; + + for (int i = 0; i < responseLength; i++) { + + if (response[i] < 0x10) + respBuffer = respBuffer + "0"; //Adds leading zeros if hex value is smaller than 0x10 + + respBuffer = respBuffer + String(response[i], HEX) + " "; + } + + Serial.print("response: "); Serial.println(respBuffer); +} + +void setupNFC() { + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} diff --git a/examples/emulate_tag_ndef/emulate_tag_ndef.ino b/examples/emulate_tag_ndef/emulate_tag_ndef.ino new file mode 100644 index 0000000..3e805b3 --- /dev/null +++ b/examples/emulate_tag_ndef/emulate_tag_ndef.ino @@ -0,0 +1,69 @@ + +#include "SPI.h" +#include "PN532_SPI.h" +#include "emulatetag.h" +#include "NdefMessage.h" + +PN532_SPI pn532spi(SPI, 10); +EmulateTag nfc(pn532spi); + +uint8_t ndefBuf[120]; +NdefMessage message; +int messageSize; + +uint8_t uid[3] = { 0x12, 0x34, 0x56 }; + +void setup() +{ + Serial.begin(115200); + Serial.println("------- Emulate Tag --------"); + + message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) { + Serial.println("ndefBuf is too small"); + while (1) { } + } + + Serial.print("Ndef encoded message size: "); + Serial.println(messageSize); + + message.encode(ndefBuf); + + // comment out this command for no ndef message + nfc.setNdefFile(ndefBuf, messageSize); + + // uid must be 3 bytes! + nfc.setUid(uid); + + nfc.init(); +} + +void loop(){ + // uncomment for overriding ndef in case a write to this tag occured + //nfc.setNdefFile(ndefBuf, messageSize); + + // start emulation (blocks) + nfc.emulate(); + + // or start emulation with timeout + /*if(!nfc.emulate(1000)){ // timeout 1 second + Serial.println("timed out"); + }*/ + + // deny writing to the tag + // nfc.setTagWriteable(false); + + if(nfc.writeOccured()){ + Serial.println("\nWrite occured !"); + uint8_t* tag_buf; + uint16_t length; + + nfc.getContent(&tag_buf, &length); + NdefMessage msg = NdefMessage(tag_buf, length); + msg.print(); + } + + delay(1000); +} diff --git a/examples/iso14443a_uid/iso14443a_uid.pde b/examples/iso14443a_uid/iso14443a_uid.pde new file mode 100644 index 0000000..388600f --- /dev/null +++ b/examples/iso14443a_uid/iso14443a_uid.pde @@ -0,0 +1,78 @@ +/**************************************************************************/ +/*! + This example will attempt to connect to an ISO14443A + card or tag and retrieve some basic information about it + that can be used to determine what type of card it is. + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h + +*/ +/**************************************************************************/ + +#include +#include +#include "PN532.h" + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + +void setup(void) { + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + nfc.setPassiveActivationRetries(0xFF); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A card"); +} + +void loop(void) { + boolean success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength); + + if (success) { + Serial.println("Found a card!"); + Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print("UID Value: "); + for (uint8_t i=0; i < uidLength; i++) + { + Serial.print(" 0x");Serial.print(uid[i], HEX); + } + Serial.println(""); + + // wait until the card is taken away + while (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength)) {} + } + else + { + // PN532 probably timed out waiting for a card + Serial.println("Timed out waiting for a card"); + } +} diff --git a/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde b/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde new file mode 100644 index 0000000..7955400 --- /dev/null +++ b/examples/mifareclassic_formatndef/mifareclassic_formatndef.pde @@ -0,0 +1,170 @@ +/**************************************************************************/ +/*! + This example attempts to format a clean Mifare Classic 1K card as + an NFC Forum tag (to store NDEF messages that can be read by any + NFC enabled Android phone, etc.) + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#include +#include +#include "PN532.h" + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + +/* + We can encode many different kinds of pointers to the card, + from a URL, to an Email address, to a phone number, and many more + check the library header .h file to see the large # of supported + prefixes! +*/ +// For a http://www. url: +const char * url = "seeedstudio.com"; +uint8_t ndefprefix = NDEF_URIPREFIX_HTTP_WWWDOT; + +// for an email address +//const char * url = "mail@example.com"; +//uint8_t ndefprefix = NDEF_URIPREFIX_MAILTO; + +// for a phone number +//const char * url = "+1 212 555 1212"; +//uint8_t ndefprefix = NDEF_URIPREFIX_TEL; + + +void setup(void) { + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + + // Use the default key + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + Serial.println(""); + Serial.println("PLEASE NOTE: Formatting your card for NDEF records will change the"); + Serial.println("authentication keys. To reformat your NDEF tag as a clean Mifare"); + Serial.println("Classic tag, use the mifareclassic_ndeftoclassic example!"); + Serial.println(""); + Serial.println("Place your Mifare Classic card on the reader to format with NDEF"); + Serial.println("and press any key to continue ..."); + // Wait for user input before proceeding + while (!Serial.available()); + // a key was pressed1 + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + Serial.print(' '); + } + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Try to format the card for NDEF data + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 0, 0, keya); + if (!success) + { + Serial.println("Unable to authenticate block 0 to enable card formatting!"); + return; + } + success = nfc.mifareclassic_FormatNDEF(); + if (!success) + { + Serial.println("Unable to format the card for NDEF"); + return; + } + + Serial.println("Card has been formatted for NDEF data using MAD1"); + + // Try to authenticate block 4 (first block of sector 1) using our key + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 4, 0, keya); + + // Make sure the authentification process didn't fail + if (!success) + { + Serial.println("Authentication failed."); + return; + } + + // Try to write a URL + Serial.println("Writing URI to sector 1 as an NDEF Message"); + + // Authenticated seems to have worked + // Try to write an NDEF record to sector 1 + // Use 0x01 for the URI Identifier Code to prepend "http://www." + // to the url (and save some space). For information on URI ID Codes + // see http://www.ladyada.net/wiki/private/articlestaging/nfc/ndef + if (strlen(url) > 38) + { + // The length is also checked in the WriteNDEFURI function, but lets + // warn users here just in case they change the value and it's bigger + // than it should be + Serial.println("URI is too long ... must be less than 38 characters long"); + return; + } + + // URI is within size limits ... write it to the card and report success/failure + success = nfc.mifareclassic_WriteNDEFURI(1, ndefprefix, url); + if (success) + { + Serial.println("NDEF URI Record written to sector 1"); + } + else + { + Serial.println("NDEF Record creation failed! :("); + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} \ No newline at end of file diff --git a/examples/mifareclassic_memdump/mifareclassic_memdump.pde b/examples/mifareclassic_memdump/mifareclassic_memdump.pde new file mode 100644 index 0000000..539de3e --- /dev/null +++ b/examples/mifareclassic_memdump/mifareclassic_memdump.pde @@ -0,0 +1,157 @@ +/**************************************************************************/ +/*! + This example attempts to dump the contents of a Mifare Classic 1K card + + Note that you need the baud rate to be 115200 because we need to print + out the data and read from the card at the same time! + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#include +#include +#include "PN532.h" + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + +void setup(void) { + // has to be fast to dump the entire memory contents! + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A Card ..."); +} + + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + uint8_t currentblock; // Counter to keep track of which block we're on + bool authenticated = false; // Flag to indicate if the sector is authenticated + uint8_t data[16]; // Array to store block data during reads + + // Keyb on NDEF and Mifare Classic should be the same + uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + for (uint8_t i = 0; i < uidLength; i++) { + Serial.print(uid[i], HEX); + Serial.print(' '); + } + Serial.println(""); + + if (uidLength == 4) + { + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Now we try to go through all 16 sectors (each having 4 blocks) + // authenticating each sector, and then dumping the blocks + for (currentblock = 0; currentblock < 64; currentblock++) + { + // Check if this is a new block so that we can reauthenticate + if (nfc.mifareclassic_IsFirstBlock(currentblock)) authenticated = false; + + // If the sector hasn't been authenticated, do so first + if (!authenticated) + { + // Starting of a new sector ... try to to authenticate + Serial.print("------------------------Sector ");Serial.print(currentblock/4, DEC);Serial.println("-------------------------"); + if (currentblock == 0) + { + // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!) + // or 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 for NDEF formatted cards using key a, + // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF) + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal); + } + else + { + // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!) + // or 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 for NDEF formatted cards using key a, + // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF) + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal); + } + if (success) + { + authenticated = true; + } + else + { + Serial.println("Authentication error"); + } + } + // If we're still not authenticated just skip the block + if (!authenticated) + { + Serial.print("Block ");Serial.print(currentblock, DEC);Serial.println(" unable to authenticate"); + } + else + { + // Authenticated ... we should be able to read the block now + // Dump the data into the 'data' array + success = nfc.mifareclassic_ReadDataBlock(currentblock, data); + if (success) + { + // Read successful + Serial.print("Block ");Serial.print(currentblock, DEC); + if (currentblock < 10) + { + Serial.print(" "); + } + else + { + Serial.print(" "); + } + // Dump the raw data + nfc.PrintHexChar(data, 16); + } + else + { + // Oops ... something happened + Serial.print("Block ");Serial.print(currentblock, DEC); + Serial.println(" unable to read this block"); + } + } + } + } + else + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + } + } + // Wait a bit before trying again + Serial.println("\n\nSend a character to run the mem dumper again!"); + Serial.flush(); + while (!Serial.available()); + while (Serial.available()) { + Serial.read(); + } + Serial.flush(); +} \ No newline at end of file diff --git a/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde b/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde new file mode 100644 index 0000000..c7b23c0 --- /dev/null +++ b/examples/mifareclassic_ndeftoclassic/mifareclassic_ndeftoclassic.pde @@ -0,0 +1,172 @@ +/**************************************************************************/ +/*! + This examples attempts to take a Mifare Classic 1K card that has been + formatted for NDEF messages using mifareclassic_formatndef, and resets + the authentication keys back to the Mifare Classic defaults + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#include +#include +#include "PN532.h" + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + + +#define NR_SHORTSECTOR (32) // Number of short sectors on Mifare 1K/4K +#define NR_LONGSECTOR (8) // Number of long sectors on Mifare 4K +#define NR_BLOCK_OF_SHORTSECTOR (4) // Number of blocks in a short sector +#define NR_BLOCK_OF_LONGSECTOR (16) // Number of blocks in a long sector + +// Determine the sector trailer block based on sector number +#define BLOCK_NUMBER_OF_SECTOR_TRAILER(sector) (((sector)>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + uint8_t blockBuffer[16]; // Buffer to store block contents + uint8_t blankAccessBits[3] = { 0xff, 0x07, 0x80 }; + uint8_t idx = 0; + uint8_t numOfSector = 16; // Assume Mifare Classic 1K for now (16 4-block sectors) + + Serial.println("Place your NDEF formatted Mifare Classic 1K card on the reader"); + Serial.println("and press any key to continue ..."); + + // Wait for user input before proceeding + while (!Serial.available()); + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // We seem to have a tag ... + // Display some basic information about it + Serial.println("Found an ISO14443A card/tag"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + Serial.println(""); + Serial.println("Reformatting card for Mifare Classic (please don't touch it!) ... "); + + // Now run through the card sector by sector + for (idx = 0; idx < numOfSector; idx++) + { + // Step 1: Authenticate the current sector using key B 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, BLOCK_NUMBER_OF_SECTOR_TRAILER(idx), 1, (uint8_t *)KEY_DEFAULT_KEYAB); + if (!success) + { + Serial.print("Authentication failed for sector "); Serial.println(numOfSector); + return; + } + + // Step 2: Write to the other blocks + if (idx == 16) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + if ((idx == 0) || (idx == 16)) + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + else + { + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 3, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 2, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + } + memset(blockBuffer, 0, sizeof(blockBuffer)); + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)) - 1, blockBuffer))) + { + Serial.print("Unable to write to sector "); Serial.println(numOfSector); + return; + } + + // Step 3: Reset both keys to 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + memcpy(blockBuffer, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + memcpy(blockBuffer + 6, blankAccessBits, sizeof(blankAccessBits)); + blockBuffer[9] = 0x69; + memcpy(blockBuffer + 10, KEY_DEFAULT_KEYAB, sizeof(KEY_DEFAULT_KEYAB)); + + // Step 4: Write the trailer block + if (!(nfc.mifareclassic_WriteDataBlock((BLOCK_NUMBER_OF_SECTOR_TRAILER(idx)), blockBuffer))) + { + Serial.print("Unable to write trailer block of sector "); Serial.println(numOfSector); + return; + } + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} \ No newline at end of file diff --git a/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde b/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde new file mode 100644 index 0000000..fbec210 --- /dev/null +++ b/examples/mifareclassic_updatendef/mifareclassic_updatendef.pde @@ -0,0 +1,145 @@ +/**************************************************************************/ +/*! + Updates a sector that is already formatted for NDEF (using + mifareclassic_formatndef.pde for example), inserting a new url + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#include +#include +#include "PN532.h" + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + + + +/* + We can encode many different kinds of pointers to the card, + from a URL, to an Email address, to a phone number, and many more + check the library header .h file to see the large # of supported + prefixes! +*/ +// For a http://www. url: +const char * url = "seeedstudio.com"; +uint8_t ndefprefix = NDEF_URIPREFIX_HTTP_WWWDOT; + +// for an email address +//const char * url = "mail@example.com"; +//uint8_t ndefprefix = NDEF_URIPREFIX_MAILTO; + +// for a phone number +//const char * url = "+1 212 555 1212"; +//uint8_t ndefprefix = NDEF_URIPREFIX_TEL; + + +void setup(void) { + Serial.begin(115200); + Serial.println("Looking for PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); +} + +void loop(void) { + uint8_t success; // Flag to check if there was an error with the PN532 + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + bool authenticated = false; // Flag to indicate if the sector is authenticated + + // Use the default NDEF keys (these would have have set by mifareclassic_formatndef.pde!) + uint8_t keya[6] = { 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5 }; + uint8_t keyb[6] = { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }; + + Serial.println("Place your NDEF formatted Mifare Classic card on the reader to update the"); + Serial.println("NDEF record and press any key to continue ..."); + // Wait for user input before proceeding + while (!Serial.available()); + // a key was pressed1 + while (Serial.available()) Serial.read(); + + // Wait for an ISO14443A type card (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + // Make sure this is a Mifare Classic card + if (uidLength != 4) + { + Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!"); + return; + } + + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Check if this is an NDEF card (using first block of sector 1 from mifareclassic_formatndef.pde) + // Must authenticate on the first key using 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 + success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, 4, 0, keyb); + if (!success) + { + Serial.println("Unable to authenticate block 4 ... is this card NDEF formatted?"); + return; + } + + Serial.println("Authentication succeeded (seems to be an NDEF/NFC Forum tag) ..."); + + // Authenticated seems to have worked + // Try to write an NDEF record to sector 1 + // Use 0x01 for the URI Identifier Code to prepend "http://www." + // to the url (and save some space). For information on URI ID Codes + // see http://www.ladyada.net/wiki/private/articlestaging/nfc/ndef + if (strlen(url) > 38) + { + // The length is also checked in the WriteNDEFURI function, but lets + // warn users here just in case they change the value and it's bigger + // than it should be + Serial.println("URI is too long ... must be less than 38 characters!"); + return; + } + + Serial.println("Updating sector 1 with URI as NDEF Message"); + + // URI is within size limits ... write it to the card and report success/failure + success = nfc.mifareclassic_WriteNDEFURI(1, ndefprefix, url); + if (success) + { + Serial.println("NDEF URI Record written to sector 1"); + Serial.println(""); + } + else + { + Serial.println("NDEF Record creation failed! :("); + } + } + + // Wait a bit before trying again + Serial.println("\n\nDone!"); + delay(1000); + Serial.flush(); + while(Serial.available()) Serial.read(); +} diff --git a/examples/ntag21x_protect/ntag21x_protect.ino b/examples/ntag21x_protect/ntag21x_protect.ino new file mode 100644 index 0000000..642fb99 --- /dev/null +++ b/examples/ntag21x_protect/ntag21x_protect.ino @@ -0,0 +1,91 @@ +// NTAG21x supports 4 bytes password to protect pages started from AUTH0 +// AUTH0 defines the page address from which the password verification is required. +// Valid address range for byte AUTH0 is from 00h to FFh. +// If AUTH0 is set to a page address which is higher than the last page from the user configuration, +// the password protection is effectively disabled + +#if 0 // Using PN532's SPI (Seeed NFC shield) +#include +#include +#include +#include + +PN532_SPI intf(SPI, 10); +PN532 nfc = PN532(intf); +#else // Using PN532's I2C +#include +#include +#include +#include + +PN532_I2C intf(Wire); +PN532 nfc = PN532(intf); +#endif + +// Using PN532's UART (Grove NFC) + +// #include +// #include +// #include +// PN532_HSU intf(Serial1); +// PN532 nfc = PN532(intf); + + +uint8_t password[4] = {0x12, 0x34, 0x56, 0x78}; +uint8_t buf[4]; +uint8_t uid[7]; +uint8_t uidLength; + +void setup(void) { + Serial.begin(9600); + Serial.println("NTAG21x R/W"); + + nfc.begin(); + nfc.SAMConfig(); +} + +void loop(void) { + Serial.println("wait for a tag"); + // wait until a tag is present + while (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) { + + } + + // if NTAG21x enables r/w protection, uncomment the following line + // nfc.ntag21x_auth(password); + + nfc.mifareultralight_ReadPage(3, buf); + int capacity = buf[2] * 8; + Serial.print(F("Tag capacity ")); + Serial.print(capacity); + Serial.println(F(" bytes")); + + uint8_t cfg_page_base = 0x29; // NTAG213 + if (capacity == 0x3E) { + cfg_page_base = 0x83; // NTAG215 + } else if (capacity == 0x6D) { + cfg_page_base = 0xE3; // NTAG216 + } + + // PWD page, set new password + nfc.mifareultralight_WritePage(cfg_page_base + 2, password); + + // disable r/w + // | PROT | CFG_LCK | RFUI | NFC_CNT_EN | NFC_CNT_PWD_PROT | AUTHLIM (2:0) | + buf[0] = (1 << 7) | 0x0; + nfc.mifareultralight_WritePage(cfg_page_base + 1, buf); + + // protect pages started from AUTH0 + uint8_t auth0 = 0x10; + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = auth0; + nfc.mifareultralight_WritePage(cfg_page_base, buf); + + + // wait until the tag is removed + while (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) { + + } +} diff --git a/examples/ntag21x_rw/ntag21x_rw.ino b/examples/ntag21x_rw/ntag21x_rw.ino new file mode 100644 index 0000000..800442c --- /dev/null +++ b/examples/ntag21x_rw/ntag21x_rw.ino @@ -0,0 +1,72 @@ + +// Clean resets a tag back to factory-like state +// For Mifare Classic, tag is zero'd and reformatted as Mifare Classic +// For Mifare Ultralight, tags is zero'd and left empty + + +#if 0 // Using PN532's SPI (Seeed NFC shield) +#include +#include +#include +#include + +PN532_SPI intf(SPI, 10); +PN532 nfc = PN532(intf); +#else // Using PN532's I2C +#include +#include +#include +#include + +PN532_I2C intf(Wire); +PN532 nfc = PN532(intf); +#endif + +// Using PN532's UART (Grove NFC) + +// #include +// #include +// #include +// PN532_HSU intf(Serial1); +// PN532 nfc = PN532(intf); + + +uint8_t password[4] = {0x12, 0x34, 0x56, 0x78}; +uint8_t buf[4]; +uint8_t uid[7]; +uint8_t uidLength; + +void setup(void) { + Serial.begin(9600); + Serial.println("NTAG21x R/W"); + + nfc.begin(); + nfc.SAMConfig(); +} + +void loop(void) { + Serial.println("wait for a tag"); + // wait until a tag is present + while (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) { + + } + + // if NTAG21x enables r/w protection, uncomment the following line + // nfc.ntag21x_auth(password); + + nfc.mifareultralight_ReadPage(3, buf); + int capacity = buf[2] * 8; + Serial.print(F("Tag capacity ")); + Serial.print(capacity); + Serial.println(F(" bytes")); + + for (int i=4; i 0) { + Serial.println("get a SNEP message:"); + for (uint8_t i = 0; i < len; i++) { + Serial.print(buf[i], HEX); + Serial.print(' '); + } + Serial.print('\n'); + for (uint8_t i = 0; i < len; i++) { + char c = buf[i]; + if (c <= 0x1f || c > 0x7f) { + Serial.print('.'); + } else { + Serial.print(c); + } + } + Serial.print('\n'); + } + delay(3000); +} + diff --git a/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino b/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino new file mode 100644 index 0000000..41f4d03 --- /dev/null +++ b/examples/p2p_with_ndef_library/p2p_with_ndef_library.ino @@ -0,0 +1,55 @@ +// send a NDEF message to adnroid or get a NDEF message +// +// note: [NDEF library](https://github.com/Don/NDEF) is needed. + +#include "SPI.h" +#include "PN532_SPI.h" +#include "snep.h" +#include "NdefMessage.h" + +PN532_SPI pn532spi(SPI, 10); +SNEP nfc(pn532spi); +uint8_t ndefBuf[128]; + +void setup() +{ + Serial.begin(115200); + Serial.println("-------Peer to Peer--------"); +} + +void loop() +{ +#if 1 + Serial.println("Send a message to Android"); + NdefMessage message = NdefMessage(); + message.addUriRecord("http://www.seeedstudio.com"); + int messageSize = message.getEncodedSize(); + if (messageSize > sizeof(ndefBuf)) { + Serial.println("ndefBuf is too small"); + while (1) { + } + + } + + message.encode(ndefBuf); + if (0 >= nfc.write(ndefBuf, messageSize)) { + Serial.println("Failed"); + } else { + Serial.println("Success"); + } + + delay(3000); +#else + Serial.println("Get a message from Android"); + int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf)); + if (msgSize > 0) { + NdefMessage msg = NdefMessage(ndefBuf, msgSize); + msg.print(); + Serial.println("\nSuccess"); + } else { + Serial.println("failed"); + } + delay(3000); +#endif +} + diff --git a/examples/readMifare/readMifare.pde b/examples/readMifare/readMifare.pde new file mode 100644 index 0000000..4777393 --- /dev/null +++ b/examples/readMifare/readMifare.pde @@ -0,0 +1,145 @@ +/**************************************************************************/ +/*! + This example will wait for any ISO14443A card or tag, and + depending on the size of the UID will attempt to read from it. + + If the card has a 4-byte UID it is probably a Mifare + Classic card, and the following steps are taken: + + - Authenticate block 4 (the first block of Sector 1) using + the default KEYA of 0XFF 0XFF 0XFF 0XFF 0XFF 0XFF + - If authentication succeeds, we can then read any of the + 4 blocks in that sector (though only block 4 is read here) + + If the card has a 7-byte UID it is probably a Mifare + Ultralight card, and the 4 byte pages can be read directly. + Page 4 is read by default since this is the first 'general- + purpose' page on the tags. + + To enable debug message, define DEBUG in PN532/PN532_debug.h +*/ +/**************************************************************************/ + +#include +#include +#include "PN532.h" + +PN532_SPI pn532spi(SPI, 10); +PN532 nfc(pn532spi); + +void setup(void) { + Serial.begin(115200); + Serial.println("Hello!"); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (! versiondata) { + Serial.print("Didn't find PN53x board"); + while (1); // halt + } + // Got ok data, print it out! + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // configure board to read RFID tags + nfc.SAMConfig(); + + Serial.println("Waiting for an ISO14443A Card ..."); +} + + +void loop(void) { + uint8_t success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) + + // Wait for an ISO14443A type cards (Mifare, etc.). When one is found + // 'uid' will be populated with the UID, and uidLength will indicate + // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (success) { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes"); + Serial.print(" UID Value: "); + nfc.PrintHex(uid, uidLength); + Serial.println(""); + + if (uidLength == 4) + { + // We probably have a Mifare Classic card ... + Serial.println("Seems to be a Mifare Classic card (4 byte UID)"); + + // Now we need to try to authenticate it for read/write access + // Try with the factory default KeyA: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF + Serial.println("Trying to authenticate block 4 with default KEYA value"); + uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + // Start with block 4 (the first block of sector 1) since sector 0 + // contains the manufacturer data and it's probably better just + // to leave it alone unless you know what you're doing + success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya); + + if (success) + { + Serial.println("Sector 1 (Blocks 4..7) has been authenticated"); + uint8_t data[16]; + + // If you want to write something to block 4 to test with, uncomment + // the following line and this text should be read back in a minute + // data = { 'a', 'd', 'a', 'f', 'r', 'u', 'i', 't', '.', 'c', 'o', 'm', 0, 0, 0, 0}; + // success = nfc.mifareclassic_WriteDataBlock (4, data); + + // Try to read the contents of block 4 + success = nfc.mifareclassic_ReadDataBlock(4, data); + + if (success) + { + // Data seems to have been read ... spit it out + Serial.println("Reading Block 4:"); + nfc.PrintHexChar(data, 16); + Serial.println(""); + + // Wait a bit before reading the card again + delay(1000); + } + else + { + Serial.println("Ooops ... unable to read the requested block. Try another key?"); + } + } + else + { + Serial.println("Ooops ... authentication failed: Try another key?"); + } + } + + if (uidLength == 7) + { + // We probably have a Mifare Ultralight card ... + Serial.println("Seems to be a Mifare Ultralight tag (7 byte UID)"); + + // Try to read the first general-purpose user page (#4) + Serial.println("Reading page 4"); + uint8_t data[32]; + success = nfc.mifareultralight_ReadPage (4, data); + if (success) + { + // Data seems to have been read ... spit it out + nfc.PrintHexChar(data, 4); + Serial.println(""); + + // Wait a bit before reading the card again + delay(1000); + } + else + { + Serial.println("Ooops ... unable to read the requested page!?"); + } + } + } +} + diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..09c45e1 --- /dev/null +++ b/license.txt @@ -0,0 +1,27 @@ +Software License Agreement (BSD License) + +Copyright (c) 2012, Adafruit Industries +Copyright (c) 2013, Seeed Technology Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/llcp.cpp b/llcp.cpp new file mode 100644 index 0000000..3c6b00b --- /dev/null +++ b/llcp.cpp @@ -0,0 +1,309 @@ + +#include "llcp.h" +#include "PN532_debug.h" + +// LLCP PDU Type Values +#define PDU_SYMM 0x00 +#define PDU_PAX 0x01 +#define PDU_CONNECT 0x04 +#define PDU_DISC 0x05 +#define PDU_CC 0x06 +#define PDU_DM 0x07 +#define PDU_I 0x0c +#define PDU_RR 0x0d + +uint8_t LLCP::SYMM_PDU[2] = {0, 0}; + +inline uint8_t getPType(const uint8_t *buf) +{ + return ((buf[0] & 0x3) << 2) + (buf[1] >> 6); +} + +inline uint8_t getSSAP(const uint8_t *buf) +{ + return buf[1] & 0x3f; +} + +inline uint8_t getDSAP(const uint8_t *buf) +{ + return buf[0] >> 2; +} + +int8_t LLCP::activate(uint16_t timeout) +{ + return link.activateAsTarget(timeout); +} + +int8_t LLCP::waitForConnection(uint16_t timeout) +{ + uint8_t type; + + mode = 1; + ns = 0; + nr = 0; + + // Get CONNECT PDU + DMSG("wait for a CONNECT PDU\n"); + do { + if (2 > link.read(headerBuf, headerBufLen)) { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CONNECT == type) { + break; + } else if (PDU_SYMM == type) { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return -2; + } + } else { + return -3; + } + + } while (1); + + // Put CC PDU + DMSG("put a CC(Connection Complete) PDU to response the CONNECT PDU\n"); + ssap = getDSAP(headerBuf); + dsap = getSSAP(headerBuf); + headerBuf[0] = (dsap << 2) + ((PDU_CC >> 2) & 0x3); + headerBuf[1] = ((PDU_CC & 0x3) << 6) + ssap; + if (!link.write(headerBuf, 2)) { + return -2; + } + + return 1; +} + +int8_t LLCP::waitForDisconnection(uint16_t timeout) +{ + uint8_t type; + + // Get DISC PDU + DMSG("wait for a DISC PDU\n"); + do { + if (2 > link.read(headerBuf, headerBufLen)) { + return -1; + } + + type = getPType(headerBuf); + if (PDU_DISC == type) { + break; + } else if (PDU_SYMM == type) { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return -2; + } + } else { + return -3; + } + + } while (1); + + // Put DM PDU + DMSG("put a DM(Disconnect Mode) PDU to response the DISC PDU\n"); + // ssap = getDSAP(headerBuf); + // dsap = getSSAP(headerBuf); + headerBuf[0] = (dsap << 2) + (PDU_DM >> 2); + headerBuf[1] = ((PDU_DM & 0x3) << 6) + ssap; + if (!link.write(headerBuf, 2)) { + return -2; + } + + return 1; +} + +int8_t LLCP::connect(uint16_t timeout) +{ + uint8_t type; + + mode = 0; + dsap = LLCP_DEFAULT_DSAP; + ssap = LLCP_DEFAULT_SSAP; + ns = 0; + nr = 0; + + // try to get a SYMM PDU + if (2 > link.read(headerBuf, headerBufLen)) { + return -1; + } + type = getPType(headerBuf); + if (PDU_SYMM != type) { + return -1; + } + + // put a CONNECT PDU + headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_CONNECT >> 2); + headerBuf[1] = ((PDU_CONNECT & 0x03) << 6) + LLCP_DEFAULT_SSAP; + uint8_t body[] = " urn:nfc:sn:snep"; + body[0] = 0x06; + body[1] = sizeof(body) - 2 - 1; + if (!link.write(headerBuf, 2, body, sizeof(body) - 1)) { + return -2; + } + + // wait for a CC PDU + DMSG("wait for a CC PDU\n"); + do { + if (2 > link.read(headerBuf, headerBufLen)) { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CC == type) { + break; + } else if (PDU_SYMM == type) { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return -2; + } + } else { + return -3; + } + + } while (1); + + return 1; +} + +int8_t LLCP::disconnect(uint16_t timeout) +{ + uint8_t type; + + // try to get a SYMM PDU + if (2 > link.read(headerBuf, headerBufLen)) { + return -1; + } + type = getPType(headerBuf); + if (PDU_SYMM != type) { + return -1; + } + + // put a DISC PDU + headerBuf[0] = (LLCP_DEFAULT_DSAP << 2) + (PDU_DISC >> 2); + headerBuf[1] = ((PDU_DISC & 0x03) << 6) + LLCP_DEFAULT_SSAP; + if (!link.write(headerBuf, 2)) { + return -2; + } + + // wait for a DM PDU + DMSG("wait for a DM PDU\n"); + do { + if (2 > link.read(headerBuf, headerBufLen)) { + return -1; + } + + type = getPType(headerBuf); + if (PDU_CC == type) { + break; + } else if (PDU_DM == type) { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return -2; + } + } else { + return -3; + } + + } while (1); + + return 1; +} + +bool LLCP::write(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + uint8_t type; + uint8_t buf[3]; + + if (mode) { + // Get a SYMM PDU + if (2 != link.read(buf, sizeof(buf))) { + return false; + } + } + + if (headerBufLen < (hlen + 3)) { + return false; + } + + for (int8_t i = hlen - 1; i >= 0; i--) { + headerBuf[i + 3] = header[i]; + } + + headerBuf[0] = (dsap << 2) + (PDU_I >> 2); + headerBuf[1] = ((PDU_I & 0x3) << 6) + ssap; + headerBuf[2] = (ns << 4) + nr; + if (!link.write(headerBuf, 3 + hlen, body, blen)) { + return false; + } + + ns++; + + // Get a RR PDU + int16_t status; + do { + status = link.read(headerBuf, headerBufLen); + if (2 > status) { + return false; + } + + type = getPType(headerBuf); + if (PDU_RR == type) { + break; + } else if (PDU_SYMM == type) { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return false; + } + } else { + return false; + } + } while (1); + + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return false; + } + + return true; +} + +int16_t LLCP::read(uint8_t *buf, uint8_t length) +{ + uint8_t type; + int16_t status; + + // Get INFO PDU + do { + status = link.read(buf, length); + if (2 > status) { + return -1; + } + + type = getPType(buf); + if (PDU_I == type) { + break; + } else if (PDU_SYMM == type) { + if (!link.write(SYMM_PDU, sizeof(SYMM_PDU))) { + return -2; + } + } else { + return -3; + } + + } while (1); + + uint8_t len = status - 3; + ssap = getDSAP(buf); + dsap = getSSAP(buf); + + headerBuf[0] = (dsap << 2) + (PDU_RR >> 2); + headerBuf[1] = ((PDU_RR & 0x3) << 6) + ssap; + headerBuf[2] = (buf[2] >> 4) + 1; + if (!link.write(headerBuf, 3)) { + return -2; + } + + for (uint8_t i = 0; i < len; i++) { + buf[i] = buf[i + 3]; + } + + nr++; + + return len; +} diff --git a/llcp.h b/llcp.h new file mode 100644 index 0000000..701c5d7 --- /dev/null +++ b/llcp.h @@ -0,0 +1,75 @@ + +#ifndef __LLCP_H__ +#define __LLCP_H__ + +#include "mac_link.h" + +#define LLCP_DEFAULT_TIMEOUT 20000 +#define LLCP_DEFAULT_DSAP 0x04 +#define LLCP_DEFAULT_SSAP 0x20 + +class LLCP { +public: + LLCP(PN532Interface &interface) : link(interface) { + headerBuf = link.getHeaderBuffer(&headerBufLen); + ns = 0; + nr = 0; + }; + + /** + * @brief Actiave PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t activate(uint16_t timeout = 0); + + int8_t waitForConnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t waitForDisconnection(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t connect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + int8_t disconnect(uint16_t timeout = LLCP_DEFAULT_TIMEOUT); + + /** + * @brief write a packet, the packet should be less than (255 - 2) bytes + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return true success + * false failed + */ + bool write(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + /** + * @brief read a packet, the packet will be less than (255 - 2) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @return >=0 length of the packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len); + + uint8_t *getHeaderBuffer(uint8_t *len) { + uint8_t *buf = link.getHeaderBuffer(len); + len -= 3; // I PDU header has 3 bytes + return buf; + }; + +private: + MACLink link; + uint8_t mode; + uint8_t ssap; + uint8_t dsap; + uint8_t *headerBuf; + uint8_t headerBufLen; + uint8_t ns; // Number of I PDU Sent + uint8_t nr; // Number of I PDU Received + + static uint8_t SYMM_PDU[2]; +}; + +#endif // __LLCP_H__ diff --git a/mac_link.cpp b/mac_link.cpp new file mode 100644 index 0000000..75aca8e --- /dev/null +++ b/mac_link.cpp @@ -0,0 +1,20 @@ + +#include "mac_link.h" +#include "PN532_debug.h" + +int8_t MACLink::activateAsTarget(uint16_t timeout) +{ + pn532.begin(); + pn532.SAMConfig(); + return pn532.tgInitAsTarget(timeout); +} + +bool MACLink::write(const uint8_t *header, uint8_t hlen, const uint8_t *body, uint8_t blen) +{ + return pn532.tgSetData(header, hlen, body, blen); +} + +int16_t MACLink::read(uint8_t *buf, uint8_t len) +{ + return pn532.tgGetData(buf, len); +} diff --git a/mac_link.h b/mac_link.h new file mode 100644 index 0000000..1575c04 --- /dev/null +++ b/mac_link.h @@ -0,0 +1,51 @@ + + +#ifndef __MAC_LINK_H__ +#define __MAC_LINK_H__ + +#include "PN532.h" + +class MACLink { +public: + MACLink(PN532Interface &interface) : pn532(interface) { + + }; + + /** + * @brief Activate PN532 as a target + * @param timeout max time to wait, 0 means no timeout + * @return > 0 success + * = 0 timeout + * < 0 failed + */ + int8_t activateAsTarget(uint16_t timeout = 0); + + /** + * @brief write a PDU packet, the packet should be less than (255 - 2) bytes + * @param header packet header + * @param hlen length of header + * @param body packet body + * @param blen length of body + * @return true success + * false failed + */ + bool write(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0); + + /** + * @brief read a PDU packet, the packet will be less than (255 - 2) bytes + * @param buf the buffer to contain the PDU packet + * @param len lenght of the buffer + * @return >=0 length of the PDU packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len); + + uint8_t *getHeaderBuffer(uint8_t *len) { + return pn532.getBuffer(len); + }; + +private: + PN532 pn532; +}; + +#endif // __MAC_LINK_H__ diff --git a/snep.cpp b/snep.cpp new file mode 100644 index 0000000..51c23d1 --- /dev/null +++ b/snep.cpp @@ -0,0 +1,122 @@ + +#include "snep.h" +#include "PN532_debug.h" + +int8_t SNEP::write(const uint8_t *buf, uint8_t len, uint16_t timeout) +{ + if (0 >= llcp.activate(timeout)) { + DMSG("failed to activate PN532 as a target\n"); + return -1; + } + + if (0 >= llcp.connect(timeout)) { + DMSG("failed to set up a connection\n"); + return -2; + } + + // response a success SNEP message + headerBuf[0] = SNEP_DEFAULT_VERSION; + headerBuf[1] = SNEP_REQUEST_PUT; + headerBuf[2] = 0; + headerBuf[3] = 0; + headerBuf[4] = 0; + headerBuf[5] = len; + if (0 >= llcp.write(headerBuf, 6, buf, len)) { + return -3; + } + + uint8_t rbuf[16]; + if (6 > llcp.read(rbuf, sizeof(rbuf))) { + return -4; + } + + // check SNEP version + if (SNEP_DEFAULT_VERSION != rbuf[0]) { + DMSG(F("The received SNEP message's major version is different, me: ")); + DMSG(SNEP_DEFAULT_VERSION); + DMSG(", their: "); + DMSG(rbuf[0]); + DMSG("\n"); + // To-do: send Unsupported Version response + return -4; + } + + // expect a put request + if (SNEP_RESPONSE_SUCCESS != rbuf[1]) { + DMSG("Expect a success response\n"); + return -4; + } + + llcp.disconnect(timeout); + + return 1; +} + +int16_t SNEP::read(uint8_t *buf, uint8_t len, uint16_t timeout) +{ + if (0 >= llcp.activate(timeout)) { + DMSG("failed to activate PN532 as a target\n"); + return -1; + } + + if (0 >= llcp.waitForConnection(timeout)) { + DMSG("failed to set up a connection\n"); + return -2; + } + + uint16_t status = llcp.read(buf, len); + if (6 > status) { + return -3; + } + + // check SNEP version + + // in case of platform specific bug, shift SNEP message for 4 bytes. + // tested on Nexus 5, Android 5.1 + if (SNEP_DEFAULT_VERSION != buf[0] && SNEP_DEFAULT_VERSION == buf[4]) { + for (uint8_t i = 0; i < len - 4; i++) { + buf[i] = buf[i + 4]; + } + } + + if (SNEP_DEFAULT_VERSION != buf[0]) { + DMSG(F("SNEP->read: The received SNEP message's major version is different, me: ")); + DMSG(SNEP_DEFAULT_VERSION); + DMSG(", their: "); + DMSG(buf[0]); + DMSG("\n"); + // To-do: send Unsupported Version response + return -4; + } + + // expect a put request + if (SNEP_REQUEST_PUT != buf[1]) { + DMSG("Expect a put request\n"); + return -4; + } + + // check message's length + uint32_t length = (buf[2] << 24) + (buf[3] << 16) + (buf[4] << 8) + buf[5]; + // length should not be more than 244 (header + body < 255, header = 6 + 3 + 2) + if (length > (status - 6)) { + DMSG("The SNEP message is too large: "); + DMSG_INT(length); + DMSG_INT(status - 6); + DMSG("\n"); + return -4; + } + for (uint8_t i = 0; i < length; i++) { + buf[i] = buf[i + 6]; + } + + // response a success SNEP message + headerBuf[0] = SNEP_DEFAULT_VERSION; + headerBuf[1] = SNEP_RESPONSE_SUCCESS; + headerBuf[2] = 0; + headerBuf[3] = 0; + headerBuf[4] = 0; + headerBuf[5] = 0; + llcp.write(headerBuf, 6); + + return length; +} diff --git a/snep.h b/snep.h new file mode 100644 index 0000000..1a51696 --- /dev/null +++ b/snep.h @@ -0,0 +1,49 @@ + + +#ifndef __SNEP_H__ +#define __SNEP_H__ + +#include "llcp.h" + +#define SNEP_DEFAULT_VERSION 0x10 // Major: 1, Minor: 0 + +#define SNEP_REQUEST_PUT 0x02 +#define SNEP_REQUEST_GET 0x01 + +#define SNEP_RESPONSE_SUCCESS 0x81 +#define SNEP_RESPONSE_REJECT 0xFF + +class SNEP { +public: + SNEP(PN532Interface &interface) : llcp(interface) { + headerBuf = llcp.getHeaderBuffer(&headerBufLen); + }; + + /** + * @brief write a SNEP packet, the packet should be less than (255 - 2 - 3) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @param timeout max time to wait, 0 means no timeout + * @return >0 success + * =0 timeout + * <0 failed + */ + int8_t write(const uint8_t *buf, uint8_t len, uint16_t timeout = 0); + + /** + * @brief read a SNEP packet, the packet will be less than (255 - 2 - 3) bytes + * @param buf the buffer to contain the packet + * @param len lenght of the buffer + * @param timeout max time to wait, 0 means no timeout + * @return >=0 length of the packet + * <0 failed + */ + int16_t read(uint8_t *buf, uint8_t len, uint16_t timeout = 0); + +private: + LLCP llcp; + uint8_t *headerBuf; + uint8_t headerBufLen; +}; + +#endif // __SNEP_H__