diff --git a/drivers/include/pn532.h b/drivers/include/pn532.h new file mode 100644 index 0000000000..32379058c5 --- /dev/null +++ b/drivers/include/pn532.h @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2016 TriaGnoSys GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_pn532 PN532 NFC Reader + * + * @{ + * @file + * @brief PN532 driver + * @author Víctor Ariño + */ + +#ifndef NFC_READER_INCLUDE_PN532_H_ +#define NFC_READER_INCLUDE_PN532_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mutex.h" +#include "periph/i2c.h" +#include "periph/spi.h" +#include "periph/gpio.h" +#include + +#if !defined(PN532_SUPPORT_I2C) && !defined(PN532_SUPPORT_SPI) +#error Please define PN532_SUPPORT_I2C and/or PN532_SUPPORT_SPI to enable \ + the functionality on this device +#endif + +/** + * @brief Data structure with the configuration parameters + */ +typedef struct { + union { +#if defined(PN532_SUPPORT_I2C) || DOXYGEN + i2c_t i2c; /**< I2C device */ +#endif +#if defined(PN532_SUPPORT_SPI) || DOXYGEN + spi_t spi; /**< SPI device */ +#endif + }; + gpio_t reset; /**< Reset pin */ + gpio_t irq; /**< Interrupt pin */ +#if defined(PN532_SUPPORT_SPI) || DOXYGEN + gpio_t nss; /**< Chip Select pin (only SPI) */ +#endif +} pn532_params_t; + +/** + * @brief Working mode of the PN532 + */ +typedef enum { + PN532_I2C, + PN532_SPI +} pn532_mode_t; + +/** + * @brief Device descriptor for the PN532 + */ +typedef struct { + const pn532_params_t *conf; /**< Configuration struct */ + pn532_mode_t mode; /**< Working mode (i2c, spi) */ + mutex_t trap; /**< Mutex to wait for chip response */ +} pn532_t; + +/** + * @brief Internal buffer size + * + * A small buffer size is enough for most applications, however if large NDEF + * files are to be written this size shall be increased. Otherwise the files + * can be written in chunks. + */ +#ifndef PN532_BUFFER_LEN +#define PN532_BUFFER_LEN (64) +#endif + +/** + * @brief Helpers to extract firmware information from word + * @{ + */ +#define PN532_IC_VERSION(fwver) ((fwver >> 24) & 0xff) +#define PN532_FW_VERSION(fwver) ((fwver >> 16) & 0xff) +#define PN532_FW_REVISION(fwver) ((fwver >> 8) & 0xff) +#define PN532_FW_FEATURES(fwver) ((fwver) & 0xff) +/** @} */ + +/** + * @brief Possible SAM configurations + */ +typedef enum { + PN532_SAM_NORMAL = 1, + PN532_SAM_VIRTUAL, + PN532_SAM_WIRED, + PN532_SAM_DUAL +} pn532_sam_conf_mode_t; + +/** + * @brief PN532 supported targets + */ +typedef enum { + PN532_BR_106_ISO_14443_A = 0, + PN532_BR_212_FELICA, + PN532_BR_424_FELICA, + PN532_BR_106_ISO_14443_B, + PN532_BR_106_JEWEL +} pn532_target_t; + +/** + * @brief ISO14443A Card types + */ +typedef enum { + ISO14443A_UNKNOWN, + ISO14443A_MIFARE, + ISO14443A_TYPE4 +} nfc_iso14443a_type_t; + +/** + * @brief ISO14443A tag description + */ +typedef struct { + char target; /**< Target */ + char auth; /**< Card has been authenticated. Do not modify manually */ + char id_len; /**< Length of the ID field */ + char sel_res; /**< SEL_RES */ + unsigned sns_res; /**< SNS_RES */ + nfc_iso14443a_type_t type; /**< Type of ISO14443A card */ + char id[8]; /**< Card ID (length given by id_len) */ +} nfc_iso14443a_t; + +/** + * @brief Mifare keys + */ +typedef enum { + PN532_MIFARE_KEY_A = 0x60, + PN532_MIFARE_KEY_B = 0x61 +} pn532_mifare_key_t; + +/** + * @brief Obtain Tag 4 data length from buffer + * + * This is useful in case the length has been read and one intents to read the + * data. + */ +#define PN532_ISO14443A_4_LEN_FROM_BUFFER(b) ((b[0] << 8) | b[1]) + +/** + * @brief Hard reset the chipset + * + * The chipset is reset by toggling the reset pins + * + * @param[in] dev target device + * + */ +void pn532_reset(pn532_t *dev); + +/** + * @brief Initialize the module and peripherals + * + * This is the first method to be called in order to interact with the pn532. + * It configures the GPIOs and the i2c/spi interface (depending on @p mode). + * + * @param[in] dev target device + * @param[in] params configuration parameters + * @param[in] mode initialization mode + * + * @return 0 on success + * @return <0 i2c/spi initialization error, the value is given + * by the i2c/spi init method. + */ +int pn532_init(pn532_t *dev, const pn532_params_t *params, pn532_mode_t mode); + + +#if defined(PN532_SUPPORT_I2C) || DOXYGEN +/** + * @brief Initialization of PN532 using i2c + * + * @see pn532_init for parameter and return value details + */ +static inline int pn532_init_i2c(pn532_t *dev, const pn532_params_t *params) +{ + return pn532_init(dev, params, PN532_I2C); +} +#endif + +#if defined(PN532_SUPPORT_SPI) || DOXYGEN +/** + * @brief Initialization of PN532 using spi + * + * @see pn532_init for parameter and return value details + */ +static inline int pn532_init_spi(pn532_t *dev, const pn532_params_t *params) +{ + return pn532_init(dev, params, PN532_SPI); +} +#endif + +/** + * @brief Get the firmware version of the pn532 + * + * The firmware version returned is a 4 byte long value: + * - ic version, + * - fw version, + * - fw revision + * - feature support + * + * @param[in] dev target device + * @param[out] fw_ver encoded firmware version + * + * @return 0 on success + */ +int pn532_fw_version(pn532_t *dev, uint32_t *fw_ver); + +/** + * @brief Read register of the pn532 + * + * Refer to the datasheet for a comprehensive list of registers and meanings. + * For SFR registers the high byte must be set to 0xff. + * + * http://www.nxp.com/documents/user_manual/141520.pdf + * + * @param[in] dev target device + * @param[out] out value of the register + * @param[in] addr address of the register to read + * + * @return 0 on success + */ +int pn532_read_reg(pn532_t *dev, char *out, unsigned addr); + +/** + * @brief Write register of the pn532 + * + * Refer to the datasheet for a comprehensive list of registers and meanings. + * + * http://www.nxp.com/documents/user_manual/141520.pdf + * + * @param[in] dev target device + * @param[in] addr address of the register to read + * @param[in] val value to write in the register + * + * @return 0 on success + */ +int pn532_write_reg(pn532_t *dev, unsigned addr, char val); + +/** + * @brief Set new settings for the Security Access Module + * + * @param[in] dev target device + * @param[in] mode new mode for the SAM + * @param[in] timeout timeout for Virtual Card mode with LSB of 50ms. + * (0 = infinite and 0xFF = 12.75s) + * + * @return 0 on success + */ +int pn532_sam_configuration(pn532_t *dev, pn532_sam_conf_mode_t mode, unsigned timeout); + +/** + * @brief Get one ISO14443-A passive target + * + * This method blocks until a target is detected. + * + * @param[in] dev target device + * @param[out] out target to be stored + * @param[in] max_retries maximum number of attempts to activate a target + * (0xff blocks indefinitely) + * + * @return 0 on success + * @return -1 when no card detected (if non blocking) + */ +int pn532_get_passive_iso14443a(pn532_t *dev, nfc_iso14443a_t *out, unsigned max_retries); + +/** + * @brief Authenticate a Mifare classic card + * + * This operation must be done before reading or writing the segment. + * + * @param[in] dev target device + * @param[in] card card to use + * @param[in] keyid which key to use + * @param[in] key buffer containing the key + * @param[in] block which block to authenticate + * + * @return 0 on success + */ +int pn532_mifareclassic_authenticate(pn532_t *dev, nfc_iso14443a_t *card, + pn532_mifare_key_t keyid, char *key, unsigned block); + +/** + * @brief Read a block of a Mifare classic card + * + * The block size is 16 bytes and it must be authenticated before read. + * + * @param[in] dev target device + * @param[out] odata buffer where to store the data + * @param[in] card card to use + * @param[in] block which block to read + * + * @return 0 on success + */ +int pn532_mifareclassic_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, unsigned block); + +/** + * @brief Write a block of a Mifare classic card + * + * The block size is 16 bytes and it must be authenticated before written. + * + * @param[in] dev target device + * @param[in] idata buffer containing the data to write + * @param[in] card card to use + * @param[in] block which block to write to + * + * @return 0 on success + */ +int pn532_mifareclassic_write(pn532_t *dev, char *idata, nfc_iso14443a_t *card, unsigned block); + +/** + * @brief Read a block of a Mifare Ultralight card + * + * The block size is 32 bytes and it must be authenticated before read. + * + * @param[in] dev target device + * @param[out] odata buffer where to store the data + * @param[in] card card to use + * @param[in] page which block to read + * + * @return 0 on success + */ +int pn532_mifareulight_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, unsigned page); + +/** + * @brief Activate the NDEF file of a ISO14443-A Type 4 tag + * + * @param[in] dev target device + * @param[in] card card to activate + * + * @return 0 on success + */ +int pn532_iso14443a_4_activate(pn532_t *dev, nfc_iso14443a_t *card); + +/** + * @brief Read data from the NDEF file of a ISO14443-A Type 4 tag + * + * The first two bytes of an NDEF file are the length of the data. Afterwards, + * at offset 0x02 starts the data itself. If one tries to read further than the + * end of the data no data is returned. + * + * @param[in] dev target device + * @param[out] odata buffer where to store the data + * @param[in] card card to activate + * @param[in] offset offset where to start reading + * @param[in] len length to read + * + * @return 0 on success + */ +int pn532_iso14443a_4_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, unsigned offset, + char len); + +/** + * @brief Deselect a previously selected passive card + * + * @param[in] dev target device + * @param[in] target_id id of the target to deselect (0x00 for all) + */ +void pn532_deselect_passive(pn532_t *dev, unsigned target_id); + +/** + * @brief Release an active passive card + * + * @param[in] dev target device + * @param[in] target_id id of the target to release (0x00 for all) + */ +void pn532_release_passive(pn532_t *dev, unsigned target_id); + +#ifdef __cplusplus +} +#endif + +#endif /* NFC_READER_INCLUDE_PN532_H_ */ +/** @} */ diff --git a/drivers/pn532/Makefile b/drivers/pn532/Makefile new file mode 100644 index 0000000000..be60b9e765 --- /dev/null +++ b/drivers/pn532/Makefile @@ -0,0 +1,3 @@ +MODULE = pn532 + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/pn532/pn532.c b/drivers/pn532/pn532.c new file mode 100644 index 0000000000..7ed31c9f0f --- /dev/null +++ b/drivers/pn532/pn532.c @@ -0,0 +1,703 @@ +/* + * Copyright (C) 2016 TriaGnoSys GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_pn532 + * + * @{ + * @file + * @brief PN532 driver + * + * @author Víctor Ariño + * @} + */ + +#include +#include + +#include "assert.h" +#include "xtimer.h" +#include "mutex.h" +#include "pn532.h" +#include "periph/gpio.h" +#include "periph/i2c.h" +#include "periph/spi.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define PN532_I2C_ADDRESS (0x24) + +/* Commands */ +#define CMD_FIRMWARE_VERSION (0x02) +#define CMD_READ_REG (0x06) +#define CMD_WRITE_REG (0x08) +#define CMD_READ_GPIO (0x0c) +#define CMD_WRITE_GPIO (0x0E) +#define CMD_SAM_CONFIG (0x14) +#define CMD_RF_CONFIG (0x32) +#define CMD_DATA_EXCHANGE (0x40) +#define CMD_DESELECT (0x44) +#define CMD_LIST_PASSIVE (0x4a) +#define CMD_RELEASE (0x52) + +/* Mifare specific commands */ +#define MIFARE_CMD_READ (0x30) +#define MIFARE_CMD_WRITE (0xA0) + +/* RF register settings */ +#define RF_CONFIG_MAX_RETRIES (0x05) + +/* Buffer operations */ +#define BUFF_CMD_START (6) +#define BUFF_DATA_START (BUFF_CMD_START + 1) +#define RAPDU_DATA_BEGIN (1) +#define RAPDU_MAX_DATA_LEN (PN532_BUFFER_LEN - BUFF_DATA_START - 5) +#define CAPDU_MAX_DATA_LEN (PN532_BUFFER_LEN - BUFF_DATA_START - 1) + +/* Constants and magic numbers */ +#define MIFARE_CLASSIC_BLOCK_SIZE (16) +#define RESET_TOGGLE_SLEEP (400000) +#define RESET_BACKOFF (10000) +#define HOST_TO_PN532 (0xD4) +#define PN532_TO_HOST (0xD5) +#define SPI_DATA_WRITE (0x80) +#define SPI_STATUS_READING (0x40) +#define SPI_DATA_READ (0xC0) +#define SPI_WRITE_DELAY_US (2000) + +/* Length for passive listings */ +#define LIST_PASSIVE_LEN_14443(num) (num * 20) + +#if ENABLE_DEBUG +#define PRINTBUFF printbuff +static void printbuff(char *buff, unsigned len) +{ + while (len) { + len--; + printf("%02x ", *buff++); + } + printf("\n"); +} +#else +#define PRINTBUFF(...) +#endif + +static void _nfc_event(void *dev) +{ + mutex_unlock(&((pn532_t *)dev)->trap); +} + +void pn532_reset(pn532_t *dev) +{ + assert(dev != NULL); + + DEBUG("pn532: reset\n"); + gpio_clear(dev->conf->reset); + xtimer_usleep(RESET_TOGGLE_SLEEP); + gpio_set(dev->conf->reset); + xtimer_usleep(RESET_BACKOFF); +} + +int pn532_init(pn532_t *dev, const pn532_params_t *params, pn532_mode_t mode) +{ + assert(dev != NULL); + + int ret = -1; + + dev->conf = params; + + gpio_init_int(dev->conf->irq, GPIO_IN_PU, GPIO_FALLING, + _nfc_event, (void *)dev); + + gpio_init(dev->conf->reset, GPIO_OUT); + gpio_set(dev->conf->reset); + dev->mode = mode; + if (mode == PN532_I2C) { +#ifdef PN532_SUPPORT_I2C + ret = i2c_init_master(dev->conf->i2c, I2C_SPEED_FAST); +#endif + } + else { +#ifdef PN532_SUPPORT_SPI + ret = spi_init_master(dev->conf->spi, SPI_CONF_FIRST_RISING, + SPI_SPEED_1MHZ); + gpio_init(dev->conf->nss, GPIO_OUT); + gpio_set(dev->conf->nss); +#endif + } + + /* cppcheck-suppress knownConditionTrueFalse + * (reason: variable set when PN532_SUPPORT_{I2C,SPI} defined) */ + if (ret == 0) { + pn532_reset(dev); + } + + mutex_init(&dev->trap); + mutex_lock(&dev->trap); + + return ret; +} + +static unsigned char chksum(char *b, unsigned len) +{ + unsigned char c = 0x00; + + while (len--) { + c -= *b++; + } + return c; +} + +#ifdef PN532_SUPPORT_SPI +static void reverse(char *buff, unsigned len) +{ + while (len--) { + buff[len] = (buff[len] & 0xF0) >> 4 | (buff[len] & 0x0F) << 4; + buff[len] = (buff[len] & 0xCC) >> 2 | (buff[len] & 0x33) << 2; + buff[len] = (buff[len] & 0xAA) >> 1 | (buff[len] & 0x55) << 1; + } +} +#endif + +static int _write(pn532_t *dev, char *buff, unsigned len) +{ + int ret = -1; + + (void)buff; + (void)len; + + if (dev->mode == PN532_I2C) { +#ifdef PN532_SUPPORT_I2C + i2c_acquire(dev->conf->i2c); + ret = i2c_write_bytes(dev->conf->i2c, PN532_I2C_ADDRESS, buff, len); + i2c_release(dev->conf->i2c); +#endif + } + else { +#ifdef PN532_SUPPORT_SPI + spi_acquire(dev->conf->spi); + gpio_clear(dev->conf->nss); + xtimer_usleep(SPI_WRITE_DELAY_US); + reverse(buff, len); + spi_transfer_byte(dev->conf->spi, SPI_DATA_WRITE, NULL); + ret = spi_transfer_bytes(dev->conf->spi, buff, NULL, len); + gpio_set(dev->conf->nss); + spi_release(dev->conf->spi); +#endif + } + DEBUG("pn532: -> "); + PRINTBUFF(buff, len); + return ret; +} + +static int _read(pn532_t *dev, char *buff, unsigned len) +{ + int ret = -1; + + (void)buff; + (void)len; + + if (dev->mode == PN532_I2C) { +#ifdef PN532_SUPPORT_I2C + i2c_acquire(dev->conf->i2c); + /* len+1 for RDY after read is accepted */ + ret = i2c_read_bytes(dev->conf->i2c, PN532_I2C_ADDRESS, buff, len + 1); + i2c_release(dev->conf->i2c); +#endif + } + else { +#ifdef PN532_SUPPORT_SPI + spi_acquire(dev->conf->spi); + gpio_clear(dev->conf->nss); + spi_transfer_byte(dev->conf->spi, SPI_DATA_READ, NULL); + ret = spi_transfer_bytes(dev->conf->spi, 0x00, &buff[1], len); + gpio_set(dev->conf->nss); + spi_release(dev->conf->spi); + if (ret >= 0) { + buff[0] = 0x80; + reverse(buff, ret); + ret += 1; + } +#endif + } + DEBUG("pn532: <- "); + PRINTBUFF(buff, len); + return ret; +} + +static int send_cmd(pn532_t *dev, char *buff, unsigned len) +{ + unsigned pos, checksum; + + buff[0] = 0x00; + buff[1] = 0x00; + buff[2] = 0xFF; + + len += 1; + if (len < 0xff) { + buff[3] = (char)len; + buff[4] = 0x00 - buff[3]; + pos = 5; + + } + else { + buff[3] = 0xff; + buff[4] = 0xff; + buff[5] = (len >> 8) & 0xff; + buff[6] = (len) & 0xff; + buff[7] = 0x00 - buff[5] - buff[6]; + pos = 8; + } + + buff[pos] = HOST_TO_PN532; + checksum = chksum(&buff[pos], len); + + len += pos; + buff[len++] = checksum; + buff[len++] = 0x00; + + return _write(dev, buff, len); +} + +static void wait_ready(pn532_t *dev) +{ + mutex_lock(&dev->trap); +} + +/* Returns >0 payload len (or <0 received len but not as expected) */ +static int read_command(pn532_t *dev, char *buff, unsigned len, int expected_cmd) +{ + int r; + unsigned j, fi, lp, lc; + + /* apply framing overhead */ + len += 8; + if (len >= 0xff) { + len += 3; + } + + r = _read(dev, buff, len); + + /* Validate frame structure and CRCs + * + * Note that all offsets are shifted by one since the first byte is always + * 0x01. */ + if ((r < len) || (buff[1] != 0x00) || (buff[2] != 0x00) || (buff[3] != 0xFF)) { + return -r; + } + + if (buff[4] == 0xff && buff[5] == 0xff) { + /* extended frame */ + lp = buff[6] << 8 | buff[7]; + lc = (buff[6] + buff[7] + buff[8]) & 0xff; + fi = 9; + + } + else { + /* normal frame */ + lp = buff[4]; + lc = (buff[4] + buff[5]) & 0xff; + fi = 6; + } + + if (lc != 0 || lp >= 265 || buff[fi] != PN532_TO_HOST) { + return -r; + } + + if (lp == 0) { + return -r; + } + + if (chksum(&buff[fi], lp) != buff[fi + lp]) { + return -r; + } + + if (buff[fi + 1] != expected_cmd) { + return -r; + } + + /* Move the meaningful data to the beginning of the buffer */ + /* start copying after command byte */ + for (j = 0, fi += 2, lp -= 2; j < lp; fi++, j++) { + buff[j] = buff[fi]; + } + + DEBUG("pn532: in cmd "); + PRINTBUFF(buff, lp); + + return lp; +} + +/* Returns 0 if OK, <0 otherwise */ +static int send_check_ack(pn532_t *dev, char *buff, unsigned len) +{ + if (send_cmd(dev, buff, len) > 0) { + static char ack[] = { 0x00, 0x00, 0xff, 0x00, 0xff, 0x00 }; + + wait_ready(dev); + if (_read(dev, buff, sizeof(ack)) != sizeof(ack) + 1) { + return -2; + } + + if (memcmp(&buff[1], ack, sizeof(ack)) != 0) { + return -3; + } + + wait_ready(dev); + return 0; + } + + return -1; +} + +/* sendl: send length, recvl: receive payload length */ +static int send_rcv(pn532_t *dev, char *buff, unsigned sendl, unsigned recvl) +{ + assert(dev != NULL); + + int expected_cmd = buff[BUFF_CMD_START] + 1; + + if (send_check_ack(dev, buff, sendl + 1)) { + return 0; + } + + recvl += 1; /* cmd response */ + return read_command(dev, buff, recvl, expected_cmd); +} + +int pn532_fw_version(pn532_t *dev, uint32_t *fw_ver) +{ + unsigned ret = -1; + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START] = CMD_FIRMWARE_VERSION; + + if (send_rcv(dev, buff, 0, 4) == 4) { + *fw_ver = (buff[0] << 24); /* ic version */ + *fw_ver += (buff[1] << 16); /* fw ver */ + *fw_ver += (buff[2] << 8); /* fw rev */ + *fw_ver += (buff[3]); /* feature support */ + ret = 0; + } + + return ret; +} + +int pn532_read_reg(pn532_t *dev, char *out, unsigned addr) +{ + int ret = -1; + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_READ_REG; + buff[BUFF_DATA_START ] = (addr >> 8) & 0xff; + buff[BUFF_DATA_START + 1] = addr & 0xff; + + if (send_rcv(dev, buff, 2, 1) == 1) { + *out = buff[0]; + ret = 0; + } + + return ret; +} + +int pn532_write_reg(pn532_t *dev, unsigned addr, char val) +{ + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_WRITE_REG; + buff[BUFF_DATA_START ] = (addr >> 8) & 0xff; + buff[BUFF_DATA_START + 1] = addr & 0xff; + buff[BUFF_DATA_START + 2] = val; + + return send_rcv(dev, buff, 3, 0); +} + +static int _rf_configure(pn532_t *dev, char *buff, unsigned cfg_item, char *config, + unsigned cfg_len) +{ + buff[BUFF_CMD_START ] = CMD_RF_CONFIG; + buff[BUFF_DATA_START] = cfg_item; + for (int i = 1; i <= cfg_len; i++) { + buff[BUFF_DATA_START + i] = *config++; + } + + return send_rcv(dev, buff, cfg_len + 1, 0); +} + +static int _set_act_retries(pn532_t *dev, char *buff, unsigned max_retries) +{ + char rtrcfg[] = { 0xff, 0x01, max_retries & 0xff }; + + return _rf_configure(dev, buff, RF_CONFIG_MAX_RETRIES, rtrcfg, sizeof(rtrcfg)); +} + +int pn532_sam_configuration(pn532_t *dev, pn532_sam_conf_mode_t mode, unsigned timeout) +{ + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_SAM_CONFIG; + buff[BUFF_DATA_START ] = (char)mode; + buff[BUFF_DATA_START + 1] = (char)(timeout / 50); + buff[BUFF_DATA_START + 2] = 0x01; + + return send_rcv(dev, buff, 3, 0); +} + +static int _list_passive_targets(pn532_t *dev, char *buff, pn532_target_t target, + unsigned max, unsigned recvl) +{ + buff[BUFF_CMD_START] = CMD_LIST_PASSIVE; + buff[BUFF_DATA_START] = (char) max; + buff[BUFF_DATA_START + 1] = (char)target; + + /* requested len depends on expected target num and type */ + return send_rcv(dev, buff, 2, recvl); +} + +int pn532_get_passive_iso14443a(pn532_t *dev, nfc_iso14443a_t *out, + unsigned max_retries) +{ + int ret = -1; + char buff[PN532_BUFFER_LEN]; + + if (_set_act_retries(dev, buff, max_retries) == 0) { + ret = _list_passive_targets(dev, buff, PN532_BR_106_ISO_14443_A, 1, + LIST_PASSIVE_LEN_14443(1)); + } + + if (ret > 0 && buff[0] > 0) { + out->target = buff[1]; + out->sns_res = (buff[2] << 8) | buff[3]; + out->sel_res = buff[4]; + out->id_len = buff[5]; + out->type = ISO14443A_UNKNOWN; + + for (int i = 0; i < out->id_len; i++) { + out->id[i] = buff[6 + i]; + } + + /* try to find out the type */ + if (out->id_len == 4) { + out->type = ISO14443A_MIFARE; + } + else if (out->id_len == 7) { + /* In the case of type 4, the first byte of RATS is the length + * of RATS including the length itself (6+7) */ + if (buff[13] == ret - 13) { + out->type = ISO14443A_TYPE4; + } + } + ret = 0; + } + else { + ret = -1; + } + + return ret; +} + +void pn532_deselect_passive(pn532_t *dev, unsigned target_id) +{ + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_DESELECT; + buff[BUFF_DATA_START] = target_id; + + send_rcv(dev, buff, 1, 1); +} + +void pn532_release_passive(pn532_t *dev, unsigned target_id) +{ + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_RELEASE; + buff[BUFF_DATA_START] = target_id; + + send_rcv(dev, buff, 1, 1); +} + +int pn532_mifareclassic_authenticate(pn532_t *dev, nfc_iso14443a_t *card, + pn532_mifare_key_t keyid, char *key, unsigned block) +{ + int ret = -1; + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_DATA_EXCHANGE; + buff[BUFF_DATA_START ] = card->target; + buff[BUFF_DATA_START + 1] = keyid; + buff[BUFF_DATA_START + 2] = block; /* current block */ + + /* + * The card ID directly follows the key in the buffer + * The key consists of 6 bytes and starts at offset 3 + */ + for (int i = 0; i < 6; i++) { + buff[BUFF_DATA_START + 3 + i] = key[i]; + } + + for (int i = 0; i < card->id_len; i++) { + buff[BUFF_DATA_START + 9 + i] = card->id[i]; + } + + ret = send_rcv(dev, buff, 9 + card->id_len, 1); + if (ret == 1) { + ret = buff[0]; + card->auth = 1; + } + + return ret; +} + +int pn532_mifareclassic_write(pn532_t *dev, char *idata, nfc_iso14443a_t *card, + unsigned block) +{ + int ret = -1; + char buff[PN532_BUFFER_LEN]; + + if (card->auth) { + + buff[BUFF_CMD_START ] = CMD_DATA_EXCHANGE; + buff[BUFF_DATA_START ] = card->target; + buff[BUFF_DATA_START + 1] = MIFARE_CMD_WRITE; + buff[BUFF_DATA_START + 2] = block; /* current block */ + memcpy(&buff[BUFF_DATA_START + 3], idata, MIFARE_CLASSIC_BLOCK_SIZE); + + if (send_rcv(dev, buff, 19, 1) == 1) { + ret = buff[0]; + } + + } + return ret; +} + +static int pn532_mifare_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, + unsigned block, unsigned len) +{ + int ret = -1; + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_DATA_EXCHANGE; + buff[BUFF_DATA_START ] = card->target; + buff[BUFF_DATA_START + 1] = MIFARE_CMD_READ; + buff[BUFF_DATA_START + 2] = block; /* current block */ + + if (send_rcv(dev, buff, 3, len + 1) == len + 1) { + memcpy(odata, &buff[1], len); + ret = 0; + } + + return ret; +} + +int pn532_mifareclassic_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, + unsigned block) +{ + if (card->auth) { + return pn532_mifare_read(dev, odata, card, block, MIFARE_CLASSIC_BLOCK_SIZE); + } + else { + return -1; + } +} + +int pn532_mifareulight_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, + unsigned page) +{ + return pn532_mifare_read(dev, odata, card, page, 32); +} + +static int send_rcv_apdu(pn532_t *dev, char *buff, unsigned slen, unsigned rlen) +{ + int ret; + + rlen += 3; + if (rlen >= RAPDU_MAX_DATA_LEN) { + return -1; + + } + else if (slen >= CAPDU_MAX_DATA_LEN) { + return -1; + } + + ret = send_rcv(dev, buff, slen, rlen); + if (ret == rlen && buff[0] == 0x00) { + ret = (buff[rlen - 2] << 8) | buff[rlen - 1]; + if (ret == 0x9000) { + ret = 0; + } + } + + return ret; +} + +int pn532_iso14443a_4_activate(pn532_t *dev, nfc_iso14443a_t *card) +{ + int ret; + char buff[PN532_BUFFER_LEN]; + + /* select app ndef tag */ + buff[BUFF_CMD_START ] = CMD_DATA_EXCHANGE; + buff[BUFF_DATA_START ] = card->target; + buff[BUFF_DATA_START + 1] = 0x00; + buff[BUFF_DATA_START + 2] = 0xa4; + buff[BUFF_DATA_START + 3] = 0x04; + buff[BUFF_DATA_START + 4] = 0x00; + buff[BUFF_DATA_START + 5] = 0x07; + buff[BUFF_DATA_START + 6] = 0xD2; + buff[BUFF_DATA_START + 7] = 0x76; + buff[BUFF_DATA_START + 8] = 0x00; + buff[BUFF_DATA_START + 9] = 0x00; + buff[BUFF_DATA_START + 10] = 0x85; + buff[BUFF_DATA_START + 11] = 0x01; + buff[BUFF_DATA_START + 12] = 0x01; + buff[BUFF_DATA_START + 13] = 0x00; + + DEBUG("pn532: select app\n"); + ret = send_rcv_apdu(dev, buff, 14, 0); + + /* select ndef file */ + buff[BUFF_CMD_START ] = CMD_DATA_EXCHANGE; + buff[BUFF_DATA_START ] = card->target; + buff[BUFF_DATA_START + 1] = 0x00; + buff[BUFF_DATA_START + 2] = 0xa4; + buff[BUFF_DATA_START + 3] = 0x00; + buff[BUFF_DATA_START + 4] = 0x0c; + buff[BUFF_DATA_START + 5] = 0x02; + buff[BUFF_DATA_START + 6] = 0x00; + buff[BUFF_DATA_START + 7] = 0x01; + + if (ret == 0) { + DEBUG("pn532: select file\n"); + ret = send_rcv_apdu(dev, buff, 8, 0); + } + + return ret; +} + +int pn532_iso14443a_4_read(pn532_t *dev, char *odata, nfc_iso14443a_t *card, + unsigned offset, char len) +{ + int ret; + char buff[PN532_BUFFER_LEN]; + + buff[BUFF_CMD_START ] = CMD_DATA_EXCHANGE; + buff[BUFF_DATA_START ] = card->target; + buff[BUFF_DATA_START + 1] = 0x00; + buff[BUFF_DATA_START + 2] = 0xb0; + buff[BUFF_DATA_START + 3] = (offset >> 8) & 0xff; + buff[BUFF_DATA_START + 4] = offset & 0xff; + buff[BUFF_DATA_START + 5] = len; + + ret = send_rcv_apdu(dev, buff, 6, len); + if (ret == 0) { + memcpy(odata, &buff[RAPDU_DATA_BEGIN], len); + } + + return ret; +} diff --git a/tests/driver_pn532/Makefile b/tests/driver_pn532/Makefile new file mode 100644 index 0000000000..a3d1995b8b --- /dev/null +++ b/tests/driver_pn532/Makefile @@ -0,0 +1,22 @@ +APPLICATION = driver_pn532 +include ../Makefile.tests_common + +FEATURES_REQUIRED = periph_i2c periph_gpio + +USEMODULE += xtimer +USEMODULE += pn532 + +# set default device parameters in case they are undefined +TEST_PN532_I2C ?= I2C_DEV\(0\) +TEST_PN532_RESET ?= GPIO_PIN\(0,0\) +TEST_PN532_IRQ ?= GPIO_PIN\(0,1\) + +# export parameters +CFLAGS += -DTEST_PN532_I2C=$(TEST_PN532_I2C) +CFLAGS += -DTEST_PN532_RESET=$(TEST_PN532_RESET) +CFLAGS += -DTEST_PN532_IRQ=$(TEST_PN532_IRQ) +CFLAGS += -DPN532_SUPPORT_I2C + +CFLAGS += -I$(CURDIR) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_pn532/README.md b/tests/driver_pn532/README.md new file mode 100644 index 0000000000..f44fb638cc --- /dev/null +++ b/tests/driver_pn532/README.md @@ -0,0 +1,7 @@ +# About +This is a manual test application for the PN532 NFC reader driver. + +# Usage +This test application initializes the PN532 and waits for a card to be placed +under the reader. When the card is detected, the content of the card is +printed. \ No newline at end of file diff --git a/tests/driver_pn532/main.c b/tests/driver_pn532/main.c new file mode 100644 index 0000000000..4ca9862a76 --- /dev/null +++ b/tests/driver_pn532/main.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 TriaGnoSys GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the PN532 NFC reader + * + * @author Víctor Ariño + * + * @} + */ + +#include "board.h" + +#include "pn532.h" +#include "pn532_params.h" +#include "xtimer.h" + +#define LOG_LEVEL LOG_INFO +#include "log.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static void printbuff(char *buff, unsigned len) +{ + while (len) { + len--; + printf("%02x ", *buff++); + } + puts(""); +} + +int main(void) +{ + static char data[16]; + static nfc_iso14443a_t card; + static pn532_t pn532; + unsigned len; + + int ret = pn532_init_i2c(&pn532, &pn532_conf[0]); + + if (ret != 0) { + LOG_INFO("init error %d\n", ret); + } + + xtimer_usleep(200000); + LOG_INFO("awake\n"); + + uint32_t fwver; + pn532_fw_version(&pn532, &fwver); + LOG_INFO("ver %d.%d\n", (unsigned)PN532_FW_VERSION(fwver), (unsigned)PN532_FW_REVISION(fwver)); + + + ret = pn532_sam_configuration(&pn532, PN532_SAM_NORMAL, 1000); + LOG_INFO("set sam %d\n", ret); + + while (1) { + /* Delay not to be always polling the interface */ + xtimer_usleep(250000UL); + + ret = pn532_get_passive_iso14443a(&pn532, &card, 0x50); + if (ret < 0) { + LOG_DEBUG("no card\n"); + continue; + } + + if (card.type == ISO14443A_TYPE4) { + if (pn532_iso14443a_4_activate(&pn532, &card) != 0) { + LOG_ERROR("act\n"); + continue; + + } + else if (pn532_iso14443a_4_read(&pn532, data, &card, 0x00, 2) != 0) { + LOG_ERROR("len\n"); + continue; + } + + len = PN532_ISO14443A_4_LEN_FROM_BUFFER(data); + len = MIN(len, sizeof(data)); + + if (pn532_iso14443a_4_read(&pn532, data, &card, 0x02, len) != 0) { + LOG_ERROR("read\n"); + continue; + } + + LOG_INFO("dumping card contents (%d bytes)\n", len); + printbuff(data, len); + pn532_release_passive(&pn532, card.target); + + } + else if (card.type == ISO14443A_MIFARE) { + char key[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + char data[32]; + + for (int i = 0; i < 64; i++) { + LOG_INFO("sector %02d, block %02d | ", i / 4, i); + if ((i & 0x03) == 0) { + ret = pn532_mifareclassic_authenticate(&pn532, &card, + PN532_MIFARE_KEY_A, key, i); + if (ret != 0) { + LOG_ERROR("auth\n"); + break; + } + } + + ret = pn532_mifareclassic_read(&pn532, data, &card, i); + if (ret == 0) { + printbuff(data, 16); + } + else { + LOG_ERROR("read\n"); + break; + } + } + + } + else { + LOG_ERROR("unknown\n"); + } + } + + return 0; +} diff --git a/tests/driver_pn532/pn532_params.h b/tests/driver_pn532/pn532_params.h new file mode 100644 index 0000000000..590a1683c3 --- /dev/null +++ b/tests/driver_pn532/pn532_params.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 TriaGnoSys GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief PN532 board configuration example + * + * @author Víctor Ariño + */ + +#ifndef PN532_PARAMS_H +#define PN532_PARAMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +static const pn532_params_t pn532_conf[] = { + { + .i2c = TEST_PN532_I2C, + .reset = TEST_PN532_RESET, + .irq = TEST_PN532_IRQ, + }, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* PN532_PARAMS_H */ +/** @} */