lego-esp32s3-gameboy/components/nfc_manager/nfc_manager.c

429 lines
11 KiB
C

/**
* @file nfc_manager.c
* @brief NFC Manager Implementation - COMPLETE PN532 Driver!
*
* Full implementation:
* - PN532 I2C communication
* - Tag detection & reading
* - ROM mapping from JSON
* - UID to ROM path lookup
*/
#include <string.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "cJSON.h"
#include "esp_vfs_fat.h"
#include "nfc_manager.h"
#include "hardware_config.h"
static const char *TAG = "NFC";
// PN532 I2C Commands
#define PN532_PREAMBLE 0x00
#define PN532_STARTCODE1 0x00
#define PN532_STARTCODE2 0xFF
#define PN532_POSTAMBLE 0x00
#define PN532_HOSTTOPN532 0xD4
#define PN532_PN532TOHOST 0xD5
// PN532 Commands
#define PN532_COMMAND_GETFIRMWAREVERSION 0x02
#define PN532_COMMAND_SAMCONFIGURATION 0x14
#define PN532_COMMAND_INLISTPASSIVETARGET 0x4A
// PN532 Mifare Commands
#define MIFARE_CMD_AUTH_A 0x60
#define MIFARE_CMD_AUTH_B 0x61
#define MIFARE_CMD_READ 0x30
#define MIFARE_CMD_WRITE 0xA0
#define PN532_I2C_TIMEOUT_MS 1000
#define PN532_I2C_READY 0x01
// ROM mapping storage
#define MAX_ROM_MAPPINGS 32
static nfc_rom_mapping_t rom_mappings[MAX_ROM_MAPPINGS];
static int rom_mapping_count = 0;
/**
* @brief Initialize I2C for PN532
*/
static esp_err_t pn532_i2c_init(void)
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = NFC_I2C_SDA,
.scl_io_num = NFC_I2C_SCL,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = NFC_I2C_FREQ_HZ,
};
ESP_ERROR_CHECK(i2c_param_config(NFC_I2C_NUM, &conf));
ESP_ERROR_CHECK(i2c_driver_install(NFC_I2C_NUM, conf.mode, 0, 0, 0));
return ESP_OK;
}
/**
* @brief Wait for PN532 to be ready
*/
static esp_err_t pn532_wait_ready(uint32_t timeout_ms)
{
uint8_t status;
uint32_t start = xTaskGetTickCount() * portTICK_PERIOD_MS;
while ((xTaskGetTickCount() * portTICK_PERIOD_MS - start) < timeout_ms) {
if (i2c_master_read_from_device(NFC_I2C_NUM, NFC_I2C_ADDR,
&status, 1, pdMS_TO_TICKS(100)) == ESP_OK) {
if (status == PN532_I2C_READY) {
return ESP_OK;
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_ERR_TIMEOUT;
}
/**
* @brief Write command to PN532
*/
static esp_err_t pn532_write_command(const uint8_t *cmd, size_t cmd_len)
{
uint8_t checksum = 0;
uint8_t frame[256];
size_t frame_len = 0;
// Build frame
frame[frame_len++] = PN532_PREAMBLE;
frame[frame_len++] = PN532_STARTCODE1;
frame[frame_len++] = PN532_STARTCODE2;
frame[frame_len++] = cmd_len + 1; // Length
frame[frame_len++] = ~(cmd_len + 1) + 1; // Length checksum
frame[frame_len++] = PN532_HOSTTOPN532;
checksum += PN532_HOSTTOPN532;
for (size_t i = 0; i < cmd_len; i++) {
frame[frame_len++] = cmd[i];
checksum += cmd[i];
}
frame[frame_len++] = ~checksum + 1; // Data checksum
frame[frame_len++] = PN532_POSTAMBLE;
return i2c_master_write_to_device(NFC_I2C_NUM, NFC_I2C_ADDR,
frame, frame_len, pdMS_TO_TICKS(100));
}
/**
* @brief Read response from PN532
*/
static esp_err_t pn532_read_response(uint8_t *response, size_t *response_len,
uint32_t timeout_ms)
{
esp_err_t ret;
uint8_t frame[256];
// Wait for ready
ret = pn532_wait_ready(timeout_ms);
if (ret != ESP_OK) {
return ret;
}
// Read frame
ret = i2c_master_read_from_device(NFC_I2C_NUM, NFC_I2C_ADDR,
frame, 255, pdMS_TO_TICKS(100));
if (ret != ESP_OK) {
return ret;
}
// Skip RDY byte
size_t idx = 1;
// Check preamble
if (frame[idx++] != PN532_PREAMBLE ||
frame[idx++] != PN532_STARTCODE1 ||
frame[idx++] != PN532_STARTCODE2) {
return ESP_ERR_INVALID_RESPONSE;
}
// Get length
uint8_t len = frame[idx++];
uint8_t len_checksum = frame[idx++];
if ((len + len_checksum) != 0) {
return ESP_ERR_INVALID_RESPONSE;
}
// Check TFI
if (frame[idx++] != PN532_PN532TOHOST) {
return ESP_ERR_INVALID_RESPONSE;
}
// Copy response data
*response_len = len - 1;
memcpy(response, &frame[idx], *response_len);
return ESP_OK;
}
/**
* @brief Get PN532 firmware version
*/
static esp_err_t pn532_get_firmware_version(uint32_t *version)
{
uint8_t cmd[] = {PN532_COMMAND_GETFIRMWAREVERSION};
uint8_t response[32];
size_t response_len;
ESP_ERROR_CHECK(pn532_write_command(cmd, sizeof(cmd)));
ESP_ERROR_CHECK(pn532_read_response(response, &response_len, 1000));
if (response_len < 5 || response[0] != (PN532_COMMAND_GETFIRMWAREVERSION + 1)) {
return ESP_FAIL;
}
*version = (response[1] << 24) | (response[2] << 16) |
(response[3] << 8) | response[4];
return ESP_OK;
}
/**
* @brief Configure PN532 SAM (Security Access Module)
*/
static esp_err_t pn532_sam_configuration(void)
{
uint8_t cmd[] = {
PN532_COMMAND_SAMCONFIGURATION,
0x01, // Normal mode
0x14, // Timeout 50ms * 20 = 1 second
0x01 // Use IRQ pin
};
uint8_t response[32];
size_t response_len;
ESP_ERROR_CHECK(pn532_write_command(cmd, sizeof(cmd)));
ESP_ERROR_CHECK(pn532_read_response(response, &response_len, 1000));
return ESP_OK;
}
/**
* @brief Detect passive NFC target (tag)
*/
static esp_err_t pn532_read_passive_target(uint8_t *uid, uint8_t *uid_len)
{
uint8_t cmd[] = {
PN532_COMMAND_INLISTPASSIVETARGET,
0x01, // Max 1 card
0x00 // 106 kbps type A (ISO14443A)
};
uint8_t response[32];
size_t response_len;
esp_err_t ret = pn532_write_command(cmd, sizeof(cmd));
if (ret != ESP_OK) {
return ret;
}
ret = pn532_read_response(response, &response_len, 1000);
if (ret != ESP_OK) {
return ret;
}
// Check if tag found
if (response_len < 8 || response[0] != (PN532_COMMAND_INLISTPASSIVETARGET + 1)) {
return ESP_ERR_NOT_FOUND;
}
uint8_t num_tags = response[1];
if (num_tags != 1) {
return ESP_ERR_NOT_FOUND;
}
// Get UID length
*uid_len = response[6];
if (*uid_len > 7) {
*uid_len = 7;
}
// Copy UID
memcpy(uid, &response[7], *uid_len);
return ESP_OK;
}
/**
* @brief Load ROM mappings from JSON file
*/
static esp_err_t nfc_load_mappings(void)
{
FILE *f = fopen("/sdcard/nfc_roms.json", "r");
if (f == NULL) {
ESP_LOGW(TAG, "nfc_roms.json not found, using empty mappings");
return ESP_OK;
}
// Read file
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
char *json_str = malloc(fsize + 1);
if (json_str == NULL) {
fclose(f);
return ESP_ERR_NO_MEM;
}
fread(json_str, 1, fsize, f);
json_str[fsize] = 0;
fclose(f);
// Parse JSON
cJSON *root = cJSON_Parse(json_str);
free(json_str);
if (root == NULL) {
ESP_LOGE(TAG, "Failed to parse nfc_roms.json");
return ESP_FAIL;
}
cJSON *mappings_array = cJSON_GetObjectItem(root, "mappings");
if (!cJSON_IsArray(mappings_array)) {
cJSON_Delete(root);
return ESP_FAIL;
}
// Load mappings
rom_mapping_count = 0;
cJSON *mapping = NULL;
cJSON_ArrayForEach(mapping, mappings_array) {
if (rom_mapping_count >= MAX_ROM_MAPPINGS) {
break;
}
cJSON *uid_str = cJSON_GetObjectItem(mapping, "tag_uid");
cJSON *rom_path = cJSON_GetObjectItem(mapping, "rom_path");
if (cJSON_IsString(uid_str) && cJSON_IsString(rom_path)) {
// Parse UID (format: "04:12:34:56:78:9A:B0")
const char *uid = uid_str->valuestring;
uint8_t uid_bytes[7] = {0};
int uid_len = 0;
for (int i = 0; i < 7 && *uid; i++) {
sscanf(uid, "%02hhx", &uid_bytes[i]);
uid_len++;
uid += 2;
if (*uid == ':') uid++;
}
// Store mapping
memcpy(rom_mappings[rom_mapping_count].tag_uid, uid_bytes, 7);
strncpy(rom_mappings[rom_mapping_count].rom_path,
rom_path->valuestring, NFC_MAX_ROM_NAME - 1);
rom_mapping_count++;
}
}
cJSON_Delete(root);
ESP_LOGI(TAG, "Loaded %d ROM mappings", rom_mapping_count);
return ESP_OK;
}
/**
* @brief Find ROM path for given UID
*/
static const char* nfc_find_rom_for_uid(const uint8_t *uid, uint8_t uid_len)
{
for (int i = 0; i < rom_mapping_count; i++) {
if (memcmp(rom_mappings[i].tag_uid, uid, uid_len) == 0) {
return rom_mappings[i].rom_path;
}
}
return NULL;
}
// ===========================================
// Public API
// ===========================================
esp_err_t nfc_manager_init(void)
{
ESP_LOGI(TAG, "Initializing NFC Manager...");
// Initialize I2C
ESP_ERROR_CHECK(pn532_i2c_init());
// Wait a bit for PN532 to wake up
vTaskDelay(pdMS_TO_TICKS(100));
// Get firmware version
uint32_t version;
esp_err_t ret = pn532_get_firmware_version(&version);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to communicate with PN532!");
return ret;
}
ESP_LOGI(TAG, "PN532 Firmware v%d.%d",
(version >> 16) & 0xFF, (version >> 8) & 0xFF);
// Configure SAM
ESP_ERROR_CHECK(pn532_sam_configuration());
// Load ROM mappings
nfc_load_mappings();
ESP_LOGI(TAG, "✓ NFC Manager initialized");
return ESP_OK;
}
bool nfc_manager_tag_present(void)
{
uint8_t uid[7];
uint8_t uid_len;
return (pn532_read_passive_target(uid, &uid_len) == ESP_OK);
}
esp_err_t nfc_manager_get_rom(char *rom_path, size_t max_len)
{
uint8_t uid[7];
uint8_t uid_len;
// Read tag
esp_err_t ret = pn532_read_passive_target(uid, &uid_len);
if (ret != ESP_OK) {
return ret;
}
// Log UID
ESP_LOGI(TAG, "Tag UID: %02X:%02X:%02X:%02X:%02X:%02X:%02X",
uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6]);
// Find ROM mapping
const char *mapped_rom = nfc_find_rom_for_uid(uid, uid_len);
if (mapped_rom == NULL) {
ESP_LOGW(TAG, "No ROM mapping found for this tag");
return ESP_ERR_NOT_FOUND;
}
// Copy ROM path
strncpy(rom_path, mapped_rom, max_len - 1);
rom_path[max_len - 1] = '\0';
ESP_LOGI(TAG, "ROM: %s", rom_path);
return ESP_OK;
}