#include #include "driver/uart.h" #include "esp_err.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "reader.h" #define PN532_UART UART_NUM_1 #define PN532_TX_GPIO 4 #define PN532_RX_GPIO 3 #define PN532_BAUD 115200 #define COMMAND_TIMEOUT_MS 1000 #define POLL_INTERVAL_MS 300 static const char *TAG = "pn532"; static const uint8_t ack[] = {0x00, 0x00, 0xff, 0x00, 0xff, 0x00}; static void pn532_uart_init(void) { const uart_config_t config = { .baud_rate = PN532_BAUD, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; ESP_ERROR_CHECK(uart_driver_install(PN532_UART, 1024, 0, 0, NULL, 0)); ESP_ERROR_CHECK(uart_param_config(PN532_UART, &config)); ESP_ERROR_CHECK(uart_set_pin(PN532_UART, PN532_TX_GPIO, PN532_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); ESP_ERROR_CHECK(uart_flush_input(PN532_UART)); ESP_LOGI(TAG, "UART ready: TX=GPIO%d RX=GPIO%d baud=%d", PN532_TX_GPIO, PN532_RX_GPIO, PN532_BAUD); } static bool write_bytes(const uint8_t *data, size_t len) { ESP_LOG_BUFFER_HEX_LEVEL(TAG, data, len, ESP_LOG_DEBUG); return uart_write_bytes(PN532_UART, data, len) == (int)len && uart_wait_tx_done(PN532_UART, pdMS_TO_TICKS(100)) == ESP_OK; } /* One deadline for the entire command, including ACK and fragmented response. */ static bool read_exact(uint8_t *data, size_t len, int64_t deadline) { size_t received = 0; while (received < len) { int64_t remaining = deadline - esp_timer_get_time(); if (remaining <= 0) { return false; } TickType_t ticks = pdMS_TO_TICKS((remaining + 999) / 1000); if (ticks == 0) { ticks = 1; } int n = uart_read_bytes(PN532_UART, data + received, len - received, ticks); if (n < 0) { return false; } received += (size_t)n; } return true; } /* Returns 0 for ACK, positive for a normal frame, -1 for malformed/timeout. * Extended frames are unnecessary for these small commands and are rejected. */ static int read_frame(uint8_t *payload, size_t capacity, int64_t deadline) { uint8_t prefix[3] = {0}; do { prefix[0] = prefix[1]; prefix[1] = prefix[2]; if (!read_exact(&prefix[2], 1, deadline)) { return -1; } } while (prefix[0] != 0 || prefix[1] != 0 || prefix[2] != 0xff); uint8_t lengths[2]; if (!read_exact(lengths, 2, deadline)) { return -1; } if (lengths[0] == 0 && lengths[1] == 0xff) { uint8_t postamble; return read_exact(&postamble, 1, deadline) && postamble == 0 ? 0 : -1; } size_t len = lengths[0]; if (len == 0 || (uint8_t)(lengths[0] + lengths[1]) != 0 || len > capacity) { return -1; } if (!read_exact(payload, len, deadline)) { return -1; } uint8_t tail[2]; if (!read_exact(tail, 2, deadline) || tail[1] != 0) { return -1; } uint8_t checksum = tail[0]; for (size_t i = 0; i < len; ++i) { checksum += payload[i]; } return checksum == 0 ? (int)len : -1; } /* Normal frame builder. cmd includes command code, excludes host TFI (D4). */ static int command(const uint8_t *cmd, size_t cmd_len, uint8_t response[128]) { uint8_t frame[64]; if (cmd_len == 0 || cmd_len > sizeof(frame) - 8) { return -1; } size_t len = cmd_len + 1; frame[0] = 0; frame[1] = 0; frame[2] = 0xff; frame[3] = (uint8_t)len; frame[4] = (uint8_t)(0 - len); frame[5] = 0xd4; memcpy(frame + 6, cmd, cmd_len); uint8_t checksum = 0xd4; for (size_t i = 0; i < cmd_len; ++i) { checksum += cmd[i]; } frame[6 + cmd_len] = (uint8_t)(0 - checksum); frame[7 + cmd_len] = 0; if (!write_bytes(frame, cmd_len + 8)) { goto failed; } int64_t deadline = esp_timer_get_time() + COMMAND_TIMEOUT_MS * 1000; int n; do { n = read_frame(response, 128, deadline); } while (n == 0); /* ACK is not the command response. */ if (n >= 2 && response[0] == 0xd5 && response[1] == (uint8_t)(cmd[0] + 1)) { ESP_LOG_BUFFER_HEX_LEVEL(TAG, response, n, ESP_LOG_DEBUG); return n; } failed: /* Host ACK aborts a pending PN532 command. Drain before the next command. */ ESP_LOGW(TAG, "command 0x%02X failed or timed out", cmd[0]); (void)write_bytes(ack, sizeof(ack)); vTaskDelay(pdMS_TO_TICKS(20)); (void)uart_flush_input(PN532_UART); return -1; } static bool initialize_reader(void) { static const uint8_t wakeup[] = {0x55, 0x55, 0, 0, 0}; static const uint8_t firmware[] = {0x02}; static const uint8_t sam[] = {0x14, 0x01, 0x14, 0x01}; /* RFConfiguration / MaxRetries: preserve ATR/PSL defaults, scan once. */ static const uint8_t retries[] = {0x32, 0x05, 0xff, 0x01, 0x00}; uint8_t response[128]; if (!write_bytes(wakeup, sizeof(wakeup))) { return false; } vTaskDelay(pdMS_TO_TICKS(10)); (void)uart_flush_input(PN532_UART); int n = command(firmware, sizeof(firmware), response); if (n != 6 || response[2] != 0x32) { ESP_LOGE(TAG, "invalid PN532 firmware response"); return false; } ESP_LOGI(TAG, "PN532 firmware %u.%u, support=0x%02X", response[3], response[4], response[5]); if (command(sam, sizeof(sam), response) != 2 || command(retries, sizeof(retries), response) != 2) { return false; } return true; } /* 1 = card, 0 = confirmed no target, -1 = transport/protocol failure. */ static int poll_card(nfc_card_t *card) { static const uint8_t poll[] = {0x4a, 0x01, 0x00}; static const uint8_t rf_off[] = {0x32, 0x01, 0x00}; uint8_t response[128]; int n = command(poll, sizeof(poll), response); int result = -1; if (n == 3 && response[2] == 0) { result = 0; } else if (n >= 8 && response[2] == 1 && nfc_uid_valid(response[7]) && n >= 8 + response[7]) { card->uid_len = response[7]; memcpy(card->uid, response + 8, card->uid_len); result = 1; } else if (n >= 0) { ESP_LOGW(TAG, "invalid target response"); } /* InListPassiveTarget turns RF on again for the next scan. * Turning it off here also lets a held card be reactivated next time. */ if (command(rf_off, sizeof(rf_off), response) != 2) { return -1; } return result; } void start_polling(pn532_card_callback_t callback) { pn532_uart_init(); vTaskDelay(pdMS_TO_TICKS(1000)); while (!initialize_reader()) { ESP_LOGE(TAG, "PN532 initialization failed; retrying in 2 seconds"); vTaskDelay(pdMS_TO_TICKS(2000)); } nfc_presence_t presence = {0}; for (;;) { nfc_card_t card = {0}; int result = poll_card(&card); bool was_present = presence.present; if (nfc_presence_update(&presence, result, &card)) { ESP_LOGI(TAG, "card detected, UID length=%u", card.uid_len); ESP_LOG_BUFFER_HEX_LEVEL(TAG, card.uid, card.uid_len, ESP_LOG_INFO); if (callback(&card)) { nfc_presence_accept(&presence, &card); } } if (was_present && !presence.present) { ESP_LOGI(TAG, "card removed"); } vTaskDelay(pdMS_TO_TICKS(POLL_INTERVAL_MS)); } }