commit 40b73ceba1a11cc118a5ea639d190fcb8e32ac3a
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-10-04T12:50:36Z |
| subject | Added PONG response |
commit 40b73ceba1a11cc118a5ea639d190fcb8e32ac3a
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-10-04T12:50:36Z
Added PONG response
---
.gitignore | 1 +
README.md | 10 +
esp32/main/CMakeLists.txt | 29 ++-
esp32/main/main.c | 260 ++++++++++++------------
esp32/main/pin_test.c | 78 ++++++++
esp32/main/{pnc32.c => pnc532.c} | 130 ++++++++++--
esp32/main/pnc532_test.c | 418 +++++++++++++++++++++++++++++++++++++++
7 files changed, 775 insertions(+), 151 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3d72984..5d0ddbd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
# ESP-IDF
esp32/build/
+esp32/build-*/
esp32/sdkconfig
esp32/sdkconfig.old
diff --git a/README.md b/README.md
index 15bb643..e9cd928 100644
--- a/README.md
+++ b/README.md
@@ -74,3 +74,13 @@ byte 4..7 mock UID = DE AD BE EF
LIBNFC_DEVICE=pn532_uart:/dev/cu.usbserial-8310 nfc-list
LIBNFC_DEVICE=pn532_uart:/dev/cu.usbserial-8310 nfc-poll
```
+
+Test pins
+```
+idf.py -B build-pin-test -D PIN_TEST=ON build flash monitor
+```
+
+Test pnc532
+```
+idf.py -B build-pnc532-test -D PNC532_TEST=ON build flash monitor
+```
diff --git a/esp32/main/CMakeLists.txt b/esp32/main/CMakeLists.txt
index 661faba..e228784 100644
--- a/esp32/main/CMakeLists.txt
+++ b/esp32/main/CMakeLists.txt
@@ -1,5 +1,24 @@
-idf_component_register(
- SRCS "main.c" "pnc32.c"
- INCLUDE_DIRS "."
- PRIV_REQUIRES bt nvs_flash driver esp_timer esp_hw_support esp_driver_uart
-)
+option(PIN_TEST "Build UART loopback test" OFF)
+option(PNC532_TEST "Build PNC532 test" OFF)
+
+if(PIN_TEST)
+ idf_component_register(
+ SRCS "pin_test.c"
+ INCLUDE_DIRS "."
+ PRIV_REQUIRES driver esp_driver_uart
+ )
+elseif(PNC532_TEST)
+ idf_component_register(
+ SRCS "pnc532_test.c"
+ INCLUDE_DIRS "."
+ PRIV_REQUIRES nvs_flash driver esp_timer
+ esp_hw_support esp_driver_uart
+ )
+else()
+ idf_component_register(
+ SRCS "main.c" "pnc532.c"
+ INCLUDE_DIRS "."
+ PRIV_REQUIRES bt nvs_flash driver esp_timer
+ esp_hw_support esp_driver_uart
+ )
+endif()
diff --git a/esp32/main/main.c b/esp32/main/main.c
index 2f6ba1c..da97b8c 100644
--- a/esp32/main/main.c
+++ b/esp32/main/main.c
@@ -1,164 +1,170 @@
#include <assert.h>
-
+#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
-#include "esp_random.h"
#include "nvs_flash.h"
-#include "freertos/FreeRTOS.h"
-#include "freertos/queue.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
-#include "nimble/nimble_npl.h"
#include "host/ble_gap.h"
+#include "host/ble_gatt.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
-
+#include "services/gatt/ble_svc_gatt.h"
#include "reader.h"
#define DEVICE_NAME "homekey-reader"
-#define ADV_DURATION_MS 1000
-#define CARD_QUEUE_LENGTH 8
-
static const char *TAG = "homekey-reader";
-static QueueHandle_t card_queue;
-static struct ble_npl_event card_event;
-
-/* The fields below belong exclusively to the NimBLE host task. */
+/* UUID bytes are least-significant first in NimBLE.
+ * Service: 7a6b0001-5b21-4f36-8e5d-9c3a26d74210
+ * Characteristic: 7a6b0002-5b21-4f36-8e5d-9c3a26d74210
+ */
+static const ble_uuid128_t service_uuid = BLE_UUID128_INIT(
+ 0x10,0x42,0xd7,0x26,0x3a,0x9c,0x5d,0x8e,0x36,0x4f,0x21,0x5b,0x01,0x00,0x6b,0x7a);
+static const ble_uuid128_t ping_uuid = BLE_UUID128_INIT(
+ 0x10,0x42,0xd7,0x26,0x3a,0x9c,0x5d,0x8e,0x36,0x4f,0x21,0x5b,0x02,0x00,0x6b,0x7a);
static uint8_t own_addr_type;
-static uint16_t event_seq;
-static bool ble_ready;
-static bool advertising;
-static bool have_pending;
-static nfc_card_t pending;
+static uint16_t connection = BLE_HS_CONN_HANDLE_NONE;
+/* Accessed only by the NimBLE host task. */
+static uint8_t reply[8];
+static bool have_reply;
-static void advertise_next(void);
-
-static int gap_event(struct ble_gap_event *event, void *arg)
+static int ping_access(uint16_t conn, uint16_t attr,
+ struct ble_gatt_access_ctxt *ctxt, void *arg)
{
- (void)arg;
- if (event->type == BLE_GAP_EVENT_ADV_COMPLETE) {
- advertising = false;
- have_pending = false;
- ESP_LOGI(TAG, "card advertisement complete");
- /* Only start again if a different card event is waiting. */
- advertise_next();
- }
- return 0;
+ (void)conn; (void)attr; (void)arg;
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ uint8_t request[8];
+ if (OS_MBUF_PKTLEN(ctxt->om) != sizeof(request))
+ return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ if (os_mbuf_copydata(ctxt->om, 0, sizeof(request), request) != 0)
+ return BLE_ATT_ERR_UNLIKELY;
+ if (memcmp(request, "PING", 4) != 0)
+ return BLE_ATT_ERR_VALUE_NOT_ALLOWED;
+ memcpy(reply, request, sizeof(reply));
+ memcpy(reply, "PONG", 4);
+ have_reply = true;
+ ESP_LOGI(TAG, "BLE PING received; PONG ready");
+ return 0;
+ }
+ if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
+ if (!have_reply) return 0; /* Empty until the first PING. */
+ return os_mbuf_append(ctxt->om, reply, sizeof(reply)) == 0
+ ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ return BLE_ATT_ERR_UNLIKELY;
}
-
-static void advertise_next(void)
+static const struct ble_gatt_svc_def services[] = {
+ { .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &service_uuid.u,
+ .characteristics = (struct ble_gatt_chr_def[]) {
+ { .uuid = &ping_uuid.u, .access_cb = ping_access,
+ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE },
+ {0}
+ } },
+ {0}
+};
+static void advertise(void);
+static int gap_event(struct ble_gap_event *event, void *arg)
{
- if (!ble_ready || advertising) {
- return;
- }
- while (have_pending || xQueueReceive(card_queue, &pending, 0) == pdTRUE) {
- if (!have_pending) {
- have_pending = true;
- ++event_seq;
- }
- uint8_t adv[31];
- size_t adv_len = build_card_adv(adv, &pending, event_seq);
- assert(adv_len > 0);
-
- int rc = ble_gap_adv_set_data(adv, (int)adv_len);
- if (rc != 0) {
- ESP_LOGE(TAG, "setting advertisement failed: %d; event dropped", rc);
- have_pending = false;
- continue;
- }
- struct ble_gap_adv_params params = {0};
- params.conn_mode = BLE_GAP_CONN_MODE_NON;
- params.disc_mode = BLE_GAP_DISC_MODE_NON;
- params.itvl_min = BLE_GAP_ADV_ITVL_MS(100);
- params.itvl_max = BLE_GAP_ADV_ITVL_MS(150);
-
- rc = ble_gap_adv_start(own_addr_type, NULL, ADV_DURATION_MS,
- ¶ms, gap_event, NULL);
- if (rc != 0) {
- ESP_LOGE(TAG, "starting advertisement failed: %d; event dropped", rc);
- have_pending = false;
- continue;
- }
- advertising = true;
- ESP_LOGI(TAG, "advertising card: event_seq=%u uid_len=%u",
- event_seq, pending.uid_len);
- ESP_LOG_BUFFER_HEX_LEVEL(TAG, pending.uid, pending.uid_len, ESP_LOG_INFO);
- return;
- }
+ (void)arg;
+ switch (event->type) {
+ case BLE_GAP_EVENT_CONNECT:
+ if (event->connect.status == 0) {
+ connection = event->connect.conn_handle;
+ have_reply = false;
+ ESP_LOGI(TAG, "BLE gateway connected");
+ } else advertise();
+ break;
+ case BLE_GAP_EVENT_DISCONNECT:
+ connection = BLE_HS_CONN_HANDLE_NONE;
+ have_reply = false;
+ ESP_LOGI(TAG, "BLE gateway disconnected: %d", event->disconnect.reason);
+ advertise();
+ break;
+ case BLE_GAP_EVENT_ADV_COMPLETE:
+ if (connection == BLE_HS_CONN_HANDLE_NONE) advertise();
+ break;
+ default: break;
+ }
+ return 0;
}
-
-static void on_card_event(struct ble_npl_event *event)
+static void advertise(void)
{
- (void)event;
- advertise_next();
+ struct ble_hs_adv_fields fields = {0};
+ fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
+ fields.uuids128 = (ble_uuid128_t *)&service_uuid;
+ fields.num_uuids128 = 1;
+ fields.uuids128_is_complete = 1;
+ int rc = ble_gap_adv_set_fields(&fields);
+ if (rc != 0) { ESP_LOGE(TAG, "advertisement fields: %d", rc); return; }
+ struct ble_hs_adv_fields scan = {0};
+ scan.name = (uint8_t *)DEVICE_NAME;
+ scan.name_len = strlen(DEVICE_NAME);
+ scan.name_is_complete = 1;
+ rc = ble_gap_adv_rsp_set_fields(&scan);
+ if (rc != 0) { ESP_LOGE(TAG, "scan response: %d", rc); return; }
+ struct ble_gap_adv_params params = {0};
+ params.conn_mode = BLE_GAP_CONN_MODE_UND;
+ params.disc_mode = BLE_GAP_DISC_MODE_GEN;
+ params.itvl_min = BLE_GAP_ADV_ITVL_MS(100);
+ params.itvl_max = BLE_GAP_ADV_ITVL_MS(150);
+ rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
+ ¶ms, gap_event, NULL);
+ if (rc != 0) ESP_LOGE(TAG, "advertisement start: %d", rc);
}
-
-/* Called from PN532 task: copy the UID, then notify the host task.
- * No BLE calls or shared advertisement buffers cross task boundaries.
+/* NFC task stays independent. Card events are logged for now;
+ * the old non-connectable UID advertisements are replaced by GATT.
*/
static bool report_card(const nfc_card_t *card)
{
- if (xQueueSend(card_queue, card, 0) != pdTRUE) {
- ESP_LOGW(TAG, "card queue full; retrying while card is present");
- return false;
- }
- ble_npl_eventq_put(nimble_port_get_dflt_eventq(), &card_event);
- return true;
+ ESP_LOGI(TAG, "NFC card detected; uid_len=%u", card->uid_len);
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, card->uid, card->uid_len, ESP_LOG_INFO);
+ return true;
}
-
static void on_reset(int reason)
{
- ble_ready = false;
- advertising = false;
- /* Keep a partially advertised event and its sequence for resync. */
- ESP_LOGE(TAG, "NimBLE reset, reason=%d", reason);
+ connection = BLE_HS_CONN_HANDLE_NONE;
+ have_reply = false;
+ ESP_LOGE(TAG, "NimBLE reset: %d", reason);
}
-
static void on_sync(void)
{
- uint8_t addr[6];
- int rc = ble_hs_util_ensure_addr(0);
- assert(rc == 0);
- rc = ble_hs_id_infer_auto(0, &own_addr_type);
- assert(rc == 0);
- rc = ble_hs_id_copy_addr(own_addr_type, addr, NULL);
- assert(rc == 0);
- ESP_LOGI(TAG, "BLE address %02X:%02X:%02X:%02X:%02X:%02X",
- addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
- ble_ready = true;
- advertise_next();
+ uint8_t addr[6];
+ int rc = ble_hs_util_ensure_addr(0);
+ assert(rc == 0);
+ rc = ble_hs_id_infer_auto(0, &own_addr_type);
+ assert(rc == 0);
+ rc = ble_hs_id_copy_addr(own_addr_type, addr, NULL);
+ assert(rc == 0);
+ ESP_LOGI(TAG, "BLE address %02X:%02X:%02X:%02X:%02X:%02X",
+ addr[5],addr[4],addr[3],addr[2],addr[1],addr[0]);
+ advertise();
}
-
-static void host_task(void *param)
+static void host_task(void *arg)
{
- (void)param;
- nimble_port_run();
- nimble_port_freertos_deinit();
+ (void)arg;
+ nimble_port_run();
+ nimble_port_freertos_deinit();
}
-
void app_main(void)
{
- esp_err_t err = nvs_flash_init();
- if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
- ESP_ERROR_CHECK(nvs_flash_erase());
- err = nvs_flash_init();
- }
- ESP_ERROR_CHECK(err);
- ESP_ERROR_CHECK(nimble_port_init());
-
- card_queue = xQueueCreate(CARD_QUEUE_LENGTH, sizeof(nfc_card_t));
- assert(card_queue != NULL);
- ble_npl_event_init(&card_event, on_card_event, NULL);
- /* Reduce duplicate sequence values following a reboot; not a unique ID. */
- event_seq = (uint16_t)esp_random();
- ble_hs_cfg.reset_cb = on_reset;
- ble_hs_cfg.sync_cb = on_sync;
- ble_svc_gap_init();
- int rc = ble_svc_gap_device_name_set(DEVICE_NAME);
- assert(rc == 0);
- nimble_port_freertos_init(host_task);
-
- /* app_main is already a FreeRTOS task; UART waits yield to NimBLE. */
- start_polling(report_card);
+ esp_err_t err = nvs_flash_init();
+ if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+ ESP_ERROR_CHECK(nvs_flash_erase());
+ err = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(err);
+ ESP_ERROR_CHECK(nimble_port_init());
+ ble_hs_cfg.reset_cb = on_reset;
+ ble_hs_cfg.sync_cb = on_sync;
+ ble_svc_gap_init();
+ ble_svc_gatt_init();
+ int rc = ble_svc_gap_device_name_set(DEVICE_NAME);
+ assert(rc == 0);
+ rc = ble_gatts_count_cfg(services);
+ assert(rc == 0);
+ rc = ble_gatts_add_svcs(services);
+ assert(rc == 0);
+ nimble_port_freertos_init(host_task);
+ start_polling(report_card);
}
diff --git a/esp32/main/pin_test.c b/esp32/main/pin_test.c
new file mode 100644
index 0000000..74fe746
--- /dev/null
+++ b/esp32/main/pin_test.c
@@ -0,0 +1,78 @@
+#include <string.h>
+
+#include "driver/uart.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#define TEST_UART UART_NUM_1
+#define TX_PIN 4
+#define RX_PIN 3
+
+static const char *TAG = "uart-loopback";
+
+void app_main(void)
+{
+ const uart_config_t config = {
+ .baud_rate = 115200,
+ .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_param_config(TEST_UART, &config));
+ ESP_ERROR_CHECK(uart_set_pin(
+ TEST_UART, TX_PIN, RX_PIN,
+ UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
+ ESP_ERROR_CHECK(uart_driver_install(
+ TEST_UART, 256, 0, 0, NULL, 0));
+
+ const uint8_t tx[] = {
+ 0x55, 0xaa, 0x00, 0xff, 0x02, 0xfe, 0xd4, 0x2a
+ };
+
+ for (;;) {
+ uint8_t rx[sizeof(tx)] = {0};
+ size_t received = 0;
+
+ ESP_ERROR_CHECK(uart_flush_input(TEST_UART));
+
+ int written = uart_write_bytes(TEST_UART, tx, sizeof(tx));
+ ESP_LOGI(TAG, "TX: %d bytes", written);
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, tx, sizeof(tx), ESP_LOG_INFO);
+
+ /* Accumulate reads: UART may return fewer bytes than requested. */
+ while (received < sizeof(rx)) {
+ int n = uart_read_bytes(
+ TEST_UART,
+ rx + received,
+ sizeof(rx) - received,
+ pdMS_TO_TICKS(500));
+
+ if (n <= 0) {
+ ESP_LOGW(TAG, "read stopped: %d", n);
+ break;
+ }
+ received += (size_t)n;
+ }
+
+ ESP_LOGI(TAG, "RX: %u bytes", (unsigned)received);
+ if (received > 0) {
+ ESP_LOG_BUFFER_HEX_LEVEL(
+ TAG, rx, received, ESP_LOG_INFO);
+ }
+
+ if (written == sizeof(tx) &&
+ received == sizeof(tx) &&
+ memcmp(tx, rx, sizeof(tx)) == 0) {
+ ESP_LOGI(TAG, "PASS: RX = TX");
+ } else {
+ ESP_LOGE(TAG, "FAIL: RX != TX");
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ }
+}
\ No newline at end of file
diff --git a/esp32/main/pnc32.c b/esp32/main/pnc532.c
similarity index 66%
rename from esp32/main/pnc32.c
rename to esp32/main/pnc532.c
index 0e220e5..d643767 100644
--- a/esp32/main/pnc32.c
+++ b/esp32/main/pnc532.c
@@ -9,8 +9,8 @@
#include "reader.h"
#define PN532_UART UART_NUM_1
-#define PN532_TX_GPIO 4
-#define PN532_RX_GPIO 3
+#define PN532_TX_GPIO 3
+#define PN532_RX_GPIO 4
#define PN532_BAUD 115200
#define COMMAND_TIMEOUT_MS 1000
#define POLL_INTERVAL_MS 300
@@ -69,41 +69,84 @@ static bool read_exact(uint8_t *data, size_t len, int64_t deadline)
/* 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 bool read_logged(uint8_t *buffer, size_t len,
+ int64_t deadline, const char *stage)
+{
+ if (!read_exact(buffer, len, deadline)) {
+ ESP_LOGW(TAG, "RX failed at %s: wanted %u bytes",
+ stage, (unsigned)len);
+ return false;
+ }
+
+#ifdef FRAME_DEBUG
+ ESP_LOGI(TAG, "RX %s (%u bytes)", stage, (unsigned)len);
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, len, ESP_LOG_INFO);
+#endif
+ return true;
+}
+
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)) {
+ if (!read_logged(&prefix[2], 1, deadline, "preamble byte")) {
return -1;
}
- } while (prefix[0] != 0 || prefix[1] != 0 || prefix[2] != 0xff);
+ } while (prefix[0] != 0 ||
+ prefix[1] != 0 ||
+ prefix[2] != 0xff);
uint8_t lengths[2];
- if (!read_exact(lengths, 2, deadline)) {
+ if (!read_logged(lengths, sizeof(lengths), deadline, "LEN/LCS")) {
return -1;
}
+
if (lengths[0] == 0 && lengths[1] == 0xff) {
uint8_t postamble;
- return read_exact(&postamble, 1, deadline) && postamble == 0 ? 0 : -1;
+ if (!read_logged(&postamble, 1, deadline, "ACK postamble")) {
+ return -1;
+ }
+ if (postamble != 0) {
+ ESP_LOGW(TAG, "invalid ACK postamble");
+ return -1;
+ }
+ return 0;
}
+
size_t len = lengths[0];
- if (len == 0 || (uint8_t)(lengths[0] + lengths[1]) != 0 || len > capacity) {
+ if (len == 0 ||
+ (uint8_t)(lengths[0] + lengths[1]) != 0 ||
+ len > capacity) {
+ ESP_LOGW(TAG, "invalid length: LEN=%u LCS=0x%02X capacity=%u",
+ (unsigned)len, lengths[1], (unsigned)capacity);
return -1;
}
- if (!read_exact(payload, len, deadline)) {
+
+ if (!read_logged(payload, len, deadline, "payload")) {
return -1;
}
+
uint8_t tail[2];
- if (!read_exact(tail, 2, deadline) || tail[1] != 0) {
+ if (!read_logged(tail, sizeof(tail), deadline, "DCS/postamble")) {
+ return -1;
+ }
+ if (tail[1] != 0) {
+ ESP_LOGW(TAG, "invalid postamble: 0x%02X", tail[1]);
return -1;
}
+
uint8_t checksum = tail[0];
for (size_t i = 0; i < len; ++i) {
checksum += payload[i];
}
- return checksum == 0 ? (int)len : -1;
+ if (checksum != 0) {
+ ESP_LOGW(TAG, "invalid checksum: sum=0x%02X", checksum);
+ return -1;
+ }
+
+ return (int)len;
}
/* Normal frame builder. cmd includes command code, excludes host TFI (D4). */
@@ -113,6 +156,7 @@ static int command(const uint8_t *cmd, size_t cmd_len, uint8_t response[128])
if (cmd_len == 0 || cmd_len > sizeof(frame) - 8) {
return -1;
}
+
size_t len = cmd_len + 1;
frame[0] = 0;
frame[1] = 0;
@@ -121,6 +165,7 @@ static int command(const uint8_t *cmd, size_t cmd_len, uint8_t response[128])
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];
@@ -128,23 +173,53 @@ static int command(const uint8_t *cmd, size_t cmd_len, uint8_t response[128])
frame[6 + cmd_len] = (uint8_t)(0 - checksum);
frame[7 + cmd_len] = 0;
+#ifdef FRAME_DEBUG
+ ESP_LOGI(TAG, "TX command 0x%02X (%u bytes)",
+ cmd[0], (unsigned)(cmd_len + 8));
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, frame, cmd_len + 8, ESP_LOG_INFO);
+#endif
+
if (!write_bytes(frame, cmd_len + 8)) {
+ ESP_LOGE(TAG, "TX write failed");
goto failed;
}
- int64_t deadline = esp_timer_get_time() + COMMAND_TIMEOUT_MS * 1000;
+
+ int64_t deadline =
+ esp_timer_get_time() + COMMAND_TIMEOUT_MS * 1000;
int n;
+
do {
n = read_frame(response, 128, deadline);
+#ifdef FRAME_DEBUG
+ if (n > 0) {
+ ESP_LOGI(TAG, "RX response (%d bytes)", n);
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, response, n, ESP_LOG_INFO);
+ } else if (n == 0) {
+ ESP_LOGI(TAG, "RX ACK");
+ } else {
+ ESP_LOGW(TAG, "RX failed or timed out: %d", n);
+ }
+#else
+ if (n < 0) {
+ ESP_LOGW(TAG, "RX failed or timed out: %d", n);
+ }
+#endif
} 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);
+
+ if (n >= 2 &&
+ response[0] == 0xd5 &&
+ response[1] == (uint8_t)(cmd[0] + 1)) {
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]);
+
+ ESP_LOGI(TAG, "TX abort ACK (%u bytes)", (unsigned)sizeof(ack));
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, ack, sizeof(ack), ESP_LOG_INFO);
(void)write_bytes(ack, sizeof(ack));
+
vTaskDelay(pdMS_TO_TICKS(20));
(void)uart_flush_input(PN532_UART);
return -1;
@@ -155,25 +230,42 @@ 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];
+
+ /* Discard stale input before starting this attempt. */
+ ESP_ERROR_CHECK(uart_flush_input(PN532_UART));
+
+ ESP_LOGI(TAG, "TX wakeup");
+ ESP_LOG_BUFFER_HEX_LEVEL(TAG, wakeup, sizeof(wakeup), ESP_LOG_INFO);
if (!write_bytes(wakeup, sizeof(wakeup))) {
+ ESP_LOGE(TAG, "wakeup write failed");
return false;
}
- vTaskDelay(pdMS_TO_TICKS(10));
- (void)uart_flush_input(PN532_UART);
+
+ /* Conservative diagnostic delay, not a specified minimum. */
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ /* Put the reader into normal mode before querying firmware. */
+ if (command(sam, sizeof(sam), response) != 2) {
+ ESP_LOGE(TAG, "SAMConfiguration failed");
+ return false;
+ }
+
int n = command(firmware, sizeof(firmware), response);
if (n != 6 || response[2] != 0x32) {
- ESP_LOGE(TAG, "invalid PN532 firmware response");
+ ESP_LOGE(TAG, "invalid firmware response: length=%d", n);
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) {
+
+ if (command(retries, sizeof(retries), response) != 2) {
+ ESP_LOGE(TAG, "RFConfiguration failed");
return false;
}
+
return true;
}
diff --git a/esp32/main/pnc532_test.c b/esp32/main/pnc532_test.c
new file mode 100644
index 0000000..d561dde
--- /dev/null
+++ b/esp32/main/pnc532_test.c
@@ -0,0 +1,418 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "driver/uart.h"
+#include "esp_log.h"
+#include "esp_err.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#define PN532_UART UART_NUM_1
+#define PN532_TX_GPIO 3
+#define PN532_RX_GPIO 4
+#define PN532_BAUD 115200
+
+static const char *TAG = "pn532";
+
+static void dump_hex(const char *prefix, const uint8_t *buf, size_t len)
+{
+ printf("%s", prefix);
+
+ for (size_t i = 0; i < len; i++) {
+ printf("%02X ", buf[i]);
+ }
+
+ printf("\n");
+}
+
+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
+ ));
+
+ 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 int pn532_read(uint8_t *buf, size_t size, int timeout_ms)
+{
+ return uart_read_bytes(
+ PN532_UART,
+ buf,
+ size,
+ pdMS_TO_TICKS(timeout_ms)
+ );
+}
+
+static void pn532_write(const uint8_t *buf, size_t len)
+{
+ dump_hex("TX: ", buf, len);
+
+ uart_write_bytes(
+ PN532_UART,
+ buf,
+ len
+ );
+
+ uart_wait_tx_done(
+ PN532_UART,
+ pdMS_TO_TICKS(100)
+ );
+}
+
+static void pn532_wakeup(void)
+{
+ /*
+ * PN532 HSU wake-up preamble.
+ *
+ * The PN532 manual specifies 55 55 followed by the
+ * normal synchronization sequence for waking from
+ * LowVbat mode.
+ */
+ static const uint8_t wakeup[] = {
+ 0x55, 0x55,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF
+ };
+
+ ESP_LOGI(TAG, "sending wakeup");
+
+ pn532_write(wakeup, sizeof(wakeup));
+
+ vTaskDelay(pdMS_TO_TICKS(10));
+
+ uart_flush_input(PN532_UART);
+}
+
+static void pn532_get_firmware_version(void)
+{
+ static const uint8_t cmd[] = {
+ /* HSU wakeup */
+ 0x55, 0x55,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ /* PN532 frame */
+ 0xFF,
+ 0x02, 0xFE,
+ 0xD4, 0x02,
+ 0x2A,
+ 0x00
+ };
+
+ uint8_t buf[64];
+
+ uart_flush_input(PN532_UART);
+
+ ESP_LOGI(TAG, "GetFirmwareVersion");
+ pn532_write(cmd, sizeof(cmd));
+
+ int len = pn532_read(buf, sizeof(buf), 1000);
+
+ if (len <= 0) {
+ ESP_LOGE(TAG, "no response from PN532");
+ return;
+ }
+
+ dump_hex("RX: ", buf, len);
+
+ len = pn532_read(buf, sizeof(buf), 500);
+
+ if (len > 0) {
+ dump_hex("RX: ", buf, len);
+ }
+}
+
+static int pn532_read_response(uint8_t *buf, size_t size, int timeout_ms)
+{
+ int len = pn532_read(buf, size, timeout_ms);
+
+ if (len <= 0) {
+ return len;
+ }
+
+ dump_hex("RX: ", buf, len);
+ return len;
+}
+
+static void pn532_poll_card(void)
+{
+ /*
+ * InListPassiveTarget
+ *
+ * D4 4A 01 00
+ * │ │ └── BrTy = 0x00: ISO14443A @ 106 kbps
+ * │ └───── MaxTg = 1
+ * └──────── command
+ *
+ * Complete PN532 frame:
+ *
+ * 00 00 FF 04 FC D4 4A 01 00 E1 00
+ */
+ static const uint8_t cmd[] = {
+ 0x00, 0x00, 0xFF,
+ 0x04, 0xFC,
+ 0xD4, 0x4A, 0x01, 0x00,
+ 0xE1,
+ 0x00
+ };
+
+ uint8_t buf[128];
+
+ uart_flush_input(PN532_UART);
+
+ pn532_write(cmd, sizeof(cmd));
+
+ int len = pn532_read_response(
+ buf,
+ sizeof(buf),
+ 1000
+ );
+
+ if (len <= 0) {
+ /*
+ * No card is normal.
+ */
+ return;
+ }
+
+ /*
+ * ACK:
+ *
+ * 00 00 FF 00 FF 00
+ *
+ * Often ACK + response arrive in one UART read.
+ *
+ * Find the response frame:
+ *
+ * 00 00 FF LEN LCS D5 4B ...
+ */
+ int frame = -1;
+
+ for (int i = 0; i + 6 < len; i++) {
+ if (
+ buf[i + 0] == 0x00 &&
+ buf[i + 1] == 0x00 &&
+ buf[i + 2] == 0xFF &&
+ buf[i + 5] == 0xD5 &&
+ buf[i + 6] == 0x4B
+ ) {
+ frame = i;
+ break;
+ }
+ }
+
+ /*
+ * Response may have arrived separately from ACK.
+ */
+ if (frame < 0) {
+ len = pn532_read_response(
+ buf,
+ sizeof(buf),
+ 1000
+ );
+
+ if (len <= 0) {
+ return;
+ }
+
+ for (int i = 0; i + 6 < len; i++) {
+ if (
+ buf[i + 0] == 0x00 &&
+ buf[i + 1] == 0x00 &&
+ buf[i + 2] == 0xFF &&
+ buf[i + 5] == 0xD5 &&
+ buf[i + 6] == 0x4B
+ ) {
+ frame = i;
+ break;
+ }
+ }
+ }
+
+ if (frame < 0) {
+ ESP_LOGW(TAG, "no InListPassiveTarget response found");
+ return;
+ }
+
+ /*
+ * Payload after D5 4B:
+ *
+ * NbTg
+ *
+ * If NbTg > 0:
+ *
+ * Tg
+ * SENS_RES[2]
+ * SEL_RES
+ * NFCIDLength
+ * NFCID[]
+ */
+ int p = frame + 7;
+
+ if (p >= len) {
+ return;
+ }
+
+ uint8_t targets = buf[p++];
+
+ if (targets == 0) {
+ return;
+ }
+
+ if (p + 5 > len) {
+ ESP_LOGW(TAG, "short target response");
+ return;
+ }
+
+ uint8_t tg = buf[p++];
+
+ uint8_t sens_res_0 = buf[p++];
+ uint8_t sens_res_1 = buf[p++];
+
+ uint8_t sel_res = buf[p++];
+
+ uint8_t uid_len = buf[p++];
+
+ if (uid_len == 0 || uid_len > 10 || p + uid_len > len) {
+ ESP_LOGW(TAG, "invalid UID length: %u", uid_len);
+ return;
+ }
+
+ ESP_LOGI(
+ TAG,
+ "card: Tg=%u ATQA=%02X%02X SAK=%02X UID len=%u",
+ tg,
+ sens_res_0,
+ sens_res_1,
+ sel_res,
+ uid_len
+ );
+
+ printf("UID: ");
+
+ for (int i = 0; i < uid_len; i++) {
+ if (i) {
+ printf(":");
+ }
+
+ printf("%02X", buf[p + i]);
+ }
+
+ printf("\n");
+}
+
+static bool pn532_sam_config(void)
+{
+ /*
+ * HSU wake-up + SAMConfiguration
+ *
+ * D4 14 01 14 01
+ * │ │ └── IRQ enabled
+ * │ └───── timeout
+ * └──────── normal mode
+ *
+ * DCS:
+ * D4 + 14 + 01 + 14 + 01 = FE
+ * checksum = 02
+ */
+ static const uint8_t cmd[] = {
+ /* HSU wake-up */
+ 0x55, 0x55,
+ 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ /* PN532 frame */
+ 0xFF,
+ 0x05, 0xFB,
+ 0xD4, 0x14, 0x01, 0x14, 0x01,
+ 0x02,
+ 0x00
+ };
+
+ uint8_t buf[64];
+
+ uart_flush_input(PN532_UART);
+
+ ESP_LOGI(TAG, "SAMConfiguration");
+ pn532_write(cmd, sizeof(cmd));
+
+ int len = pn532_read(buf, sizeof(buf), 1000);
+
+ if (len <= 0) {
+ ESP_LOGE(TAG, "SAMConfiguration: no response");
+ return false;
+ }
+
+ dump_hex("RX: ", buf, len);
+
+ /*
+ * Expected somewhere in the response:
+ *
+ * ACK:
+ * 00 00 FF 00 FF 00
+ *
+ * response:
+ * 00 00 FF 02 FE D5 15 16 00
+ */
+
+ return true;
+}
+
+void app_main(void)
+{
+ ESP_LOGI(TAG, "PN532 NFC reader");
+
+ pn532_uart_init();
+
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ pn532_get_firmware_version();
+
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ if (!pn532_sam_config()) {
+ ESP_LOGE(TAG, "PN532 initialization failed");
+ return;
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ while (1) {
+ pn532_poll_card();
+ vTaskDelay(pdMS_TO_TICKS(300));
+ }
+}