/* * MTG RFID Companion - MFRC522 RFID manager (Core 0) */ #include #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, .flags = SPI_DEVICE_HALFDUPLEX, /* RX-only reads like the rc522 driver */ }; 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; }