235 lines
7.9 KiB
C
235 lines
7.9 KiB
C
/*
|
|
* MTG RFID Companion - MFRC522 RFID manager (Core 0)
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "esp_log.h"
|
|
#include "esp_event.h"
|
|
#include "rc522.h"
|
|
#include "rc522_picc.h"
|
|
#include "driver/rc522_spi.h"
|
|
#include "hw_pins.h"
|
|
#include "spi_bus_manager.h"
|
|
#include "rfid_manager.h"
|
|
|
|
static const char *TAG = "rfid";
|
|
|
|
/* FreeRTOS task names are truncated to configMAX_TASK_NAME_LEN-1 (15)
|
|
* printable chars, and xTaskGetHandle() asserts if the query is 16+.
|
|
* Keep this <= 15 chars and in sync with the rc522 library's
|
|
* xTaskCreate("rc522_poll", ...). */
|
|
#define RC522_TASK_NAME "rc522_poll"
|
|
#define RC522_POLL_INTERVAL_MS (100)
|
|
#define RC522_TASK_STACK_SIZE (6 * 1024)
|
|
#define RC522_TASK_PRIORITY (4)
|
|
|
|
/* Conservative clock for MFRC522 clones. If the FIFO self-test still
|
|
* complains, lower further or check power/wiring. */
|
|
#define RC522_SPI_CLK_HZ (1000000)
|
|
#define RC522_START_ATTEMPTS (3)
|
|
|
|
/* Some clone MFRC522 modules (esp. those wired with RST left floating and
|
|
* only an IRQ header exposed) fail the library's strict PCD rw self-test even
|
|
* though scanning works fine. Setting this to 1 skips that gate and starts
|
|
* the polling task anyway. */
|
|
#ifndef CONFIG_MTG_RFID_SKIP_SELFTEST
|
|
#define CONFIG_MTG_RFID_SKIP_SELFTEST (1)
|
|
#endif
|
|
|
|
static rc522_driver_handle_t s_driver;
|
|
static rc522_handle_t s_scanner;
|
|
|
|
static char s_last_uid[RFID_UID_STR_MAX_LEN] = {0};
|
|
static bool s_pending_card = false;
|
|
|
|
static void uid_to_hex_string(const rc522_picc_uid_t *uid, char *out)
|
|
{
|
|
int i;
|
|
for (i = 0; i < uid->length && i < RC522_PICC_UID_SIZE_MAX; i++) {
|
|
snprintf(out + (i * 2), 3, "%02X", uid->value[i]);
|
|
}
|
|
}
|
|
|
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
|
{
|
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
|
rc522_picc_t *picc = event->picc;
|
|
|
|
(void)arg;
|
|
(void)base;
|
|
(void)event_id;
|
|
|
|
if (picc->state == RC522_PICC_STATE_ACTIVE) {
|
|
uid_to_hex_string(&picc->uid, s_last_uid);
|
|
s_pending_card = true;
|
|
ESP_LOGI(TAG, "Card scanned: UID=%s (%d bytes, %s)",
|
|
s_last_uid, picc->uid.length, rc522_picc_type_name(picc->type));
|
|
} else if ((picc->state == RC522_PICC_STATE_IDLE || picc->state == RC522_PICC_STATE_HALT)
|
|
&& event->old_state >= RC522_PICC_STATE_ACTIVE) {
|
|
ESP_LOGI(TAG, "Card removed");
|
|
}
|
|
}
|
|
|
|
/* Read the MFRC522 VersionReg (0x37) directly over SPI3 with a temporary
|
|
* hardware-CS device. Any plausible value (0x88/0x91/0x92) means the chip is
|
|
* alive and MISO works; 0x00/0xFF means MISO/power/wiring is dead. This is a
|
|
* pure diagnostic and does not depend on the rc522 driver's reset path. */
|
|
static void rfid_probe_version(void)
|
|
{
|
|
spi_device_handle_t probe = NULL;
|
|
spi_device_interface_config_t devcfg = {
|
|
.clock_speed_hz = RC522_SPI_CLK_HZ,
|
|
.mode = 0,
|
|
.spics_io_num = HW_RC522_CS_GPIO,
|
|
.queue_size = 1,
|
|
.command_bits = 1, /* read/write select bit */
|
|
.address_bits = 6, /* register address */
|
|
.dummy_bits = 1,
|
|
};
|
|
if (spi_bus_add_device(HW_RFID_SPI_HOST, &devcfg, &probe) != ESP_OK || probe == NULL) {
|
|
ESP_LOGW(TAG, "probe: could not add temp SPI device on SPI3");
|
|
return;
|
|
}
|
|
|
|
uint8_t version = 0;
|
|
spi_transaction_t t = {
|
|
.cmd = 1, /* read */
|
|
.addr = 0x37, /* VersionReg */
|
|
.rxlength = 8,
|
|
.rx_buffer = &version,
|
|
};
|
|
esp_err_t ret = spi_device_polling_transmit(probe, &t);
|
|
ESP_LOGI(TAG, "MFRC522 VersionReg=0x%02X (%s). 0x88/0x91/0x92=alive; 0x00/0xFF=MISO/power/wiring",
|
|
version, esp_err_to_name(ret));
|
|
|
|
spi_bus_remove_device(probe);
|
|
}
|
|
|
|
esp_err_t rfid_manager_init(void)
|
|
{
|
|
/* Dedicated SPI3_HOST (HSPI) bus for the reader - independent controller
|
|
* so the display (SPI2_HOST) and the RFID never share bus timing. We
|
|
* initialize SPI3 here (with a real MISO) and hand the already-initialized
|
|
* bus to the rc522 driver via bus_config=NULL so it only adds its device. */
|
|
static spi_bus_config_t rfid_bus_cfg = {
|
|
.sclk_io_num = HW_RFID_SCK_GPIO,
|
|
.mosi_io_num = HW_RFID_MOSI_GPIO,
|
|
.miso_io_num = HW_RFID_MISO_GPIO,
|
|
.quadwp_io_num = -1,
|
|
.quadhd_io_num = -1,
|
|
};
|
|
ESP_LOGI(TAG, "initializing MFRC522 on SPI3_HOST (SCK=%d MOSI=%d MISO=%d CS=%d)",
|
|
HW_RFID_SCK_GPIO, HW_RFID_MOSI_GPIO, HW_RFID_MISO_GPIO, HW_RC522_CS_GPIO);
|
|
|
|
esp_err_t ret = spi_bus_initialize(HW_RFID_SPI_HOST, &rfid_bus_cfg, SPI_DMA_CH_AUTO);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "spi_bus_initialize(SPI3) failed: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
rfid_probe_version();
|
|
|
|
rc522_spi_config_t driver_config = {
|
|
.host_id = HW_RFID_SPI_HOST,
|
|
.bus_config = NULL, /* SPI3 already initialized above */
|
|
.dev_config = {
|
|
.spics_io_num = HW_RC522_CS_GPIO,
|
|
.clock_speed_hz = RC522_SPI_CLK_HZ,
|
|
},
|
|
.dma_chan = SPI_DMA_CH_AUTO,
|
|
/* RST left unconnected (-1): the module comes up on its own power-on
|
|
* reset and uses the library soft-reset. Wire RST=GPIO22 and set this
|
|
* to HW_RC522_RST_GPIO if your module needs the hard-reset line. */
|
|
.rst_io_num = -1,
|
|
};
|
|
|
|
ret = rc522_spi_create(&driver_config, &s_driver);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "rc522_spi_create failed: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
ret = rc522_driver_install(s_driver);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "rc522 driver install failed: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
rc522_config_t scanner_config = {
|
|
.driver = s_driver,
|
|
.poll_interval_ms = RC522_POLL_INTERVAL_MS,
|
|
.task_stack_size = RC522_TASK_STACK_SIZE,
|
|
.task_priority = RC522_TASK_PRIORITY,
|
|
};
|
|
|
|
ret = rc522_create(&scanner_config, &s_scanner);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "rc522_create failed: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
ESP_ERROR_CHECK(
|
|
rc522_register_events(s_scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL));
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t rfid_manager_start(void)
|
|
{
|
|
esp_err_t ret = ESP_FAIL;
|
|
for (int attempt = 1; attempt <= RC522_START_ATTEMPTS; attempt++) {
|
|
ret = rc522_start(s_scanner);
|
|
if (ret == ESP_OK) {
|
|
break;
|
|
}
|
|
ESP_LOGW(TAG, "rc522_start attempt %d/%d failed: %s",
|
|
attempt, RC522_START_ATTEMPTS, esp_err_to_name(ret));
|
|
vTaskDelay(pdMS_TO_TICKS(250));
|
|
}
|
|
|
|
#if CONFIG_MTG_RFID_SKIP_SELFTEST
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGW(TAG, "MFRC522 self-test failed (%s) - continuing anyway "
|
|
"(clone readers often scan fine; the polling task stays up).",
|
|
esp_err_to_name(ret));
|
|
ret = ESP_OK;
|
|
}
|
|
#else
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "MFRC522 self-test failed (%s). Check: reader powered "
|
|
"(3.3V), SPI wiring (SCK=16/MOSI=17/MISO=13, CS=26, RST=22), "
|
|
"and if it is a clone try lowering RC522_SPI_CLK_HZ", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* Pin the library's polling task to Core 0 so that RFID I/O never
|
|
* competes with the UI renderer on Core 1. */
|
|
TaskHandle_t task = xTaskGetHandle(RC522_TASK_NAME);
|
|
if (task != NULL) {
|
|
vTaskCoreAffinitySet(task, (UBaseType_t)(1U << 0));
|
|
ESP_LOGI(TAG, "RC522 polling task pinned to Core 0");
|
|
} else {
|
|
ESP_LOGW(TAG, "%s handle not found, affinity not set", RC522_TASK_NAME);
|
|
}
|
|
|
|
ESP_LOGI(TAG, "RFID scanner ready (SPI3_HOST, CS=%d)RST=%d)",
|
|
HW_RC522_CS_GPIO, HW_RC522_RST_GPIO);
|
|
return ESP_OK;
|
|
}
|
|
|
|
const char *rfid_manager_last_uid(void)
|
|
{
|
|
return s_last_uid;
|
|
}
|
|
|
|
bool rfid_manager_has_pending_card(void)
|
|
{
|
|
return s_pending_card;
|
|
}
|
|
|
|
void rfid_manager_consume_card(void)
|
|
{
|
|
s_pending_card = false;
|
|
} |