mirror of
https://github.com/sweidac/kidsbox.git
synced 2026-10-27 22:52:57 +01:00
Vendor esp-idf-rc522 as plain source instead of submodule
The library is modified (NDEF reading, cascade anticollision) so vendoring it directly avoids needing push access to the upstream fork. https://claude.ai/code/session_01MhAnh68DPNnTjcKqjNYDmk
This commit is contained in:
7 files changed
+865
-5
No files matched your search
@@ -1,7 +1,3 @@
|
||||
[submodule "device/components/esp-idf-rc522"]
|
||||
path = device/components/esp-idf-rc522
|
||||
url = https://github.com/sweidac/esp-idf-rc522.git
|
||||
|
||||
[submodule "device/components/arduino-esp32"]
|
||||
path = device/components/arduino-esp32
|
||||
url = https://github.com/espressif/arduino-esp32.git
|
||||
Submodule device/components/esp-idf-rc522 deleted from beb7e87877.
@@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "rc522.c"
|
||||
INCLUDE_DIRS "."
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Alija Bobija
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -0,0 +1,746 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "soc/gpio_struct.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "rc522.h"
|
||||
|
||||
static const char* TAG = "ESP-RC522";
|
||||
|
||||
struct rc522 {
|
||||
bool running;
|
||||
rc522_config_t* config;
|
||||
spi_device_handle_t spi;
|
||||
TaskHandle_t task_handle;
|
||||
bool scan_started;
|
||||
uint8_t* lastSerialNumber;
|
||||
};
|
||||
|
||||
typedef struct rc522* rc522_handle_t;
|
||||
|
||||
static rc522_handle_t hndl = NULL;
|
||||
|
||||
#define rc522_fw_version() rc522_read(0x37)
|
||||
|
||||
bool rc522_is_inited() {
|
||||
return hndl != NULL;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_spi_init() {
|
||||
if(! hndl || ! hndl->config) {
|
||||
ESP_LOGE(TAG, "Fail to init SPI. Invalid handle");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if(hndl->spi) {
|
||||
ESP_LOGW(TAG, "SPI already initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = hndl->config->miso_io,
|
||||
.mosi_io_num = hndl->config->mosi_io,
|
||||
.sclk_io_num = hndl->config->sck_io,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1
|
||||
};
|
||||
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.clock_speed_hz = 5000000,
|
||||
.mode = 0,
|
||||
.spics_io_num = hndl->config->sda_io,
|
||||
.queue_size = 7,
|
||||
.flags = SPI_DEVICE_HALFDUPLEX
|
||||
};
|
||||
|
||||
esp_err_t err = spi_bus_initialize(hndl->config->spi_host_id, &buscfg, 0);
|
||||
|
||||
if(err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = spi_bus_add_device(hndl->config->spi_host_id, &devcfg, &hndl->spi);
|
||||
|
||||
if(err != ESP_OK) {
|
||||
spi_bus_free(hndl->config->spi_host_id);
|
||||
hndl->spi = NULL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_write_n(uint8_t addr, uint8_t n, uint8_t *data) {
|
||||
uint8_t* buffer = (uint8_t*) malloc(n + 1);
|
||||
buffer[0] = (addr << 1) & 0x7E;
|
||||
|
||||
for (uint8_t i = 1; i <= n; i++) {
|
||||
buffer[i] = data[i-1];
|
||||
}
|
||||
|
||||
spi_transaction_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
t.length = 8 * (n + 1);
|
||||
t.tx_buffer = buffer;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(hndl->spi, &t);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_write(uint8_t addr, uint8_t val) {
|
||||
return rc522_write_n(addr, 1, &val);
|
||||
}
|
||||
|
||||
static uint8_t* rc522_read_n(uint8_t addr, uint8_t n) {
|
||||
if (n <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
spi_transaction_t t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
uint8_t* buffer = (uint8_t*) malloc(n);
|
||||
|
||||
t.flags = SPI_TRANS_USE_TXDATA;
|
||||
t.length = 8;
|
||||
t.tx_data[0] = ((addr << 1) & 0x7E) | 0x80;
|
||||
t.rxlength = 8 * n;
|
||||
t.rx_buffer = buffer;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(hndl->spi, &t);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static uint8_t rc522_read(uint8_t addr) {
|
||||
uint8_t* buffer = rc522_read_n(addr, 1);
|
||||
uint8_t res = buffer[0];
|
||||
free(buffer);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_set_bitmask(uint8_t addr, uint8_t mask) {
|
||||
return rc522_write(addr, rc522_read(addr) | mask);
|
||||
}
|
||||
|
||||
static esp_err_t rc522_clear_bitmask(uint8_t addr, uint8_t mask) {
|
||||
return rc522_write(addr, rc522_read(addr) & ~mask);
|
||||
}
|
||||
|
||||
static esp_err_t rc522_antenna_on() {
|
||||
esp_err_t ret;
|
||||
|
||||
if(~ (rc522_read(0x14) & 0x03)) {
|
||||
ret = rc522_set_bitmask(0x14, 0x03);
|
||||
|
||||
if(ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return rc522_write(0x26, 0x60); // 43dB gain
|
||||
}
|
||||
|
||||
static void rc522_task(void* arg);
|
||||
|
||||
esp_err_t rc522_init(rc522_config_t* config) {
|
||||
if(! config) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if(hndl) {
|
||||
ESP_LOGW(TAG, "Already initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if(! (hndl = calloc(1, sizeof(struct rc522)))) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if(! (hndl->config = calloc(1, sizeof(rc522_config_t)))) {
|
||||
rc522_destroy();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// copy config considering defaults
|
||||
hndl->config->callback = config->callback;
|
||||
hndl->config->ndef_callback = config->ndef_callback;
|
||||
hndl->config->miso_io = config->miso_io == 0 ? RC522_DEFAULT_MISO : config->miso_io;
|
||||
hndl->config->mosi_io = config->mosi_io == 0 ? RC522_DEFAULT_MOSI : config->mosi_io;
|
||||
hndl->config->sck_io = config->sck_io == 0 ? RC522_DEFAULT_SCK : config->sck_io;
|
||||
hndl->config->sda_io = config->sda_io == 0 ? RC522_DEFAULT_SDA : config->sda_io;
|
||||
hndl->config->spi_host_id = config->spi_host_id == 0 ? RC522_DEFAULT_SPI_HOST : config->spi_host_id;
|
||||
hndl->config->scan_interval_ms = config->scan_interval_ms < 50 ? RC522_DEFAULT_SCAN_INTERVAL_MS : config->scan_interval_ms;
|
||||
hndl->config->task_stack_size = config->task_stack_size == 0 ? RC522_DEFAULT_TACK_STACK_SIZE : config->task_stack_size;
|
||||
hndl->config->task_priority = config->task_priority == 0 ? RC522_DEFAULT_TACK_STACK_PRIORITY : config->task_priority;
|
||||
|
||||
esp_err_t err = rc522_spi_init();
|
||||
|
||||
if(err != ESP_OK) {
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
// ---------- RW test ------------
|
||||
const uint8_t test_addr = 0x24, test_val = 0x25;
|
||||
for(uint8_t i = test_val; i < test_val + 2; i++) {
|
||||
if((err = rc522_write(test_addr, i)) != ESP_OK || rc522_read(test_addr) != i) {
|
||||
ESP_LOGE(TAG, "RW test fail");
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
// ------- End of RW test --------
|
||||
|
||||
rc522_write(0x01, 0x0F);
|
||||
rc522_write(0x2A, 0x8D);
|
||||
rc522_write(0x2B, 0x3E);
|
||||
rc522_write(0x2D, 0x1E);
|
||||
rc522_write(0x2C, 0x00);
|
||||
rc522_write(0x15, 0x40);
|
||||
rc522_write(0x11, 0x3D);
|
||||
|
||||
rc522_antenna_on();
|
||||
|
||||
hndl->running = true;
|
||||
if (xTaskCreate(rc522_task, "rc522_task", hndl->config->task_stack_size, NULL, hndl->config->task_priority, &hndl->task_handle) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Fail to create rc522 task");
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
if(err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Fail to create timer");
|
||||
rc522_destroy();
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initialized (firmware: 0x%x)", rc522_fw_version());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint64_t rc522_sn_to_u64(uint8_t* sn) {
|
||||
if(!sn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t result = 0;
|
||||
for(int i = 4; i >= 0; i--) {
|
||||
result |= ((uint64_t) sn[i] << (i * 8));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Returns pointer to dynamically allocated array of two element */
|
||||
static uint8_t* rc522_calculate_crc(uint8_t *data, uint8_t n) {
|
||||
rc522_clear_bitmask(0x05, 0x04);
|
||||
rc522_set_bitmask(0x0A, 0x80);
|
||||
|
||||
rc522_write_n(0x09, n, data);
|
||||
|
||||
rc522_write(0x01, 0x03);
|
||||
|
||||
uint8_t i = 255;
|
||||
uint8_t nn = 0;
|
||||
|
||||
for(;;) {
|
||||
nn = rc522_read(0x05);
|
||||
i--;
|
||||
|
||||
if(! (i != 0 && ! (nn & 0x04))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* res = (uint8_t*) malloc(2);
|
||||
|
||||
res[0] = rc522_read(0x22);
|
||||
res[1] = rc522_read(0x21);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t* rc522_card_write(uint8_t cmd, uint8_t *data, uint8_t n, uint8_t* res_n) {
|
||||
uint8_t *result = NULL;
|
||||
uint8_t irq = 0x00;
|
||||
uint8_t irq_wait = 0x00;
|
||||
uint8_t last_bits = 0;
|
||||
uint8_t nn = 0;
|
||||
|
||||
if(cmd == 0x0E) {
|
||||
irq = 0x12;
|
||||
irq_wait = 0x10;
|
||||
}
|
||||
else if(cmd == 0x0C) {
|
||||
irq = 0x77;
|
||||
irq_wait = 0x30;
|
||||
}
|
||||
|
||||
rc522_write(0x02, irq | 0x80);
|
||||
rc522_clear_bitmask(0x04, 0x80);
|
||||
rc522_set_bitmask(0x0A, 0x80);
|
||||
rc522_write(0x01, 0x00);
|
||||
|
||||
rc522_write_n(0x09, n, data);
|
||||
|
||||
rc522_write(0x01, cmd);
|
||||
|
||||
if(cmd == 0x0C) {
|
||||
rc522_set_bitmask(0x0D, 0x80);
|
||||
}
|
||||
|
||||
uint16_t i = 1000;
|
||||
|
||||
for(;;) {
|
||||
nn = rc522_read(0x04);
|
||||
i--;
|
||||
|
||||
if(! (i != 0 && (((nn & 0x01) == 0) && ((nn & irq_wait) == 0)))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc522_clear_bitmask(0x0D, 0x80);
|
||||
|
||||
if(i != 0) {
|
||||
if((rc522_read(0x06) & 0x1B) == 0x00) {
|
||||
if(cmd == 0x0C) {
|
||||
nn = rc522_read(0x0A);
|
||||
last_bits = rc522_read(0x0C) & 0x07;
|
||||
|
||||
if (last_bits != 0) {
|
||||
*res_n = (nn - 1) + last_bits;
|
||||
} else {
|
||||
*res_n = nn;
|
||||
}
|
||||
|
||||
result = (uint8_t*) malloc(*res_n);
|
||||
|
||||
for(i = 0; i < *res_n; i++) {
|
||||
result[i] = rc522_read(0x09);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t* rc522_request(uint8_t* res_n) {
|
||||
uint8_t* result = NULL;
|
||||
rc522_write(0x0D, 0x07);
|
||||
|
||||
uint8_t req_mode = 0x26;
|
||||
result = rc522_card_write(0x0C, &req_mode, 1, res_n);
|
||||
|
||||
if(*res_n * 8 != 0x10) {
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t* rc522_anticoll() {
|
||||
uint8_t res_n;
|
||||
|
||||
rc522_write(0x0D, 0x00);
|
||||
uint8_t* result = rc522_card_write(0x0C, (uint8_t[]) { 0x93, 0x20 }, 2, &res_n);
|
||||
|
||||
if(result && res_n != 5) { // all cards/tags serial numbers is 5 bytes long (?)
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Send HALT and clear crypto bit. Card will only respond to WUPA after this. */
|
||||
static void rc522_halt_card() {
|
||||
uint8_t buf[] = { 0x50, 0x00, 0x00, 0x00 };
|
||||
uint8_t* crc = rc522_calculate_crc(buf, 2);
|
||||
buf[2] = crc[0];
|
||||
buf[3] = crc[1];
|
||||
free(crc);
|
||||
|
||||
uint8_t res_n;
|
||||
uint8_t* res = rc522_card_write(0x0C, buf, 4, &res_n);
|
||||
free(res);
|
||||
rc522_clear_bitmask(0x08, 0x08);
|
||||
}
|
||||
|
||||
static uint8_t* rc522_get_tag() {
|
||||
uint8_t* result = NULL;
|
||||
uint8_t* res_data = NULL;
|
||||
uint8_t res_data_n;
|
||||
|
||||
res_data = rc522_request(&res_data_n);
|
||||
|
||||
if(res_data != NULL) {
|
||||
free(res_data);
|
||||
result = rc522_anticoll();
|
||||
/* NOTE: card is NOT halted here — caller must call rc522_halt_card() */
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* URI prefix table for NDEF URI records (RFC 3986 shortened forms) */
|
||||
static const char* const RC522_URI_PREFIXES[] = {
|
||||
"", /* 0x00 no prefix */
|
||||
"http://www.", /* 0x01 */
|
||||
"https://www.", /* 0x02 */
|
||||
"http://", /* 0x03 */
|
||||
"https://", /* 0x04 */
|
||||
"tel:", /* 0x05 */
|
||||
"mailto:", /* 0x06 */
|
||||
"ftp://anonymous:anonymous@", /* 0x07 */
|
||||
"ftp://ftp.", /* 0x08 */
|
||||
"ftps://", /* 0x09 */
|
||||
"sftp://", /* 0x0A */
|
||||
"smb://", /* 0x0B */
|
||||
"nfs://", /* 0x0C */
|
||||
"ftp://", /* 0x0D */
|
||||
"dav://", /* 0x0E */
|
||||
"news:", /* 0x0F */
|
||||
"telnet://", /* 0x10 */
|
||||
"imap:", /* 0x11 */
|
||||
"rtsp://", /* 0x12 */
|
||||
"urn:", /* 0x13 */
|
||||
"pop:", /* 0x14 */
|
||||
"sip:", /* 0x15 */
|
||||
"sips:", /* 0x16 */
|
||||
"tftp:", /* 0x17 */
|
||||
"btspp://", /* 0x18 */
|
||||
"btl2cap://", /* 0x19 */
|
||||
"btgoep://", /* 0x1A */
|
||||
"tcpobex://", /* 0x1B */
|
||||
"irdaobex://", /* 0x1C */
|
||||
"file://", /* 0x1D */
|
||||
"urn:epc:id:", /* 0x1E */
|
||||
"urn:epc:tag:", /* 0x1F */
|
||||
"urn:epc:pat:", /* 0x20 */
|
||||
"urn:epc:raw:", /* 0x21 */
|
||||
"urn:epc:", /* 0x22 */
|
||||
"urn:nfc:", /* 0x23 */
|
||||
};
|
||||
#define RC522_URI_PREFIX_COUNT (sizeof(RC522_URI_PREFIXES) / sizeof(RC522_URI_PREFIXES[0]))
|
||||
|
||||
/* Parse an NDEF message TLV block and extract the first URI record URL.
|
||||
* Returns heap-allocated null-terminated URL string, or NULL if not found. */
|
||||
static char* rc522_parse_ndef_url(const uint8_t* data, int len) {
|
||||
int i = 0;
|
||||
|
||||
while(i < len) {
|
||||
uint8_t tlv_type = data[i++];
|
||||
|
||||
if(tlv_type == 0xFE) break; /* Terminator TLV */
|
||||
if(tlv_type == 0x00) continue; /* Null TLV — no length byte */
|
||||
|
||||
if(i >= len) break;
|
||||
|
||||
/* Read TLV length (1 or 3 bytes) */
|
||||
uint16_t tlv_len;
|
||||
if(data[i] == 0xFF) {
|
||||
i++;
|
||||
if(i + 1 >= len) break;
|
||||
tlv_len = ((uint16_t)data[i] << 8) | data[i + 1];
|
||||
i += 2;
|
||||
} else {
|
||||
tlv_len = data[i++];
|
||||
}
|
||||
|
||||
if(tlv_type != 0x03 || tlv_len == 0 || i + (int)tlv_len > len) {
|
||||
i += tlv_len;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Parse NDEF record header */
|
||||
uint8_t header = data[i++];
|
||||
uint8_t tnf = header & 0x07;
|
||||
bool sr = (header & 0x10) != 0; /* Short Record */
|
||||
bool il = (header & 0x08) != 0; /* ID Length present */
|
||||
|
||||
if(i >= len) break;
|
||||
uint8_t type_len = data[i++];
|
||||
|
||||
uint32_t payload_len;
|
||||
if(sr) {
|
||||
if(i >= len) break;
|
||||
payload_len = data[i++];
|
||||
} else {
|
||||
if(i + 3 >= len) break;
|
||||
payload_len = ((uint32_t)data[i] << 24) | ((uint32_t)data[i+1] << 16)
|
||||
| ((uint32_t)data[i+2] << 8) | data[i+3];
|
||||
i += 4;
|
||||
}
|
||||
|
||||
uint8_t id_len = 0;
|
||||
if(il) {
|
||||
if(i >= len) break;
|
||||
id_len = data[i++];
|
||||
}
|
||||
|
||||
/* Only handle Well-Known URI records (TNF=0x01, Type='U') */
|
||||
if(tnf == 0x01 && type_len == 1 && i < len && data[i] == 0x55) {
|
||||
i++; /* skip type byte 'U' */
|
||||
i += id_len; /* skip optional ID */
|
||||
|
||||
if(payload_len > 0 && i < len) {
|
||||
uint8_t prefix_code = data[i++];
|
||||
payload_len--;
|
||||
|
||||
const char* prefix = (prefix_code < RC522_URI_PREFIX_COUNT) ? RC522_URI_PREFIXES[prefix_code] : "";
|
||||
size_t prefix_len = strlen(prefix);
|
||||
size_t body_len = (payload_len < (uint32_t)(len - i)) ? payload_len : (uint32_t)(len - i);
|
||||
|
||||
if(prefix_len + body_len > 255) body_len = 255 - prefix_len;
|
||||
|
||||
char* url = (char*) malloc(prefix_len + body_len + 1);
|
||||
if(!url) return NULL;
|
||||
|
||||
memcpy(url, prefix, prefix_len);
|
||||
memcpy(url + prefix_len, data + i, body_len);
|
||||
url[prefix_len + body_len] = '\0';
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip over this record */
|
||||
i += type_len + id_len + (int)payload_len;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Wake a halted card with WUPA (0x52), perform cascade anticollision (CL1 + optional CL2),
|
||||
* SELECT the card, read pages 4–15 of user memory (NDEF area), parse, then halt.
|
||||
* Returns heap-allocated URL string or NULL. Card is always left in HALT state on return. */
|
||||
static char* rc522_read_ndef_url() {
|
||||
uint8_t res_n;
|
||||
|
||||
/* WUPA: wake halted card — identical bit-framing to REQA but byte is 0x52 */
|
||||
rc522_write(0x0D, 0x07);
|
||||
uint8_t wupa_cmd = 0x52;
|
||||
uint8_t* atqa = rc522_card_write(0x0C, &wupa_cmd, 1, &res_n);
|
||||
if(!atqa || res_n * 8 != 0x10) {
|
||||
free(atqa);
|
||||
return NULL;
|
||||
}
|
||||
free(atqa);
|
||||
|
||||
/* CL1 anti-collision */
|
||||
rc522_write(0x0D, 0x00);
|
||||
uint8_t* cl1 = rc522_card_write(0x0C, (uint8_t[]){ 0x93, 0x20 }, 2, &res_n);
|
||||
if(!cl1 || res_n != 5) {
|
||||
free(cl1);
|
||||
rc522_halt_card();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool cascade = (cl1[0] == 0x88); /* cascade tag byte indicates 7-byte UID */
|
||||
|
||||
/* CL1 SELECT */
|
||||
uint8_t sel1[9] = { 0x93, 0x70, cl1[0], cl1[1], cl1[2], cl1[3], cl1[4] };
|
||||
uint8_t* crc = rc522_calculate_crc(sel1, 7);
|
||||
sel1[7] = crc[0]; sel1[8] = crc[1]; free(crc);
|
||||
uint8_t sel_n;
|
||||
uint8_t* sak1 = rc522_card_write(0x0C, sel1, 9, &sel_n);
|
||||
free(cl1);
|
||||
if(!sak1) {
|
||||
rc522_halt_card();
|
||||
return NULL;
|
||||
}
|
||||
free(sak1);
|
||||
|
||||
if(cascade) {
|
||||
/* CL2 anti-collision */
|
||||
rc522_write(0x0D, 0x00);
|
||||
uint8_t* cl2 = rc522_card_write(0x0C, (uint8_t[]){ 0x95, 0x20 }, 2, &res_n);
|
||||
if(!cl2 || res_n != 5) {
|
||||
free(cl2);
|
||||
rc522_halt_card();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* CL2 SELECT */
|
||||
uint8_t sel2[9] = { 0x95, 0x70, cl2[0], cl2[1], cl2[2], cl2[3], cl2[4] };
|
||||
crc = rc522_calculate_crc(sel2, 7);
|
||||
sel2[7] = crc[0]; sel2[8] = crc[1]; free(crc);
|
||||
uint8_t* sak2 = rc522_card_write(0x0C, sel2, 9, &sel_n);
|
||||
free(cl2);
|
||||
if(!sak2) {
|
||||
rc522_halt_card();
|
||||
return NULL;
|
||||
}
|
||||
free(sak2);
|
||||
}
|
||||
|
||||
/* Card is now SELECTED. Verify NDEF capability container at page 3.
|
||||
* For NTAG/MIFARE Ultralight the NDEF magic byte is 0xE1 at CC[0]. */
|
||||
uint8_t read_cc[4] = { 0x30, 0x03 };
|
||||
crc = rc522_calculate_crc(read_cc, 2);
|
||||
read_cc[2] = crc[0]; read_cc[3] = crc[1]; free(crc);
|
||||
|
||||
uint8_t* cc_data = rc522_card_write(0x0C, read_cc, 4, &res_n);
|
||||
if(!cc_data || res_n < 4 || cc_data[0] != 0xE1) {
|
||||
free(cc_data);
|
||||
rc522_halt_card();
|
||||
return NULL;
|
||||
}
|
||||
free(cc_data);
|
||||
|
||||
/* Read NDEF user pages 4–15 (12 pages × 4 bytes = 48 bytes).
|
||||
* READ command (0x30) returns 16 bytes (4 pages) per call. */
|
||||
uint8_t ndef_buf[48];
|
||||
int ndef_len = 0;
|
||||
|
||||
for(uint8_t page = 4; page <= 12; page += 4) { /* 3 reads × 16 bytes = 48 bytes */
|
||||
uint8_t read_cmd[4] = { 0x30, page };
|
||||
crc = rc522_calculate_crc(read_cmd, 2);
|
||||
read_cmd[2] = crc[0]; read_cmd[3] = crc[1]; free(crc);
|
||||
|
||||
uint8_t* page_data = rc522_card_write(0x0C, read_cmd, 4, &res_n);
|
||||
if(!page_data || res_n < 16) {
|
||||
free(page_data);
|
||||
break;
|
||||
}
|
||||
|
||||
int copy = (48 - ndef_len < 16) ? 48 - ndef_len : 16;
|
||||
memcpy(ndef_buf + ndef_len, page_data, copy);
|
||||
ndef_len += copy;
|
||||
free(page_data);
|
||||
}
|
||||
|
||||
rc522_halt_card();
|
||||
|
||||
if(ndef_len == 0) return NULL;
|
||||
return rc522_parse_ndef_url(ndef_buf, ndef_len);
|
||||
}
|
||||
|
||||
esp_err_t rc522_start(rc522_start_args_t start_args) {
|
||||
esp_err_t err = rc522_init(&start_args);
|
||||
return err != ESP_OK ? err : rc522_start2();
|
||||
}
|
||||
|
||||
esp_err_t rc522_start2() {
|
||||
if(! hndl) { return ESP_ERR_INVALID_STATE; }
|
||||
|
||||
hndl->scan_started = true;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rc522_pause() {
|
||||
if(! hndl) { return ESP_ERR_INVALID_STATE; }
|
||||
|
||||
if(! hndl->scan_started) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
hndl->scan_started = false;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void rc522_destroy() {
|
||||
if(! hndl) { return; }
|
||||
|
||||
rc522_pause(); // stop timer
|
||||
hndl->running = false; // task will delete itself
|
||||
|
||||
if(hndl->spi) {
|
||||
spi_bus_remove_device(hndl->spi);
|
||||
spi_bus_free(hndl->config->spi_host_id);
|
||||
hndl->spi = NULL;
|
||||
}
|
||||
|
||||
free(hndl->config);
|
||||
hndl->config = NULL;
|
||||
|
||||
free(hndl);
|
||||
hndl = NULL;
|
||||
}
|
||||
|
||||
static bool isSameSerialNumber(uint8_t* one, uint8_t* other) {
|
||||
if (one == NULL && other == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (one == NULL || other == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return one[0] == other[0]
|
||||
&& one[1] == other[1]
|
||||
&& one[2] == other[2]
|
||||
&& one[3] == other[3]
|
||||
&& one[4] == other[4];
|
||||
}
|
||||
|
||||
static void rc522_task(void* arg) {
|
||||
while(hndl->running) {
|
||||
if(!hndl->scan_started) {
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t* serial_no = rc522_get_tag(); /* card NOT halted yet */
|
||||
|
||||
if(!isSameSerialNumber(serial_no, hndl->lastSerialNumber)) {
|
||||
rc522_tag_callback_t cb = hndl->config->callback;
|
||||
rc522_ndef_callback_t ndef_cb = hndl->config->ndef_callback;
|
||||
|
||||
if(serial_no == NULL) {
|
||||
free(hndl->lastSerialNumber);
|
||||
hndl->lastSerialNumber = NULL;
|
||||
|
||||
if(cb) cb(NULL);
|
||||
if(ndef_cb) ndef_cb(NULL, NULL);
|
||||
} else {
|
||||
if(!hndl->lastSerialNumber) {
|
||||
hndl->lastSerialNumber = malloc(5);
|
||||
}
|
||||
memcpy(hndl->lastSerialNumber, serial_no, 5);
|
||||
|
||||
if(cb) cb(serial_no);
|
||||
|
||||
if(ndef_cb) {
|
||||
/* rc522_read_ndef_url() handles HALT internally after reading */
|
||||
char* url = rc522_read_ndef_url();
|
||||
ndef_cb(serial_no, url);
|
||||
if(url) free(url);
|
||||
} else {
|
||||
rc522_halt_card();
|
||||
}
|
||||
}
|
||||
} else if(serial_no != NULL) {
|
||||
/* Same card still present — halt it again to prevent continuous REQA responses */
|
||||
rc522_halt_card();
|
||||
}
|
||||
|
||||
int delay_ms = hndl->config->scan_interval_ms;
|
||||
if(serial_no != NULL) {
|
||||
free(serial_no);
|
||||
serial_no = NULL;
|
||||
delay_ms *= 2;
|
||||
}
|
||||
|
||||
vTaskDelay(delay_ms / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
#define RC522_DEFAULT_MISO (25)
|
||||
#define RC522_DEFAULT_MOSI (23)
|
||||
#define RC522_DEFAULT_SCK (19)
|
||||
#define RC522_DEFAULT_SDA (22)
|
||||
#define RC522_DEFAULT_SPI_HOST (VSPI_HOST)
|
||||
#define RC522_DEFAULT_SCAN_INTERVAL_MS (125)
|
||||
#define RC522_DEFAULT_TACK_STACK_SIZE (4 * 1024)
|
||||
#define RC522_DEFAULT_TACK_STACK_PRIORITY (4)
|
||||
|
||||
typedef void(*rc522_tag_callback_t)(uint8_t*);
|
||||
|
||||
/* Called when a tag is detected (url != NULL) or removed (sn == NULL, url == NULL).
|
||||
* url is the NDEF URI record content, or NULL if no NDEF was found on the tag.
|
||||
* The caller must NOT free sn or url — they are freed by the library. */
|
||||
typedef void(*rc522_ndef_callback_t)(uint8_t* sn, const char* url);
|
||||
|
||||
typedef struct {
|
||||
int miso_io; /*<! MFRC522 MISO gpio (Default: 25) */
|
||||
int mosi_io; /*<! MFRC522 MOSI gpio (Default: 23) */
|
||||
int sck_io; /*<! MFRC522 SCK gpio (Default: 19) */
|
||||
int sda_io; /*<! MFRC522 SDA gpio (Default: 22) */
|
||||
spi_host_device_t spi_host_id; /*<! Default VSPI_HOST (SPI3) */
|
||||
rc522_tag_callback_t callback; /*<! Called with 5-byte serial number, or NULL when tag removed */
|
||||
rc522_ndef_callback_t ndef_callback; /*<! Called with (sn, url) on tag change; url may be NULL */
|
||||
uint16_t scan_interval_ms; /*<! How fast will ESP32 scan for nearby tags, in miliseconds. Default: 125ms */
|
||||
size_t task_stack_size; /*<! Stack size of rc522 task (Default: 4 * 1024) */
|
||||
uint8_t task_priority; /*<! Priority of rc522 task (Default: 4) */
|
||||
} rc522_config_t;
|
||||
|
||||
typedef rc522_config_t rc522_start_args_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize RC522 module.
|
||||
* To start scanning tags - call rc522_resume or rc522_start2 function.
|
||||
* @param config Configuration
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_init(rc522_config_t* config);
|
||||
|
||||
/**
|
||||
* @brief Convert serial number (array of 5 bytes) to uint64_t number
|
||||
* @param sn Serial number
|
||||
* @return Serial number in number representation. If fail, 0 will be retured
|
||||
*/
|
||||
uint64_t rc522_sn_to_u64(uint8_t* sn);
|
||||
|
||||
/**
|
||||
* @brief Check if RC522 is inited
|
||||
* @return true if RC522 is inited
|
||||
*/
|
||||
bool rc522_is_inited();
|
||||
|
||||
/**
|
||||
* @brief This function will call rc522_init function and immediately start to scan tags by calling rc522_resume function.
|
||||
* @param start_args Configuration
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_start(rc522_start_args_t start_args);
|
||||
|
||||
/**
|
||||
* @brief Start to scan tags. If already started, ESP_OK will just be returned.
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_start2();
|
||||
|
||||
/**
|
||||
* @brief Start to scan tags. If already started, ESP_OK will just be returned.
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
#define rc522_resume() rc522_start2()
|
||||
|
||||
/**
|
||||
* @brief Pause scan tags. If already paused, ESP_OK will just be returned.
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t rc522_pause();
|
||||
|
||||
/**
|
||||
* @brief Destroy RC522 and free all resources
|
||||
*/
|
||||
void rc522_destroy();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user