Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow

This commit is contained in:
2026-09-12 10:49:39 +02:00
parent 8ac465229c
commit 1d954287a2
82 changed files with 7611 additions and 395 deletions

View File

@@ -0,0 +1,114 @@
#include <string.h>
#include "rc522_helpers_internal.h"
#include "rc522_types_internal.h"
#include "rc522_driver_internal.h"
#include "driver/rc522_i2c.h"
#define RC522_I2C_RW_TIMEOUT_MS 1000
RC522_LOG_DEFINE_BASE();
static esp_err_t rc522_i2c_install(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
rc522_i2c_config_t *conf = (rc522_i2c_config_t *)(driver->config);
if (!conf->bus) {
RC522_CHECK(conf->bus_config == NULL);
RC522_RETURN_ON_ERROR(i2c_new_master_bus(conf->bus_config, &conf->bus));
}
RC522_RETURN_ON_ERROR(
i2c_master_bus_add_device(conf->bus, &conf->dev_config, (i2c_master_dev_handle_t *)(&driver->device)));
if (conf->rst_io_num > GPIO_NUM_NC) {
RC522_RETURN_ON_ERROR(rc522_driver_init_rst_pin(conf->rst_io_num));
}
return ESP_OK;
}
static esp_err_t rc522_i2c_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
RC522_CHECK_BYTES(bytes);
// FIXME: Find a way to send [address + buffer]
// without need for second buffer
uint8_t buffer2[64];
buffer2[0] = address;
memcpy(buffer2 + 1, bytes->ptr, bytes->length);
RC522_RETURN_ON_ERROR(i2c_master_transmit((i2c_master_dev_handle_t)driver->device,
buffer2,
bytes->length + 1,
RC522_I2C_RW_TIMEOUT_MS));
return ESP_OK;
}
static esp_err_t rc522_i2c_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
RC522_CHECK_BYTES(bytes);
ESP_ERROR_CHECK(i2c_master_transmit_receive((i2c_master_dev_handle_t)driver->device,
&address,
1,
bytes->ptr,
bytes->length,
RC522_I2C_RW_TIMEOUT_MS));
return ESP_OK;
}
static esp_err_t rc522_i2c_reset(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
rc522_i2c_config_t *conf = (rc522_i2c_config_t *)(driver->config);
if (conf->rst_io_num < 0) {
return RC522_ERR_RST_PIN_UNUSED;
}
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
return ESP_OK;
}
static esp_err_t rc522_i2c_uninstall(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
RC522_RETURN_ON_ERROR(i2c_master_bus_rm_device((i2c_master_dev_handle_t)driver->device));
// TODO: remove bus if handle was NULL?
return ESP_OK;
}
esp_err_t rc522_i2c_create(const rc522_i2c_config_t *config, rc522_driver_handle_t *driver)
{
RC522_CHECK(config == NULL);
RC522_CHECK(driver == NULL);
RC522_RETURN_ON_ERROR(rc522_driver_create(config, sizeof(rc522_i2c_config_t), driver));
(*driver)->install = rc522_i2c_install;
(*driver)->send = rc522_i2c_send;
(*driver)->receive = rc522_i2c_receive;
(*driver)->reset = rc522_i2c_reset;
(*driver)->uninstall = rc522_i2c_uninstall;
return ESP_OK;
}

View File

@@ -0,0 +1,216 @@
#include <string.h>
#include "rc522_helpers_internal.h"
#include "rc522_types_internal.h"
#include "rc522_driver_internal.h"
#include "driver/rc522_spi.h"
RC522_LOG_DEFINE_BASE();
typedef struct
{
gpio_num_t cs_io_num;
} rc522_spi_meta_t;
static void rc522_spi_transaction_pre_cb(spi_transaction_t *trans);
static void rc522_spi_transaction_post_cb(spi_transaction_t *trans);
static esp_err_t rc522_spi_uninstall(const rc522_driver_handle_t driver);
static esp_err_t rc522_spi_install(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
esp_err_t ret = ESP_OK;
rc522_spi_meta_t *meta = calloc(1, sizeof(rc522_spi_meta_t));
ESP_GOTO_ON_FALSE(meta != NULL, ESP_ERR_NO_MEM, error, TAG, "nomem");
driver->meta = (void *)meta;
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
if (conf->bus_config) {
// overwrite bus config
conf->bus_config->quadwp_io_num = GPIO_NUM_NC;
conf->bus_config->quadhd_io_num = GPIO_NUM_NC;
ESP_GOTO_ON_ERROR(spi_bus_initialize(conf->host_id, conf->bus_config, conf->dma_chan),
error,
TAG,
"spi bus initialization failed");
}
else {
RC522_LOGD("skips spi bus initialization");
}
// {{ overwrite device config
if (conf->dev_config.clock_speed_hz == 0) {
conf->dev_config.clock_speed_hz = 5000000;
}
conf->dev_config.mode = 0;
if (conf->dev_config.queue_size == 0) {
conf->dev_config.queue_size = 7;
}
if (conf->dev_config.flags == 0) {
conf->dev_config.flags = SPI_DEVICE_HALFDUPLEX;
}
conf->dev_config.command_bits = 8;
conf->dev_config.address_bits = 0;
conf->dev_config.dummy_bits = 0;
// }}
// ESP32 SPI bus has limitation of 3 CS lines, so we need to use
// software control for CS line in order to use more devices.
{
meta->cs_io_num = conf->dev_config.spics_io_num;
conf->dev_config.spics_io_num = GPIO_NUM_NC;
gpio_config_t cs_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << meta->cs_io_num),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
ESP_GOTO_ON_ERROR(gpio_config(&cs_conf), error, TAG, "cs gpio config failed");
ESP_GOTO_ON_ERROR(gpio_set_level(meta->cs_io_num, 1), error, TAG, "cs set level failed");
conf->dev_config.pre_cb = &rc522_spi_transaction_pre_cb;
conf->dev_config.post_cb = &rc522_spi_transaction_post_cb;
}
ESP_GOTO_ON_ERROR(spi_bus_add_device(conf->host_id, &conf->dev_config, (spi_device_handle_t *)(&driver->device)),
error,
TAG,
"spi bus add device failed");
if (conf->rst_io_num > GPIO_NUM_NC) {
ESP_GOTO_ON_ERROR(rc522_driver_init_rst_pin(conf->rst_io_num), error, TAG, "init rst pin failed");
}
goto exit;
error:
RC522_LOG_ON_ERROR(rc522_spi_uninstall(driver));
exit:
return ret;
}
static esp_err_t rc522_spi_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->device == NULL);
RC522_CHECK_BYTES(bytes);
uint8_t cmd_byte = (address << 1) & 0x7E;
esp_err_t ret = spi_device_polling_transmit((spi_device_handle_t)(driver->device),
&(spi_transaction_t) {
.cmd = cmd_byte,
.length = 8 * bytes->length,
.tx_buffer = bytes->ptr,
.user = driver,
});
return ret;
}
static esp_err_t rc522_spi_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->device == NULL);
RC522_CHECK_BYTES(bytes);
uint8_t cmd_byte = ((address << 1) & 0x7E) | 0x80;
for (uint8_t i = 0; i < bytes->length; i++) {
RC522_RETURN_ON_ERROR(spi_device_polling_transmit((spi_device_handle_t)(driver->device),
&(spi_transaction_t) {
.cmd = cmd_byte,
.rxlength = 8,
.rx_buffer = (bytes->ptr + i),
.user = driver,
}));
}
return ESP_OK;
}
static esp_err_t rc522_spi_reset(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
if (conf->rst_io_num < 0) {
return RC522_ERR_RST_PIN_UNUSED;
}
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
return ESP_OK;
}
static esp_err_t rc522_spi_uninstall(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
if (driver->meta) {
free(driver->meta);
driver->meta = NULL;
}
if (driver->device) {
RC522_LOG_ON_ERROR(spi_bus_remove_device((spi_device_handle_t)(driver->device)));
driver->device = NULL;
}
if (driver->config) {
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
if (conf->bus_config) {
RC522_LOG_ON_ERROR(spi_bus_free(conf->host_id));
}
}
return ESP_OK;
}
esp_err_t rc522_spi_create(const rc522_spi_config_t *config, rc522_driver_handle_t *driver)
{
RC522_CHECK(config == NULL);
RC522_CHECK(driver == NULL);
RC522_RETURN_ON_ERROR(rc522_driver_create(config, sizeof(rc522_spi_config_t), driver));
(*driver)->install = rc522_spi_install;
(*driver)->send = rc522_spi_send;
(*driver)->receive = rc522_spi_receive;
(*driver)->reset = rc522_spi_reset;
(*driver)->uninstall = rc522_spi_uninstall;
return ESP_OK;
}
static void rc522_spi_transaction_pre_cb(spi_transaction_t *trans)
{
rc522_driver_handle_t driver = (rc522_driver_handle_t)trans->user;
rc522_spi_meta_t *meta = (rc522_spi_meta_t *)(driver->meta);
RC522_LOG_ON_ERROR(gpio_set_level(meta->cs_io_num, 0));
}
static void rc522_spi_transaction_post_cb(spi_transaction_t *trans)
{
rc522_driver_handle_t driver = (rc522_driver_handle_t)trans->user;
rc522_spi_meta_t *meta = (rc522_spi_meta_t *)(driver->meta);
RC522_LOG_ON_ERROR(gpio_set_level(meta->cs_io_num, 1));
}

View File

@@ -0,0 +1,604 @@
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* # Memory access management via access bits
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* The access conditions for every data block and sector trailer are defined by 3 bits, which
* are stored non-inverted and inverted in the sector trailer of the specified sector.
*
* The access bits control the rights of memory access using the secret keys A and B. The
* access conditions may be altered, provided one knows the relevant key and the current
* access condition allows this operation.
*
* @warning
* With each memory access the internal logic verifies the format of the access
* conditions. If it detects a format violation the whole sector is irreversibly blocked.
*
* +-----------------------------------------------------------------------------------+
* | Sector Trailer |
* +=============+===+===+===+===+===+===+===+===+===+===+====+====+====+====+====+====+
* | Byte number | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
* +-------------+---+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
* | Description | Key A | Access Bits | Key B (optional) |
* +-------------+-----------------------+---------------+-----------------------------+
*
* Bytes 6, 7 and 8 of sector trailer block are bytes that holds access bits.
* Next table shows descriptions for each bit of those bytes.
*
* +-----+------+------+------+------+------+------+------+------+
* | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* +-----+------+------+------+------+------+------+------+------+
* | [6] | ~C23 | ~C22 | ~C21 | ~C20 | ~C13 | ~C12 | ~C11 | ~C10 |
* +-----+------+------+------+------+------+------+------+------+
* | [7] | C13 | C12 | C11 | C10 | ~C33 | ~C32 | ~C31 | ~C30 |
* +-----+------+------+------+------+------+------+------+------+
* | [8] | C33 | C32 | C31 | C30 | C23 | C22 | C21 | C20 |
* +-----+------+------+------+------+------+------+------+------+
*
* Where Cxy is access bit Cx for the block (group) y, for example:
* - C10 is the access bit C1 for the block (group) 0,
* - C20 is the access bit C2 for the block (group) 0,
* - ...,
* - C33 is the access bit C3 for the sector trailer,
* - ~C10 is the inverted bit C10.
*/
#include <string.h>
#include "rc522_types_internal.h"
#include "rc522_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "picc/rc522_mifare.h"
RC522_LOG_DEFINE_BASE();
/**
* The commands used for MIFARE Classic
*/
enum
{
/**
* Perform authentication with Key A
*/
RC522_MIFARE_AUTH_KEY_A_CMD = 0x60,
/**
* Perform authentication with Key B
*/
RC522_MIFARE_AUTH_KEY_B_CMD = 0x61,
/**
* Reads one 16 byte block from the authenticated sector of the PICC
*/
RC522_MIFARE_READ_CMD = 0x30,
/**
* Writes one 16 byte block to the authenticated sector of the PICC
*/
RC522_MIFARE_WRITE_CMD = 0xA0,
/**
* Decrements the contents of a block and stores the result in the internal data register
*/
RC522_MIFARE_DECREMENT_CMD = 0xC0,
/**
* Increments the contents of a block and stores the result in the internal data register
*/
RC522_MIFARE_INCREMENT_CMD = 0xC1,
/**
* Reads the contents of a block into the internal data register
*/
RC522_MIFARE_RESTORE_CMD = 0xC2,
/**
* Writes the contents of the internal data register to a block
*/
RC522_MIFARE_TRANSFER_CMD = 0xB0,
};
/**
* Checks if PICC type is compatible with MIFARE Classic protocol
*/
bool rc522_mifare_type_is_classic_compatible(rc522_picc_type_t type)
{
return type == RC522_PICC_TYPE_MIFARE_MINI || type == RC522_PICC_TYPE_MIFARE_1K
|| type == RC522_PICC_TYPE_MIFARE_4K;
}
inline esp_err_t rc522_mifare_auth_sector(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_key_t *key)
{
RC522_CHECK(sector_desc == NULL);
RC522_RETURN_ON_ERROR(rc522_mifare_auth(rc522, picc, sector_desc->block_0_address, key));
return ESP_OK;
}
esp_err_t rc522_mifare_auth(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address, const rc522_mifare_key_t *key)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(key == NULL);
// TODO: Validate block_address
RC522_LOGD("MIFARE AUTH (block_address=%02" RC522_X ")", block_address);
uint8_t send_data[12];
switch (key->type) {
case RC522_MIFARE_KEY_A:
send_data[0] = RC522_MIFARE_AUTH_KEY_A_CMD;
break;
case RC522_MIFARE_KEY_B:
send_data[0] = RC522_MIFARE_AUTH_KEY_B_CMD;
break;
default:
RC522_LOGE("Invalid key type");
return ESP_ERR_INVALID_ARG;
}
send_data[1] = block_address;
memcpy(send_data + 2, key->value, RC522_MIFARE_KEY_SIZE);
// Use the last uid bytes
// 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.
memcpy(send_data + 8, picc->uid.value + picc->uid.length - 4, 4);
// Start the authentication.
rc522_picc_transaction_t transaction = {
.pcd_command = RC522_PCD_MF_AUTH_CMD,
.expected_interrupts = RC522_PCD_IDLE_IRQ_BIT,
.bytes = { .ptr = send_data, .length = sizeof(send_data) },
};
esp_err_t ret = rc522_picc_send(rc522, &transaction, NULL);
// 10.3.1.9
//
// "If an error occurs during authentication,
// the ErrorReg register’s ProtocolErr bit is set to logic 1
// and the Status2Reg register’s Crypto1On bit is set to logic 0"
// Timer interrupt fires before ProtocolErr bit is set to 1
// so we will only check for Crypto1On bit (even if ret == ESP_OK)
uint8_t status2;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_STATUS_2_REG, &status2));
if (!(status2 & RC522_PCD_MF_CRYPTO1_ON_BIT)) {
return RC522_ERR_MIFARE_AUTHENTICATION_FAILED;
}
return ret;
}
esp_err_t rc522_mifare_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
uint8_t out_buffer[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_buffer == NULL);
RC522_LOGD("MIFARE READ (block_address=%02" RC522_X ")", block_address);
uint8_t cmd_buffer[4] = { 0 };
// Build command buffer
cmd_buffer[0] = RC522_MIFARE_READ_CMD;
cmd_buffer[1] = block_address;
// Calculate CRC_A
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = cmd_buffer, .length = 2 }, &crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
uint8_t block_buffer[RC522_MIFARE_BLOCK_SIZE + 2] = { 0 }; // +2 for CRC_A
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
.check_crc = true,
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = block_buffer, .length = sizeof(block_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_CHECK_AND_RETURN((result.bytes.length - 2) != RC522_MIFARE_BLOCK_SIZE, ESP_FAIL);
memcpy(out_buffer, block_buffer, sizeof(block_buffer) - 2); // -2 cuz of CRC_A
return ESP_OK;
}
static esp_err_t rc522_mifare_send(const rc522_handle_t rc522, const uint8_t *send_data, uint8_t send_length)
{
RC522_CHECK(send_data == NULL);
RC522_CHECK(send_length > RC522_MIFARE_BLOCK_SIZE);
#pragma GCC diagnostic ignored "-Wcast-qual"
uint8_t *sdata = (uint8_t *)send_data;
#pragma GCC diagnostic pop
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = sdata, .length = send_length }, &crc));
uint8_t buffer[RC522_MIFARE_BLOCK_SIZE + 2]; // +2 for CRC_A
memcpy(buffer, send_data, send_length);
buffer[send_length] = crc.lsb;
buffer[send_length + 1] = crc.msb;
send_length += 2;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = send_length },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
// The PICC must reply with a 4 bit ACK
if (result.bytes.length != 1 || result.valid_bits != 4) {
return ESP_FAIL; // TODO: use custom err
}
if (result.bytes.ptr[0] != RC522_MIFARE_ACK) {
return RC522_ERR_MIFARE_NACK;
}
return ESP_OK;
}
esp_err_t rc522_mifare_get_number_of_sectors(rc522_picc_type_t type, uint8_t *out_result)
{
RC522_CHECK(rc522_mifare_type_is_classic_compatible(type) == false);
RC522_CHECK(out_result == NULL);
switch (type) {
case RC522_PICC_TYPE_MIFARE_MINI:
// Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
*out_result = 5;
return ESP_OK;
case RC522_PICC_TYPE_MIFARE_1K:
// Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
*out_result = 16;
return ESP_OK;
case RC522_PICC_TYPE_MIFARE_4K:
// Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes.
*out_result = 40;
return ESP_OK;
default:
return ESP_FAIL;
}
}
inline uint8_t rc522_mifare_get_sector_index_by_block_address(uint8_t block_address)
{
if (block_address < 128) {
return block_address / 4;
}
else {
return 32 + ((block_address - 128) / 16);
}
}
inline esp_err_t rc522_mifare_get_number_of_blocks_in_sector(uint8_t sector_index, uint8_t *out_result)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_result == NULL);
if (sector_index < 32) {
*out_result = 4;
return ESP_OK;
}
*out_result = 16;
return ESP_OK;
}
inline esp_err_t rc522_mifare_get_sector_block_0_address(uint8_t sector_index, uint8_t *out_result)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_result == NULL);
if (sector_index < 32) {
*out_result = sector_index * 4;
return ESP_OK;
}
*out_result = 128 + (sector_index - 32) * 16;
return ESP_OK;
}
esp_err_t rc522_mifare_get_sector_desc(uint8_t sector_index, rc522_mifare_sector_desc_t *out_sector_desc)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_sector_desc == NULL);
rc522_mifare_sector_desc_t desc;
memset(&desc, 0, sizeof(desc));
desc.index = sector_index;
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_blocks_in_sector(sector_index, &desc.number_of_blocks));
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_0_address(sector_index, &desc.block_0_address));
memcpy(out_sector_desc, &desc, sizeof(rc522_mifare_sector_desc_t));
return ESP_OK;
}
static esp_err_t rc522_mifare_block_at_address_is_sector_trailer(uint8_t block_address, bool *result)
{
RC522_CHECK(result == NULL);
uint8_t sector_index = rc522_mifare_get_sector_index_by_block_address(block_address);
rc522_mifare_sector_desc_t sector = { 0 };
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_desc(sector_index, &sector));
*result = (block_address == (sector.block_0_address + sector.number_of_blocks - 1));
return ESP_OK;
}
static esp_err_t rc522_mifare_verify_access_bits_integrity(const uint8_t trailer_bytes[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(trailer_bytes == NULL);
uint8_t c1 = 0, c2 = 0, c3 = 0;
uint8_t c1_ = 0, c2_ = 0, c3_ = 0;
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[6], &c2_, &c1_));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, &c3_));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
if ((c1 != (~c1_ & 0x0F)) || (c2 != (~c2_ & 0x0F)) || (c3 != (~c3_ & 0x0F))) {
return RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION;
}
return ESP_OK;
}
esp_err_t rc522_mifare_write(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
const uint8_t buffer[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(!rc522_mifare_type_is_classic_compatible(picc->type));
bool is_trailer = false;
RC522_RETURN_ON_ERROR(rc522_mifare_block_at_address_is_sector_trailer(block_address, &is_trailer));
if (is_trailer) {
#ifdef CONFIG_RC522_PREVENT_SECTOR_TRAILER_WRITE
ESP_LOGE(TAG, "");
ESP_LOGE(TAG,
"The block at address %d that you are trying to update is a Sector Trailer block.",
block_address);
ESP_LOGE(TAG, "");
ESP_LOGE(TAG, "Writing to Sector Trailer blocks is prevented by default in the component configuration");
ESP_LOGE(TAG, "to protect inexperienced users from accidentally overwriting keys");
ESP_LOGE(TAG, "or writing incorrect access bits, which could make the sector unusable.");
ESP_LOGE(TAG, "");
ESP_LOGE(TAG, "If you know what you are doing, please use menuconfig to disable this protection.");
ESP_LOGE(TAG, "");
return RC522_ERR_MIFARE_SECTOR_TRAILER_WRITE_NOT_ALLOWED;
#endif
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(buffer));
}
RC522_LOGD("MIFARE WRITE (block_address=%02" RC522_X ")", block_address);
uint8_t cmd_buffer[] = { RC522_MIFARE_WRITE_CMD, block_address };
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, cmd_buffer, sizeof(cmd_buffer)));
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, buffer, RC522_MIFARE_BLOCK_SIZE));
return ESP_OK;
}
inline esp_err_t rc522_mifare_deauth(const rc522_handle_t rc522, const rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_stop_crypto1(rc522));
return ESP_OK;
}
esp_err_t rc522_mifare_get_desc(const rc522_picc_t *picc, rc522_mifare_desc_t *out_mifare_desc)
{
RC522_CHECK(picc == NULL);
RC522_CHECK(out_mifare_desc == NULL);
rc522_mifare_desc_t desc;
memset(&desc, 0, sizeof(desc));
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_sectors(picc->type, &desc.number_of_sectors));
memcpy(out_mifare_desc, &desc, sizeof(rc522_mifare_desc_t));
return ESP_OK;
}
static esp_err_t rc522_mifare_nibbles_to_access_bits(
uint8_t c1, uint8_t c2, uint8_t c3, uint8_t offset, rc522_mifare_access_bits_t *access_bits)
{
RC522_CHECK(offset > 3);
RC522_CHECK(access_bits == NULL);
rc522_mifare_access_bits_t bits = { 0 };
bits.c1 = (c1 & (1 << offset)) >> offset;
bits.c2 = (c2 & (1 << offset)) >> offset;
bits.c3 = (c3 & (1 << offset)) >> offset;
memcpy(access_bits, &bits, sizeof(bits));
return ESP_OK;
}
static esp_err_t rc522_mifare_parse_sector_trailer(
const uint8_t *trailer_bytes, uint8_t trailer_bytes_size, rc522_mifare_sector_trailer_info_t *out_trailer_info)
{
RC522_CHECK(trailer_bytes == NULL);
RC522_CHECK(trailer_bytes_size != RC522_MIFARE_BLOCK_SIZE);
RC522_CHECK(out_trailer_info == NULL);
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(trailer_bytes));
uint8_t c1 = 0, c2 = 0, c3 = 0;
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, NULL));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
rc522_mifare_sector_trailer_info_t info = { 0 };
for (uint8_t i = 0; i < 4; i++) {
RC522_RETURN_ON_ERROR(rc522_mifare_nibbles_to_access_bits(c1, c2, c3, i, &info.access_bits[i]));
}
memcpy(out_trailer_info, &info, sizeof(info));
return ESP_OK;
}
/**
* Checks if block is Value block based on access bits
*/
inline static bool rc522_mifare_block_is_value(rc522_mifare_access_bits_t access_bits)
{
uint8_t bits = (access_bits.c1 << 2) | (access_bits.c2 << 1) | (access_bits.c3 << 0);
return bits == 0b110 || bits == 0b001;
}
static esp_err_t rc522_mifare_parse_value_block(
const uint8_t *bytes, uint8_t bytes_size, rc522_mifare_sector_value_block_info_t *out_value_block_info)
{
RC522_CHECK(bytes == NULL);
RC522_CHECK(bytes_size != RC522_MIFARE_BLOCK_SIZE);
RC522_CHECK(out_value_block_info == NULL);
rc522_mifare_sector_value_block_info_t value_block_info;
memset(&value_block_info, 0, sizeof(value_block_info));
value_block_info.value = 0;
value_block_info.value |= ((int32_t)(bytes[3]) << (8 * 3));
value_block_info.value |= ((int32_t)(bytes[2]) << (8 * 2));
value_block_info.value |= ((int32_t)(bytes[1]) << (8 * 1));
value_block_info.value |= ((int32_t)(bytes[0]) << (8 * 0));
value_block_info.addr = bytes[12];
// TODO: Check for integrity violation
// Use RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION
memcpy(out_value_block_info, &value_block_info, sizeof(value_block_info));
return ESP_OK;
}
inline static esp_err_t rc522_mifare_get_sector_block_group_index(
const rc522_mifare_sector_desc_t *sector, uint8_t block_offset, uint8_t *out_group)
{
RC522_CHECK(sector == NULL);
RC522_CHECK(out_group == NULL);
RC522_CHECK(block_offset >= sector->number_of_blocks);
if (sector->number_of_blocks > 4) {
*out_group = block_offset / 5;
return ESP_OK;
}
*out_group = block_offset;
return ESP_OK;
}
esp_err_t rc522_mifare_read_sector_trailer_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, rc522_mifare_sector_block_t *out_trailer)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(sector_desc == NULL);
RC522_CHECK(out_trailer == NULL);
rc522_mifare_sector_block_t block;
memset(&block, 0x00, sizeof(block));
block.address = (sector_desc->block_0_address + sector_desc->number_of_blocks - 1);
block.type = RC522_MIFARE_BLOCK_TRAILER;
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
block.error = rc522_mifare_parse_sector_trailer(block.bytes, sizeof(block.bytes), &block.trailer_info);
memcpy(&block.access_bits, &block.trailer_info.access_bits[3], sizeof(rc522_mifare_access_bits_t));
memcpy(out_trailer, &block, sizeof(block));
return ESP_OK;
}
esp_err_t rc522_mifare_read_sector_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_sector_block_t *trailer, uint8_t block_offset,
rc522_mifare_sector_block_t *out_block)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(sector_desc == NULL);
RC522_CHECK(trailer == NULL);
RC522_CHECK(block_offset >= sector_desc->number_of_blocks);
RC522_CHECK(out_block == NULL);
RC522_CHECK_WITH_MESSAGE(block_offset == sector_desc->number_of_blocks - 1,
"use rc522_mifare_read_sector_trailer_block() to read the sector trailer block");
RC522_CHECK(trailer->type != RC522_MIFARE_BLOCK_TRAILER);
rc522_mifare_sector_block_t block;
memset(&block, 0, sizeof(block));
block.address = (sector_desc->block_0_address + block_offset);
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
uint8_t group = 0;
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_group_index(sector_desc, block_offset, &group));
memcpy(&block.access_bits, &trailer->trailer_info.access_bits[group], sizeof(rc522_mifare_access_bits_t));
if (block.address == 0) {
block.type = RC522_MIFARE_BLOCK_MANUFACTURER_DATA;
}
else if (rc522_mifare_block_is_value(block.access_bits)) {
block.type = RC522_MIFARE_BLOCK_VALUE;
block.error = rc522_mifare_parse_value_block(block.bytes, sizeof(block.bytes), &block.value_info);
}
else {
block.type = RC522_MIFARE_BLOCK_DATA;
}
memcpy(out_block, &block, sizeof(block));
return ESP_OK;
}

View File

@@ -0,0 +1,618 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_picc.h"
#include "rc522_types_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "picc/rc522_nxp.h"
#include "picc/rc522_mifare.h"
RC522_LOG_DEFINE_BASE();
const uint8_t RC522_NXP_DEFAULT_PWD[RC522_NXP_PWD_SIZE] = { 0xFF, 0xFF, 0xFF, 0xFF };
const uint8_t RC522_NXP_DEFAULT_PACK[RC522_NXP_PACK_SIZE] = { 0x00 };
// Helper to check if the card responded with a NACK
inline esp_err_t rc522_nxp_check_for_nak(rc522_picc_transaction_result_t *result)
{
if (result->bytes.length == 1 && result->valid_bits == 4 && result->bytes.ptr[0] != RC522_MIFARE_ACK) {
return RC522_ERR_NXP_NACK;
}
return ESP_OK;
}
// WRITE supported? (not classic write - see COMPAT_WRITE)
inline bool rc522_nxp_type_has_write(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
case RC522_PICC_TYPE_MIFARE_UL_C:
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG2xx:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// FAST_READ supported?
inline bool rc522_nxp_type_has_fast_read(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// READ_CNT supported?
inline bool rc522_nxp_type_has_read_cnt(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// PWD_AUTH supported?
inline bool rc522_nxp_type_has_pwd_auth(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// Get originality signature size, or 0 if not supported
inline uint8_t rc522_nxp_type_sig_size(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 48;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return 32;
default:
return 0;
}
}
// Convert GET_VERSION response to type
static rc522_picc_type_t rc522_nxp_type_from_version(rc522_nxp_picc_version_t *version)
{
rc522_nxp_product_type_t type = version->product_type;
rc522_nxp_major_version_t major_version = version->major_version;
uint8_t size = version->storage_size;
if (type == RC522_NXP_PRODUCT_TYPE_NTAG) {
if (major_version != RC522_NXP_MAJ_VER_NTAG21) { // Not an NTAG21x
RC522_LOGW("Unknown NTAG product version: %02" RC522_X, major_version);
}
else if (size == 0x0F) { // 0x0F => between 128 and 256 user bytes
return RC522_PICC_TYPE_NTAG213;
}
else if (size == 0x11) { // 0x11 => between 256 and 512 user bytes
return RC522_PICC_TYPE_NTAG215;
}
else if (size == 0x13) { // 0x13 => between 512 and 1024 user byets
return RC522_PICC_TYPE_NTAG216;
}
else { // Unknown size?
RC522_LOGW("Unknown NTAG size: %02" RC522_X, size);
}
}
else if (type == RC522_NXP_PRODUCT_TYPE_UL) {
if (major_version == RC522_NXP_MAJ_VER_UL_EV1) {
if (size == 0x0B) { // 0x0B => between 32 and 64 user bytes
return RC522_PICC_TYPE_MIFARE_UL_EV1_1;
}
else if (size == 0x0E) { // 0x0E => 128 user bytes
return RC522_PICC_TYPE_MIFARE_UL_EV1_2;
}
}
else if (major_version == RC522_NXP_MAJ_VER_UL_NANO) {
return RC522_PICC_TYPE_MIFARE_UL_NANO;
}
else if (major_version == RC522_NXP_MAJ_VER_UL_AES) {
return RC522_PICC_TYPE_MIFARE_UL_AES;
}
else {
RC522_LOGW("Unknown UL product version: %02" RC522_X, major_version);
}
}
else {
RC522_LOGE("Unknown product type: %02" RC522_X, type);
}
return RC522_PICC_TYPE_UNKNOWN;
}
uint8_t rc522_nxp_get_user_page_count(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 12;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 36;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 12;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 32;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 10;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 36;
case RC522_PICC_TYPE_NTAG213:
return 36;
case RC522_PICC_TYPE_NTAG215:
return 126;
case RC522_PICC_TYPE_NTAG216:
return 222;
default:
return 0;
}
}
uint8_t rc522_nxp_get_user_mem_start(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
case RC522_PICC_TYPE_MIFARE_UL_C:
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG2xx:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return 0x04;
default:
return 0;
}
}
uint8_t rc522_nxp_get_user_mem_end(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 0x0F;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 0x27;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 0x0F;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 0x23;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 0x0D;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 0x27;
case RC522_PICC_TYPE_NTAG213:
return 0x27;
case RC522_PICC_TYPE_NTAG215:
return 0x81;
case RC522_PICC_TYPE_NTAG216:
return 0xE1;
default:
return 0;
}
}
uint8_t rc522_nxp_get_page_count(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 16;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 48;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 20;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 41;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 14;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 60;
case RC522_PICC_TYPE_NTAG213:
return 45;
case RC522_PICC_TYPE_NTAG215:
return 135;
case RC522_PICC_TYPE_NTAG216:
return 231;
default:
return 0;
}
}
esp_err_t rc522_nxp_keyauth_supported(const rc522_handle_t rc522, const rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_LOGD("AUTHENTICATE (support check)");
uint8_t buffer[11] = { 0 }; // combined send(4)/recv(11) buffer
buffer[0] = RC522_PICC_CMD_UL_AUTH;
// buffer[1] is argument; 0x00 is fine for support check
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc));
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = 4 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret == ESP_OK && buffer[0] != 0xAF) {
return RC522_ERR_NXP_NACK;
}
return ret;
}
esp_err_t rc522_nxp_get_type(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_type_t *out_type)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_type == NULL);
RC522_CHECK(picc->type != RC522_PICC_TYPE_MIFARE_UL);
RC522_LOGD("Attempting to get NXP PICC type");
RC522_LOGD("Checking for GET_VERSION support...");
// In case of early exit due to errors
rc522_picc_type_t type = RC522_PICC_TYPE_MIFARE_UL;
rc522_nxp_picc_version_t version_info = { 0 };
esp_err_t ret = rc522_nxp_get_version(rc522, picc, &version_info);
if (ret == ESP_OK) { // GET_VERSION supported
type = rc522_nxp_type_from_version(&version_info);
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT) { // No GET_VERSION
// Failure probably resulted in a HALT, so make sure we can still connect
RC522_LOGD("No L3 GET_VERSION; reselecting");
rc522_picc_uid_t uid;
uint8_t sak;
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
// Fall back to checking for authentication availability (C)
RC522_LOGD("Checking for authentication support...");
ret = rc522_nxp_keyauth_supported(rc522, picc);
if (ret == ESP_OK) {
// Chip supports authentication
// TODO: Do we need to complete authentication, or can we leave it here?
// Note datasheet also mentions a "MIFARE Hospitality" here, but there
// doesn't seem to be any more information about it
type = RC522_PICC_TYPE_MIFARE_UL_C;
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_MIFARE_NACK) { // Not supported
// Only Ultralight without auth is the original
type = RC522_PICC_TYPE_MIFARE_UL_;
}
else { // Some other error in AUTHENTICATE
RC522_RETURN_ON_ERROR(ret);
}
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
}
else { // A different error occured in GET_VERSION
RC522_RETURN_ON_ERROR(ret);
}
RC522_LOGD("Found %s", rc522_picc_type_name(type));
*out_type = type;
return ESP_OK;
}
esp_err_t rc522_nxp_get_version(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_picc_version_t *out_version)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_version == NULL);
RC522_LOGD("GET_VERSION");
uint8_t buffer[10]; // Combined buffer; response is 8-byte data + 2-byte CRC
buffer[0] = RC522_PICC_CMD_GETV;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 1 }, &crc));
buffer[1] = crc.lsb;
buffer[2] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = 3 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
memcpy(out_version, buffer, sizeof(rc522_nxp_picc_version_t));
return ESP_OK;
}
esp_err_t rc522_nxp_read(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, uint8_t out_buffer[RC522_NXP_PAGE_SIZE * 4])
{
// Same protocol as MIFARE cards, so defer
return rc522_mifare_read(rc522, picc, address, out_buffer);
}
esp_err_t rc522_nxp_fast_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t start_page,
uint8_t end_page, rc522_nxp_fast_read_data_t *out_buffer)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_buffer == NULL);
RC522_CHECK(!rc522_nxp_type_has_fast_read(picc->type));
// some sanity checks - valid range, output buffer sufficiently large
RC522_CHECK(start_page > end_page);
RC522_CHECK(out_buffer->buffer_size < (end_page - start_page + 1) * 4);
RC522_LOGD("NXP FAST_READ (start=%02" RC522_X ", end=%02" RC522_X ")", start_page, end_page);
uint8_t cmd_buffer[5] = { RC522_NXP_FAST_READ, start_page, end_page, 0, 0 };
uint8_t byte_count = (end_page - start_page + 1) * 4;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = 3,
},
&crc));
cmd_buffer[3] = crc.lsb;
cmd_buffer[4] = crc.msb;
// I'd rather not malloc() here, but we need extra space for the CRC which
// we're not guaranteed to have in out_buffer
// TODO: Allow for separate CRC fetch and processing to avoid this?
uint8_t *recv_buffer = malloc(byte_count + 2); // Allow for CRC_A
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
.check_crc = true,
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = recv_buffer, .length = byte_count + 2 },
};
// Bunch of manual handling here to make sure free() is called
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
else if ((result.bytes.length - 2) != byte_count) {
free(recv_buffer);
return ESP_FAIL;
}
memcpy(out_buffer->bytes, recv_buffer, byte_count);
out_buffer->read_size = byte_count;
free(recv_buffer);
return ESP_OK;
}
esp_err_t rc522_nxp_write(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, const uint8_t buffer[RC522_NXP_PAGE_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(!rc522_nxp_type_has_write(picc->type));
RC522_LOGD("NXP WRITE (address=%02" RC522_X ")", address);
uint8_t cmd_buffer[RC522_NXP_PAGE_SIZE + 4] = { 0 };
cmd_buffer[0] = RC522_NXP_WRITE;
cmd_buffer[1] = address;
memcpy(&cmd_buffer[2], buffer, RC522_NXP_PAGE_SIZE);
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2 + RC522_NXP_PAGE_SIZE] = crc.lsb;
cmd_buffer[2 + RC522_NXP_PAGE_SIZE + 1] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
return rc522_nxp_check_for_nak(&result);
}
esp_err_t rc522_nxp_read_cnt(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t counter_no, uint32_t *out_count)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_count == NULL);
RC522_CHECK(!rc522_nxp_type_has_read_cnt(picc->type));
RC522_LOGD("NXP READ_CNT (counter=%02" RC522_X ")", counter_no);
uint8_t cmd_buffer[5] = {
// send(4)/recv(5) buffer
RC522_NXP_READ_CNT,
counter_no,
0,
0, // CRC
0 // Space for reply (3 bytes + CRC)
};
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = 4 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
// TODO: Verify this counter is correct
*out_count = (cmd_buffer[2] << 16) | (cmd_buffer[1] << 8) | cmd_buffer[0];
return ESP_OK;
}
esp_err_t rc522_nxp_pwd_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
const uint8_t pwd[RC522_NXP_PWD_SIZE], const uint8_t pack[RC522_NXP_PACK_SIZE], rc522_picc_state_t *out_state)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(pwd == NULL);
RC522_CHECK(pack == NULL);
RC522_CHECK(!rc522_nxp_type_has_pwd_auth(picc->type));
RC522_LOGD("NXP PWD_AUTH");
uint8_t cmd_buffer[RC522_NXP_PWD_SIZE + 3] = { 0 }; // send(11)/recv(4) buffer
cmd_buffer[0] = RC522_NXP_PWD_AUTH;
memcpy(&cmd_buffer[1], pwd, RC522_NXP_PWD_SIZE);
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[1 + RC522_NXP_PWD_SIZE] = crc.lsb;
cmd_buffer[1 + RC522_NXP_PWD_SIZE + 1] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
// If authentication fails (timeout or NAK), the card will HALT, so assume
// it has until we know otherwise
*out_state = RC522_PICC_STATE_HALT;
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
// Successful authentication, set state
*out_state = RC522_PICC_STATE_AUTHENTICATED;
// Validate the PACK - not critical to authenticate the card, but good
// practice to verify
RC522_CHECK_AND_RETURN(memcmp(pack, cmd_buffer, RC522_NXP_PACK_SIZE) != 0, RC522_ERR_NXP_PACK_MISMATCH);
*out_state = RC522_PICC_STATE_AUTHENTICATED;
return ESP_OK;
}
esp_err_t rc522_nxp_read_sig(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_sig_t *out_sig)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_sig == NULL);
uint8_t sig_size = rc522_nxp_type_sig_size(picc->type);
RC522_CHECK(sig_size == 0);
RC522_CHECK(out_sig->buffer_size < sig_size);
RC522_LOGD("NXP READ_SIG (size=%d)", sig_size);
uint8_t cmd_buffer[4] = {
RC522_NXP_READ_SIG,
0,
0,
0 // CRC
};
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
// TODO: Combine to single malloc() for both buffers?
// Again, I'd rather not do a large malloc() here, but we need the space for
// CRC. See TODO in FAST_READ
uint8_t *recv_buffer = malloc(sig_size + 2); // +2 for CRC
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = recv_buffer, .length = sig_size + 2 },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
ret = rc522_nxp_check_for_nak(&result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
memcpy(out_sig->bytes, recv_buffer, sig_size);
out_sig->sig_size = sig_size;
free(recv_buffer);
return ESP_OK;
}

View File

@@ -0,0 +1,342 @@
#include <esp_system.h>
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include <sys/time.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_types_internal.h"
#include "rc522_internal.h"
RC522_LOG_DEFINE_BASE();
ESP_EVENT_DEFINE_BASE(RC522_EVENTS);
inline static bool rc522_is_able_to_start(const rc522_handle_t rc522)
{
return rc522->state >= RC522_STATE_CREATED && rc522->state != RC522_STATE_POLLING;
}
static esp_err_t rc522_clone_config(const rc522_config_t *config, rc522_config_t **result)
{
rc522_config_t *config_clone = calloc(1, sizeof(rc522_config_t));
RC522_CHECK_AND_RETURN(config_clone == NULL, ESP_ERR_NO_MEM);
memcpy(config_clone, config, sizeof(rc522_config_t));
// defaults
if (config_clone->poll_interval_ms < RC522_POLL_INTERVAL_MS_MIN) {
config_clone->poll_interval_ms = RC522_POLL_INTERVAL_MS_DEFAULT;
}
if (config_clone->task_stack_size == 0) {
config_clone->task_stack_size = RC522_TASK_STACK_SIZE_DEFAULT;
}
if (config_clone->task_priority == 0) {
config_clone->task_priority = RC522_TASK_PRIORITY_DEFAULT;
}
// ~defaults
*result = config_clone;
return ESP_OK;
}
esp_err_t rc522_register_events(
const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler, void *event_handler_arg)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(event_handler == NULL);
return esp_event_handler_register_with(rc522->event_handle, RC522_EVENTS, event, event_handler, event_handler_arg);
}
esp_err_t rc522_unregister_events(const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(event_handler == NULL);
return esp_event_handler_unregister_with(rc522->event_handle, RC522_EVENTS, event, event_handler);
}
esp_err_t rc522_start(rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
ESP_RETURN_ON_FALSE(rc522 != NULL, ESP_ERR_INVALID_ARG, TAG, "rc522 is null");
ESP_RETURN_ON_FALSE(rc522_is_able_to_start(rc522),
ESP_ERR_INVALID_STATE,
TAG,
"Unable to start (state=%d)",
rc522->state);
if (rc522->state == RC522_STATE_PAUSED) {
// Scanning has been paused. No need for reinitialization. Just resume
rc522->state = RC522_STATE_POLLING;
return ESP_OK;
}
RC522_RETURN_ON_ERROR(rc522_pcd_reset(rc522, 150));
esp_err_t rw_ret = rc522_pcd_rw_test(rc522);
if (rw_ret != ESP_OK) {
ESP_LOGW(TAG, "rw test warning (%s), proceeding with pcd_init...", esp_err_to_name(rw_ret));
}
ESP_RETURN_ON_ERROR(rc522_pcd_init(rc522), TAG, "unable to init pcd");
rc522->state = RC522_STATE_POLLING;
return ESP_OK;
}
esp_err_t rc522_pause(rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
ESP_RETURN_ON_FALSE(rc522->state == RC522_STATE_POLLING,
ESP_ERR_INVALID_STATE,
TAG,
"Invalid state (state=%d)",
rc522->state);
rc522->state = RC522_STATE_PAUSED;
return ESP_OK;
}
/**
* @brief Exit and delete the task
*/
inline static void rc522_request_task_to_exit(const rc522_handle_t rc522)
{
rc522->exit_requested = true; // task will delete itself
}
esp_err_t rc522_create(const rc522_config_t *config, rc522_handle_t *out_rc522)
{
RC522_CHECK(config == NULL);
RC522_CHECK(out_rc522 == NULL);
rc522_handle_t rc522 = calloc(1, sizeof(struct rc522));
ESP_RETURN_ON_FALSE(rc522 != NULL, ESP_ERR_NO_MEM, TAG, "nomem");
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, false);
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_ERROR(rc522_clone_config(config, &(rc522->config)), _error, TAG, "clone config failed");
esp_event_loop_args_t event_args = {
.queue_size = 1,
.task_name = NULL, // no task will be created
};
rc522->bits = xEventGroupCreate();
ESP_GOTO_ON_FALSE(rc522->bits != NULL, ESP_ERR_NO_MEM, _error, TAG, "nomem");
ESP_GOTO_ON_ERROR(esp_event_loop_create(&event_args, &rc522->event_handle),
_error,
TAG,
"Failed to create event loop");
BaseType_t task_create_result = xTaskCreate(rc522_task,
"rc522_poll",
rc522->config->task_stack_size,
rc522,
rc522->config->task_priority,
&rc522->task_handle);
ESP_GOTO_ON_FALSE(task_create_result == pdTRUE, ESP_FAIL, _error, TAG, "task create failed");
goto _success;
_error:
rc522_destroy(rc522);
rc522 = NULL;
goto _return;
_success:
rc522->state = RC522_STATE_CREATED;
*out_rc522 = rc522;
_return:
return ret;
}
esp_err_t rc522_destroy(rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
ESP_RETURN_ON_FALSE(xTaskGetCurrentTaskHandle() != rc522->task_handle,
ESP_ERR_INVALID_STATE,
TAG,
"Cannot destroy from event handler");
rc522_request_task_to_exit(rc522);
if (rc522->bits) {
xEventGroupWaitBits(rc522->bits, RC522_TASK_STOPPED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
vEventGroupDelete(rc522->bits);
rc522->bits = NULL;
}
if (rc522->event_handle) {
if (esp_event_loop_delete(rc522->event_handle) != ESP_OK) {
ESP_LOGW(TAG, "Failed to delete event loop");
}
rc522->event_handle = NULL;
}
if (rc522->config) {
free(rc522->config);
rc522->config = NULL;
}
free(rc522);
return ESP_OK;
}
esp_err_t rc522_dispatch_event(const rc522_handle_t rc522, rc522_event_t event, const void *data, size_t data_size)
{
RC522_RETURN_ON_ERROR(esp_event_post_to(rc522->event_handle, RC522_EVENTS, event, data, data_size, portMAX_DELAY));
return esp_event_loop_run(rc522->event_handle, 0);
}
void rc522_task(void *arg)
{
esp_err_t ret = ESP_OK;
rc522_handle_t rc522 = (rc522_handle_t)arg;
uint32_t last_poll_ms = 0;
const uint32_t task_delay_ms = 50;
const uint32_t picc_heartbeat_failure_threshold_ms = (2 * task_delay_ms);
uint32_t picc_heartbeat_failure_at_ms = 0;
bool mutex_taken = false;
const uint16_t mutex_take_timeout_ms = 4000;
xEventGroupClearBits(rc522->bits, RC522_TASK_STOPPED_BIT);
while (!rc522->exit_requested) {
if (mutex_taken && rc522->config->task_mutex != NULL) {
if (xSemaphoreGive(rc522->config->task_mutex) == pdTRUE) {
mutex_taken = false;
}
else {
RC522_LOGW("failed to give mutex");
}
}
if (rc522->state != RC522_STATE_POLLING) {
// waiting for state change to polling
rc522_delay_ms(100);
continue;
}
rc522_delay_ms(task_delay_ms);
if (rc522->config->task_mutex != NULL) {
if (xSemaphoreTake(rc522->config->task_mutex, pdMS_TO_TICKS(mutex_take_timeout_ms)) == pdTRUE) {
mutex_taken = true;
}
else {
RC522_LOGW("failed to take mutex in %d ms", mutex_take_timeout_ms);
continue;
}
}
bool should_poll = (rc522_millis() - last_poll_ms) > rc522->config->poll_interval_ms;
if (rc522->picc.state == RC522_PICC_STATE_IDLE || rc522->picc.state == RC522_PICC_STATE_HALT) {
rc522_picc_atqa_desc_t atqa;
if (rc522->picc.state == RC522_PICC_STATE_IDLE && ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK)) {
continue;
}
if (rc522->picc.state == RC522_PICC_STATE_HALT && ((ret = rc522_picc_wupa(rc522, &atqa)) != ESP_OK)) {
continue;
}
ESP_LOGI(TAG, "Tag detected in field (ATQA=0x%04X)", atqa.source);
// card is present
rc522->picc.atqa = atqa;
if (rc522->picc.state == RC522_PICC_STATE_IDLE) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_READY, true);
}
else if (rc522->picc.state == RC522_PICC_STATE_HALT) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_READY_H, true);
}
}
if (should_poll
&& (rc522->picc.state == RC522_PICC_STATE_READY || rc522->picc.state == RC522_PICC_STATE_READY_H)) {
rc522_picc_uid_t uid;
uint8_t sak;
ret = rc522_picc_select(rc522, &uid, &sak, false);
last_poll_ms = rc522_millis();
if (ret != ESP_OK) {
if (ret != RC522_ERR_RX_TIMEOUT && ret != RC522_ERR_INVALID_ATQA && ret != RC522_ERR_INVALID_SAK) {
RC522_LOGW("select failed (err=%04" RC522_X ")", ret);
}
else {
RC522_LOGD("select failed (err=%04" RC522_X ")", ret);
}
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, true);
continue;
}
memcpy(&rc522->picc.uid, &uid, sizeof(rc522_picc_uid_t));
rc522->picc.sak = sak;
rc522->picc.type = rc522_picc_get_type(&rc522->picc);
ESP_LOGI(TAG, "Tag selected (SAK=0x%02X, length=%d)", sak, uid.length);
if (rc522->picc.state == RC522_PICC_STATE_READY) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_ACTIVE, true);
}
else if (rc522->picc.state == RC522_PICC_STATE_READY_H) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_ACTIVE_H, true);
}
picc_heartbeat_failure_at_ms = 0;
continue;
}
if (rc522->picc.state == RC522_PICC_STATE_ACTIVE || rc522->picc.state == RC522_PICC_STATE_ACTIVE_H) {
if (picc_heartbeat_failure_at_ms != 0
&& ((rc522_millis() - picc_heartbeat_failure_at_ms) > picc_heartbeat_failure_threshold_ms)) {
picc_heartbeat_failure_at_ms = 0;
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, true);
continue;
}
if ((ret = rc522_picc_heartbeat(rc522, &rc522->picc, NULL, NULL)) == ESP_OK) {
picc_heartbeat_failure_at_ms = 0;
}
else if (picc_heartbeat_failure_at_ms == 0) {
picc_heartbeat_failure_at_ms = rc522_millis();
}
if (ret != ESP_OK) {
RC522_LOGD("heartbeat failed (err=%04" RC522_X ")", ret);
}
// card is still in the field
continue;
}
}
xEventGroupSetBits(rc522->bits, RC522_TASK_STOPPED_BIT);
ESP_LOGI(TAG, "Task exited");
vTaskDelete(NULL); // self-delete
}

View File

@@ -0,0 +1,108 @@
#include <string.h>
#include <driver/gpio.h>
#include "rc522_types_internal.h"
#include "rc522_driver_internal.h"
RC522_LOG_DEFINE_BASE();
esp_err_t rc522_driver_init_rst_pin(gpio_num_t rst_io_num)
{
RC522_CHECK(rst_io_num < 0);
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << rst_io_num),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
RC522_RETURN_ON_ERROR(gpio_config(&io_conf));
RC522_RETURN_ON_ERROR(gpio_set_level(rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
return ESP_OK;
}
inline esp_err_t rc522_driver_install(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
return driver->install(driver);
}
inline esp_err_t rc522_driver_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK_BYTES(bytes);
return driver->send(driver, address, bytes);
}
inline esp_err_t rc522_driver_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK_BYTES(bytes);
return driver->receive(driver, address, bytes);
}
inline esp_err_t rc522_driver_reset(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
return driver->reset(driver);
}
inline esp_err_t rc522_driver_uninstall(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
return driver->uninstall(driver);
}
esp_err_t rc522_driver_create(const void *config, size_t config_size, rc522_driver_handle_t *driver)
{
RC522_CHECK(config == NULL);
RC522_CHECK(config_size == 0);
RC522_CHECK(driver == NULL);
esp_err_t ret = ESP_OK;
rc522_driver_handle_t _driver = calloc(1, sizeof(struct rc522_driver_handle));
ESP_RETURN_ON_FALSE(_driver != NULL, ESP_ERR_NO_MEM, TAG, "nomem");
_driver->config = calloc(1, config_size);
ESP_GOTO_ON_FALSE(_driver->config != NULL, ESP_ERR_NO_MEM, error, TAG, "nomem");
memcpy(_driver->config, config, config_size);
goto success;
error:
rc522_driver_destroy(_driver);
goto exit;
success:
*driver = _driver;
exit:
return ret;
}
esp_err_t rc522_driver_destroy(rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
if (driver->config) {
free(driver->config);
driver->config = NULL;
}
driver->install = NULL;
driver->send = NULL;
driver->receive = NULL;
driver->uninstall = NULL;
driver->device = NULL;
free(driver);
return ESP_OK;
}

View File

@@ -0,0 +1,65 @@
#include <esp_system.h>
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include <sys/time.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "rc522_types_internal.h"
#include "rc522_helpers_internal.h"
#include <esp_timer.h>
RC522_LOG_DEFINE_BASE();
uint32_t rc522_millis()
{
return (uint32_t)(esp_timer_get_time() / 1000ULL);
}
void rc522_delay_ms(uint32_t ms)
{
TickType_t ticks = pdMS_TO_TICKS(ms);
if (ticks == 0 && ms > 0) {
ticks = 1;
}
vTaskDelay(ticks);
}
esp_err_t rc522_buffer_to_hex_str(
const uint8_t *buffer, uint8_t buffer_length, char *str_buffer, uint8_t str_buffer_length)
{
RC522_CHECK(buffer == NULL);
RC522_CHECK(buffer_length < 1);
RC522_CHECK(str_buffer == NULL);
const char *format = "%02" RC522_X " ";
const uint8_t formatted_length = 3;
RC522_CHECK(str_buffer_length < (buffer_length * formatted_length));
uint16_t len = 0;
for (uint16_t i = 0; i < buffer_length; i++) {
len += sprintf(str_buffer + len, format, buffer[i]);
}
str_buffer[len - 1] = 0x00;
return ESP_OK;
}
inline esp_err_t rc522_nibbles(uint8_t byte, uint8_t *msb, uint8_t *lsb)
{
RC522_CHECK(msb == NULL && lsb == NULL);
if (msb) {
*msb = byte >> 4;
}
if (lsb) {
*lsb = byte & 0x0F;
}
return ESP_OK;
}

View File

@@ -0,0 +1,370 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_types_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_driver_internal.h"
#include "rc522_pcd_internal.h"
RC522_LOG_DEFINE_BASE();
/**
* @see https://stackoverflow.com/a/48705557
*/
esp_err_t rc522_pcd_calculate_crc(const rc522_handle_t rc522, const rc522_bytes_t *bytes, rc522_pcd_crc_t *result)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK_BYTES(bytes);
RC522_CHECK(result == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_DIV_INT_REQ_REG, RC522_PCD_CRC_IRQ_BIT));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, bytes));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_CALC_CRC_CMD));
uint32_t deadline_ms = rc522_millis() + 90;
bool calculation_done = false;
do {
uint8_t irq;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_DIV_INT_REQ_REG, &irq));
if (irq & RC522_PCD_CRC_IRQ_BIT) {
calculation_done = true;
break;
}
rc522_delay_ms(2);
}
while (rc522_millis() < deadline_ms);
if (!calculation_done) { // Deadline reached
return ESP_ERR_TIMEOUT;
}
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CRC_RESULT_MSB_REG, &crc.msb));
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CRC_RESULT_LSB_REG, &crc.lsb));
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
char debug_buffer[64];
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
RC522_LOGD("crc(%s) = 0x%04" RC522_X, debug_buffer, crc.value);
}
*result = crc;
return ESP_OK;
}
static esp_err_t rc522_pcd_wait_for_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
RC522_CHECK(rc522 == NULL);
esp_err_t ret = ESP_OK;
bool power_down_bit = true;
uint32_t start_ms = rc522_millis();
// Wait for the PowerDown bit in CommandReg to be cleared
do {
rc522_delay_ms(25);
uint8_t cmd;
if ((ret = rc522_pcd_read(rc522, RC522_PCD_COMMAND_REG, &cmd)) != ESP_OK) {
RC522_LOGD("wait for reset error %04" RC522_X, ret);
continue;
}
if (!(power_down_bit = (cmd & RC522_PCD_POWER_DOWN_BIT))) {
break;
}
}
while ((rc522_millis() - start_ms) < timeout_ms);
return power_down_bit ? ESP_ERR_TIMEOUT : ESP_OK;
}
inline static esp_err_t rc522_pcd_hard_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
RC522_CHECK(rc522 == NULL);
RC522_LOGD("hard reset");
esp_err_t ret = rc522_driver_reset(rc522->config->driver);
return ret == ESP_OK ? rc522_pcd_wait_for_reset(rc522, timeout_ms) : ret;
}
inline static esp_err_t rc522_pcd_soft_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
RC522_CHECK(rc522 == NULL);
RC522_LOGD("soft reset");
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_SOFT_RESET_CMD));
return rc522_pcd_wait_for_reset(rc522, timeout_ms);
}
esp_err_t rc522_pcd_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
esp_err_t ret = ESP_OK;
if ((ret = rc522_pcd_hard_reset(rc522, timeout_ms)) != ESP_OK) {
if (ret != RC522_ERR_RST_PIN_UNUSED) {
RC522_LOGW("hard reset failed, trying soft reset");
}
ret = rc522_pcd_soft_reset(rc522, timeout_ms);
}
return ret;
}
inline static esp_err_t rc522_pcd_tx_enable(const rc522_handle_t rc522)
{
return rc522_pcd_set_bits(rc522, RC522_PCD_TX_CONTROL_REG, (RC522_PCD_TX2_RF_EN_BIT | RC522_PCD_TX1_RF_EN_BIT));
}
static esp_err_t rc522_pcd_configure_timer(const rc522_handle_t rc522, uint8_t mode, uint16_t prescaler)
{
uint8_t prescaler_hi = (prescaler >> 8) & 0x0F;
uint8_t timer_mode = (mode & 0xF0) | prescaler_hi;
uint8_t prescaler_lo = (prescaler & 0xFF);
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_MODE_REG, timer_mode));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_PRESCALER_REG, prescaler_lo));
return ESP_OK;
}
inline static esp_err_t rc522_pcd_set_timer_reload_value(const rc522_handle_t rc522, uint16_t value)
{
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_RELOAD_MSB_REG, (value >> 8) & 0xFF));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_RELOAD_LSB_REG, value & 0xFF));
return ESP_OK;
}
inline esp_err_t rc522_pcd_set_rx_gain(const rc522_handle_t rc522, rc522_pcd_rx_gain_t gain)
{
uint8_t value = 0;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_RF_CFG_REG, &value));
return rc522_pcd_write(rc522, RC522_PCD_RF_CFG_REG, (value & ~0x70) | (gain & 0x70));
}
esp_err_t rc522_pcd_init(const rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
// Reset baud rates
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TX_MODE_REG, RC522_PCD_TX_MODE_REG_RESET_VALUE));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_RX_MODE_REG, RC522_PCD_RX_MODE_REG_RESET_VALUE));
// Reset modulation width
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_MOD_WIDTH_REG, RC522_PCD_MOD_WIDTH_REG_RESET_VALUE));
// 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
// TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
RC522_RETURN_ON_ERROR(rc522_pcd_configure_timer(rc522, RC522_PCD_T_AUTO_BIT, 169));
// Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
RC522_RETURN_ON_ERROR(rc522_pcd_set_timer_reload_value(rc522, 1000));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TX_ASK_REG, RC522_PCD_FORCE_100_ASK_BIT));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_MODE_REG, 0x3D));
// Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
RC522_RETURN_ON_ERROR(rc522_pcd_tx_enable(rc522));
// Set maximum receiver antenna gain (48dB) for compact antenna on MH-ET LIVE module
RC522_RETURN_ON_ERROR(rc522_pcd_set_rx_gain(rc522, RC522_PCD_48_DB_RX_GAIN));
rc522_pcd_firmware_t fw;
ESP_RETURN_ON_ERROR(rc522_pcd_firmware(rc522, &fw), TAG, "read fw version failed");
// When 0x00 or 0xFF is returned, communication probably failed
if (fw == 0x00 || fw == 0xFF) {
RC522_LOGE("Communication failure, is the MFRC522 properly connected?");
return ESP_FAIL; // TODO: use custom err
}
uint8_t mode_reg = 0, tx_ctrl = 0, rf_cfg = 0;
rc522_pcd_read(rc522, RC522_PCD_MODE_REG, &mode_reg);
rc522_pcd_read(rc522, RC522_PCD_TX_CONTROL_REG, &tx_ctrl);
rc522_pcd_read(rc522, RC522_PCD_RF_CFG_REG, &rf_cfg);
ESP_LOGI(TAG, "PCD (fw=%s) initialized [ModeReg=0x%02X TxControlReg=0x%02X RFCfgReg=0x%02X]",
rc522_pcd_firmware_name(fw), mode_reg, tx_ctrl, rf_cfg);
return ESP_OK;
}
esp_err_t rc522_pcd_firmware(const rc522_handle_t rc522, rc522_pcd_firmware_t *fw)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(fw == NULL);
uint8_t value;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_VERSION_REG, &value));
*fw = (rc522_pcd_firmware_t)value;
return ESP_OK;
}
char *rc522_pcd_firmware_name(rc522_pcd_firmware_t firmware)
{
switch (firmware) {
case RC522_PCD_FIRMWARE_CLONE:
return "clone";
case RC522_PCD_FIRMWARE_00:
return "v0.0";
case RC522_PCD_FIRMWARE_10:
return "v1.0";
case RC522_PCD_FIRMWARE_20:
return "v2.0";
case RC522_PCD_FIRMWARE_COUNTERFEIT:
return "counterfeit_chip";
default:
return "unknown";
}
}
inline esp_err_t rc522_pcd_stop_active_command(const rc522_handle_t rc522)
{
return rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_IDLE_CMD);
}
inline esp_err_t rc522_pcd_clear_all_com_interrupts(const rc522_handle_t rc522)
{
return rc522_pcd_write(rc522, RC522_PCD_COM_INT_REQ_REG, (uint8_t)(~RC522_PCD_SET_1_BIT));
}
inline esp_err_t rc522_pcd_fifo_write(const rc522_handle_t rc522, const rc522_bytes_t *bytes)
{
return rc522_pcd_write_n(rc522, RC522_PCD_FIFO_DATA_REG, bytes);
}
inline esp_err_t rc522_pcd_fifo_read(const rc522_handle_t rc522, rc522_bytes_t *bytes)
{
return rc522_pcd_read_n(rc522, RC522_PCD_FIFO_DATA_REG, bytes);
}
inline esp_err_t rc522_pcd_fifo_flush(const rc522_handle_t rc522)
{
return rc522_pcd_write(rc522, RC522_PCD_FIFO_LEVEL_REG, RC522_PCD_FLUSH_BUFFER_BIT);
}
inline esp_err_t rc522_pcd_start_data_transmission(const rc522_handle_t rc522)
{
return rc522_pcd_set_bits(rc522, RC522_PCD_BIT_FRAMING_REG, RC522_PCD_START_SEND_BIT);
}
inline esp_err_t rc522_pcd_stop_data_transmission(const rc522_handle_t rc522)
{
return rc522_pcd_clear_bits(rc522, RC522_PCD_BIT_FRAMING_REG, RC522_PCD_START_SEND_BIT);
}
inline esp_err_t rc522_pcd_stop_crypto1(const rc522_handle_t rc522)
{
return rc522_pcd_clear_bits(rc522, RC522_PCD_STATUS_2_REG, RC522_PCD_MF_CRYPTO1_ON_BIT);
}
esp_err_t rc522_pcd_rw_test(const rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
uint8_t tmp;
ESP_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &tmp), TAG, "Cannot read FIFO length");
ESP_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522), TAG, "Cannot flush FIFO");
uint8_t buffer1[] = { 0x13, 0x33, 0x37 };
const uint8_t buffer_size = sizeof(buffer1);
uint8_t buffer2[buffer_size];
ESP_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &(rc522_bytes_t) { .ptr = buffer1, .length = buffer_size }),
TAG,
"Cannot write to FIFO");
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &tmp));
ESP_RETURN_ON_FALSE(tmp == buffer_size, ESP_FAIL, TAG, "FIFO length missmatch after write");
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &(rc522_bytes_t) { .ptr = buffer2, .length = buffer_size }));
if (memcmp(buffer1, buffer2, buffer_size) != 0) {
RC522_LOGE("Buffers content missmatch");
RC522_LOGE("Buffer1: ");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer1, buffer_size, ESP_LOG_ERROR);
RC522_LOGE("Buffer2: ");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer2, buffer_size, ESP_LOG_ERROR);
return ESP_FAIL;
}
return ESP_OK;
}
inline esp_err_t rc522_pcd_write_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, const rc522_bytes_t *bytes)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK_BYTES(bytes);
if (RC522_LOG_LEVEL >= ESP_LOG_VERBOSE) {
char debug_buffer[64];
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
RC522_LOGV("pcd [0x%02" RC522_X "] <<< %s", addr, debug_buffer);
}
RC522_RETURN_ON_ERROR(rc522_driver_send(rc522->config->driver, addr, bytes));
return ESP_OK;
}
inline esp_err_t rc522_pcd_write(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t val)
{
return rc522_pcd_write_n(rc522, addr, &(rc522_bytes_t) { .ptr = &val, .length = 1 });
}
inline esp_err_t rc522_pcd_read_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, rc522_bytes_t *bytes)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK_BYTES(bytes);
esp_err_t ret = rc522_driver_receive(rc522->config->driver, addr, bytes);
if (RC522_LOG_LEVEL >= ESP_LOG_VERBOSE) {
char debug_buffer[64];
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
RC522_LOGV("pcd [0x%02" RC522_X "] >>> %s", addr, debug_buffer);
}
return ret;
}
inline esp_err_t rc522_pcd_read(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t *value_ref)
{
return rc522_pcd_read_n(rc522, addr, &(rc522_bytes_t) { .ptr = value_ref, .length = 1 });
}
inline esp_err_t rc522_pcd_set_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits)
{
uint8_t value;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, addr, &value));
return rc522_pcd_write(rc522, addr, value | bits);
}
inline esp_err_t rc522_pcd_clear_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits)
{
uint8_t value;
ESP_RETURN_ON_ERROR(rc522_pcd_read(rc522, addr, &value), TAG, "");
return rc522_pcd_write(rc522, addr, value & (~bits));
}

View File

@@ -0,0 +1,858 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_internal.h"
#include "rc522_types_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
RC522_LOG_DEFINE_BASE();
struct rc522_picc_transaction_context
{
const rc522_picc_transaction_t *transaction;
uint8_t interrupts;
bool completed;
uint8_t error_reg;
};
esp_err_t rc522_picc_send(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
rc522_picc_transaction_context_t *out_context)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(transaction == NULL);
RC522_CHECK_BYTES(&transaction->bytes);
RC522_CHECK(
transaction->pcd_command != RC522_PCD_TRANSCEIVE_CMD && transaction->pcd_command != RC522_PCD_MF_AUTH_CMD);
RC522_CHECK(transaction->expected_interrupts == 0);
rc522_picc_transaction_context_t context = {
.transaction = transaction,
};
// Prepare values for bit framing
uint8_t bit_framing = (transaction->rx_align << 4) + transaction->valid_bits;
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
RC522_LOGD("rx_align=%d,tx_last_bits=%d, bit_framing=%d",
transaction->rx_align,
transaction->valid_bits,
bit_framing);
char debug_buffer[64];
rc522_buffer_to_hex_str(transaction->bytes.ptr, transaction->bytes.length, debug_buffer, sizeof(debug_buffer));
RC522_LOGD("picc << %s", debug_buffer);
}
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_clear_all_com_interrupts(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &transaction->bytes));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, bit_framing));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, transaction->pcd_command));
if (transaction->pcd_command == RC522_PCD_TRANSCEIVE_CMD) {
RC522_RETURN_ON_ERROR(rc522_pcd_start_data_transmission(rc522));
}
// TAuto flag in TModeReg is set.
// This means the timer automatically starts when the PCD stops transmitting.
const uint32_t deadline = rc522_millis() + 36;
do {
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_COM_INT_REQ_REG, &context.interrupts));
if (context.interrupts & transaction->expected_interrupts) {
context.completed = true;
break;
}
// Timer interrupt - nothing received
if (context.interrupts & RC522_PCD_TIMER_IRQ_BIT) {
RC522_LOGD("timer interrupt (irq=0x%02" RC522_X ")", context.interrupts);
rc522_pcd_stop_active_command(rc522);
return RC522_ERR_RX_TIMER_TIMEOUT;
}
rc522_delay_ms(2);
}
while (rc522_millis() < deadline);
// Deadline reached and nothing happened (normal when no card is present).
if (!context.completed) {
rc522_pcd_stop_active_command(rc522);
return RC522_ERR_RX_TIMEOUT;
}
// Stop now if any errors except collisions were detected.
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_ERROR_REG, &context.error_reg));
if (context.error_reg & RC522_PCD_BUFFER_OVFL_BIT) {
return RC522_ERR_PCD_FIFO_BUFFER_OVERFLOW;
}
else if (context.error_reg & RC522_PCD_PARITY_ERR_BIT) {
RC522_LOGD("parity error detected");
return RC522_ERR_PCD_PARITY_CHECK_FAILED;
}
else if (context.error_reg & RC522_PCD_PROTOCOL_ERR_BIT) {
RC522_LOGD("protocol error detected");
return RC522_ERR_PCD_PROTOCOL_ERROR;
}
if (out_context) {
memcpy(out_context, &context, sizeof(context));
}
return ESP_OK;
}
static esp_err_t rc522_picc_receive(const rc522_handle_t rc522, const rc522_picc_transaction_context_t *context,
rc522_picc_transaction_result_t *out_result)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(context == NULL);
RC522_CHECK(context->transaction == NULL);
RC522_CHECK(out_result == NULL);
RC522_CHECK_BYTES(&context->transaction->bytes);
uint8_t fifo_level = 0;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &fifo_level));
if (fifo_level < 1) {
RC522_LOGD("fifo empty (irq=0x%02" RC522_X ")", context->interrupts);
return RC522_ERR_PCD_FIFO_EMPTY;
}
RC522_CHECK(fifo_level > out_result->bytes.length);
rc522_picc_transaction_result_t result = {
.bytes = {
.ptr = out_result->bytes.ptr, // Use buffer provided by caller
.length = fifo_level,
},
};
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &result.bytes));
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
char debug_buffer[64];
rc522_buffer_to_hex_str(result.bytes.ptr, result.bytes.length, debug_buffer, sizeof(debug_buffer));
RC522_LOGD("picc >> %s", debug_buffer);
}
if (context->transaction->rx_align) {
RC522_LOGD("applying mask (rx_align=%d)", context->transaction->rx_align);
// Apply mask for rx_align..7 of the first byte
result.bytes.ptr[0] &= (0xFF << context->transaction->rx_align);
}
// RxLastBits[2:0] indicates the number of valid bits in the last received byte.
// If this value is 0, the whole byte is valid.
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CONTROL_REG, &result.valid_bits));
result.valid_bits &= 0x07;
if (result.valid_bits) {
RC522_LOGD("not full byte received, valid_bits=%d", result.valid_bits);
}
if (context->error_reg & RC522_PCD_COLL_ERR_BIT) {
return RC522_ERR_COLLISION;
}
// Perform CRC_A validation
if (context->transaction->check_crc) {
// We need at least the CRC_A value
// and all 8 bits of the last byte
RC522_CHECK_AND_RETURN(result.bytes.length < 3, ESP_ERR_INVALID_STATE);
RC522_CHECK_AND_RETURN(result.valid_bits != 0, ESP_ERR_INVALID_STATE);
// Verify CRC_A
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) { .ptr = result.bytes.ptr, .length = result.bytes.length - 2 },
&crc));
if (memcmp(result.bytes.ptr + result.bytes.length - 2, &crc, sizeof(crc)) != 0) {
return RC522_ERR_CRC_WRONG;
}
}
memcpy(out_result, &result, sizeof(result));
return ESP_OK;
}
esp_err_t rc522_picc_transceive(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
rc522_picc_transaction_result_t *out_result)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(transaction == NULL);
rc522_picc_transaction_t transaction_clone = { 0 };
memcpy(&transaction_clone, transaction, sizeof(transaction_clone));
transaction_clone.pcd_command = RC522_PCD_TRANSCEIVE_CMD;
transaction_clone.expected_interrupts = RC522_PCD_RX_IRQ_BIT | RC522_PCD_IDLE_IRQ_BIT;
rc522_picc_transaction_context_t context = { 0 };
RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_send(rc522, &transaction_clone, &context));
if (out_result) {
if (!(context.interrupts & RC522_PCD_RX_IRQ_BIT)) {
return RC522_ERR_RX_TIMEOUT;
}
RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_receive(rc522, &context, out_result));
}
return ESP_OK;
}
inline static esp_err_t rc522_picc_parse_atqa(uint16_t atqa, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(out_atqa == NULL);
out_atqa->source = atqa;
out_atqa->rfu4 = (atqa >> 12) & 0x0F;
out_atqa->prop_coding = (atqa >> 8) & 0x0F;
out_atqa->uid_size = (atqa >> 6) & 0x03;
out_atqa->rfu1 = (atqa >> 5) & 0x01;
out_atqa->anticollision = atqa & 0x1F;
return ESP_OK;
}
static esp_err_t rc522_picc_reqa_or_wupa(const rc522_handle_t rc522, uint8_t picc_cmd, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc_cmd != RC522_PICC_CMD_REQA && picc_cmd != RC522_PICC_CMD_WUPA);
RC522_CHECK(out_atqa == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT));
uint8_t buffer[2] = { 0 };
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = &picc_cmd, .length = 1 },
.valid_bits = 7, // REQA and WUPA use short frame format
};
rc522_picc_transaction_result_t transaction_result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &transaction_result);
if (ret != ESP_OK) {
// Timeouts are expected if no PICC are in the field, log other errors
if (ret != RC522_ERR_RX_TIMER_TIMEOUT && ret != RC522_ERR_RX_TIMEOUT) {
RC522_LOGD("non-timeout error: %04" RC522_X, ret);
}
return ret;
}
if (transaction_result.bytes.length != 2 || transaction_result.valid_bits != 0) {
return RC522_ERR_INVALID_ATQA;
}
uint16_t atqa = (buffer[0] << 8) | buffer[1];
RC522_RETURN_ON_ERROR(rc522_picc_parse_atqa(atqa, out_atqa));
return ESP_OK;
}
inline esp_err_t rc522_picc_reqa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(out_atqa == NULL);
RC522_LOGD("REQA");
return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_REQA, out_atqa);
}
inline esp_err_t rc522_picc_wupa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(out_atqa == NULL);
RC522_LOGD("WUPA");
return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_WUPA, out_atqa);
}
/**
* Resolve collision and SELECT a PICC
*/
esp_err_t rc522_picc_select(const rc522_handle_t rc522, rc522_picc_uid_t *out_uid, uint8_t *out_sak, bool skip_anticoll)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(skip_anticoll && (out_uid == NULL || out_uid->length < RC522_PICC_UID_SIZE_MIN));
bool uid_complete;
bool select_done;
bool use_cascade_tag;
uint8_t cascade_level = 1;
esp_err_t ret;
uint8_t count;
uint8_t check_bit;
uint8_t index;
uint8_t uid_index; // The first index in uid->uidByte[] that is used in the current Cascade Level.
int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level.
uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A
uint8_t buffer_used; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO.
uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received.
uint8_t tx_last_bits = 0; // Used in BitFramingReg. The number of valid bits in the last transmitted byte.
uint8_t *response_buffer;
uint8_t response_length;
rc522_picc_uid_t uid;
if (skip_anticoll) {
memcpy(&uid, out_uid, sizeof(rc522_picc_uid_t));
}
else {
memset(&uid, 0, sizeof(uid));
}
uint8_t sak;
// Description of buffer structure:
// Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
// Byte 1: NVB Number of Valid Bits (in complete command, not just the UID):
// High nibble: complete bytes, Low nibble: Extra bits.
// Byte 2: UID-data or CT See explanation below. CT means Cascade Tag.
// Byte 3: UID-data
// Byte 4: UID-data
// Byte 5: UID-data
// Byte 6: BCC Block 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)
// 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
// Prepare MFRC522
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT));
// Repeat Cascade Level loop until we have a complete UID.
uid_complete = false;
while (!uid_complete) {
RC522_LOGD("cascade_level=%d, uid.length=%d", cascade_level, uid.length);
// 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] = RC522_PICC_CMD_SEL_CL1;
uid_index = 0;
// When we know that the UID has more than 4 bytes
use_cascade_tag = uid.length > 4;
break;
case 2:
buffer[0] = RC522_PICC_CMD_SEL_CL2;
uid_index = 3;
// When we know that the UID has more than 7 bytes
use_cascade_tag = uid.length > 7;
break;
case 3:
buffer[0] = RC522_PICC_CMD_SEL_CL3;
uid_index = 6;
use_cascade_tag = false; // Never used in CL3.
break;
default:
return ESP_FAIL; // TODO: use custom err
break;
}
RC522_LOGD("cl=%d, uid_index=%d, use_cascade_tag=%d", cascade_level, uid_index, use_cascade_tag);
// How many UID bits are known in this Cascade Level?
current_level_known_bits = skip_anticoll ? (4 * 8) : 0;
// Copy the known bits from uid.uidByte[] to buffer[]
index = 2; // destination index in buffer[]
if (use_cascade_tag) {
buffer[index++] = RC522_PICC_CMD_CT;
}
// The 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.value[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.
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.
RC522_LOGD("SELECT (cl=%d)", cascade_level);
// 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
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 7 }, &crc));
buffer[7] = crc.lsb;
buffer[8] = crc.msb;
tx_last_bits = 0; // 0 => All 8 bits are valid.
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.
RC522_LOGD("ANTICOLLISION (cl=%d)", cascade_level);
tx_last_bits = current_level_known_bits % 8;
count = current_level_known_bits / 8; // Number of whole bytes in the UID part.
index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs
buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid 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;
}
// Set bit adjustments
// Having a separate variable is overkill. But it makes the next line easier to read.
rx_align = tx_last_bits;
// RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, (rx_align << 4) + tx_last_bits));
// Transmit the buffer and receive the response.
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = buffer_used },
.rx_align = rx_align,
.valid_bits = tx_last_bits,
};
rc522_picc_transaction_result_t transaction_result = {
.bytes = { .ptr = response_buffer, .length = response_length },
};
ret = rc522_picc_transceive(rc522, &transaction, &transaction_result);
if (ret == ESP_OK) {
response_length = transaction_result.bytes.length;
tx_last_bits = transaction_result.valid_bits;
}
if (ret == RC522_ERR_COLLISION) { // More than one PICC in the field => collision.
RC522_LOGD("collision detected (cl=%d, skip_anticoll=%d)", cascade_level, skip_anticoll);
if (skip_anticoll) {
// If we are skipping anticoll, we should not have collisions
RC522_LOGD("unexpected collision detected");
return ret;
}
// CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
uint8_t value_of_coll_reg;
rc522_pcd_read(rc522, RC522_PCD_COLL_REG, &value_of_coll_reg);
if (value_of_coll_reg & RC522_PCD_COLL_POS_NOT_VALID_BIT) {
// Without a valid collision position we cannot continue
RC522_LOGD("collision position not valid, coll_poss[4:0] out of range");
return RC522_ERR_COLLISION_UNSOLVABLE;
}
uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32.
if (collision_pos == 0) {
collision_pos = 32;
}
if (collision_pos <= current_level_known_bits) { // No progress - should not happen
RC522_LOGD("collision_pos (%d) <= current_level_known_bits (%d)",
collision_pos,
current_level_known_bits);
return RC522_ERR_COLLISION_UNSOLVABLE;
}
// Choose the PICC with the bit set.
current_level_known_bits = collision_pos;
count = current_level_known_bits % 8; // The bit to modify
check_bit = (current_level_known_bits - 1) % 8;
index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First byte is index 0.
buffer[index] |= (1 << check_bit);
}
else if (ret != ESP_OK) {
RC522_LOGD("transceive failed");
return ret;
}
else { // ESP_OK
if (current_level_known_bits >= 32) { // This was a SELECT.
// No more anticollision
// We continue below outside the while.
select_done = true;
}
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.
}
}
} // End of while (!selectDone)
RC522_LOGD("SELECT (cl=%d) done", cascade_level);
// We do not check the CBB - it was constructed by us above.
// Copy the found UID bytes from buffer[] to uid.uidByte[]
index = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 2; // source index in buffer[]
bytes_to_copy = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 4;
for (count = 0; count < bytes_to_copy; count++) {
uid.value[uid_index + count] = buffer[index++];
}
// Check response SAK (Select Acknowledge)
if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A).
RC522_LOGD("invalid sak");
return RC522_ERR_INVALID_SAK;
}
// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed
// anymore.
// compiler complains about uninitialized response_buffer even is
// no chance that response_buffer is NULL here, so ignore warning here
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = response_buffer, .length = 1 }, &crc));
if (memcmp(response_buffer + 1, &crc, sizeof(crc)) != 0) {
RC522_LOGD("crc wrong");
return RC522_ERR_CRC_WRONG;
}
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes
cascade_level++;
}
else {
uid_complete = true;
sak = response_buffer[0];
}
#pragma GCC diagnostic pop
} // End of while (!uidComplete)
// Set correct uid.size
uid.length = 3 * cascade_level + 1;
RC522_LOGD("sak=0x%02" RC522_X, sak);
if (out_uid) {
memcpy(out_uid, &uid, sizeof(uid));
}
if (out_sak) {
*out_sak = sak;
}
return ESP_OK;
}
/**
* Checks if PICC is still in the PCD field
*/
esp_err_t rc522_picc_heartbeat(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_uid_t *out_uid, uint8_t *out_sak)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(picc->state != RC522_PICC_STATE_ACTIVE && picc->state != RC522_PICC_STATE_ACTIVE_H);
esp_err_t ret = ESP_OK;
const uint8_t retries = 5;
uint8_t retry = 1;
do {
rc522_picc_atqa_desc_t atqa;
if (retry <= 2) {
if (picc->state == RC522_PICC_STATE_ACTIVE) {
ret = rc522_picc_reqa(rc522, &atqa);
}
else if (picc->state == RC522_PICC_STATE_ACTIVE_H) {
ret = rc522_picc_wupa(rc522, &atqa);
}
}
else {
if ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK) {
ret = rc522_picc_wupa(rc522, &atqa);
}
}
if (ret == ESP_OK) {
break;
}
rc522_delay_ms(5);
}
while (retry++ < retries);
if (ret != ESP_OK) {
return ret;
}
rc522_picc_uid_t uid;
uint8_t sak;
memcpy(&uid, &picc->uid, sizeof(rc522_picc_uid_t));
ret = rc522_picc_select(rc522, &uid, &sak, true);
if (ret != ESP_OK) {
return ret;
}
if (picc->sak != sak) {
return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH;
}
for (uint8_t i = 0; i < uid.length; i++) {
if (picc->uid.value[i] != uid.value[i]) {
return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH;
}
}
if (out_uid) {
memcpy(out_uid, &uid, sizeof(uid));
}
if (out_sak) {
*out_sak = sak;
}
return ESP_OK;
}
esp_err_t rc522_picc_uid_to_str(const rc522_picc_uid_t *uid, char *buffer, uint8_t buffer_size)
{
RC522_CHECK(uid == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(buffer_size < RC522_PICC_UID_STR_BUFFER_SIZE_MAX);
return rc522_buffer_to_hex_str(uid->value, uid->length, buffer, buffer_size);
}
esp_err_t rc522_picc_halta(const rc522_handle_t rc522, rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_LOGD("HALTA");
uint8_t buffer[4] = { 0 };
buffer[0] = RC522_PICC_CMD_HLTA;
buffer[1] = 0;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc));
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, NULL);
// If the PICC responds with any modulation during a period of 1 ms after the HLTA,
// response shall be interpreted as 'not acknowledge', so timeout is not an error.
if (ret == ESP_OK) {
return RC522_ERR_HLTA_NOT_ACKED;
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_RX_TIMEOUT) {
RC522_RETURN_ON_ERROR(rc522_picc_set_state(rc522, picc, RC522_PICC_STATE_HALT, true));
return ESP_OK;
}
return ret;
}
esp_err_t rc522_picc_set_state(
const rc522_handle_t rc522, rc522_picc_t *picc, rc522_picc_state_t new_state, bool fire_event)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
esp_err_t ret = ESP_OK;
if (picc->state == new_state) {
return ESP_OK;
}
RC522_LOGD("changing state from %d to %d (fire=%d)", picc->state, new_state, fire_event);
rc522_picc_state_t old_state = picc->state;
picc->state = new_state;
if (fire_event) {
rc522_picc_state_changed_event_t event_data = {
.old_state = old_state,
.picc = picc,
};
if ((ret = rc522_dispatch_event(rc522, RC522_EVENT_PICC_STATE_CHANGED, &event_data, sizeof(event_data)))
!= ESP_OK) {
RC522_LOGW("picc_state_changed event dispatch failed (err=%04" RC522_X ")", ret);
}
}
return ret;
}
rc522_picc_type_t rc522_picc_get_type(const rc522_picc_t *picc)
{
RC522_CHECK(picc == NULL);
uint8_t sak = picc->sak;
// http://www.nxp.com/documents/application_note/AN10833.pdf
// Section: Coding of Select Acknowledge (SAK)
// ignore 8th (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 0x09:
return RC522_PICC_TYPE_MIFARE_MINI;
case 0x08:
return RC522_PICC_TYPE_MIFARE_1K;
case 0x18:
return RC522_PICC_TYPE_MIFARE_4K;
case 0x00:
return RC522_PICC_TYPE_MIFARE_UL;
case 0x10:
case 0x11:
return RC522_PICC_TYPE_MIFARE_PLUS;
case 0x01:
return RC522_PICC_TYPE_TNP3XXX;
case 0x20:
return picc->atqa.source == 0x4400 ? RC522_PICC_TYPE_MIFARE_DESFIRE : RC522_PICC_TYPE_ISO_14443_4;
case 0x40:
return RC522_PICC_TYPE_ISO_18092;
default:
return RC522_PICC_TYPE_UNKNOWN;
}
}
char *rc522_picc_type_name(rc522_picc_type_t type)
{
switch (type) {
/* SAK-determined */
case RC522_PICC_TYPE_ISO_14443_4:
return "PICC compliant with ISO/IEC 14443-4";
case RC522_PICC_TYPE_ISO_18092:
return "PICC compliant with ISO/IEC 18092 (NFC)";
case RC522_PICC_TYPE_MIFARE_MINI:
return "MIFARE Mini, 320 bytes";
case RC522_PICC_TYPE_MIFARE_1K:
return "MIFARE 1K";
case RC522_PICC_TYPE_MIFARE_4K:
return "MIFARE 4K";
case RC522_PICC_TYPE_MIFARE_UL:
// SAK=0x00 for both of these families
return "MIFARE Ultralight or NXP NTAG";
case RC522_PICC_TYPE_MIFARE_PLUS:
return "MIFARE Plus";
case RC522_PICC_TYPE_MIFARE_DESFIRE:
return "MIFARE DESFire";
case RC522_PICC_TYPE_TNP3XXX:
return "MIFARE TNP3XXX";
/* Further ID processing */
case RC522_PICC_TYPE_MIFARE_UL_:
return "MIFARE Ultralight";
case RC522_PICC_TYPE_MIFARE_UL_C:
return "MIFARE Ultralight C";
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return "MIFARE Ultralight EV1 (80 bytes)";
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return "MIFARE Ultralight EV1 (164 bytes)";
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return "MIFARE Ultralight NANO";
case RC522_PICC_TYPE_MIFARE_UL_AES:
return "MIFARE Ultralight AES";
case RC522_PICC_TYPE_NTAG2xx:
return "NTAG2xx";
case RC522_PICC_TYPE_NTAG213:
return "NTAG213";
case RC522_PICC_TYPE_NTAG215:
return "NTAG215";
case RC522_PICC_TYPE_NTAG216:
return "NTAG216";
case RC522_PICC_TYPE_UNDEFINED:
case RC522_PICC_TYPE_UNKNOWN:
default:
return "unknown";
}
}
esp_err_t rc522_picc_print(const rc522_picc_t *picc)
{
RC522_CHECK(picc == NULL);
char uid_str[RC522_PICC_UID_STR_BUFFER_SIZE_MAX];
RC522_RETURN_ON_ERROR(rc522_picc_uid_to_str(&picc->uid, uid_str, sizeof(uid_str)));
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "╔══════════════╗");
ESP_LOGI(TAG, "║ ║ Type: %s", rc522_picc_type_name(picc->type));
ESP_LOGI(TAG, "║ RFID ║ UID: %s", uid_str);
ESP_LOGI(TAG, "║ CARD ║ ATQA: 0x%04" RC522_X, picc->atqa.source);
ESP_LOGI(TAG, "║ ║ SAK: 0x%02" RC522_X, picc->sak);
ESP_LOGI(TAG, "╚══════════════╝");
ESP_LOGI(TAG, "");
return ESP_OK;
}