esp32/main/pnc32.c
raw ยท 7609 bytes
#include <string.h>
#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));
}
}