From f599994fc0754864e8762e4cc4c889a3f6e3724f Mon Sep 17 00:00:00 2001 From: Hendrik van Essen Date: Thu, 12 Aug 2021 22:29:34 +0200 Subject: [PATCH] drivers/mfrc522: add new driver --- drivers/include/mfrc522.h | 856 ++++++++ drivers/mfrc522/Kconfig | 19 + drivers/mfrc522/Makefile | 1 + drivers/mfrc522/Makefile.dep | 5 + drivers/mfrc522/Makefile.include | 2 + drivers/mfrc522/include/mfrc522_params.h | 85 + drivers/mfrc522/include/mfrc522_regs.h | 1168 +++++++++++ drivers/mfrc522/mfrc522.c | 2319 ++++++++++++++++++++++ 8 files changed, 4455 insertions(+) create mode 100644 drivers/include/mfrc522.h create mode 100644 drivers/mfrc522/Kconfig create mode 100644 drivers/mfrc522/Makefile create mode 100644 drivers/mfrc522/Makefile.dep create mode 100644 drivers/mfrc522/Makefile.include create mode 100644 drivers/mfrc522/include/mfrc522_params.h create mode 100644 drivers/mfrc522/include/mfrc522_regs.h create mode 100644 drivers/mfrc522/mfrc522.c diff --git a/drivers/include/mfrc522.h b/drivers/include/mfrc522.h new file mode 100644 index 000000000000..c39b214fac3a --- /dev/null +++ b/drivers/include/mfrc522.h @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * 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_mfrc522 MFRC522 RFID controller + * @ingroup drivers_actuators + * @brief Device driver for the NXP MFRC522 RFID controller + * + * With this driver MIFARE and NTAG tags/keys can be read and written contactless. + * + * Supported features: + * - Communication (Crypto1) with MIFARE Classic (1k, 4k, Mini) + * - Communication (Crypto1) with MIFARE Classic compatible PICCs + * - Firmware self check of MFRC522 + * - Set the UID, write to sector 0, and unbrick Chinese UID changeable MIFARE cards + * + * Partially supported features: + * - Communication with MIFARE Ultralight + * - Other PICCs (Ntag216) + * + * Data sheet available here: https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf + * + * This driver is a port of a driver originally written for Arduino, you can find + * the original here: https://github.com/miguelbalboa/rfid (currently based on + * commit 0a568b45baf1852883630e90ea125786e88f5322) + * + * @{ + * + * @author Hendrik van Essen + * @file + */ + +#ifndef MFRC522_H +#define MFRC522_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "stdbool.h" + +#include "periph/spi.h" + +#include "mfrc522_regs.h" + +/** + * @brief Maximum size for uid byte array + */ +#define MFRC522_UID_MAX_SIZE 10 + +/** + * @brief The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK + */ +#define MFRC522_MF_ACK 0xA + +/** + * @brief A Mifare Crypto1 key is 6 bytes + */ +#define MFRC522_MF_KEY_SIZE 6 + +/** + * @brief MFRC522 device initialization parameters + */ +typedef struct { + spi_t spi_dev; /**< SPI bus the controller is connected to */ + spi_clk_t spi_clk; /**< SPI clock speed */ + gpio_t sck_pin; /**< SCK pin */ + gpio_t miso_pin; /**< MISO pin */ + gpio_t mosi_pin; /**< MOSI pin */ + gpio_t cs_pin; /**< Slave select pin */ + gpio_t rst_pin; /**< Reset pin */ +} mfrc522_params_t; + +/** + * @brief MFRC522 device data structure type + */ +typedef struct { + mfrc522_params_t params; /**< Device initialization parameters */ +} mfrc522_t; + +/** + * @brief A struct used for passing the UID of a PICC + */ +typedef struct { + uint8_t size; /**< Number of bytes in the UID. 4, 7 or 10 */ + uint8_t uid_byte[MFRC522_UID_MAX_SIZE]; /**< UID */ + uint8_t sak; /**< The SAK (Select acknowledge) byte returned from + the PICC after successful selection */ +} mfrc522_uid_t; + +/** + * @brief A struct used for passing a MIFARE Crypto1 key + */ +typedef struct { + uint8_t key_byte[MFRC522_MF_KEY_SIZE]; /**< Crypto1 key bytes */ +} mfrc522_mifare_key_t; + +/** + * @brief MFRC522 status codes + */ +typedef enum { + MFRC522_STATUS_OK, /**< Success */ + MFRC522_STATUS_ERROR, /**< Error in communication */ + MFRC522_STATUS_COLLISION, /**< Collision detected */ + MFRC522_STATUS_TIMEOUT, /**< Timeout in communication */ + MFRC522_STATUS_NO_ROOM, /**< A buffer is not big enough */ + MFRC522_STATUS_INTERNAL_ERROR, /**< Internal error in the code */ + MFRC522_STATUS_INVALID, /**< Invalid argument */ + MFRC522_STATUS_CRC_WRONG, /**< The CRC_A does not match */ + MFRC522_STATUS_MIFARE_NACK, /**< A MIFARE PICC responded with NAK */ + MFRC522_STATUS_UNKNOWN /**< Status used for mfrc522_status_code_names */ +} mfrc522_status_code_t; + +/** + * @brief PICC types + */ +typedef enum { + MFRC522_PICC_TYPE_ISO_14443_4, /**< PICC compliant with ISO/IEC 14443-4 */ + MFRC522_PICC_TYPE_ISO_18092, /**< PICC compliant with ISO/IEC 18092 (NFC) */ + MFRC522_PICC_TYPE_MIFARE_MINI, /**< MIFARE Classic protocol, 320 bytes */ + MFRC522_PICC_TYPE_MIFARE_1K, /**< MIFARE Classic protocol, 1KB */ + MFRC522_PICC_TYPE_MIFARE_4K, /**< MIFARE Classic protocol, 4KB */ + MFRC522_PICC_TYPE_MIFARE_UL, /**< MIFARE Ultralight or Ultralight C */ + MFRC522_PICC_TYPE_MIFARE_PLUS, /**< MIFARE Plus */ + MFRC522_PICC_TYPE_MIFARE_DESFIRE, /**< MIFARE DESFire */ + MFRC522_PICC_TYPE_TNP3XXX, /**< Only mentioned in NXP AN 10833 MIFARE + Type Identification Procedure */ + MFRC522_PICC_TYPE_NOT_COMPLETE, /**< SAK indicates UID is not complete */ + MFRC522_PICC_TYPE_UNKNOWN /**< unknown type */ +} mfrc522_picc_type_t; + +/** + * @brief Sets the bits given in mask in register reg + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] reg Register to write to + * @param[in] mask Bitmask with the bits to set + */ +void mfrc522_pcd_set_register_bitmask(mfrc522_t *dev, + mfrc522_pcd_register_t reg, uint8_t mask); + +/** + * @brief Clears the bits given in mask from register reg + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] reg Register to write to + * @param[in] mask Bitmask with the bits to clear + */ +void mfrc522_pcd_clear_register_bitmask(mfrc522_t *dev, + mfrc522_pcd_register_t reg, uint8_t mask); + +/** + * @brief Use the CRC coprocessor in the MFRC522 to calculate a CRC_A + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] data Data to transfer to the FIFO for CRC calculation + * @param[in] length Number of bytes to transfer + * @param[out] result Result buffer. Result is written to result[0..1], low + * byte first. + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_pcd_calculate_crc(mfrc522_t *dev, + const uint8_t *data, + uint8_t length, + uint8_t *result); + +/** + * @brief Initialization + * + * @param[out] dev Device descriptor of the MFRC522 + * @param[in] params Parameters for device initialization + * + * @retval 0 Success + * @retval -ENXIO Invalid device + * @retval -EINVAL Invalid CS pin/line + */ +int mfrc522_pcd_init(mfrc522_t *dev, const mfrc522_params_t *params); + +/** + * @brief Performs a soft reset on the MFRC522 chip and waits for it to be ready + * again + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_reset(mfrc522_t *dev); + +/** + * @brief Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins are disabled. + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_antenna_on(mfrc522_t *dev); + +/** + * @brief Turns the antenna off by disabling pins TX1 and TX2 + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_antenna_off(mfrc522_t *dev); + +/** + * @brief Get the current MFRC522 receiver gain + * + * @param[in] dev Device descriptor of the MFRC522 + * + * @return Receiver gain + */ +mfrc522_pcd_rx_gain_t mfrc522_pcd_get_antenna_gain(mfrc522_t *dev); + +/** + * @brief Set the MFRC522 receiver gain + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] rx_gain Antenna gain + */ +void mfrc522_pcd_set_antenna_gain(mfrc522_t *dev, mfrc522_pcd_rx_gain_t rx_gain); + +/** + * @brief Set the MFRC522 to soft power-down mode + * + * @note Only soft power down mode is available through software. + * + * @note Calling any other function that uses MFRC522_REG_COMMAND will disable + * soft power down mode! For more details about power control, refer to + * the datasheet - page 33 (8.6) + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_soft_power_down(mfrc522_t *dev); + +/** + * @brief Set the MFRC522 to leave soft power-down mode + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_soft_power_up(mfrc522_t* dev); + +/** + * @brief Executes MFRC522_CMD_TRANSCEIVE. + * + * @note CRC validation can only be done if back_data and back_len are specified + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] send_data Data to transfer to the FIFO + * @param[in] send_len Number of bytes to transfer to the FIFO + * @param[out] back_data Buffer if data should be read back after executing + * the command, otherwise NULL + * @param[inout] back_len Max number of bytes to write to *back_data. + * Returns number of bytes returned. + * @param[inout] valid_bits Number of valid bits in the last byte. 0 for 8 + * valid bits + * @param[in] rx_align Defines the bit position in back_data[0] for the + * first bit received + * @param[in] check_crc If true, the last two bytes of the response are + * assumed to be a CRC_A that must be validated + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_pcd_transceive_data(mfrc522_t *dev, + const uint8_t *send_data, + uint8_t send_len, + uint8_t *back_data, + uint8_t *back_len, + uint8_t *valid_bits, + uint8_t rx_align, + bool check_crc); + +/** + * @brief Transfers data to MFRC522's FIFO, executes a command, waits for + * completion and transfers data back from the FIFO + * + * @note CRC validation can only be done if back_data and back_len are specified + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] command The command to execute + * @param[in] wait_irq The bits in the ComIrqReg register that signals + * successful completion of the command + * @param[in] send_data Data to transfer to the FIFO + * @param[in] send_len Number of bytes to transfer to the FIFO + * @param[out] back_data Buffer if data should be read back after executing + * the command, otherwise NULL + * @param[inout] back_len Max number of bytes to write to *back_data. + * Returns number of bytes returned. + * @param[inout] valid_bits Number of valid bits in the last byte. 0 for 8 + * valid bits. + * @param[in] rx_align Defines the bit position in back_data[0] for the + * first bit received + * @param[in] check_crc True => The last two bytes of the response is + * assumed to be a CRC_A that must be validated + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_pcd_communicate_with_picc(mfrc522_t *dev, + mfrc522_pcd_command_t command, + uint8_t wait_irq, + const uint8_t *send_data, + uint8_t send_len, + uint8_t *back_data, + uint8_t *back_len, + uint8_t *valid_bits, + uint8_t rx_align, + bool check_crc); + +/** + * @brief Transmits REQA, Type A. Invites PICCs in state IDLE to go to READY and + * prepare for anti-collision or selection. 7 bit frame. + * + * @note When two PICCs are in the field at the same time we often get + * MFRC522_STATUS_TIMEOUT - probably due do bad antenna design. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[out] buffer_atqa Buffer to store the ATQA in + * @param[inout] buffer_size Buffer size, at least two bytes. Also number of + * bytes returned if MFRC522_STATUS_OK. + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_picc_request_a(mfrc522_t *dev, + uint8_t *buffer_atqa, + uint8_t *buffer_size); + +/** + * @brief Transmits WUPA, Type A. Invites PICCs in state IDLE and HALT to go to + * READY(*) and prepare for anti-collision or selection. 7 bit frame. + * + * @note When two PICCs are in the field at the same time we often get + * MFRC522_STATUS_TIMEOUT - probably due do bad antenna design. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[out] buffer_atqa Buffer to store the ATQA in + * @param[inout] buffer_size Buffer size, at least two bytes. Also number of + * bytes returned if MFRC522_STATUS_OK. + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_picc_wakeup_a(mfrc522_t *dev, + uint8_t *buffer_atqa, + uint8_t *buffer_size); + +/** + * @brief Transmits REQA (Type A) or WUPA (Type A) commands + * + * @note When two PICCs are in the field at the same time we often get + * MFRC522_STATUS_TIMEOUT - probably due do bad antenna design. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] command Command to send - MFRC522_PICC_CMD_ISO_14443_REQA + * or MFRC522_PICC_CMD_ISO_14443_WUPA + * @param[out] buffer_atqa Buffer to store the ATQA in + * @param[inout] buffer_size Buffer size, at least two bytes. Also number of + * bytes returned if MFRC522_STATUS_OK. + * + * @return MFRC522_STATUS_OK on success + * @return MFRC522_STATUS_INVALID if command was neither + * MFRC522_PICC_CMD_ISO_14443_REQA nor MFRC522_PICC_CMD_ISO_14443_WUPA, + * MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_picc_reqa_or_wupa(mfrc522_t *dev, + mfrc522_picc_command_t command, + uint8_t *buffer_atqa, + uint8_t *buffer_size); + +/** + * @brief Transmits SELECT/ANTICOLLISION commands to select a single PICC + * + * @note Before calling this function the PICCs must be placed in the READY(*) + * state by calling mfrc522_picc_request_a() or mfrc522_picc_wakeup_a(). + * On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have + * returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along + * with the SAK. + * + * A PICC UID consists of 4, 7 or 10 bytes. + * Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two + * or three iterations are used: + * + * UID size Number of UID bytes Cascade levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE Classic + * double 7 2 MIFARE Ultralight + * triple 10 3 Not currently in use? + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[inout] uid Normally output, but can also be used to supply a + * known UID + * @param[in] valid_bits Number of known UID bits supplied in *uid. + * Normally 0. If set you must also supply uid->size. + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_picc_select(mfrc522_t *dev, + mfrc522_uid_t *uid, uint8_t valid_bits); + +/** + * @brief Instructs a PICC in state ACTIVE(*) to go to state HALT + * + * @param[in] dev Device descriptor of the MFRC522 + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_picc_halt_a(mfrc522_t *dev); + +/** + * @brief Executes the MFRC522 MFAuthent command + * + * This command manages MIFARE authentication to enable a secure communication + * to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. The authentication is + * described in the MFRC522 datasheet section 10.3.1.9 and + * http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. For use + * with MIFARE Classic PICCs. The PICC must be selected - i.e. in state + * ACTIVE(*) - before calling this function. Remember to call + * mfrc522_pcd_stop_crypto1() after communicating with the authenticated PICC - + * otherwise no new communications can start. + * + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] command MFRC522_PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + * @param[in] block_addr Block number. See numbering in the comments in the .h file. + * @param[in] key Crypteo1 key to use (6 bytes) + * @param[in] uid PICC's UID + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_pcd_authenticate(mfrc522_t *dev, + mfrc522_picc_command_t command, + uint8_t block_addr, + const mfrc522_mifare_key_t *key, + const mfrc522_uid_t *uid); + +/** + * @brief Used to exit the PCD from its authenticated state + * + * @note Remember to call this function after communicating with an + * authenticated PICC - otherwise no new communications can start. + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_stop_crypto1(mfrc522_t *dev); + +/** + * @brief Read 16 bytes (+ 2 bytes CRC_A) from the active PICC + * + * @note For MIFARE Classic the sector containing the block must be + * authenticated before calling this function. + * + * For MIFARE Ultralight only addresses 00h to 0Fh are decoded. The MF0ICU1 + * returns a NAK for higher addresses. The MF0ICU1 responds to the READ command + * by sending 16 bytes starting from the page address defined by the command + * argument. For example; if block_addr is 03h then pages 03h, 04h, 05h, 06h are + * returned. A roll-back is implemented: If block_addr is 0Eh, then the contents + * of pages 0Eh, 0Fh, 00h and 01h are returned. + * + * The buffer must be at least 18 bytes because a CRC_A is also returned. + * Checks the CRC_A before returning MFRC522_STATUS_OK. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr MIFARE Classic: The block (0-0xff) number. + * MIFARE Ultralight: The first page to return data from. + * @param[out] buffer Buffer to store the data in + * @param[inout] buffer_size Buffer size, at least 18 bytes. Also number of + * bytes returned if MFRC522_STATUS_OK. + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_read(mfrc522_t *dev, + uint8_t block_addr, + uint8_t *buffer, + uint8_t *buffer_size); + +/** + * @brief Write 16 bytes to the active PICC + * + * @note For MIFARE Classic the sector containing the block must be + * authenticated before calling this function. + * + * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". Even + * though 16 bytes are transferred to the Ultralight PICC, only the least + * significant 4 bytes (bytes 0 to 3) are written to the specified address. It + * is recommended to set the remaining bytes 04h to 0Fh to all logic 0. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr MIFARE Classic: The block (0-0xff) number. + * MIFARE Ultralight: The page (2-15) to write to. + * @param[in] buffer The 16 bytes to write to the PICC + * @param[in] buffer_size Buffer size, must be at least 16 bytes. Exactly 16 + * bytes are written. + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_write(mfrc522_t *dev, + uint8_t block_addr, + const uint8_t *buffer, + uint8_t buffer_size); + +/** + * @brief Write a 4 byte page to the active MIFARE Ultralight PICC + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] page The page (2-15) to write to + * @param[in] buffer The 4 bytes to write to the PICC + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_ultralight_write(mfrc522_t *dev, + uint8_t page, + const uint8_t *buffer); + +/** + * @brief Subtract the operand from the value of the addressed block, and store + * the result in the Transfer Buffer + * + * MIFARE Decrement subtracts the delta from the value of the addressed block, + * and stores the result in a volatile memory. For MIFARE Classic only. The + * sector containing the block must be authenticated before calling this + * function. Only for blocks in "value block" mode, ie with access bits + * [C1 C2 C3] = [110] or [001]. Use MIFARE_Transfer() to store the result in a + * block. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr Block (0-0xff) number + * @param[in] delta Number to be subtracted from the value of block block_addr + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_decrement(mfrc522_t *dev, + uint8_t block_addr, + int32_t delta); + +/** + * @brief Add the operand to the value of the addressed block, and store the + * result in the Transfer Buffer + * + * MIFARE Increment adds the delta to the value of the addressed block, and + * stores the result in a volatile memory. For MIFARE Classic only. The sector + * containing the block must be authenticated before calling this function. Only + * for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr Block (0-0xff) number + * @param[in] delta Number to be added to the value of block block_addr + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_increment(mfrc522_t *dev, + uint8_t block_addr, + int32_t delta); + +/** + * @brief Copies the value of the addressed block into the Transfer Buffer + * + * MIFARE Restore copies the value of the addressed block into a volatile memory. + * For MIFARE Classic only. The sector containing the block must be + * authenticated before calling this function. Only for blocks in "value block" + * mode, i.e. with access bits [C1 C2 C3] = [110] or [001]. Use + * mfrc522_mifare_transfer() to store the result in a block. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr Block (0-0xff) number + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_restore(mfrc522_t *dev, uint8_t block_addr); + +/** + * @brief Write the value from the Transfer Buffer into destination block + * + * MIFARE Transfer writes the value stored in the volatile memory into one + * MIFARE Classic block. For MIFARE Classic only. The sector containing the + * block must be authenticated before calling this function. Only for blocks in + * "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr Block (0-0xff) number + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_transfer(mfrc522_t *dev, uint8_t block_addr); + +/** + * @brief Helper routine to read the current value from a Value Block + * + * @note Only for MIFARE Classic and only for blocks in "value block" mode, + * that is with access bits [C1 C2 C3] = [110] or [001]. The sector + * containing the block must be authenticated before calling this function. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr Block (0x00-0xff) number + * @param[out] value Current value of the Value Block + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_get_value(mfrc522_t *dev, + uint8_t block_addr, int32_t *value); + +/** + * @brief Helper routine to write a specific value into a Value Block + * + * @note Only for MIFARE Classic and only for blocks in "value block" mode, + * that is with access bits [C1 C2 C3] = [110] or [001]. The sector + * containing the block must be authenticated before calling this function. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] block_addr Block (0x00-0xff) number + * @param[in] value New value of the Value Block + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_set_value(mfrc522_t *dev, + uint8_t block_addr, int32_t value); + +/** + * @brief Authenticate with a NTAG216 + * + * @note Only for NTAG216 + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] password Password (must have a size of exactly 4 bytes) + * @param[out] p_ack result + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_pcd_ntag216_auth(mfrc522_t *dev, + const uint8_t* password, + uint8_t p_ack[]); + +/** + * @brief Wrapper for MIFARE protocol communication + * + * Adds CRC_A, executes the Transceive command and checks that the response is + * MFRC522_MF_ACK or a timeout. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] send_data Data to transfer to the FIFO. Do NOT include the CRC_A. + * @param[in] send_len Number of bytes in send_data + * @param[in] accept_timeout If true, then a timeout is also a success + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_pcd_mifare_transceive(mfrc522_t *dev, + const uint8_t *send_data, + uint8_t send_len, + bool accept_timeout); + +/** + * @brief Returns the name for a status code + * + * @param[in] code Status code enum + * + * @return Status code name + */ +const char* mfrc522_get_status_code_string(mfrc522_status_code_t code); + +/** + * @brief Translates the SAK (Select Acknowledge) to a PICC type + * + * @param[in] sak The SAK byte returned from mfrc522_picc_select() + * + * @return PICC type + */ +mfrc522_picc_type_t mfrc522_picc_get_type(uint8_t sak); + +/** + * @brief Returns the name for the PICC type. + * + * @param[in] picc_type PICC type enum + * + * @return PICC type name + */ +const char* mfrc522_picc_get_type_string(mfrc522_picc_type_t picc_type); + +/** + * @brief Calculates the bit pattern needed for the specified access bits. In + * the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1). + * + * @param[out] access_bit_buffer Pointer to byte 6, 7 and 8 in the sector + * trailer. Bytes [0..2] will be set. + * @param[in] g0 Access bits [C1 C2 C3] for block 0 (for + * sectors 0-31) or blocks 0-4 (for sectors 32-39) + * @param[in] g1 Access bits [C1 C2 C3] for block 1 (for + * sectors 0-31) or blocks 5-9 (for sectors 32-39) + * @param[in] g2 Access bits [C1 C2 C3] for block 2 (for + * sectors 0-31) or blocks 10-14 (for sectors 32-39) + * @param[in] g3 Access bits [C1 C2 C3] for the sector trailer, + * block 3 (for sectors 0-31) or block 15 (for sectors 32-39) + */ +void mfrc522_mifare_set_access_bits(uint8_t *access_bit_buffer, + uint8_t g0, uint8_t g1, uint8_t g2, uint8_t g3); + +/** + * @brief Performs the "magic sequence" needed to get Chinese UID changeable + * Mifare cards to allow writing to sector 0, where the card UID is stored. + * + * @note Note that you do not need to have selected the card through REQA or WUPA, + * this sequence works immediately when the card is in the reader vicinity. + * This means you can use this method even on "bricked" cards that your + * reader does not recognise anymore (see mfrc522_mifare_unbrick_uid_sector). + * Of course with non-bricked devices, you are free to select them before + * calling this function. + * + * @param[in] dev Device descriptor of the MFRC522 + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_open_uid_backdoor(mfrc522_t *dev); + +/** + * @brief Read entire block 0, including all manufacturer data, and overwrites + * that block with the new UID, a freshly calculated BCC, and the original + * manufacturer data. + * + * @note It assumes a default KEY A of 0xFFFFFFFFFFFF. Make + * sure to have selected the PICC before this function is called and that + * the UID of the PICC is changeable. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] uid PICC's UID + * @param[in] new_uid New UID to set on PICC + * @param[in] new_uid_size Size of new UID + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_set_uid(mfrc522_t *dev, + mfrc522_uid_t *uid, + const uint8_t *new_uid, + uint8_t new_uid_size); + +/** + * @brief Reset entire sector 0 to zeroes, so the card can be read again by readers + * + * @param[in] dev Device descriptor of the MFRC522 + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_mifare_unbrick_uid_sector(mfrc522_t *dev); + +/** + * @brief Checks whether a new card could be detected + * + * @note Only "new" cards in state IDLE are invited. Sleeping cards in state + * HALT are ignored. + * + * @param[in] dev Device descriptor of the MFRC522 + * + * @return true if new card was detected, false otherwise + */ +bool mfrc522_picc_is_new_card_present(mfrc522_t *dev); + +/** + * @brief Simple wrapper around mfrc522_picc_select() + * + * @note Remember to call mfrc522_picc_is_new_card_present(), + * mfrc522_picc_request_a() or mfrc522_picc_wakeup_a() first. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] uid PICC's UID + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + */ +mfrc522_status_code_t mfrc522_picc_read_card_serial(mfrc522_t *dev, + mfrc522_uid_t *uid); + +/** + * @brief Dump debug info about the connected PCD to stdout + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_pcd_dump_version_to_serial(mfrc522_t *dev); + +/** + * @brief Dump debug info about the selected PICC to stdout + * + * @note On success the PICC is halted after dumping the data. + * For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] uid PICC's UID + * + */ +void mfrc522_picc_dump_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid); + +/** + * @brief Dump card info (UID, SAK, Type) about the selected PICC to stdout + * + * @param[in] uid PICC's UID + */ +void mfrc522_picc_dump_details_to_serial(mfrc522_uid_t *uid); + +/** + * @brief Dump memory contents of a MIFARE Classic PICC to stdout + * + * @note On success the PICC is halted after dumping the data. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] uid PICC's UID + * @param[in] picc_type PICC type enum + * @param[in] key Key A used for all sectors. + * + */ +void mfrc522_picc_dump_mifare_classic_to_serial(mfrc522_t *dev, + mfrc522_uid_t *uid, + mfrc522_picc_type_t picc_type, + mfrc522_mifare_key_t *key); + +/** + * @brief Dump memory contents of a sector of a MIFARE Classic PICC to stdout + * + * Uses mfrc522_pcd_authenticate(), mfrc522_mifare_read() and + * mfrc522_pcd_stop_crypto1(). Always uses MFRC522_PICC_CMD_MF_AUTH_KEY_A + * because only Key A can always read the sector trailer access bits. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] uid PICC's UID + * @param[in] key Key A for the sector. + * @param[in] sector The sector to dump, 0..39 + * + */ +void mfrc522_picc_dump_mifare_classic_sector_to_serial(mfrc522_t *dev, + mfrc522_uid_t *uid, + mfrc522_mifare_key_t *key, + uint8_t sector); + +/** + * @brief Dump memory contents of a MIFARE Ultralight PICC to stdout + * + * @param[in] dev Device descriptor of the MFRC522 + */ +void mfrc522_picc_dump_mifare_ultralight_to_serial(mfrc522_t *dev); + +/** + * @brief Perform a self-test of the MFRC522 + * + * (See section 16.1.1) + * + * @param[in] dev Device descriptor of the MFRC522 + * + * @return true if the test passed, false otherwise and if no firmware reference is available. + */ +bool mfrc522_pcd_perform_self_test(mfrc522_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* MFRC522_H */ +/** @} */ diff --git a/drivers/mfrc522/Kconfig b/drivers/mfrc522/Kconfig new file mode 100644 index 000000000000..da37cff479fd --- /dev/null +++ b/drivers/mfrc522/Kconfig @@ -0,0 +1,19 @@ +# Copyright (c) 2022 Freie Universität Berlin +# +# 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. +# + +config MODULE_MFRC522 + bool "MFRC522 RFID controller" + depends on HAS_PERIPH_GPIO + depends on HAS_PERIPH_SPI + depends on TEST_KCONFIG + select MODULE_PERIPH_GPIO + select MODULE_PERIPH_SPI + select MODULE_ZTIMER + select MODULE_ZTIMER_USEC + select MODULE_ZTIMER_MSEC + help + Device driver for the NXP MFRC522 RFID controller diff --git a/drivers/mfrc522/Makefile b/drivers/mfrc522/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/drivers/mfrc522/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mfrc522/Makefile.dep b/drivers/mfrc522/Makefile.dep new file mode 100644 index 000000000000..13f08a46aa88 --- /dev/null +++ b/drivers/mfrc522/Makefile.dep @@ -0,0 +1,5 @@ +FEATURES_REQUIRED += periph_gpio +FEATURES_REQUIRED += periph_spi + +USEMODULE += ztimer_usec +USEMODULE += ztimer_msec diff --git a/drivers/mfrc522/Makefile.include b/drivers/mfrc522/Makefile.include new file mode 100644 index 000000000000..61a0c01b46ed --- /dev/null +++ b/drivers/mfrc522/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_mfrc522 := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mfrc522) diff --git a/drivers/mfrc522/include/mfrc522_params.h b/drivers/mfrc522/include/mfrc522_params.h new file mode 100644 index 000000000000..ae3fe42e7834 --- /dev/null +++ b/drivers/mfrc522/include/mfrc522_params.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * 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_actuators + * @brief Default configuration for the MFRC522 controller + * + * @{ + * + * @author Hendrik van Essen + * @file + */ +#ifndef MFRC522_PARAMS_H +#define MFRC522_PARAMS_H + +#include + +#include "periph/spi.h" + +#include "mfrc522_regs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** +* @name Default configuration parameters for MFRC522 driver +* @{ +*/ +#ifndef MFRC522_PARAM_SPI_DEV +#define MFRC522_PARAM_SPI_DEV SPI_DEV(0) /**< Default SPI device */ +#endif +#ifndef MFRC522_PARAM_SPI_CLK +#define MFRC522_PARAM_SPI_CLK SPI_CLK_5MHZ /**< Default SPI speed */ +#endif +#ifndef MFRC522_PARAM_SCK_PIN +#define MFRC522_PARAM_SCK_PIN GPIO_PIN(0, 18) /**< Default SCK pin */ +#endif +#ifndef MFRC522_PARAM_MISO_PIN +#define MFRC522_PARAM_MISO_PIN GPIO_PIN(0, 19) /**< Default MISO pin */ +#endif +#ifndef MFRC522_PARAM_MOSI_PIN +#define MFRC522_PARAM_MOSI_PIN GPIO_PIN(0, 23) /**< Default MOSI pin */ +#endif +#ifndef MFRC522_PARAM_CS_PIN +#define MFRC522_PARAM_CS_PIN GPIO_PIN(0, 5) /**< Default CS pin */ +#endif +#ifndef MFRC522_PARAM_RST_PIN +#define MFRC522_PARAM_RST_PIN GPIO_PIN(0, 17) /**< Default RST pin */ +#endif + +#ifndef MFRC522_PARAMS +#define MFRC522_PARAMS \ + { \ + .spi_dev = MFRC522_PARAM_SPI_DEV, \ + .spi_clk = MFRC522_PARAM_SPI_CLK, \ + .sck_pin = MFRC522_PARAM_SCK_PIN, \ + .miso_pin = MFRC522_PARAM_MISO_PIN, \ + .mosi_pin = MFRC522_PARAM_MOSI_PIN, \ + .cs_pin = MFRC522_PARAM_CS_PIN, \ + .rst_pin = MFRC522_PARAM_RST_PIN, \ + } /**< Struct with default configuration parameters */ +#endif /* MFRC522_PARAMS */ +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const mfrc522_params_t mfrc522_params[] = +{ + MFRC522_PARAMS +}; + +#ifdef __cplusplus +} +#endif + +#endif /* MFRC522_PARAMS_H */ +/** @} */ diff --git a/drivers/mfrc522/include/mfrc522_regs.h b/drivers/mfrc522/include/mfrc522_regs.h new file mode 100644 index 000000000000..0423c340927b --- /dev/null +++ b/drivers/mfrc522/include/mfrc522_regs.h @@ -0,0 +1,1168 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * 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_mfrc522 + * @brief Register definitions for the MFRC522 controller + * + * The documentation is using the following terms: + * PCD = proximity coupling device + * PICC = proximity integrated circuit card + * + * @author Hendrik van Essen + * @file + * @{ + */ + +#ifndef MFRC522_REGS_H +#define MFRC522_REGS_H + +#include "bitarithm.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Register definitions + * @{ + */ +typedef enum { + /* Command and status register definitions (page 36, table 20) */ + MFRC522_REG_COMMAND = 0x01, /**< Starts and stops command execution */ + MFRC522_REG_COML_EN = 0x02, /**< Enable and disable interrupt request control bits */ + MFRC522_REG_DIVL_EN = 0x03, /**< Enable and disable interrupt request control bits */ + MFRC522_REG_COM_IRQ = 0x04, /**< Interrupt request bits */ + MFRC522_REG_DIV_IRQ = 0x05, /**< Interrupt request bits */ + MFRC522_REG_ERROR = 0x06, /**< Error bits showing the error status of the last command executed */ + MFRC522_REG_STATUS_1 = 0x07, /**< Communication status bits */ + MFRC522_REG_STATUS_2 = 0x08, /**< Receiver and transmitter status bits */ + MFRC522_REG_FIFO_DATA = 0x09, /**< Input and output of 64 byte FIFO buffer */ + MFRC522_REG_FIFO_LEVEL = 0x0A, /**< Number of bytes stored in the FIFO buffer */ + MFRC522_REG_WATER_LEVEL = 0x0B, /**< Level for FIFO underflow and overflow warning */ + MFRC522_REG_CONTROL = 0x0C, /**< Miscellaneous control registers */ + MFRC522_REG_BIT_FRAMING = 0x0D, /**< Adjustments for bit-oriented frames */ + MFRC522_REG_COLL = 0x0E, /**< Bit position of the first bit-collision detected on the RF interface */ + + /* Command register definitions (page 36, table 20) */ + MFRC522_REG_MODE = 0x11, /**< Defines general modes for transmitting and receiving */ + MFRC522_REG_TX_MODE = 0x12, /**< Defines transmission data rate and framing */ + MFRC522_REG_RX_MODE = 0x13, /**< Defines reception data rate and framing */ + MFRC522_REG_TX_CONTROL = 0x14, /**< Controls the logical behavior of the antenna driver pins TX1 and TX2 */ + MFRC522_REG_TX_ASK = 0x15, /**< Controls the setting of the transmission modulation */ + MFRC522_REG_TX_SEL = 0x16, /**< Selects the internal sources for the antenna driver */ + MFRC522_REG_RX_SEL = 0x17, /**< Selects internal receiver settings */ + MFRC522_REG_RX_THRESHHOLD = 0x18, /**< Selects thresholds for the bit decoder */ + MFRC522_REG_DEMOD = 0x19, /**< Defines demodulator settings */ + MFRC522_REG_MF_TX = 0x1C, /**< Controls some MIFARE communication transmit parameters */ + MFRC522_REG_MF_RX = 0x1D, /**< Controls some MIFARE communication receive parameters */ + MFRC522_REG_SERIAL_SPEED = 0x1F, /**< Selects the speed of the serial UART interface */ + + /* Configuration register definitions (page 36-37, table 20) */ + MFRC522_REG_CRC_RESULT_MSB = 0x21, /**< Shows the higher 8 bits of the CRC calculation */ + MFRC522_REG_CRC_RESULT_LSB = 0x22, /**< Shows the lower 8 bits of the CRC calculation */ + MFRC522_REG_MOD_WIDTH = 0x24, /**< Controls the ModWidth setting */ + MFRC522_REG_RF_CFG = 0X26, /**< Configures the receiver gain */ + MFRC522_REG_GS_N = 0x27, /**< Selects the conductance of the antenna driver pins TX1 and TX2 for modulation */ + MFRC522_REG_CW_GS_P = 0x28, /**< Defines the conductance of the p-driver output during periods of no modulation */ + MFRC522_REG_MOD_GS_P = 0x29, /**< Defines the conductance of the p-driver output during periods of modulation */ + MFRC522_REG_T_MODE = 0x2A, /**< Defines settings for the internal timer */ + MFRC522_REG_T_PRESCALER = 0x2B, /**< Defines settings for the internal timer */ + MFRC522_REG_T_RELOAD_MSB = 0x2C, /**< Defines the higher 8 bits of 16-bit timer reload value */ + MFRC522_REG_T_RELOAD_LSB = 0x2D, /**< Defines the lower 8 bits of 16-bit timer reload value */ + MFRC522_REG_T_COUNTER_VAL_MSB = 0x2E, /**< Shows the higher 8 bits of 16-bit timer value */ + MFRC522_REG_T_COUNTER_VAL_LSB = 0x2F, /**< Shows the lower 8 bits of 16-bit timer value */ + + /* Test register definitions (page 37, table 20) */ + MFRC522_REG_TEST_SEL_1 = 0x31, /**< General test signal configuration */ + MFRC522_REG_TEST_SEL_2 = 0x32, /**< General test signal configuration and PRBS control */ + MFRC522_REG_TEST_PIN_EN = 0x33, /**< Enables pin output driver on pins D1 to D7 */ + MFRC522_REG_TEST_PIN_VALUE = 0x34, /**< Defines the values for D1 to D7 when it is used as an I/O bus */ + MFRC522_REG_TEST_BUS = 0x35, /**< Shows the status of the internal test bus */ + MFRC522_REG_AUTO_TEST = 0x36, /**< Controls the digital self test */ + MFRC522_REG_VERSION = 0x37, /**< Shows the software version */ + MFRC522_REG_ANALOG_TEST = 0x38, /**< Controls the pins AUX1 and AUX2 */ + MFRC522_REG_TEST_DAC_1 = 0x39, /**< Defines the test value for TestDAC1 */ + MFRC522_REG_TEST_DAC2 = 0x3A, /**< Defines the test value for TestDAC2 */ + MFRC522_REG_TEST_ADC = 0x3B, /**< Shows the value of ADC I and Q channels */ +} mfrc522_pcd_register_t; +/** @} */ + +/** + * @brief Command definitions + * @{ + */ +typedef enum { + /* Command codes (page 70, table 149) */ + MFRC522_CMD_IDLE = 0x00, /**< No action, cancels current command execution */ + MFRC522_CMD_MEM = 0x01, /**< Stores 25 bytes into the internal buffer */ + MFRC522_CMD_GENERATE_RANDOM_ID = 0x02, /**< Generates a 10-byte random ID number */ + MFRC522_CMD_CALC_CRC = 0x03, /**< Activates the CRC coprocessor or performs a self test */ + MFRC522_CMD_TRANSMIT = 0x04, /**< Transmits data from the FIFO buffer */ + MFRC522_CMD_NO_CMD_CHANGE = 0x07, /**< No command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit */ + MFRC522_CMD_RECEIVE = 0x08, /**< Activates the receiver circuits */ + MFRC522_CMD_TRANSCEIVE = 0x0C, /**< Transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission */ + MFRC522_CMD_MF_AUTHENT = 0x0E, /**< Performs the MIFARE standard authentication as a reader */ + MFRC522_CMD_SOFT_RESET = 0x0F, /**< Resets the MFRC522 */ +} mfrc522_pcd_command_t; +/** @} */ + +/** + * @brief Receiver gain definitions + * @{ + */ +typedef enum { + /* Receiver gain definition (page 70, table 149) */ + MFRC522_RXGAIN_18_DB = 0x00, /**< 18 dB, minimum */ + MFRC522_RXGAIN_23_DB = 0x01, /**< 23 dB */ + MFRC522_RXGAIN_18_DB_2 = 0x02, /**< 18 dB, duplicate for MFRC522_RXGAIN_18_DB */ + MFRC522_RXGAIN_23_DB_2 = 0x03, /**< 23 dB, duplicate for MFRC522_RXGAIN_23_DB */ + MFRC522_RXGAIN_33_DB = 0x04, /**< 33 dB, average, and typical default */ + MFRC522_RXGAIN_38_DB = 0x05, /**< 38 dB */ + MFRC522_RXGAIN_43_DB = 0x06, /**< 43 dB */ + MFRC522_RXGAIN_48_DB = 0x07, /**< 48 dB, maximum */ + MFRC522_RXGAIN_MIN = MFRC522_RXGAIN_18_DB, /**< 18 dB, minimum, convenience for MFRC522_RXGAIN_18_DB */ + MFRC522_RXGAIN_AVG = MFRC522_RXGAIN_33_DB, /**< 33 dB, average, convenience for MFRC522_RXGAIN_33_DB */ + MFRC522_RXGAIN_MAX = MFRC522_RXGAIN_48_DB, /**< 48 dB, maximum, convenience for MFRC522_RXGAIN_48_DB */ +} mfrc522_pcd_rx_gain_t; +/** @} */ + +/** + * @brief PICC command definitions + * @{ + */ +typedef enum { + /* Commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) */ + MFRC522_PICC_CMD_ISO_14443_REQA = 0x26, /**< REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. */ + MFRC522_PICC_CMD_ISO_14443_WUPA = 0x52, /**< Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. */ + MFRC522_PICC_CMD_ISO_14443_SEL_CL1 = 0x93, /**< Anti collision/Select, Cascade Level 1 */ + MFRC522_PICC_CMD_ISO_14443_SEL_CL2 = 0x95, /**< Anti collision/Select, Cascade Level 2 */ + MFRC522_PICC_CMD_ISO_14443_SEL_CL3 = 0x97, /**< Anti collision/Select, Cascade Level 3 */ + MFRC522_PICC_CMD_ISO_14443_HLTA = 0x50, /**< HLTA command, Type A. Instructs an ACTIVE PICC to go to state HALT. */ + MFRC522_PICC_CMD_ISO_14443_RATS = 0xE0, /**< Request command for Answer To Reset */ + + /* Commands used for MIFARE Classic EV1 (https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf, page 13, table 9) */ + MFRC522_PICC_CMD_MF_AUTH_KEY_A = 0x60, /**< Authentication with Key A */ + MFRC522_PICC_CMD_MF_AUTH_KEY_B = 0x61, /**< Authentication with Key B */ + MFRC522_PICC_CMD_MF_PERS_UID_USAGE = 0x40, /**< Set anti-collision, selection and authentication behaviour */ + MFRC522_PICC_CMD_MF_SET_MOD_TYPE = 0x43, /**< Set load modulation strength */ + MFRC522_PICC_CMD_MF_READ = 0x30, /**< Read one block of 16 bytes */ + MFRC522_PICC_CMD_MF_WRITE = 0xA0, /**< Write one block of 16 bytes */ + MFRC522_PICC_CMD_MF_DECREMENT = 0xC0, /**< Decrement: Subtracts the operand from the value of the addressed block, and stores the result in the Transfer Buffer */ + MFRC522_PICC_CMD_MF_INCREMENT = 0xC1, /**< Increment: Adds the operand to the value of the addressed block, and stores the result in the Transfer Buffer */ + MFRC522_PICC_CMD_MF_RESTORE = 0xC2, /**< Restore: Copies the value of the addressed block into the Transfer Buffer. */ + MFRC522_PICC_CMD_MF_TRANSFER = 0xB0, /**< Write the value from the Transfer Buffer into destination block */ + + /* Commands used for MIFARE Ultralight (https://www.nxp.com/docs/en/data-sheet/MF0ICU1.pdf, Section 7.6) */ + MFRC522_PICC_CMD_MF_UL_WRITE = 0xA2, /**< Transfers 16 bytes, but only writes least significant 4 bytes */ + MFRC522_PICC_CMD_MF_UL_READ = MFRC522_PICC_CMD_MF_READ, /**< Read one block of 16 bytes */ + MFRC522_PICC_CMD_MF_UL_COMPAT_WRITE = MFRC522_PICC_CMD_MF_WRITE, /**< Write one block of 16 bytes */ +} mfrc522_picc_command_t; +/** @} */ + +/** + * @brief FIFO buffer size + */ +#define MFRC522_FIFO_BUF_SIZE 64 + +/** + * @brief Cascade Tag, used during anti collision. + */ +# define MFRC522_PICC_CASCADE_TAG 0x88 + +/** + * @brief 1 = Analog part of the receiver is switched off + */ +#define MFRC522_BIT_COMMAND_RCV_OFF BIT5 + +/** + * @brief 0 = MFRC522 starts the wake up procedure during which this bit is read + * as a logic 1; it is read as a logic 0 when the MFRC522 is ready; + * see Section 8.6.2 on page 33. Remark: The PowerDown bit cannot be + * set when the SoftReset command is activated + * 1 = Soft power-down mode entered. + * + */ +#define MFRC522_BIT_COMMAND_POWER_DOWN BIT4 + +/** + * @brief Activates a command based on the Command value; reading this register + * shows which command is executed; see Section 10.3 on page 70 + * + * MFRC522_REG_COMMAND[3:0] + * + */ +#define MFRC522_BITMASK_COMMAND_POWER_DOWN 0x0F + + + +/** + * @brief 0 = Signal on pin IRQ is equal to the IRq bit; in combination with the + * DivIEnReg register’s IRqPushPull bit, the default value of logic 1 + * ensures that the output level on pin IRQ is 3-state + * 1 = Signal on pin IRQ is inverted with respect to the Status1Reg + * register’s IRq bit + * + */ +#define MFRC522_BIT_COML_EN_IRQ_INV BIT7 + +/** + * @brief Allows the transmitter interrupt request (TxIRq bit) to be propagated + * to pin IRQ + */ +#define MFRC522_BIT_COML_EN_TX_I_EN BIT6 + +/** + * @brief Allows the receiver interrupt request (RxIRq bit) to be propagated to + * pin IRQ + */ +#define MFRC522_BIT_COML_EN_RX_I_EN BIT5 + +/** + * @brief Allows the idle interrupt request (IdleIRq bit) to be propagated to + * pin IRQ + */ +#define MFRC522_BIT_COML_EN_IDLE_I_EN BIT4 + +/** + * @brief Allows the high alert interrupt request (HiAlertIRq bit) to be + * propagated to pin IRQ + */ +#define MFRC522_BIT_COML_EN_HI_ALERT_I_EN BIT3 + +/** + * @brief Allows the low alert interrupt request (LoAlertIRq bit) to be + * propagated to pin IRQ + */ +#define MFRC522_BIT_COML_EN_LO_ALERT_I_EN BIT2 + +/** + * @brief Allows the error interrupt request (ErrIRq bit) to be propagated to + * pin IRQ + */ +#define MFRC522_BIT_COML_EN_ERR_I_EN BIT1 + +/** + * @brief Allows the timer interrupt request (TimerIRq bit) to be propagated to + * pin IRQ + */ +#define MFRC522_BIT_COML_EN_TIMER_I_EN BIT0 + + + +/** + * @brief 0 = Pin IRQ is an open-drain output pin + * 1 = Pin IRQ is a standard CMOS output pin + */ +#define MFRC522_BIT_DIVL_EN_IRQ_PUSH_PULL BIT7 + +/** + * @brief Allows the MFIN active interrupt request to be propagated to pin IRQ + */ +#define MFRC522_BIT_DIVL_EN_MFIN_ACT_I_EN BIT4 + +/** + * @brief Allows the CRC interrupt request, indicated by the DivIrqReg + * register’s CRCIRq bit, to be propagated to pin IRQ + */ +#define MFRC522_BIT_DIVL_EN_CRC_I_EN BIT2 + + + +/** + * @brief 0 = Indicates that the marked bits in the ComIrqReg register are cleared + * 1 = Indicates that the marked bits in the ComIrqReg register are set + * + */ +#define MFRC522_BIT_COM_IRQ_SET_1 BIT7 + +/** + * @brief 1 = Set immediately after the last bit of the transmitted data was + * sent out + */ +#define MFRC522_BIT_COM_IRQ_TX_IRQ BIT6 + +/** + * @brief 1 = Receiver has detected the end of a valid data stream if the + * RxModeReg register’s RxNoErr bit is set to logic 1, the RxIRq bit + * is only set to logic 1 when data bytes are available in the FIFO + */ +#define MFRC522_BIT_COM_IRQ_RX_IRQ BIT5 + +/** + * @brief 1 = If a command terminates, for example, when the CommandReg changes + * its value from any command to the Idle command (see Table 149 on + * page 70) if an unknown command is started, the CommandReg register + * Command[3:0] value changes to the idle state and the IdleIRq bit + * is set. The microcontroller starting the Idle command does not set + * the IdleIRq bit + */ +#define MFRC522_BIT_COM_IRQ_IDLE_IRQ BIT4 + +/** + * @brief 1 = The Status1Reg register’s HiAlert bit is set in opposition to the + * HiAlert bit, the HiAlertIRq bit stores this event and can only be + * reset as indicated by the Set1 bit in this register + */ +#define MFRC522_BIT_COM_IRQ_HI_ALERT_IRQ BIT3 + +/** + * @brief 1 = Status1Reg register’s LoAlert bit is set in opposition to the + * LoAlert bit, the LoAlertIRq bit stores this event and can only be + * reset as indicated by the Set1 bit in this register + */ +#define MFRC522_BIT_COM_IRQ_LO_ALERT_IRQ BIT2 + +/** + * @brief 1 = Any error bit in the ErrorReg register is set + */ +#define MFRC522_BIT_COM_IRQ_ERR_IRQ BIT1 + +/** + * @brief 1 = The timer decrements the timer value in register TCounterValReg to zero + */ +#define MFRC522_BIT_COM_IRQ_TIMER_IRQ BIT0 + + + +/** + * @brief 0 = Indicates that the marked bits in the DivIrqReg register are cleared + * 1 = Indicates that the marked bits in the DivIrqReg register are set + */ +#define MFRC522_BIT_DIV_IRQ_SET_2 BIT7 + +/** + * @brief 1 = MFIN is active; this interrupt is set when either a rising or + * falling signal edge is detected + */ +#define MFRC522_BIT_DIV_IRQ_MFIN_ACT_IRQ BIT4 + +/** + * @brief 1 = The CalcCRC command is active and all data is processed + */ +#define MFRC522_BIT_DIV_IRQ_CRC_IRQ BIT2 + + + +/** + * @brief 1 = Data is written into the FIFO buffer by the host during the + * MFAuthent command or if data is written into the FIFO buffer by + * the host during the time between sending the last bit on the RF + * interface and receiving the last bit on the RF interface + */ +#define MFRC522_BIT_ERROR_WR_ERR BIT7 + +/** + * @brief 1 = Internal temperature sensor detects overheating, in which case the + * antenna drivers are automatically switched off + */ +#define MFRC522_BIT_ERROR_TEMP_ERR BIT6 + +/** + * @brief 1 = The host or a MFRC522’s internal state machine (e.g. receiver) + * tries to write data to the FIFO buffer even though it is already full + */ +#define MFRC522_BIT_ERROR_BUFFER_OVFL BIT4 + +/** + * @brief 1 = A bit-collision is detected; cleared automatically at receiver + * start-up phase; only valid during the bitwise anticollision at + * 106 kBd; always set to logic 0 during communication protocols at + * 212 kBd, 424 kBd and 848 kBd + */ +#define MFRC522_BIT_ERROR_COLL_ERR BIT3 + +/** + * @brief 1 = The RxModeReg register’s RxCRCEn bit is set and the CRC + * calculation fails; automatically cleared to logic 0 during + * receiver start-up phase + */ +#define MFRC522_BIT_ERROR_CRC_ERR BIT2 + +/** + * @brief 1 = Parity check failed; automatically cleared during receiver + * start-up phase; only valid for ISO/IEC 14443 A/MIFARE + * communication at 106 kBd + */ +#define MFRC522_BIT_ERROR_PARITY_ERR BIT1 + +/** + * @brief 1 = Set to logic 1 if the SOF is incorrect; automatically cleared + * during receiver start-up phase; bit is only valid for 106 kBd; + * during the MFAuthent command, the ProtocolErr bit is set to + * logic 1 if the number of bytes received in one data stream is + * incorrect + */ +#define MFRC522_BIT_ERROR_PROTOCOL_ERR BIT0 + + + +/** + * @brief 1 = The CRC result is zero; for data transmission and reception, the + * CRCOk bit is undefined: use the ErrorReg register’s CRCErr bit; + * indicates the status of the CRC coprocessor, during calculation + * the value changes to logic 0, when the calculation is done + * correctly the value changes to logic 1 + */ +#define MFRC522_BIT_STATUS_1_CRC_OK BIT6 + +/** + * @brief 1 = The CRC calculation has finished; only valid for the CRC + * coprocessor calculation using the CalcCRC command + */ +#define MFRC522_BIT_STATUS_1_CRC_READY BIT5 + +/** + * @brief Indicates if any interrupt source requests attention with respect to + * the setting of the interrupt enable bits: see the ComIEnReg and + * DivIEnReg registers + */ +#define MFRC522_BIT_STATUS_1_IRQ BIT4 + +/** + * @brief 1 = MFRC522’s timer unit is running, i.e. the timer will decrement the + * TCounterValReg register with the next timer clock; Remark: in + * gated mode, the TRunning bit is set to logic 1 when the timer is + * enabled by TModeReg register’s TGated[1:0] bits; this bit is not + * influenced by the gated signal + */ +#define MFRC522_BIT_STATUS_1_T_RUNNING BIT3 + +/** + * @brief 1 = The number of bytes stored in the FIFO buffer corresponds to equation: + * HiAlert = (64 - FIFOLength) <= WaterLevel + * example: + * FIFO length = 60, WaterLevel = 4 -> HiAlert = 1 + * FIFO length = 59, WaterLevel = 4 -> HiAlert = 0 + */ +#define MFRC522_BIT_STATUS_1_HI_ALERT BIT1 + +/** + * @brief 1 = The number of bytes stored in the FIFO buffer corresponds to equation: + * LoAlert = FIFOLength <= WaterLevel + * example: + * FIFO length = 4, WaterLevel = 4 -> LoAlert = 1 + * FIFO length = 5, WaterLevel = 4 -> LoAlert = 0 + */ +#define MFRC522_BIT_STATUS_1_LO_ALERT BIT0 + + + +/** + * @brief 1 = Clears the temperature error if the temperature is below the alarm + * limit of 125 °C + */ +#define MFRC522_BIT_STATUS_2_TEMP_SENS_CLEAR BIT7 + +/** + * @brief I2C-bus input filter settings: + * 0 = the I2C-bus input filter is set to the I2C-bus protocol used + * 1 = the I2C-bus input filter is set to the High-speed mode independent + * of the I2C-bus protocol + */ +#define MFRC522_BIT_STATUS_2_I2C_FORCE_HS BIT6 + +/** + * @brief Indicates that the MIFARE Crypto1 unit is switched on and therefore + * all data communication with the card is encrypted; can only be set to + * logic 1 by a successful execution of the MFAuthent command; only valid + * in Read/Write mode for MIFARE standard cards; this bit is cleared by + * software + */ +#define MFRC522_BIT_STATUS_2_MF_CRYPTO_1_ON BIT3 + +/** + * @brief Shows the state of the transmitter and receiver state machines: + * + * 000 idle + * 001 wait for the BitFramingReg register’s StartSend bit + * 010 TxWait: wait until RF field is present if the TModeReg register’s + * TxWaitRF bit is set to logic 1; the minimum time for TxWait is + * defined by the TxWaitReg register + * 011 transmitting + * 100 RxWait: wait until RF field is present if the TModeReg register’s + * TxWaitRF bit is set to logic 1; the minimum time for RxWait is + * defined by the RxWaitReg register + * 101 wait for data + * 110 receiving + * + * MFRC522_REG_STATUS_2[2:0] + */ +#define MFRC522_BITMASK_STATUS_2_MODEM_STATE_2 0x07 + + + +/** + * @brief Data input and output port for the internal 64-byte FIFO buffer; + * FIFO buffer acts as parallel in/parallel out converter for all serial + * data stream inputs and outputs + * + * MFRC522_REG_FIFO_DATA[7:0] + */ +#define MFRC522_BITMASK_FIFO_DATA 0xFF + + + +/** + * @brief 1 = Immediately clears the internal FIFO buffer’s read and write + * pointer and ErrorReg register’s BufferOvfl bit; reading this bit + * always returns 0 + */ +#define MFRC522_BIT_FIFO_LEVEL_FLUSH_BUFFER BIT7 + +/** + * @brief Indicates the number of bytes stored in the FIFO buffer; writing to the + * FIFODataReg register increments and reading decrements the FIFOLevel + * value + * + * MFRC522_REG_FIFO_LEVEL[6:0] + */ +#define MFRC522_BITMASK_FIFO_LEVEL_FIFO_LEVEL 0x7F + + + +/** + * @brief Defines a warning level to indicate a FIFO buffer overflow or underflow: + * Status1Reg register’s HiAlert bit is set to logic 1 if the remaining + * number of bytes in the FIFO buffer space is equal to, or less than the + * defined number of WaterLevel bytes. + * Status1Reg register’s LoAlert bit is set to logic 1 if equal to, or + * less than the WaterLevel bytes in the FIFO buffer. + * Remark: to calculate values for HiAlert and LoAlert see Section + * 9.3.1.8 on page 42. + * + * MFRC522_REG_WATER_LEVEL[5:0] + */ +#define MFRC522_BITMASK_WATER_LEVEL_WATER_LEVEL 0x3F + + + +/** + * @brief 1 = Timer stops immediately; + * reading this bit always returns it to logic 0 + */ +#define MFRC522_BIT_CONTROL_T_STOP_NOW BIT7 + +/** + * @brief 1 = Timer starts immediately; + * reading this bit always returns it to logic 0 + */ +#define MFRC522_BIT_CONTROL_T_START_NOW BIT6 + +/** + * @brief Indicates the number of valid bits in the last received byteif this + * value is 000b, the whole byte is valid + * + * MFRC522_REG_CONTROL[2:0] + */ +#define MFRC522_BITMASK_CONTROL_RX_LAST_BITS 0x07 + + + +/** + * @brief Starts the transmission of data; only valid in combination with the + * Transceive command + */ +#define MFRC522_BIT_BIT_FRAMING_START_SEND BIT7 + +/** + * @brief Used for reception of bit-oriented frames: defines the bit position + * for the first bit received to be stored in the FIFO buffer + * example: + * 0 = LSB of the received bit is stored at bit position 0, the second + * received bit is stored at bit position 1 + * 1 = LSB of the received bit is stored at bit position 1, the second + * received bit is stored at bit position 2 + * 7 = LSB of the received bit is stored at bit position 7, the second + * received bit is stored in the next byte that follows at bit position 0; + * + * These bits are only to be used for bitwise anticollision at 106 kBd, + * for all other modes they are set to 0 + * + * MFRC522_REG_BIT_FRAMING[6:4] + */ +#define MFRC522_BITMASK_BIT_FRAMING_RX_ALIGN 0x70 + +/** + * @brief Used for transmission of bit oriented frames: defines the number of + * bits of the last byte that will be transmitted; 000b indicates that + * all bits of the last byte will be transmitted + * + * BIT_FRAMING[2:0] + */ +#define MFRC522_BIT_BIT_FRAMING_TX_LAST_BITS 0x07 + + + +/** + * @brief 0 = All received bits will be cleared after a collision; only used + * during bitwise anticollision at 106 kBd, otherwise it is set to logic 1 + */ +#define MFRC522_BIT_COLL_VALUES_AFTER_COLL BIT7 + +/** + * @brief 1 = No collision detected or the position of the collision is out of + * the range of CollPos[4:0] + */ +#define MFRC522_BIT_COLL_COLL_POS_NOT_VALID BIT5 + +/** + * @brief Shows the bit position of the first detected collision in a received frame; + * only data bits are interpreted; example: + * 00h = indicates a bit-collision in the 32nd bit + * 01h = indicates a bit-collision in the 1st bit + * 08h = indicates a bit-collision in the 8th bit + * + * These bits will only be interpreted if the CollPosNotValid bit is set + * to logic 0 + * + * MFRC522_REG_COLL[4:0] + */ +#define MFRC522_BITMASK_COLL_COLL_POS 0x1F + + + +/** + * @brief 1 = CRC coprocessor calculates the CRC with MSB first; in the + * CRCResultReg register the values for the CRCResultMSB[7:0] bits + * and the CRCResultLSB[7:0] bits are bit reversed; Remark: during RF + * communication this bit is ignored + */ +#define MFRC522_BIT_MODE_MSB_FIRST BIT7 + +/** + * @brief 1 = Transmitter can only be started if an RF field is generated + */ +#define MFRC522_BIT_MODE_TX_WAIT_RF BIT5 + +/** + * @brief Defines the polarity of pin MFIN; Remark: the internal envelope signal + * is encoded active LOW, changing this bit generates a MFinActIRq event + * + * 0 = polarity of pin MFIN is active LOW + * 1 = polarity of pin MFIN is active HIGH + */ +#define MFRC522_BIT_MODE_POL_MFIN BIT3 + +/** + * @brief Defines the preset value for the CRC coprocessor for the CalcCRC + * command; Remark: during any communication, the preset values are + * selected automatically according to the definition of bits in the + * RxModeReg and TxModeReg registers + * + * 00 = 0000h + * 01 = 6363h + * 10 = A671h + * 11 = FFFFh + * + * MFRC522_REG_MODE[1:0] + */ +#define MFRC522_BITMASK_MODE_CRC_PRESET 0x03 + + + +/** + * @brief 1 = Enables CRC generation during data transmission; Remark: can only + * be set to logic 0 at 106 kBd + */ +#define MFRC522_BIT_TX_MODE_TX_CRC_EN BIT7 + +/** + * @brief Defines the bit rate during data transmission; the MFRC522 handles + * transfer speeds up to 848 kBd + * + * 000 = 106 kBd + * 001 = 212 kBd + * 010 = 424 kBd + * 011 = 848 kBd + * + * MFRC522_REG_TX_MODE[6:4] + */ +#define MFRC522_BITMASK_TX_MODE_TX_SPEED 0x70 + +/** + * @brief 1 = Modulation of transmitted data is inverted + */ +#define MFRC522_BIT_TX_MODE_INV_MOD BIT3 + + + +/** + * @brief 1 = Enables the CRC calculation during reception; Remark: can only be + * set to logic 0 at 106 kBd + */ +#define MFRC522_BIT_RX_MODE_RX_CRC_EN BIT7 + +/** + * @brief Defines the bit rate while receiving data; the MFRC522 handles + * transfer speeds up to 848 kBd + * + * 000 = 106 kBd + * 001 = 212 kBd + * 010 = 424 kBd + * 011 = 848 kBd + * + * MFRC522_REG_RX_MODE[6:4] + */ +#define MFRC522_BITMASK_RX_MODE_RX_SPEED 0x70 + +/** + * @brief 1 = An invalid received data stream (less than 4 bits received) will be + * ignored and the receiver remains active + */ +#define MFRC522_BIT_RX_MODE_RX_NO_ERR BIT3 + +/** + * @brief 0 = Receiver is deactivated after receiving a data frame + * 1 = Able to receive more than one data frame; only valid for data + * rates above 106 kBd in order to handle the polling command; after + * setting this bit the Receive and Transceive commands will not + * terminate automatically. Multiple reception can only be + * deactivated by writing any command (except the Receive command) to + * the CommandReg register, or by the host clearing the bit; if set + * to logic 1, an error byte is added to the FIFO buffer at the end + * of a received data stream which is a copy of the ErrorReg register + * value. For the MFRC522 version 2.0 the CRC status is reflected in + * the signal CRCOk, which indicates the actual status of the CRC + * coprocessor. For the MFRC522 version 1.0 the CRC status is + * reflected in the signal CRCErr. + */ +#define MFRC522_BIT_RX_MODE_RX_MULTIPLE BIT2 + + + +/** + * @brief 1 = Output signal on pin TX2 inverted when driver TX2 is enabled + */ +#define MFRC522_BIT_TX_CONTROL_INV_TX2_RF_ON BIT7 + +/** + * @brief 1 = Output signal on pin TX1 inverted when driver TX1 is enabled + */ +#define MFRC522_BIT_TX_CONTROL_INV_TX1_RF_ON BIT6 + +/** + * @brief 1 = Output signal on pin TX2 inverted when driver TX2 is disabled + */ +#define MFRC522_BIT_TX_CONTROL_INV_TX2_RF_OFF BIT5 + +/** + * @brief 1 = Output signal on pin TX1 inverted when driver TX1 is disabled + */ +#define MFRC522_BIT_TX_CONTROL_INV_TX1_RF_OFF BIT4 + +/** + * @brief 0 = Tx2CW bit is enabled to modulate the 13.56 MHz energy carrier + * 1 = Output signal on pin TX2 continuously delivers the unmodulated + * 13.56 MHz energy carrier + */ +#define MFRC522_BIT_TX_CONTROL_TX2_CW BIT3 + +/** + * @brief 1 = Output signal on pin TX2 delivers the 13.56 MHz energy carrier + * modulated by the transmission data + */ +#define MFRC522_BIT_TX_CONTROL_TX2_RF_EN BIT1 + +/** + * @brief 1 = Output signal on pin TX1 delivers the 13.56 MHz energy carrier + * modulated by the transmission data + */ +#define MFRC522_BIT_TX_CONTROL_TX1_RF_EN BIT0 + + + +/** + * @brief 1 = Forces a 100 % ASK modulation independent of the ModGsPReg + * register setting + */ +#define MFRC522_BIT_TX_ASK_FORCE_100_ASK BIT6 + + + +/** + * @brief Selects the input of drivers TX1 and TX2 + * + * 00 = 3-state; in soft power-down the drivers are only in 3-state mode + * if the DriverSel[1:0] value is set to 3-state mode + * 01 = Modulation signal (envelope) from the internal encoder, Miller + * pulse encoded + * 10 = Modulation signal (envelope) from pin MFIN + * 11 = HIGH; the HIGH level depends on the setting of bits + * InvTx1RFOn/InvTx1RFOff and InvTx2RFOn/InvTx2RFOff + * + * MFRC522_REG_TX_SEL[5:4] + */ +#define MFRC522_BITMASK_TX_SEL_DRIVER_SEL 0x30 + +/** + * @brief Selects the input for pin MFOUT + * + * 0000 = 3-state + * 0001 = LOW + * 0010 = HIGH + * 0011 = Test bus signal as defined by the TestSel1Reg register’s + * TstBusBitSel[2:0] value + * 0100 = Modulation signal (envelope) from the internal encoder, Miller + * pulse encoded + * 0101 = Serial data stream to be transmitted, data stream before Miller + * encoder + * 0111 = Serial data stream received, data stream after Manchester + * decoder + * + * MFRC522_REG_TX_SEL[3:0] + */ +#define MFRC522_BITMASK_TX_SEL_MF_OUT_SEL 0x0F + + + +/** + * @brief Selects the input of the contactless UART + * + * 00 = Constant LOW + * 01 = Manchester with subcarrier from pin MFIN + * 10 = Modulated signal from the internal analog module, default + * 11 = NRZ coding without subcarrier from pin MFIN which is only valid + * for transfer speeds above 106 kBd + * + * MFRC522_REG_RX_SEL[7:6] + */ +#define MFRC522_BITMASK_RX_SEL_UART_SEL 0xC0 + +/** + * @brief After data transmission the activation of the receiver is delayed for + * RxWait bit-clocks, during this ‘frame guard time’ any signal on pin RX + * is ignored; this parameter is ignored by the Receive command; all + * other commands, such as Transceive, MFAuthent use this parameter; the + * counter starts immediately after the external RF field is switched on + * + * MFRC522_REG_RX_SEL[5:0] + */ +#define MFRC522_BITMASK_RX_SEL_RX_WAIT 0x3F + + + +/** + * @brief Defines the minimum signal strength at the decoder input that will be + * accepted; if the signal strength is below this level it is not + * evaluated + * + * MFRC522_REG_RX_THRESHHOLD[7:4] + */ +#define MFRC522_BITMASK_RX_THRESHHOLD_MIN_LEVEL 0xF0 + +/** + * @brief Defines the minimum signal strength at the decoder input that must be + * reached by the weaker half-bit of the Manchester encoded signal to + * generate a bit-collision relative to the amplitude of the stronger + * half-bit + * + * MFRC522_REG_RX_THRESHHOLD[2:0] + */ +#define MFRC522_BITMASK_RX_THRESHHOLD_COLL_LEVEL 0x07 + + + +/** + * @brief Defines the use of I and Q channel during reception; Remark: the FixIQ + * bit must be set to logic 0 to enable the following settings: + * + * 00 = Selects the stronger channel + * 01 = Selects the stronger channel and freezes the selected channel + * during communication + * + * MFRC522_REG_DEMOD[7:6] + */ +#define MFRC522_BITMASK_DEMOD_ADD_IQ 0xC0 + +/** + * @brief 1 = If AddIQ[1:0] are set to X0b, the reception is fixed to I channel + * If AddIQ[1:0] are set to X1b, the reception is fixed to Q channel + */ +#define MFRC522_BIT_DEMOD_FIX_IQ BIT5 + +/** + * @brief Available on RC522 version 1.0 and version 2.0: + * If set to logic 0 the following formula is used to calculate the timer + * frequency of the prescaler: + * f_timer = 13.56 MHz / (2 * TPreScaler + 1) + + * Only available on version 2.0: + * If set to logic 1 the following formula is used to calculate the timer + * frequency of the prescaler: + * f_timer = 13.56 MHz / (2 * TPreScaler + 2) + * Default TPrescalEven bit is logic 0, find more information on the + * prescaler in Section 8.5. + */ +#define MFRC522_BIT_DEMOD_T_PRESCAL_EVEN BIT4 + +/** + * @brief Changes the time-constant of the internal PLL during data reception + * Remark: if set to 00b the PLL is frozen during data reception + * + * MFRC522_REG_DEMOD[3:2] + */ +#define MFRC522_BITMASK_DEMOD_TAU_RCV 0x0C + +/** + * @brief Changes the time-constant of the internal PLL during burst + * + * MFRC522_REG_DEMOD[1:0] + */ +#define MFRC522_BITMASK_DEMOD_TAU_SYNC 0x03 + + + +/** + * @brief Defines the additional response time; 7 bits are added to the value of + * the register bit by default + * + * MFRC522_REG_MF_TX[1:0] + */ +#define MFRC522_BITMASK_MF_TX_TX_WAIT 0x03 + + + +/** + * @brief 1 = Generation of the parity bit for transmission and the parity check + * for receiving is switched off; the received parity bit is handled + * like a data bit + */ +#define MFRC522_BIT_MF_RX_PARITY_DISABLE BIT4 + + + +/** + * @brief Factor BR_T0 adjusts the transfer speed: for description, see Section + * 8.1.3.2 on page 12 + * + * MFRC522_REG_SERIAL_SPEED[7:5] + */ +#define MFRC522_BITMASK_SERIAL_SPEED_BR_T0 0xE0 + +/** + * @brief Factor BR_T1 adjusts the transfer speed: for description, see Section + * 8.1.3.2 on page 12 + * + * MFRC522_REG_SERIAL_SPEED[4:0] + */ +#define MFRC522_BITMASK_SERIAL_SPEED_BR_T1 0x1F + + + +/** + * @brief Shows the value of the CRCResultReg register’s most significant byte; + * only valid if Status1Reg register’s CRCReady bit is set to logic 1 + * + * MFRC522_REG_CRC_RESULT_MSB[7:0] + */ +#define MFRC522_BITMASK_CRC_RESULT_MSB_CRC_RESULT_MSB 0xFF + +/** + * @brief Shows the value of the least significant byte of the CRCResultReg + * register; only valid if Status1Reg register’s CRCReady bit is set to + * logic 1 + * + * MFRC522_REG_CRC_RESULT_LSB[7:0] + */ +#define MFRC522_BITMASK_CRC_RESULT_LSB_CRC_RESULT_LSB 0xFF + + + +/** + * @brief Defines the width of the Miller modulation as multiples of the carrier + * frequency (ModWidth + 1 / f_clk); the maximum value is half the bit + * period + * + * MFRC522_REG_MOD_WIDTH[7:0] + */ +#define MFRC522_BITMASK_MOD_WIDTH 0xFF + + + +/** + * @brief Defines the receiver’s signal voltage gain factor: + * + * 000 = 18 dB + * 001 = 23 dB + * 010 = 18 dB + * 011 = 23 dB + * 100 = 33 dB + * 101 = 38 dB + * 110 = 43 dB + * 111 = 48 dB + * + * MFRC522_REG_RF_CFG[6:4] + * + * @see mfrc522_pcd_rx_gain + */ +#define MFRC522_BITMASK_RF_CFG_RX_GAIN 0x70 + + +/** + * @brief Defines the conductance of the output n-driver during periods without + * modulation which can be used to regulate the output power and + * subsequently current consumption and operating distance; Remark: the + * conductance value is binary-weighted; during soft Power-down mode the + * highest bit is forced to logic 1; value is only used if driver TX1 or + * TX2 is switched on + * + * MFRC522_REG_GS_N[7:4] + */ +#define MFRC522_BITMASK_GS_N_CW_GS_N 0xF0 + +/** + * @brief Defines the conductance of the output n-driver during periods without + * modulation which can be used to regulate the modulation index; + * Remark: the conductance value is binary weighted; during soft + * Power-down mode the highest bit is forced to logic 1; value is only + * used if driver TX1 or TX2 is switched on + * + * MFRC522_REG_GS_N[3:0] + */ +#define MFRC522_BITMASK_GS_N_MOD_GS_N 0x0F + + + +/** + * @brief Defines the conductance of the p-driver output which can be used to + * regulate the output power and subsequently current consumption and + * operating distance; Remark: the conductance value is binary weighted; + * during soft Power-down mode the highest bit is forced to logic 1 + * + * MFRC522_REG_CW_GS_P[5:0] + */ +#define MFRC522_BITMASK_CW_GS_P_CW_GS_P 0x3F + + + +/** + * @brief Defines the conductance of the p-driver output during modulation which + * can be used to regulate the modulation index; Remark: the conductance + * value is binary weighted; during soft Power-down mode the highest bit + * is forced to logic 1; if the TxASKReg register’s Force100ASK bit is + * set to logic 1 the value of ModGsP has no effect + * + * MFRC522_REG_MOD_GS_P[5:0] + */ +#define MFRC522_BITMASK_MOD_GS_P_MOD_GS_P 0x3F + + + +/** + * @brief 0 = Indicates that the timer is not influenced by the protocol + * 1 = Timer starts automatically at the end of the transmission in all + * communication modes at all speeds; if the RxModeReg register’s + * RxMultiple bit is not set, the timer stops immediately after + * receiving the 5th bit (1 startbit, 4 data bits); if the RxMultiple + * bit is set to logic 1 the timer never stops, in which case the + * timer can be stopped by setting the ControlReg register’s TStopNow + * bit to logic 1 + */ +#define MFRC522_BIT_T_MODE_T_AUTO BIT7 + +/** + * @brief Internal timer is running in gated mode; Remark: in gated mode, the + * Status1Reg register’s TRunning bit is logic 1 when the timer is + * enabled by the TModeReg register’s TGated[1:0] bits; this bit does not + * influence the gating signal + * + * 00 = non-gated mode + * 01 = gated by pin MFIN + * 10 = gated by pin AUX1 + * + * MFRC522_REG_T_MODE[6:5] + */ +#define MFRC522_BITMASK_T_MODE_T_GATED 0x60 + +/** + * @brief 0 = Timer decrements to 0 and the ComIrqReg register’s TimerIRq bit is + * set to logic 1 + * 1 = Timer automatically restarts its count-down from the 16-bit timer + * reload value instead of counting down to zero + */ +#define MFRC522_BIT_T_MODE_T_AUTO_RESTART BIT4 + +/** + * @brief Defines the higher 4 bits of the TPrescaler value; + * + * The following formula is used to calculate the timer frequency if the + * DemodReg register’s TPrescalEven bit in Demot Regis set to logic 0: + * f_timer = 13.56 MHz / (2 * TPreScaler + 1) + * Where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo] (TPrescaler value + * on 12 bits) (Default TPrescalEven bit is logic 0); + * + * The following formula is used to calculate the timer frequency if the + * DemodReg register’s TPrescalEven bit is set to logic 1: + * f_timer = 13.56 MHz / (2 * TPreScaler + 2) +* See Section 8.5 “Timer unit”. + * + * MFRC522_REG_T_MODE[3:0] + */ +#define MFRC522_BITMASK_T_MODE_T_PRESCALER_HI 0x0F + +/** + * @brief Defines the lower 8 bits of the TPrescaler value + * The following formula is used to calculate the timer frequency if the + * DemodReg register’s TPrescalEven bit is set to logic 0: + * f_timer = 13.56 MHz / (2 * TPreScaler + 1) + * Where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo] (TPrescaler value on + * 12 bits) (Default TPrescalEven bit is logic 0) + * + * The following formula is used to calculate the timer frequency if the + * DemodReg register’s TPrescalEven bit inDemoReg is set to logic 1: + * f_timer = 13.56 MHz / (2 * TPreScaler + 2) + * See Section 8.5 “Timer unit”. + * + * MFRC522_REG_T_PRESCALER[7:0] + */ +#define MFRC522_BITMASK_T_PRESCALER_T_PRESCALER_LO 0xFF + + + +/** + * @brief Defines the higher 8 bits of the 16-bit timer reload value; on a start + * event, the timer loads the timer reload value; changing this register + * affects the timer only at the next start event + * + * MFRC522_REG_T_RELOAD_MSB[7:0] + */ +#define MFRC522_BITMASK_T_RELOAD_MSB_T_RELOAD_VAL_HI 0xFF + +/** + * @brief Defines the lower 8 bits of the 16-bit timer reload value; on a start + * event, the timer loads the timer reload value; changing this register + * affects the timer only at the next start event + * + * MFRC522_REG_T_RELOAD_LSB[7:0] + */ +#define MFRC522_BITMASK_T_RELOAD_LSB_T_RELOAD_VAL_LO 0xFF + + + +/** + * @brief Timer value higher 8 bits + * + * MFRC522_REG_T_COUNTER_VAL_MSB[7:0] + */ +#define MFRC522_BITMASK_T_COUNTER_VAL_MSB_T_COUNTER_VAL_HI 0xFF + +/** + * @brief Timer value lower 8 bits + * + * MFRC522_REG_T_COUNTER_VAL_LSB[7:0] + */ +#define MFRC522_BITMASK_T_COUNTER_VAL_LSB_T_COUNTER_VAL_LO 0xFF + +#ifdef __cplusplus +} +#endif + +#endif /* MFRC522_REGS_H */ +/** @} */ diff --git a/drivers/mfrc522/mfrc522.c b/drivers/mfrc522/mfrc522.c new file mode 100644 index 000000000000..55e5d4467ad2 --- /dev/null +++ b/drivers/mfrc522/mfrc522.c @@ -0,0 +1,2319 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * 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_mfrc522 + * @brief Device driver for the MFRC522 controller + * @author Hendrik van Essen + * @file + * @{ + */ + +#include +#include + +#include "periph/gpio.h" +#include "ztimer.h" + +#include "mfrc522.h" +#include "mfrc522_regs.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define MFRC522_STATUS_CODE_NAMES_ARRAY_SIZE (MFRC522_STATUS_UNKNOWN + 1) + +const char mfrc522_status_code_names[MFRC522_STATUS_CODE_NAMES_ARRAY_SIZE][50] = { + "Success.", + "Error in communication.", + "Collision detected.", + "Timeout in communication.", + "A buffer is not big enough.", + "Internal error in the code. Should not happen.", + "Invalid argument.", + "The CRC_A does not match.", + "A MIFARE PICC responded with NAK.", + "Unknown status." +}; + +#define MFRC522_PICC_TYPE_NAMES_ARRAY_SIZE (MFRC522_PICC_TYPE_UNKNOWN + 1) + +const char mfrc522_picc_type_names[MFRC522_PICC_TYPE_NAMES_ARRAY_SIZE][50] = { + "PICC compliant with ISO/IEC 14443-4", + "PICC compliant with ISO/IEC 18092 (NFC)", + "MIFARE Mini, 320 bytes", + "MIFARE 1KB", + "MIFARE 4KB", + "MIFARE Ultralight or Ultralight C", + "MIFARE Plus", + "MIFARE DESFire", + "MIFARE TNP3XXX", + "SAK indicates UID is not complete.", + "Unknown type" +}; + +/* + * @brief Write single byte to a given register of the device. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] reg Register to write to + * @param[in] value Byte to write# + */ +static void _device_write(mfrc522_t* dev, + mfrc522_pcd_register_t reg, uint8_t value); + +/* + * @brief Write n bytes to a given register of the device. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] reg Register to write to + * @param[in] count Number of bytes to write + * @param[in] values Bytes to write + */ +static void _device_write_n(mfrc522_t* dev, + mfrc522_pcd_register_t reg, + uint8_t count, + const uint8_t *values); + +/* + * @brief Read single byte from a given register of the device. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] reg Register to read from + * @param[in] byte uint8_t pointer to store the value in + */ +static void _device_read(mfrc522_t* dev, mfrc522_pcd_register_t reg, + uint8_t *value); + +/* + * @brief Read n bytes from a given register of the device. + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] reg Register to read from + * @param[in] count Number of bytes to read + * @param[out] values Byte array to store the values in + * @param[in] rx_align Only bit positions rxAlign..7 in values[0] are updated. + */ +static void _device_read_n(mfrc522_t* dev, + mfrc522_pcd_register_t reg, + uint8_t count, + uint8_t *values, + uint8_t rx_align); + +/** + * @brief Helper function for the two-step MIFARE Classic protocol operations + * Decrement, Increment and Restore + * + * @param[in] dev Device descriptor of the MFRC522 + * @param[in] command Command to execute - MFRC522_PICC_CMD_MF_INCREMENT, + * MFRC522_PICC_CMD_MF_DECREMENT + * or MFRC522_PICC_CMD_MF_RESTORE + * @param[in] block_addr Block (0-0xff) number + * @param[in] data Data to transfer in step 2 + * + * @return MFRC522_STATUS_OK on success, MFRC522_STATUS_??? otherwise + * @return MFRC522_STATUS_INVALID if command was not out of + * [MFRC522_PICC_CMD_MF_INCREMENT, MFRC522_PICC_CMD_MF_DECREMENT, + * MFRC522_PICC_CMD_MF_RESTORE], MFRC522_STATUS_??? otherwise + */ +static mfrc522_status_code_t +mfrc522_mifare_two_step_helper(mfrc522_t *dev, mfrc522_picc_command_t command, + uint8_t block_addr, int32_t data); + +void mfrc522_pcd_set_register_bitmask(mfrc522_t *dev, + mfrc522_pcd_register_t reg, uint8_t mask) +{ + assert(dev); + + uint8_t tmp; + _device_read(dev, reg, &tmp); + + /* set bit mask */ + _device_write(dev, reg, tmp | mask); +} + +void mfrc522_pcd_clear_register_bitmask(mfrc522_t *dev, + mfrc522_pcd_register_t reg, uint8_t mask) +{ + assert(dev); + + uint8_t tmp; + _device_read(dev, reg, &tmp); + + /* clear bit mask */ + _device_write(dev, reg, tmp & (~mask)); +} + +mfrc522_status_code_t mfrc522_pcd_calculate_crc(mfrc522_t *dev, + const uint8_t *data, + uint8_t length, + uint8_t *result) +{ + assert(dev); + + /* Stop any active command */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE); + + /* Clear the CRCIRq interrupt request bit */ + _device_write(dev, MFRC522_REG_DIV_IRQ, MFRC522_BIT_DIV_IRQ_CRC_IRQ); + + /* FlushBuffer = 1, FIFO initialization */ + _device_write(dev, MFRC522_REG_FIFO_LEVEL, 0x80); + + /* Write data to the FIFO */ + _device_write_n(dev, MFRC522_REG_FIFO_DATA, length, data); + + /* Start the calculation */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_CALC_CRC); + + /* Wait for the CRC calculation to complete */ + for (uint16_t i = 5000; i > 0; i--) { + + /* 5000 * 18 us sums up to 90 ms */ + ztimer_sleep(ZTIMER_USEC, 18); + + uint8_t n; + _device_read(dev, MFRC522_REG_DIV_IRQ, &n); + + /* CRCIRq bit set - calculation done */ + if (n & MFRC522_BIT_DIV_IRQ_CRC_IRQ) { + /* Stop calculating CRC for new content in the FIFO */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE); + /* Transfer the result from the registers to the result buffer */ + _device_read(dev, MFRC522_REG_CRC_RESULT_LSB, &result[0]); + _device_read(dev, MFRC522_REG_CRC_RESULT_MSB, &result[1]); + + return MFRC522_STATUS_OK; + } + } + + /* 90 ms passed and nothing happened. Communication with the MFRC522 might be down. */ + return MFRC522_STATUS_TIMEOUT; +} + +int mfrc522_pcd_init(mfrc522_t *dev, const mfrc522_params_t *params) +{ + assert(dev); + assert(params); + + dev->params = *params; + + int rc = 0; + + /* Initialize SPI bus */ + rc = spi_init_cs(dev->params.spi_dev, dev->params.cs_pin); + if (rc < 0) { + puts("error: unable to initialize the given chip select line"); + return rc; + } + + bool hard_reset = false; + + /* If a valid pin number has been set, pull device out of power down / reset state */ + if (dev->params.rst_pin != 0) { + + gpio_init(dev->params.rst_pin, GPIO_IN); + + if (gpio_read(dev->params.rst_pin) == 0) { + /* The MFRC522 chip is in power down mode */ + + gpio_init(dev->params.rst_pin, GPIO_OUT); + gpio_write(dev->params.rst_pin, 0); + /* 8.8.1 Reset timing requirements says about 100ns. Let us be generous: 2μs */ + ztimer_sleep(ZTIMER_USEC, 2); + /* Exit power down mode. This triggers a hard reset. */ + gpio_write(dev->params.rst_pin, 1); + /* Section 8.8.2 in the datasheet says the oscillator start-up time + * is the start-up time of the crystal + 37,74μs. Let us be generous: 50ms */ + ztimer_sleep(ZTIMER_MSEC, 50); + hard_reset = true; + } + } + + if (!hard_reset) { + mfrc522_pcd_reset(dev); + } + + /* Reset baud rates */ + _device_write(dev, MFRC522_REG_TX_MODE, 0x00); + _device_write(dev, MFRC522_REG_RX_MODE, 0x00); + + /* Reset MFRC522_REG_MOD_WIDTH */ + _device_write(dev, MFRC522_REG_MOD_WIDTH, 0x26); + + /* When communicating with a PICC we need a timeout if something goes wrong. + * f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. + * TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. */ + + /* TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds */ + _device_write(dev, MFRC522_REG_T_MODE, 0x80); + + /* TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer = + * 40kHz, i.e. a timer period of 25μs. */ + _device_write(dev, MFRC522_REG_T_PRESCALER, 0xA9); + + /* Reload timer with 0x3E8 = 1000, i.e. 25ms before timeout */ + _device_write(dev, MFRC522_REG_T_RELOAD_MSB, 0x03); + _device_write(dev, MFRC522_REG_T_RELOAD_LSB, 0xE8); + + /* Default 0x00. Force a 100 % ASK modulation independent of the + * MFRC522_REG_MOD_GS_P register setting */ + _device_write(dev, MFRC522_REG_TX_ASK, 0x40); + + /* Default 0x3F. Set the preset value for the CRC coprocessor for the + * MFRC522_CMD_CALC_CRC command to 0x6363 (ISO 14443-3 part 6.2.4) */ + _device_write(dev, MFRC522_REG_MODE, 0x3D); + + /* Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) */ + mfrc522_pcd_antenna_on(dev); + + return MFRC522_STATUS_OK; +} + +void mfrc522_pcd_reset(mfrc522_t *dev) +{ + assert(dev); + + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_SOFT_RESET); + + /* The datasheet does not mention how long the SoftRest command takes to + * complete. But the MFRC522 might have been in soft power-down mode + * (triggered by bit 4 of CommandReg) Section 8.8.2 in the datasheet says + * the oscillator start-up time is the start-up time of the crystal + 37,74μs. + * Let us be generous: 50ms. */ + + uint8_t count = 0; + uint8_t value; + do { + /* Wait for the PowerDown bit in CommandReg to be cleared (max 3x50ms) */ + ztimer_sleep(ZTIMER_MSEC, 50); + _device_read(dev, MFRC522_REG_COMMAND, &value); + } while ((value & MFRC522_BIT_COMMAND_POWER_DOWN) && (++count) < 3); +} + +void mfrc522_pcd_antenna_on(mfrc522_t *dev) +{ + assert(dev); + + uint8_t value; + _device_read(dev, MFRC522_REG_TX_CONTROL, &value); + + uint8_t tx_enabled_mask = + MFRC522_BIT_TX_CONTROL_TX1_RF_EN | MFRC522_BIT_TX_CONTROL_TX2_RF_EN; + + if ((value & tx_enabled_mask) != tx_enabled_mask) { + _device_write(dev, MFRC522_REG_TX_CONTROL, value | tx_enabled_mask); + } +} + +void mfrc522_pcd_antenna_off(mfrc522_t *dev) +{ + assert(dev); + + uint8_t tx_enabled_mask = + MFRC522_BIT_TX_CONTROL_TX1_RF_EN | MFRC522_BIT_TX_CONTROL_TX2_RF_EN; + + mfrc522_pcd_clear_register_bitmask(dev, MFRC522_REG_TX_CONTROL, tx_enabled_mask); +} + +mfrc522_pcd_rx_gain_t mfrc522_pcd_get_antenna_gain(mfrc522_t *dev) +{ + assert(dev); + + /* See 9.3.3.6 / table 98 + * Return value scrubbed with MFRC522_BITMASK_RF_CFG_RX_GAIN + * MFRC522_REG_RF_CFG may use reserved bits. */ + + uint8_t reg_content; + _device_read(dev, MFRC522_REG_RF_CFG, ®_content); + mfrc522_pcd_rx_gain_t gain = (reg_content & MFRC522_BITMASK_RF_CFG_RX_GAIN) >> 4; + + return gain; +} + +void mfrc522_pcd_set_antenna_gain(mfrc522_t *dev, mfrc522_pcd_rx_gain_t rx_gain) +{ + assert(dev); + + if (mfrc522_pcd_get_antenna_gain(dev) != rx_gain) { + + /* See 9.3.3.6 / table 98 + * Given rx_gain is scrubbed with MFRC522_BITMASK_RF_CFG_RX_GAIN + * MFRC522_REG_RF_CFG may use reserved bits.*/ + + mfrc522_pcd_clear_register_bitmask(dev, MFRC522_REG_RF_CFG, MFRC522_BITMASK_RF_CFG_RX_GAIN); + mfrc522_pcd_set_register_bitmask(dev, MFRC522_REG_RF_CFG, (rx_gain << 4) & MFRC522_BITMASK_RF_CFG_RX_GAIN); + } +} + +void mfrc522_pcd_soft_power_down(mfrc522_t *dev) +{ + assert(dev); + + uint8_t value; + _device_read(dev, MFRC522_REG_COMMAND, &value); + + /* set PowerDown bit to 1 */ + value |= MFRC522_BIT_COMMAND_POWER_DOWN; + _device_write(dev, MFRC522_REG_COMMAND, value); +} + +void mfrc522_pcd_soft_power_up(mfrc522_t *dev) +{ + assert(dev); + + uint8_t value; + _device_read(dev, MFRC522_REG_COMMAND, &value); + + /* set PowerDown bit to 0 */ + value &= ~MFRC522_BIT_COMMAND_POWER_DOWN; + _device_write(dev, MFRC522_REG_COMMAND, value); + + /* wait until PowerDown bit is cleared (this indicates end of wake-up procedure) */ + + /* create timer for timeout within 500 ms (just in case) */ + const uint64_t timeout = ztimer_now(ZTIMER_MSEC) + 500; + + while (ztimer_now(ZTIMER_MSEC) <= timeout) { + + _device_read(dev, MFRC522_REG_COMMAND, &value); + + /* if powerdown bit is 0 */ + if(!(value & MFRC522_BIT_COMMAND_POWER_DOWN)){ + /* wake up procedure is finished */ + break; + } + } +} + +mfrc522_status_code_t mfrc522_pcd_transceive_data(mfrc522_t *dev, + const uint8_t *send_data, + uint8_t send_len, + uint8_t *back_data, + uint8_t *back_len, + uint8_t *valid_bits, + uint8_t rx_align, + bool check_crc) +{ + assert(dev); + + uint8_t wait_irq = MFRC522_BIT_COM_IRQ_RX_IRQ | MFRC522_BIT_COM_IRQ_IDLE_IRQ; + + return mfrc522_pcd_communicate_with_picc(dev, MFRC522_CMD_TRANSCEIVE, wait_irq, + send_data, send_len, + back_data, back_len, + valid_bits, rx_align, check_crc); +} + +mfrc522_status_code_t mfrc522_pcd_communicate_with_picc(mfrc522_t *dev, + mfrc522_pcd_command_t command, + uint8_t wait_irq, + const uint8_t *send_data, + uint8_t send_len, + uint8_t *back_data, + uint8_t *back_len, + uint8_t *valid_bits, + uint8_t rx_align, + bool check_crc) +{ + assert(dev); + + /* prepare values for BitFramingReg */ + uint8_t tx_last_bits = valid_bits ? *valid_bits : 0; + + /* RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] */ + uint8_t bit_framing = (rx_align << 4) + tx_last_bits; + + /* stop any active command */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE); + + /* clear all seven interrupt request bits */ + uint8_t irq_bits = MFRC522_BIT_COM_IRQ_TIMER_IRQ + | MFRC522_BIT_COM_IRQ_ERR_IRQ + | MFRC522_BIT_COM_IRQ_LO_ALERT_IRQ + | MFRC522_BIT_COM_IRQ_HI_ALERT_IRQ + | MFRC522_BIT_COM_IRQ_IDLE_IRQ + | MFRC522_BIT_COM_IRQ_RX_IRQ + | MFRC522_BIT_COM_IRQ_TX_IRQ; + + _device_write(dev, MFRC522_REG_COM_IRQ, irq_bits); + + /* FlushBuffer = 1, FIFO initialization */ + _device_write(dev, MFRC522_REG_FIFO_LEVEL, 0x80); + + /* write send_data to the FIFO */ + _device_write_n(dev, MFRC522_REG_FIFO_DATA, send_len, send_data); + + /* bit adjustments */ + _device_write(dev, MFRC522_REG_BIT_FRAMING, bit_framing); + + /* execute the command */ + _device_write(dev, MFRC522_REG_COMMAND, command); + + if (command == MFRC522_CMD_TRANSCEIVE) { + /* StartSend=1, transmission of data starts */ + mfrc522_pcd_set_register_bitmask(dev, MFRC522_REG_BIT_FRAMING, 0x80); + } + + /* Wait for the command to complete. In mfrc522_pcd_init() we set the TAuto + * flag in TModeReg. This means the timer automatically starts when the PCD + * stops transmitting. */ + uint16_t i; + for (i = 2000; i > 0; i--) { + + /* 2000 * 18 us sums up to 36 ms */ + ztimer_sleep(ZTIMER_USEC, 18); + + uint8_t n; + _device_read(dev, MFRC522_REG_COM_IRQ, &n); + + /* One of the interrupts that signals success has been set */ + if (n & wait_irq) { + break; + } + + /* Timer interrupt - nothing received in 25ms */ + if (n & MFRC522_BIT_COM_IRQ_TIMER_IRQ) { + return MFRC522_STATUS_TIMEOUT; + } + } + + /* 36ms and nothing happened. Communication with the MFRC522 might be down. */ + if (i == 0) { + return MFRC522_STATUS_TIMEOUT; + } + + uint8_t error_reg_value; + _device_read(dev, MFRC522_REG_ERROR, &error_reg_value); + + uint8_t error_mask = MFRC522_BIT_ERROR_BUFFER_OVFL + | MFRC522_BIT_ERROR_PARITY_ERR + | MFRC522_BIT_ERROR_PROTOCOL_ERR; + + /* Stop now if any errors except collisions were detected */ + if (error_reg_value & error_mask) { + return MFRC522_STATUS_ERROR; + } + + uint8_t _valid_bits = 0; + + /* If the caller wants data back, get it from the MFRC522 */ + if (back_data && back_len) { + /* Number of bytes in the FIFO */ + uint8_t n; + _device_read(dev, MFRC522_REG_FIFO_LEVEL, &n); + if (n > *back_len) { + return MFRC522_STATUS_NO_ROOM; + } + /* Number of bytes returned */ + *back_len = n; + + /* Get received data from FIFO */ + _device_read_n(dev, MFRC522_REG_FIFO_DATA, n, back_data, rx_align); + + /* RxLastBits[2:0] indicates the number of valid bits in the last + * received byte. If this value is 000b, the whole byte is valid. */ + uint8_t tmp; + _device_read(dev, MFRC522_REG_CONTROL, &tmp); + _valid_bits = tmp & MFRC522_BITMASK_CONTROL_RX_LAST_BITS; + + if (valid_bits) { + *valid_bits = _valid_bits; + } + } + + if (error_reg_value & MFRC522_BIT_ERROR_COLL_ERR) { + return MFRC522_STATUS_COLLISION; + } + + /* Perform CRC_A validation if requested */ + if (back_data && back_len && check_crc) { + + /* In this case a MIFARE Classic NAK is not OK */ + if (*back_len == 1 && _valid_bits == 4) { + return MFRC522_STATUS_MIFARE_NACK; + } + + /* We need at least the CRC_A value and all 8 bits of the last byte must + * be received */ + if (*back_len < 2 || _valid_bits != 0) { + return MFRC522_STATUS_CRC_WRONG; + } + + /* Verify CRC_A - do our own calculation and store the control in + * control_buffer */ + uint8_t control_buffer[2]; + mfrc522_status_code_t status = + mfrc522_pcd_calculate_crc(dev, &back_data[0], *back_len - 2, + &control_buffer[0]); + + if (status != MFRC522_STATUS_OK) { + return status; + } + + if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { + return MFRC522_STATUS_CRC_WRONG; + } + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_picc_request_a(mfrc522_t *dev, + uint8_t *buffer_atqa, + uint8_t *buffer_size) +{ + assert(dev); + + return mfrc522_picc_reqa_or_wupa( + dev, MFRC522_PICC_CMD_ISO_14443_REQA, buffer_atqa, buffer_size); +} + +mfrc522_status_code_t mfrc522_picc_wakeup_a(mfrc522_t* dev, + uint8_t *buffer_atqa, + uint8_t *buffer_size) +{ + assert(dev); + + return mfrc522_picc_reqa_or_wupa( + dev, MFRC522_PICC_CMD_ISO_14443_WUPA, buffer_atqa, buffer_size); +} + +mfrc522_status_code_t mfrc522_picc_reqa_or_wupa(mfrc522_t *dev, + mfrc522_picc_command_t command, + uint8_t *buffer_atqa, + uint8_t *buffer_size) +{ + assert(dev); + + uint8_t valid_bits; + mfrc522_status_code_t status; + + if ( command != MFRC522_PICC_CMD_ISO_14443_REQA + && command != MFRC522_PICC_CMD_ISO_14443_WUPA) { + return MFRC522_STATUS_INVALID; + } + + /* The ATQA response is 2 bytes long */ + if (buffer_atqa == NULL || *buffer_size < 2) { + return MFRC522_STATUS_NO_ROOM; + } + + /* Bits received after collision are cleared */ + mfrc522_pcd_clear_register_bitmask( + dev, MFRC522_REG_COLL, MFRC522_BIT_COLL_VALUES_AFTER_COLL); + + /* For REQA and WUPA we need the short frame format - transmit only 7 bits + * of the last (and only) byte. TxLastBits = BitFramingReg[2..0] */ + valid_bits = 7; + status = mfrc522_pcd_transceive_data(dev, (uint8_t*) &command, 1, + buffer_atqa, buffer_size, + &valid_bits, 0, false); + + if (status != MFRC522_STATUS_OK) { + return status; + } + + /* ATQA must be exactly 16 bits */ + if (*buffer_size != 2 || valid_bits != 0) { + return MFRC522_STATUS_ERROR; + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_picc_select(mfrc522_t *dev, + mfrc522_uid_t *uid, uint8_t valid_bits) +{ + assert(dev); + + bool uid_complete; + bool use_cascade_tag; + uint8_t cascade_level = 1; + mfrc522_status_code_t result; + uint8_t count; + uint8_t check_bit; + + /* First index in uid->uid_byte[] that is used in the current Cascade Level */ + uint8_t uid_index; + + /* SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A */ + uint8_t buffer[9]; + + /* Number of bytes used in the buffer, i.e. the number of bytes to transfer to the FIFO */ + uint8_t buffer_used; + + /* Used in BitFramingReg. Defines the bit position for the first bit received */ + uint8_t rx_align; + + /* Used in BitFramingReg. The number of valid bits in the last transmitted byte */ + uint8_t tx_last_bits; + uint8_t *response_buffer; + uint8_t response_length; + + /* Description of buffer structure: + * Byte 0: SEL Indicates the Cascade Level: + * MFRC522_PICC_CMD_ISO_14443_SEL_CL1, MFRC522_PICC_CMD_ISO_14443_SEL_CL2 + * or MFRC522_PICC_CMD_ISO_14443_SEL_CL3 + * Byte 1: NVBNumber of Valid Bits (in complete command, not just the UID): + * High nibble: complete bytes, Low nibble: Extra bits. + * Byte 2: UID-data or CTSee explanation below. CT means Cascade Tag. + * Byte 3: UID-data + * Byte 4: UID-data + * Byte 5: UID-data + * Byte 6: BCCBlock Check Character - XOR of bytes 2-5 + * Byte 7: CRC_A + * Byte 8: CRC_A + * + * The BCC and CRC_A are only transmitted if we know all the UID bits of the + * current Cascade Level. + * + * Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: + * UID contents and cascade levels) + * Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: + * UID contents and cascade levels) + * + * UID size Cascade level Byte2 Byte3 Byte4 Byte5 + * ======== ============= ===== ===== ===== ===== + * 4 bytes 1 uid0 uid1 uid2 uid3 + * 7 bytes 1 CT uid0 uid1 uid2 + * 2 uid3 uid4 uid5 uid6 + * 10 bytes 1 CT uid0 uid1 uid2 + * 2 CT uid3 uid4 uid5 + * 3 uid6 uid7 uid8 uid9 + */ + + /* Sanity check */ + if (valid_bits > 80) { + return MFRC522_STATUS_INVALID; + } + + /* Bits received after collision are cleared */ + mfrc522_pcd_clear_register_bitmask( + dev, MFRC522_REG_COLL, MFRC522_BIT_COLL_VALUES_AFTER_COLL); + + /* Repeat Cascade Level loop until we have a complete UID */ + uid_complete = false; + while (!uid_complete) { + /* Set the Cascade Level in the SEL byte, find out if we need to use the + * Cascade Tag in byte 2 */ + switch (cascade_level) { + + case 1: + buffer[0] = MFRC522_PICC_CMD_ISO_14443_SEL_CL1; + uid_index = 0; + + /* When we know that the UID has more than 4 bytes */ + use_cascade_tag = valid_bits && uid->size > 4; + break; + + case 2: + buffer[0] = MFRC522_PICC_CMD_ISO_14443_SEL_CL2; + uid_index = 3; + + /* When we know that the UID has more than 7 bytes */ + use_cascade_tag = valid_bits && uid->size > 7; + break; + + case 3: + buffer[0] = MFRC522_PICC_CMD_ISO_14443_SEL_CL3; + uid_index = 6; + + /* Never used in CL3 */ + use_cascade_tag = false; + break; + + default: + return MFRC522_STATUS_INTERNAL_ERROR; + break; + } + + /* Number of known UID bits in the current Cascade Level */ + int8_t current_level_known_bits; + + /* How many UID bits are known in this Cascade Level? */ + current_level_known_bits = valid_bits - (8 * uid_index); + if (current_level_known_bits < 0) { + current_level_known_bits = 0; + } + + + /* destination index in buffer[] */ + uint8_t index = 2; + if (use_cascade_tag) { + buffer[index++] = MFRC522_PICC_CASCADE_TAG; + } + + /* Number of bytes needed to represent the known bits for this level */ + uint8_t bytes_to_copy = + current_level_known_bits / 8 + ((current_level_known_bits % 8) ? 1 : 0); + + if (bytes_to_copy) { + /* Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag */ + uint8_t max_bytes = use_cascade_tag ? 3 : 4; + + if (bytes_to_copy > max_bytes) { + bytes_to_copy = max_bytes; + } + + for (count = 0; count < bytes_to_copy; count++) { + buffer[index++] = uid->uid_byte[uid_index + count]; + } + } + + /* Now that the data has been copied we need to include the 8 bits in CT + * in current_level_known_bits */ + if (use_cascade_tag) { + current_level_known_bits += 8; + } + + /* Repeat anti collision loop until we can transmit all UID bits + BCC + * and receive a SAK - max 32 iterations */ + bool select_done = false; + while (!select_done) { + /* Find out how many bits and bytes to send and receive */ + if (current_level_known_bits >= 32) { + /* All UID bits in this Cascade Level are known. This is a SELECT. */ + + /* NVB - Number of Valid Bits: Seven whole bytes */ + buffer[1] = 0x70; + + /* Calculate BCC - Block Check Character */ + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + + /* Calculate CRC_A */ + result = mfrc522_pcd_calculate_crc(dev, buffer, 7, &buffer[7]); + + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* 0 => All 8 bits are valid */ + tx_last_bits = 0; + buffer_used = 9; + + /* Store response in the last 3 bytes of buffer (BCC and CRC_A - + * not needed after tx) */ + response_buffer = &buffer[6]; + response_length = 3; + } + else { + /* This is an ANTICOLLISION */ + + tx_last_bits = current_level_known_bits % 8; + + /* Number of whole bytes in the UID part */ + count = current_level_known_bits / 8; + + /* Number of whole bytes: SEL + NVB + UIDs */ + index = 2 + count; + + /* NVB - Number of Valid Bits */ + buffer[1] = (index << 4) + tx_last_bits; + buffer_used = index + (tx_last_bits ? 1 : 0); + + /* Store response in the unused part of buffer */ + response_buffer = &buffer[index]; + response_length = sizeof(buffer) - index; + } + + rx_align = tx_last_bits; + /* RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] */ + _device_write(dev, MFRC522_REG_BIT_FRAMING, (rx_align << 4) + tx_last_bits); + + /* Transmit the buffer and receive the response */ + result = mfrc522_pcd_transceive_data(dev, buffer, buffer_used, + response_buffer, &response_length, + &tx_last_bits, rx_align, false); + + if (result == MFRC522_STATUS_COLLISION) { + /* More than one PICC in the field => collision */ + + uint8_t value_of_coll_reg; + _device_read(dev, MFRC522_REG_COLL, &value_of_coll_reg); + + if (value_of_coll_reg & MFRC522_BIT_COLL_COLL_POS_NOT_VALID) { + /* Without a valid collision position we cannot continue */ + return MFRC522_STATUS_COLLISION; + } + + /* Values 0-31, 0 means bit 32 */ + uint8_t collision_pos = value_of_coll_reg & 0x1F; + if (collision_pos == 0) { + collision_pos = 32; + } + + /* No progress - should not happen */ + if (collision_pos <= current_level_known_bits) { + return MFRC522_STATUS_INTERNAL_ERROR; + } + + /* Choose the PICC with the bit set */ + current_level_known_bits = collision_pos; + + /* The bit to modify */ + count = current_level_known_bits % 8; + check_bit = (current_level_known_bits - 1) % 8; + + /* First byte is index 0 */ + index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); + buffer[index] |= (1 << check_bit); + } + else if (result != MFRC522_STATUS_OK) { + return result; + } + else { + /* MFRC522_STATUS_OK */ + + if (current_level_known_bits >= 32) { + /* This was a SELECT */ + + /* No more anticollision */ + select_done = true; + /* We continue below outside the while */ + } + else { + /* This was an ANTICOLLISION */ + + /* We now have all 32 bits of the UID in this Cascade Level */ + current_level_known_bits = 32; + /* Run loop again to do the SELECT */ + } + } + } + + /* We do not check the CBB - it was constructed by us above */ + + index = (buffer[2] == MFRC522_PICC_CASCADE_TAG) ? 3 : 2; + bytes_to_copy = (buffer[2] == MFRC522_PICC_CASCADE_TAG) ? 3 : 4; + for (count = 0; count < bytes_to_copy; count++) { + uid->uid_byte[uid_index + count] = buffer[index++]; + } + + /* SAK must be exactly 24 bits (1 byte + CRC_A) */ + if (response_length != 3 || tx_last_bits != 0) { + return MFRC522_STATUS_ERROR; + } + + /* Verify CRC_A - do our own calculation and store the control in + * buffer[2..3] - those bytes are not needed anymore */ + result = mfrc522_pcd_calculate_crc(dev, response_buffer, 1, &buffer[2]); + + if (result != MFRC522_STATUS_OK) { + return result; + } + + if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { + return MFRC522_STATUS_CRC_WRONG; + } + + /* Cascade bit set - UID not complete yes*/ + if (response_buffer[0] & 0x04) { + cascade_level++; + } + else { + uid_complete = true; + uid->sak = response_buffer[0]; + } + } + + /* Set correct uid->size */ + uid->size = 3 * cascade_level + 1; + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_picc_halt_a(mfrc522_t *dev) +{ + assert(dev); + + mfrc522_status_code_t result; + uint8_t buffer[4]; + + /* Build command buffer */ + buffer[0] = MFRC522_PICC_CMD_ISO_14443_HLTA; + buffer[1] = 0; + + /* Calculate CRC_A */ + result = mfrc522_pcd_calculate_crc(dev, buffer, 2, &buffer[2]); + + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* Send the command. The standard says: If the PICC responds with any + * modulation during a period of 1 ms after the end of the frame containing + * the HLTA command, this response shall be interpreted as 'not acknowledge'. + * We interpret that this way: Only MFRC522_STATUS_TIMEOUT is a success. */ + result = mfrc522_pcd_transceive_data(dev, buffer, sizeof(buffer), NULL, NULL, NULL, 0, false); + if (result == MFRC522_STATUS_TIMEOUT) { + return MFRC522_STATUS_OK; + } + + /* That is ironically NOT ok in this case ;-) */ + if (result == MFRC522_STATUS_OK) { + return MFRC522_STATUS_ERROR; + } + + return result; +} + +mfrc522_status_code_t mfrc522_pcd_authenticate(mfrc522_t *dev, + mfrc522_picc_command_t command, + uint8_t block_addr, + const mfrc522_mifare_key_t *key, + const mfrc522_uid_t *uid) +{ + assert(dev); + + uint8_t wait_irq = MFRC522_BIT_COM_IRQ_IDLE_IRQ; + + uint8_t send_data[12]; + send_data[0] = command; + send_data[1] = block_addr; + + /* 6 key bytes */ + for (uint8_t i = 0; i < MFRC522_MF_KEY_SIZE; i++) { + send_data[2 + i] = key->key_byte[i]; + } + + /* Use the last uid bytes as specified in + * http://cache.nxp.com/documents/application_note/AN10927.pdf section 3.2.5 + * "MIFARE Classic Authentication". The only missed case is the MF1Sxxxx + * shortcut activation, but it requires cascade tag (CT) byte, that is not + * part of uid. */ + + for (uint8_t i = 0; i < 4; i++) { + /* The last 4 bytes of the UID */ + send_data[8 + i] = uid->uid_byte[i + uid->size-4]; + } + + /* Start the authentication */ + return mfrc522_pcd_communicate_with_picc( + dev, MFRC522_CMD_MF_AUTHENT, wait_irq, + &send_data[0], sizeof(send_data), NULL, NULL, NULL, 0, false); +} + +void mfrc522_pcd_stop_crypto1(mfrc522_t *dev) +{ + assert(dev); + + mfrc522_pcd_clear_register_bitmask( + dev, MFRC522_REG_STATUS_2, MFRC522_BIT_STATUS_2_MF_CRYPTO_1_ON); +} + +mfrc522_status_code_t mfrc522_mifare_read(mfrc522_t *dev, + uint8_t block_addr, + uint8_t *buffer, + uint8_t *buffer_size) +{ + assert(dev); + + mfrc522_status_code_t result; + + /* Sanity check */ + if (buffer == NULL || *buffer_size < 18) { + return MFRC522_STATUS_NO_ROOM; + } + + /* Build command buffer */ + buffer[0] = MFRC522_PICC_CMD_MF_READ; + buffer[1] = block_addr; + + /* Calculate CRC_A */ + result = mfrc522_pcd_calculate_crc(dev, buffer, 2, &buffer[2]); + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* Transmit the buffer and receive the response, validate CRC_A */ + return mfrc522_pcd_transceive_data( + dev, buffer, 4, buffer, buffer_size, NULL, 0, true); +} + +mfrc522_status_code_t mfrc522_mifare_write(mfrc522_t *dev, + uint8_t block_addr, + const uint8_t *buffer, + uint8_t buffer_size) +{ + assert(dev); + + mfrc522_status_code_t result; + + /* Sanity check */ + if (buffer == NULL || buffer_size < 16) { + return MFRC522_STATUS_INVALID; + } + + /* Mifare Classic protocol requires two communications to perform a write. + * Step 1: Tell the PICC we want to write to block block_addr */ + uint8_t cmd_buffer[2]; + cmd_buffer[0] = MFRC522_PICC_CMD_MF_WRITE; + cmd_buffer[1] = block_addr; + + /* Adds CRC_A and checks that the response is MFRC522_MF_ACK */ + result = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 2, false); + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* Step 2: Transfer the data */ + /* Adds CRC_A and checks that the response is MFRC522_MF_ACK */ + result = mfrc522_pcd_mifare_transceive(dev, buffer, buffer_size, false); + if (result != MFRC522_STATUS_OK) { + return result; + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_mifare_ultralight_write(mfrc522_t *dev, + uint8_t page, + const uint8_t *buffer) +{ + assert(dev); + + mfrc522_status_code_t result; + + /* Sanity check */ + if (buffer == NULL) { + return MFRC522_STATUS_INVALID; + } + + /* Build command buffer */ + uint8_t cmd_buffer[6]; + cmd_buffer[0] = MFRC522_PICC_CMD_MF_UL_WRITE; + cmd_buffer[1] = page; + memcpy(&cmd_buffer[2], buffer, 4); + + /* Perform the write */ + /* Adds CRC_A and checks that the response is MFRC522_MF_ACK */ + result = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 6, false); + if (result != MFRC522_STATUS_OK) { + return result; + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_mifare_decrement(mfrc522_t *dev, + uint8_t block_addr, + int32_t delta) +{ + assert(dev); + + return mfrc522_mifare_two_step_helper( + dev, MFRC522_PICC_CMD_MF_DECREMENT, block_addr, delta); +} + +mfrc522_status_code_t mfrc522_mifare_increment(mfrc522_t *dev, + uint8_t block_addr, + int32_t delta) +{ + assert(dev); + + return mfrc522_mifare_two_step_helper( + dev, MFRC522_PICC_CMD_MF_INCREMENT, block_addr, delta); +} + +mfrc522_status_code_t mfrc522_mifare_restore(mfrc522_t *dev, uint8_t block_addr) +{ + assert(dev); + + /* The datasheet describes Restore as a two step operation, but does not + * explain what data to transfer in step 2. Doing only a single step does + * not work, so transfer 0L in step two. */ + return mfrc522_mifare_two_step_helper( + dev, MFRC522_PICC_CMD_MF_RESTORE, block_addr, 0L); +} + +static mfrc522_status_code_t mfrc522_mifare_two_step_helper(mfrc522_t *dev, + mfrc522_picc_command_t command, + uint8_t block_addr, + int32_t data) +{ + assert(dev); + + if ( command != MFRC522_PICC_CMD_MF_INCREMENT + && command != MFRC522_PICC_CMD_MF_DECREMENT + && command != MFRC522_PICC_CMD_MF_RESTORE) { + return MFRC522_STATUS_INVALID; + } + + mfrc522_status_code_t result; + + /* We only need room for 2 bytes */ + uint8_t cmd_buffer[2]; + + /* Step 1: Tell the PICC the command and block address */ + cmd_buffer[0] = command; + cmd_buffer[1] = block_addr; + + /* Adds CRC_A and checks that the response is MFRC522_MF_ACK */ + result = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 2, false); + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* Step 2: Transfer the data */ + /* Adds CRC_A and accept timeout as success */ + result = mfrc522_pcd_mifare_transceive(dev, (uint8_t *) &data, 4, true); + if (result != MFRC522_STATUS_OK) { + return result; + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_mifare_transfer(mfrc522_t *dev, uint8_t block_addr) +{ + assert(dev); + + mfrc522_status_code_t result; + + /* We only need room for 2 bytes */ + uint8_t cmd_buffer[2]; + + /* Tell the PICC we want to transfer the result into block block_addr */ + cmd_buffer[0] = MFRC522_PICC_CMD_MF_TRANSFER; + cmd_buffer[1] = block_addr; + + /* Adds CRC_A and checks that the response is MFRC522_MF_ACK */ + result = mfrc522_pcd_mifare_transceive(dev, cmd_buffer, 2, false); + if (result != MFRC522_STATUS_OK) { + return result; + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_mifare_get_value(mfrc522_t *dev, + uint8_t block_addr, int32_t *value) +{ + assert(dev); + + mfrc522_status_code_t status; + uint8_t buffer[18]; + uint8_t size = sizeof(buffer); + + status = mfrc522_mifare_read(dev, block_addr, buffer, &size); + if (status == MFRC522_STATUS_OK) { + /* Extract the value */ + *value = (((int32_t) buffer[3]) << 24) + | (((int32_t) buffer[2]) << 16) + | (((int32_t) buffer[1]) << 8) + | ((int32_t) buffer[0]); + } + return status; +} + +mfrc522_status_code_t mfrc522_mifare_set_value(mfrc522_t *dev, + uint8_t block_addr, int32_t value) +{ + assert(dev); + + uint8_t buffer[18]; + + /* Translate the int32_t into 4 bytes; repeated 2x in value block */ + buffer[0] = buffer[ 8] = (value & 0xFF); + buffer[1] = buffer[ 9] = (value & 0xFF00) >> 8; + buffer[2] = buffer[10] = (value & 0xFF0000) >> 16; + buffer[3] = buffer[11] = (value & 0xFF000000) >> 24; + + /* Inverse 4 bytes also found in value block */ + buffer[4] = ~buffer[0]; + buffer[5] = ~buffer[1]; + buffer[6] = ~buffer[2]; + buffer[7] = ~buffer[3]; + + /* Address 2x with inverse address 2x */ + buffer[12] = buffer[14] = block_addr; + buffer[13] = buffer[15] = ~block_addr; + + /* Write the whole data block */ + return mfrc522_mifare_write(dev, block_addr, buffer, 16); +} + +mfrc522_status_code_t mfrc522_pcd_ntag216_auth(mfrc522_t *dev, + const uint8_t* password, + uint8_t p_ack[]) +{ + assert(dev); + + /* TODO: Fix cmd_buffer length and rx_len. They really should match. + * (Better still, rx_len should not even be necessary.) */ + + mfrc522_status_code_t result; + + /* We need room for 16 bytes data and 2 bytes CRC_A */ + uint8_t cmd_buffer[18]; + + /* Command for authentication */ + cmd_buffer[0] = 0x1B; + + for (uint8_t i = 0; i < 4; i++) { + cmd_buffer[i + 1] = password[i]; + } + + result = mfrc522_pcd_calculate_crc(dev, cmd_buffer, 5, &cmd_buffer[5]); + + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* Transceive the data, store the reply in cmd_buffer[] */ + /* RxIRq and IdleIRq */ + uint8_t wait_irq = 0x30; + /* uint8_t cmd_buffer_size = sizeof(cmd_buffer); */ + uint8_t valid_bits = 0; + uint8_t rx_len = 5; + result = mfrc522_pcd_communicate_with_picc( + dev, MFRC522_CMD_TRANSCEIVE, wait_irq, cmd_buffer, 7, cmd_buffer, + &rx_len, &valid_bits, 0, false); + + p_ack[0] = cmd_buffer[0]; + p_ack[1] = cmd_buffer[1]; + + if (result != MFRC522_STATUS_OK) { + return result; + } + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_pcd_mifare_transceive(mfrc522_t *dev, + const uint8_t *send_data, + uint8_t send_len, + bool accept_timeout) +{ + assert(dev); + + mfrc522_status_code_t result; + + /* We need room for 16 bytes data and 2 bytes CRC_A */ + uint8_t cmd_buffer[18]; + + /* Sanity check */ + if (send_data == NULL || send_len > 16) { + return MFRC522_STATUS_INVALID; + } + + /* Copy send_data[] to cmd_buffer[] and add CRC_A */ + memcpy(cmd_buffer, send_data, send_len); + + result = mfrc522_pcd_calculate_crc(dev, cmd_buffer, send_len, &cmd_buffer[send_len]); + if (result != MFRC522_STATUS_OK) { + return result; + } + send_len += 2; + + /* Transceive the data, store the reply in cmd_buffer[] */ + uint8_t wait_irq = MFRC522_BIT_COM_IRQ_RX_IRQ | MFRC522_BIT_COM_IRQ_IDLE_IRQ; + uint8_t cmd_buffer_size = sizeof(cmd_buffer); + uint8_t valid_bits = 0; + + result = mfrc522_pcd_communicate_with_picc( + dev, MFRC522_CMD_TRANSCEIVE, wait_irq, cmd_buffer, send_len, + cmd_buffer, &cmd_buffer_size, &valid_bits, 0, false); + + if (accept_timeout && result == MFRC522_STATUS_TIMEOUT) { + return MFRC522_STATUS_OK; + } + + if (result != MFRC522_STATUS_OK) { + return result; + } + + /* The PICC must reply with a 4 bit ACK */ + if (cmd_buffer_size != 1 || valid_bits != 4) { + return MFRC522_STATUS_ERROR; + } + + if (cmd_buffer[0] != MFRC522_MF_ACK) { + return MFRC522_STATUS_MIFARE_NACK; + } + + return MFRC522_STATUS_OK; +} + +const char* mfrc522_get_status_code_string(mfrc522_status_code_t code) +{ + switch (code) { + case MFRC522_STATUS_OK: + return mfrc522_status_code_names[MFRC522_STATUS_OK]; + + case MFRC522_STATUS_ERROR: + return mfrc522_status_code_names[MFRC522_STATUS_ERROR]; + + case MFRC522_STATUS_COLLISION: + return mfrc522_status_code_names[MFRC522_STATUS_COLLISION]; + + case MFRC522_STATUS_TIMEOUT: + return mfrc522_status_code_names[MFRC522_STATUS_TIMEOUT]; + + case MFRC522_STATUS_NO_ROOM: + return mfrc522_status_code_names[MFRC522_STATUS_NO_ROOM]; + + case MFRC522_STATUS_INTERNAL_ERROR: + return mfrc522_status_code_names[MFRC522_STATUS_INTERNAL_ERROR]; + + case MFRC522_STATUS_INVALID: + return mfrc522_status_code_names[MFRC522_STATUS_INVALID]; + + case MFRC522_STATUS_CRC_WRONG: + return mfrc522_status_code_names[MFRC522_STATUS_CRC_WRONG]; + + case MFRC522_STATUS_MIFARE_NACK: + return mfrc522_status_code_names[MFRC522_STATUS_MIFARE_NACK]; + + default: + return mfrc522_status_code_names[MFRC522_STATUS_UNKNOWN]; + } +} + +mfrc522_picc_type_t mfrc522_picc_get_type(uint8_t sak) +{ + /* http://www.nxp.com/documents/application_note/AN10833.pdf 3.2 Coding of + * Select Acknowledge (SAK): ignore 8-bit (iso14443 starts with LSBit = bit 1) + * fixes wrong type for manufacturer Infineon + * (http://nfc-tools.org/index.php?title=ISO14443A) */ + + sak &= 0x7F; + switch (sak) { + case 0x04: + /* UID not complete */ + return MFRC522_PICC_TYPE_NOT_COMPLETE; + + case 0x09: + return MFRC522_PICC_TYPE_MIFARE_MINI; + + case 0x08: + return MFRC522_PICC_TYPE_MIFARE_1K; + + case 0x18: + return MFRC522_PICC_TYPE_MIFARE_4K; + + case 0x00: + return MFRC522_PICC_TYPE_MIFARE_UL; + + case 0x10: + /* fall through */ + case 0x11: + return MFRC522_PICC_TYPE_MIFARE_PLUS; + + case 0x01: + return MFRC522_PICC_TYPE_TNP3XXX; + + case 0x20: + return MFRC522_PICC_TYPE_ISO_14443_4; + + case 0x40: + return MFRC522_PICC_TYPE_ISO_18092; + + default: + return MFRC522_PICC_TYPE_UNKNOWN; + } +} + +const char* mfrc522_picc_get_type_string(mfrc522_picc_type_t picc_type) +{ + switch (picc_type) { + case MFRC522_PICC_TYPE_ISO_14443_4: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_ISO_14443_4]; + + case MFRC522_PICC_TYPE_ISO_18092: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_ISO_18092]; + + case MFRC522_PICC_TYPE_MIFARE_MINI: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_MINI]; + + case MFRC522_PICC_TYPE_MIFARE_1K: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_1K]; + + case MFRC522_PICC_TYPE_MIFARE_4K: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_4K]; + + case MFRC522_PICC_TYPE_MIFARE_UL: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_UL]; + + case MFRC522_PICC_TYPE_MIFARE_PLUS: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_PLUS]; + + case MFRC522_PICC_TYPE_MIFARE_DESFIRE: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_MIFARE_DESFIRE]; + + case MFRC522_PICC_TYPE_TNP3XXX: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_TNP3XXX]; + + case MFRC522_PICC_TYPE_NOT_COMPLETE: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_NOT_COMPLETE]; + + case MFRC522_PICC_TYPE_UNKNOWN: + /* fall through */ + default: + return mfrc522_picc_type_names[MFRC522_PICC_TYPE_UNKNOWN]; + } +} + +void mfrc522_mifare_set_access_bits(uint8_t *access_bit_buffer, + uint8_t g0, uint8_t g1, uint8_t g2, uint8_t g3) +{ + uint8_t c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); + uint8_t c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); + uint8_t c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); + + access_bit_buffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); + access_bit_buffer[1] = c1 << 4 | (~c3 & 0xF); + access_bit_buffer[2] = c3 << 4 | c2; +} + +mfrc522_status_code_t mfrc522_mifare_open_uid_backdoor(mfrc522_t *dev) +{ + assert(dev); + + /* Magic sequence: + * > 50 00 57 CD (HALT + CRC) + * > 40 (7 bits only) + * < A (4 bits only) + * > 43 + * < A (4 bits only) + * Then you can write to sector 0 without authenticating */ + + /* 50 00 57 CD */ + mfrc522_picc_halt_a(dev); + + uint8_t cmd = 0x40; + + /* Our command is only 7 bits. After receiving card response, + * this will contain amount of valid response bits. */ + uint8_t valid_bits = 7; + + /* Card's response is written here */ + uint8_t response[32]; + uint8_t received = sizeof(response); + + /* 40 */ + mfrc522_status_code_t status = mfrc522_pcd_transceive_data( + dev, &cmd, 1, response, &received, &valid_bits, 0, false); + if(status != MFRC522_STATUS_OK) { + return status; + } + + if (received != 1 || response[0] != 0x0A) { + return MFRC522_STATUS_ERROR; + } + + cmd = 0x43; + valid_bits = 8; + + /* 43 */ + status = mfrc522_pcd_transceive_data( + dev, &cmd, 1, response, &received, &valid_bits, 0, false); + if(status != MFRC522_STATUS_OK) { + return status; + } + + if (received != 1 || response[0] != 0x0A) { + return MFRC522_STATUS_ERROR; + } + + /* You can now write to sector 0 without authenticating! */ + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_mifare_set_uid(mfrc522_t *dev, + mfrc522_uid_t *uid, + const uint8_t *new_uid, + uint8_t new_uid_size) +{ + assert(dev); + + /* UID + BCC byte can not be larger than 15 together */ + if (!new_uid || !new_uid_size || new_uid_size > 15) { + return MFRC522_STATUS_INVALID; + } + + /* Authenticate for reading */ + mfrc522_mifare_key_t key = { .key_byte = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }; + mfrc522_status_code_t status = mfrc522_pcd_authenticate( + dev, MFRC522_PICC_CMD_MF_AUTH_KEY_A, 1, &key, uid); + if (status != MFRC522_STATUS_OK) { + + if (status == MFRC522_STATUS_TIMEOUT) { + /* We get a read timeout if no card is selected yet, so let's select one */ + + /* Wake the card up again if sleeping + * uint8_t atqa_answer[2]; + * uint8_t atqa_size = 2; + * mfrc522_picc_wakeup_a(atqa_answer, &atqa_size); */ + + if (!mfrc522_picc_is_new_card_present(dev)) { + return MFRC522_STATUS_ERROR; + } + + status = mfrc522_picc_read_card_serial(dev, uid); + if (status != MFRC522_STATUS_OK) { + return status; + } + + status = mfrc522_pcd_authenticate( + dev, MFRC522_PICC_CMD_MF_AUTH_KEY_A, 1, &key, uid); + if (status != MFRC522_STATUS_OK) { + /* We tried, time to give up */ + return status; + } + } + else { + return status; + } + } + + /* Read block 0 */ + uint8_t block0_buffer[18]; + uint8_t byte_count = sizeof(block0_buffer); + status = mfrc522_mifare_read(dev, 0, block0_buffer, &byte_count); + if (status != MFRC522_STATUS_OK) { + return status; + } + + /* Write new UID to the data we just read, and calculate BCC byte */ + uint8_t bcc = 0; + for (uint8_t i = 0; i < new_uid_size; i++) { + block0_buffer[i] = new_uid[i]; + bcc ^= new_uid[i]; + } + + block0_buffer[new_uid_size] = bcc; + + /* Stop encrypted traffic so we can send raw bytes */ + mfrc522_pcd_stop_crypto1(dev); + + /* Activate UID backdoor */ + status = mfrc522_mifare_open_uid_backdoor(dev); + if (status != MFRC522_STATUS_OK) { + return status; + } + + /* Write modified block 0 back to card */ + status = mfrc522_mifare_write(dev, 0, block0_buffer, 16); + if (status != MFRC522_STATUS_OK) { + return status; + } + + /* Wake the card up again */ + uint8_t atqa_answer[2]; + uint8_t atqa_size = 2; + mfrc522_picc_wakeup_a(dev, atqa_answer, &atqa_size); + + return MFRC522_STATUS_OK; +} + +mfrc522_status_code_t mfrc522_mifare_unbrick_uid_sector(mfrc522_t *dev) +{ + assert(dev); + + mfrc522_status_code_t status = mfrc522_mifare_open_uid_backdoor(dev); + if (status != MFRC522_STATUS_OK) { + return status; + } + + uint8_t block0_buffer[] = { 0x01, 0x02, 0x03, 0x04, 0x04, 0x08, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + /* Write modified block 0 back to card */ + return mfrc522_mifare_write(dev, (uint8_t) 0, block0_buffer, (uint8_t) 16); +} + +bool mfrc522_picc_is_new_card_present(mfrc522_t *dev) +{ + assert(dev); + + uint8_t buffer_ATQA[2]; + uint8_t buffer_size = sizeof(buffer_ATQA); + + /* Reset baud rates */ + _device_write(dev, MFRC522_REG_TX_MODE, 0x00); + _device_write(dev, MFRC522_REG_RX_MODE, 0x00); + + /* Reset ModWidthReg */ + _device_write(dev, MFRC522_REG_MOD_WIDTH, 0x26); + + mfrc522_status_code_t result = mfrc522_picc_request_a(dev, buffer_ATQA, &buffer_size); + + return (result == MFRC522_STATUS_OK || result == MFRC522_STATUS_COLLISION); +} + +mfrc522_status_code_t mfrc522_picc_read_card_serial(mfrc522_t *dev, + mfrc522_uid_t *uid) +{ + assert(dev); + + return mfrc522_picc_select(dev, uid, 0); +} + +static void _device_write(mfrc522_t *dev, + mfrc522_pcd_register_t reg, uint8_t value) +{ + assert(dev); + + _device_write_n(dev, reg, 1, &value); +} + +static void _device_write_n(mfrc522_t *dev, + mfrc522_pcd_register_t reg, + uint8_t count, + const uint8_t *values) +{ + assert(dev); + + spi_acquire(dev->params.spi_dev, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk); + + /* LSB always 0 and address needs to be shifted left by one. (Datasheet 8.1.2.3) */ + reg = reg << 1; + + /* MSB == 0 is for writing. LSB is not used in address. (Datasheet 8.1.2.3) */ + CLRBIT(reg, 0x80); + + /* Tell MFRC522 which address we want to write */ + spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg); + + for (uint8_t index = 0; index < count; index++) { + + bool stop = (index == count - 1) ? false : true; + + spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, stop, values[index]); + } + + spi_release(dev->params.spi_dev); +} + +static void _device_read(mfrc522_t *dev, mfrc522_pcd_register_t reg, + uint8_t *value) +{ + assert(dev); + + _device_read_n(dev, reg, 1, value, 0); +} + +static void _device_read_n(mfrc522_t *dev, + mfrc522_pcd_register_t reg, + uint8_t count, + uint8_t *values, + uint8_t rx_align) +{ + assert(dev); + + if (count == 0) { + return; + } + + /* last read operation is done outside the loop */ + count -= 1; + + spi_acquire(dev->params.spi_dev, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk); + + /* LSB always 0 and address needs to be shifted left by one. (Datasheet 8.1.2.3) */ + reg = reg << 1; + + /* MSB == 1 is for reading. LSB is not used in address. (Datasheet 8.1.2.3) */ + SETBIT(reg, 0x80); + + /* Tell MFRC522 which address we want to read */ + spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg); + + /* Index in values array */ + uint8_t index = 0; + + /* Only update bit positions rx_align..7 in values[0] */ + if (rx_align) { + /* Create bit mask for bit positions rx_align..7 */ + uint8_t mask = (0xFF << rx_align) & 0xFF; + + /* Read value and tell that we want to read the same address again */ + uint8_t value = spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg); + + /* Apply mask to both current value of values[0] and the new data in value */ + values[0] = (values[0] & ~mask) | (value & mask); + index++; + } + + while (index < count) { + /* Read value and tell that we want to read the same address again */ + values[index] = spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, true, reg); + index++; + } + + /* Read the final byte. Send 0 to stop reading */ + values[count] = spi_transfer_byte(dev->params.spi_dev, dev->params.cs_pin, false, 0); + + spi_release(dev->params.spi_dev); +} + +void mfrc522_pcd_dump_version_to_serial(mfrc522_t *dev) +{ + assert(dev); + + /* Get the MFRC522 firmware version */ + uint8_t version; + _device_read(dev, MFRC522_REG_VERSION, &version); + printf("0x%x", version); + + /* Lookup which version */ + switch(version) { + case 0x88: + printf(" = (clone)"); + break; + case 0x90: + printf(" = v0.0"); + break; + case 0x91: + printf(" = v1.0"); + break; + case 0x92: + printf(" = v2.0"); + break; + case 0x12: + printf(" = counterfeit chip"); + break; + default: + printf(" = (unknown)"); + break; + } + + /* When 0x00 or 0xFF is returned, communication probably failed */ + if ((version == 0x00) || (version == 0xFF)) { + printf("WARNING: Communication failure, is the MFRC522 properly connected?"); + } + + printf("\n"); +} + +void mfrc522_picc_dump_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid) +{ + assert(dev); + + mfrc522_mifare_key_t key; + + /* Dump UID, SAK and Type */ + mfrc522_picc_dump_details_to_serial(uid); + + /* Dump contents */ + mfrc522_picc_type_t picc_type = mfrc522_picc_get_type(uid->sak); + + switch (picc_type) { + case MFRC522_PICC_TYPE_MIFARE_MINI: + /* fall through */ + case MFRC522_PICC_TYPE_MIFARE_1K: + /* fall through */ + case MFRC522_PICC_TYPE_MIFARE_4K: + /* All keys are set to FFFFFFFFFFFFh at chip delivery from the factory */ + for (uint8_t i = 0; i < 6; i++) { + key.key_byte[i] = 0xFF; + } + mfrc522_picc_dump_mifare_classic_to_serial(dev, uid, picc_type, &key); + break; + + case MFRC522_PICC_TYPE_MIFARE_UL: + mfrc522_picc_dump_mifare_ultralight_to_serial(dev); + break; + + case MFRC522_PICC_TYPE_ISO_14443_4: + /* fall through */ + case MFRC522_PICC_TYPE_MIFARE_DESFIRE: + /* fall through */ + case MFRC522_PICC_TYPE_ISO_18092: + /* fall through */ + case MFRC522_PICC_TYPE_MIFARE_PLUS: + /* fall through */ + case MFRC522_PICC_TYPE_TNP3XXX: + printf("Dumping memory contents not implemented for that PICC type."); + break; + + case MFRC522_PICC_TYPE_UNKNOWN: + /* fall through */ + case MFRC522_PICC_TYPE_NOT_COMPLETE: + /* fall through */ + default: + /* No memory dump here */ + break; + } + + printf("\n"); + + /* Already done if it was a MIFARE Classic PICC */ + mfrc522_picc_halt_a(dev); +} + +void mfrc522_picc_dump_details_to_serial(mfrc522_uid_t *uid) +{ + printf("Card UID:"); + for (uint8_t i = 0; i < uid->size; i++) { + if(uid->uid_byte[i] < 0x10){ + printf(" 0"); + } + else { + printf(" "); + } + printf("%x", uid->uid_byte[i]); + } + printf("\n"); + + printf("Card SAK: "); + if(uid->sak < 0x10) { + printf("0"); + } + printf("%x\n", uid->sak); + + /* (suggested) PICC type */ + mfrc522_picc_type_t picc_type = mfrc522_picc_get_type(uid->sak); + printf("PICC type: %s\n", mfrc522_picc_get_type_string(picc_type)); +} + +void mfrc522_picc_dump_mifare_classic_to_serial(mfrc522_t *dev, + mfrc522_uid_t *uid, + mfrc522_picc_type_t picc_type, + mfrc522_mifare_key_t *key) +{ + assert(dev); + + uint8_t no_of_sectors = 0; + + switch (picc_type) { + case MFRC522_PICC_TYPE_MIFARE_MINI: + /* Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes */ + no_of_sectors = 5; + break; + + case MFRC522_PICC_TYPE_MIFARE_1K: + /* Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes */ + no_of_sectors = 16; + break; + + case MFRC522_PICC_TYPE_MIFARE_4K: + /* Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes */ + no_of_sectors = 40; + break; + + default: + /* Should not happen. Ignore */ + break; + } + + /* Dump sectors, highest address first */ + if (no_of_sectors) { + printf("Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AccessBits\n"); + for (int8_t i = no_of_sectors - 1; i >= 0; i--) { + mfrc522_picc_dump_mifare_classic_sector_to_serial(dev, uid, key, i); + } + } + + /* Halt the PICC before stopping the encrypted session */ + mfrc522_picc_halt_a(dev); + mfrc522_pcd_stop_crypto1(dev); +} + +void mfrc522_picc_dump_mifare_classic_sector_to_serial(mfrc522_t *dev, + mfrc522_uid_t *uid, + mfrc522_mifare_key_t *key, + uint8_t sector) +{ + assert(dev); + + mfrc522_status_code_t status; + + /* Address of lowest address to dump actually last block dumped */ + uint8_t first_block; + /* Number of blocks in sector */ + uint8_t no_of_blocks; + /* Set to true while handling the "last" (i.e. highest address) in the sector */ + bool is_sector_trailer; + + /* The access bits are stored in a peculiar fashion. + * There are four groups: + * g[3] Access bits for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) + * g[2] Access bits for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + * g[1] Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + * g[0] Access bits for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + * Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is LSB. + * The four CX bits are stored together in a nible cx and an inverted nible cx_ */ + + /* Nibbles */ + uint8_t c1, c2, c3; + + /* Inverted nibbles */ + uint8_t c1_, c2_, c3_; + + /* True if one of the inverted nibbles did not match */ + bool inverted_error = false; + + /* Access bits for each of the four groups */ + uint8_t g[4]; + + /* 0-3 - active group for access bits */ + uint8_t group; + + /* True for the first block dumped in the group */ + bool first_in_group; + + /* Determine position and size of sector */ + if (sector < 32) { + /* Sectors 0..31 has 4 blocks each */ + no_of_blocks = 4; + first_block = sector * no_of_blocks; + } + else if (sector < 40) { + /* Sectors 32-39 has 16 blocks each */ + no_of_blocks = 16; + first_block = 128 + (sector - 32) * no_of_blocks; + } + else { + /* Illegal input, no MIFARE Classic PICC has more than 40 sectors */ + return; + } + + /* Dump blocks, highest address first */ + uint8_t byte_count; + uint8_t buffer[18]; + is_sector_trailer = true; + + for (int8_t blockOffset = no_of_blocks - 1; blockOffset >= 0; blockOffset--) { + uint8_t block_addr = first_block + blockOffset; + + /* Sector number - only on first line */ + if (is_sector_trailer) { + if(sector < 10) { + /* Pad with spaces */ + printf(" "); + } + else { + /* Pad with spaces */ + printf(" "); + } + printf("%d", sector); + /* Pad with spaces */ + printf(" "); + } + else { + /* Pad with spaces */ + printf(" "); + } + + /* Block number */ + if(block_addr < 10){ + /* Pad with spaces */ + printf(" "); + } + else { + if(block_addr < 100){ + /* Pad with spaces */ + printf(" "); + } + else { + /* Pad with spaces */ + printf(" "); + } + } + printf("%d", block_addr); + printf(" "); + + /* Establish encrypted communications before reading the first block */ + if (is_sector_trailer) { + status = mfrc522_pcd_authenticate( + dev, MFRC522_PICC_CMD_MF_AUTH_KEY_A, first_block, key, uid); + if (status != MFRC522_STATUS_OK) { + printf("mfrc522_pcd_authenticate() failed: %s\n", mfrc522_get_status_code_string(status)); + return; + } + } + + /* Read block */ + byte_count = sizeof(buffer); + status = mfrc522_mifare_read(dev, block_addr, buffer, &byte_count); + if (status != MFRC522_STATUS_OK) { + printf("mfrc522_mifare_read() failed: %s\n", mfrc522_get_status_code_string(status)); + continue; + } + + /* Dump data */ + for (uint8_t index = 0; index < 16; index++) { + + if(buffer[index] < 0x10) { + printf(" 0"); + } + else { + printf(" "); + } + + printf("%x", buffer[index]); + if ((index % 4) == 3) { + printf(" "); + } + } + + /* Parse sector trailer data */ + if (is_sector_trailer) { + c1 = buffer[7] >> 4; + c2 = buffer[8] & 0xF; + c3 = buffer[8] >> 4; + c1_ = buffer[6] & 0xF; + c2_ = buffer[6] >> 4; + c3_ = buffer[7] & 0xF; + inverted_error = (c1 != (~c1_ & 0xF)) || (c2 != (~c2_ & 0xF)) || (c3 != (~c3_ & 0xF)); + g[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0); + g[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1); + g[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2); + g[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3); + is_sector_trailer = false; + } + + /* Which access group is this block in? */ + if (no_of_blocks == 4) { + group = blockOffset; + first_in_group = true; + } + else { + group = blockOffset / 5; + first_in_group = (group == 3) || (group != (blockOffset + 1) / 5); + } + + if (first_in_group) { + /* Print access bits */ + printf(" [ "); + printf("%d", (g[group] >> 2) & 1); + printf(" "); + printf("%d", (g[group] >> 1) & 1); + printf(" "); + printf("%d", (g[group] >> 0) & 1); + printf(" ] "); + if (inverted_error) { + printf(" Inverted access bits did not match! "); + } + } + + /* Not a sector trailer, a value block */ + if (group != 3 && (g[group] == 1 || g[group] == 6)) { + uint32_t value = (((uint32_t) buffer[3]) << 24) + | (((uint32_t) buffer[2]) << 16) + | (((uint32_t) buffer[1]) << 8) + | ((uint32_t) buffer[0]); + printf(" Value=0x"); + printf("%lx", ((unsigned long)value)); + printf(" Addr=0x"); + printf("%x", buffer[12]); + } + printf("\n"); + } + + return; +} + +void mfrc522_picc_dump_mifare_ultralight_to_serial(mfrc522_t *dev) +{ + assert(dev); + + mfrc522_status_code_t status; + uint8_t byte_count; + uint8_t buffer[18]; + uint8_t i; + + printf("Page 0 1 2 3\n"); + /* Try the mpages of the original Ultralight. Ultralight C has more pages. */ + /* Read returns data for 4 pages at a time */ + for (uint8_t page = 0; page < 16; page += 4) { + /* Read pages */ + byte_count = sizeof(buffer); + status = mfrc522_mifare_read(dev, page, buffer, &byte_count); + if (status != MFRC522_STATUS_OK) { + printf("mfrc522_mifare_read() failed: %s\n", mfrc522_get_status_code_string(status)); + break; + } + + /* Dump data */ + for (uint8_t offset = 0; offset < 4; offset++) { + i = page + offset; + if(i < 10) { + /* Pad with spaces */ + printf(" "); + } + else { + /* Pad with spaces */ + printf(" "); + } + + printf("%d", i); + printf(" "); + for (uint8_t index = 0; index < 4; index++) { + i = 4 * offset + index; + + if(buffer[i] < 0x10) { + printf(" 0"); + } + else { + printf(" "); + } + + printf("%x", buffer[i]); + } + printf("\n"); + } + } +} + +/* Firmware data for self-test + * Reference values based on firmware version */ + +/* Version 0.0 (0x90) + * Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August 2005; 16.1 self-test + */ +const uint8_t MFRC522_FIRMWARE_REFERENCEV0_0[] = { + 0x00, 0x87, 0x98, 0x0f, 0x49, 0xFF, 0x07, 0x19, + 0xBF, 0x22, 0x30, 0x49, 0x59, 0x63, 0xAD, 0xCA, + 0x7F, 0xE3, 0x4E, 0x03, 0x5C, 0x4E, 0x49, 0x50, + 0x47, 0x9A, 0x37, 0x61, 0xE7, 0xE2, 0xC6, 0x2E, + 0x75, 0x5A, 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78, + 0x32, 0xFF, 0x58, 0x3B, 0x7C, 0xE9, 0x00, 0x94, + 0xB4, 0x4A, 0x59, 0x5B, 0xFD, 0xC9, 0x29, 0xDF, + 0x35, 0x96, 0x98, 0x9E, 0x4F, 0x30, 0x32, 0x8D +}; + +/* Version 1.0 (0x91) + * NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test + */ +const uint8_t MFRC522_FIRMWARE_REFERENCEV1_0[] = { + 0x00, 0xC6, 0x37, 0xD5, 0x32, 0xB7, 0x57, 0x5C, + 0xC2, 0xD8, 0x7C, 0x4D, 0xD9, 0x70, 0xC7, 0x73, + 0x10, 0xE6, 0xD2, 0xAA, 0x5E, 0xA1, 0x3E, 0x5A, + 0x14, 0xAF, 0x30, 0x61, 0xC9, 0x70, 0xDB, 0x2E, + 0x64, 0x22, 0x72, 0xB5, 0xBD, 0x65, 0xF4, 0xEC, + 0x22, 0xBC, 0xD3, 0x72, 0x35, 0xCD, 0xAA, 0x41, + 0x1F, 0xA7, 0xF3, 0x53, 0x14, 0xDE, 0x7E, 0x02, + 0xD9, 0x0F, 0xB5, 0x5E, 0x25, 0x1D, 0x29, 0x79 +}; + +/* Version 2.0 (0x92) + * NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test + */ +const uint8_t MFRC522_FIRMWARE_REFERENCEV2_0[] = { + 0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95, + 0xD0, 0xE3, 0x0D, 0x3D, 0x27, 0x89, 0x5C, 0xDE, + 0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, 0x89, 0x82, + 0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49, + 0x7C, 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81, + 0x5D, 0x48, 0x76, 0xD5, 0x71, 0x61, 0x21, 0xA9, + 0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, 0x6D, + 0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F +}; + +/* Clone + * Fudan Semiconductor FM17522 (0x88) + */ +const uint8_t MFRC522_FM17522_FIRMWARE_REFERENCE[] = { + 0x00, 0xD6, 0x78, 0x8C, 0xE2, 0xAA, 0x0C, 0x18, + 0x2A, 0xB8, 0x7A, 0x7F, 0xD3, 0x6A, 0xCF, 0x0B, + 0xB1, 0x37, 0x63, 0x4B, 0x69, 0xAE, 0x91, 0xC7, + 0xC3, 0x97, 0xAE, 0x77, 0xF4, 0x37, 0xD7, 0x9B, + 0x7C, 0xF5, 0x3C, 0x11, 0x8F, 0x15, 0xC3, 0xD7, + 0xC1, 0x5B, 0x00, 0x2A, 0xD0, 0x75, 0xDE, 0x9E, + 0x51, 0x64, 0xAB, 0x3E, 0xE9, 0x15, 0xB5, 0xAB, + 0x56, 0x9A, 0x98, 0x82, 0x26, 0xEA, 0x2A, 0x62 +}; + +bool mfrc522_pcd_perform_self_test(mfrc522_t *dev) +{ + assert(dev); + + /* This follows directly the steps outlined in 16.1.1 */ + /* 1. Perform a soft reset */ + mfrc522_pcd_reset(dev); + + /* 2. Clear the internal buffer by writing 25 bytes of 00h */ + uint8_t ZEROES[25] = { 0 }; + + /* flush the FIFO buffer */ + _device_write(dev, MFRC522_REG_FIFO_LEVEL, 0x80); + + /* write 25 bytes of 00h to FIFO */ + _device_write_n(dev, MFRC522_REG_FIFO_DATA, 25, ZEROES); + + /* transfer to internal buffer */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_MEM); + + /* 3. Enable self-test */ + _device_write(dev, MFRC522_REG_AUTO_TEST, 0x09); + + /* 4. Write 00h to FIFO buffer */ + _device_write(dev, MFRC522_REG_FIFO_DATA, 0x00); + + /* 5. Start self-test by issuing the CalcCRC command */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_CALC_CRC); + + /* 6. Wait for self-test to complete */ + for (uint8_t i = 0; i < 0xFF; i++) { + /* The datasheet does not specify exact completion condition except + * that FIFO buffer should contain 64 bytes. While selftest is initiated + * by CalcCRC command it behaves differently from normal CRC computation, + * so one can't reliably use DivIrqReg to check for completion. It is + * reported that some devices does not trigger CRCIRq flag during selftest. */ + + uint8_t n; + _device_read(dev, MFRC522_REG_FIFO_LEVEL, &n); + if (n >= 64) { + break; + } + } + + /* Stop calculating CRC for new content in the FIFO */ + _device_write(dev, MFRC522_REG_COMMAND, MFRC522_CMD_IDLE); + + /* 7. Read out resulting 64 bytes from the FIFO buffer */ + uint8_t result[64]; + _device_read_n(dev, MFRC522_REG_FIFO_DATA, 64, result, 0); + + /* Auto self-test done */ + /* Reset AutoTestReg register to be 0 again. Required for normal operation. */ + _device_write(dev, MFRC522_REG_AUTO_TEST, 0x00); + + /* Determine firmware version (see section 9.3.4.8 in spec) */ + uint8_t version; + _device_read(dev, MFRC522_REG_VERSION, &version); + + /* Pick the appropriate reference values */ + const uint8_t *reference; + switch (version) { + /* Fudan Semiconductor FM17522 clone */ + case 0x88: + reference = MFRC522_FM17522_FIRMWARE_REFERENCE; + break; + /* Version 0.0 */ + case 0x90: + reference = MFRC522_FIRMWARE_REFERENCEV0_0; + break; + /* Version 1.0 */ + case 0x91: + reference = MFRC522_FIRMWARE_REFERENCEV1_0; + break; + /* Version 2.0 */ + case 0x92: + reference = MFRC522_FIRMWARE_REFERENCEV2_0; + break; + /* Unknown version */ + default: + /* abort test */ + return false; + } + + /* Verify that the results match up to our expectations */ + for (uint8_t i = 0; i < 64; i++) { + if (result[i] != reference[i]) { + return false; + } + } + + /* Test passed; all is good */ + return true; +}