You dont have javascript enabled! Please enable it!

Caso práctico: Acceso NFC con ESP32, PN532 y e‑paper 2.9

Caso práctico: Acceso NFC con ESP32, PN532 y e‑paper 2.9 — hero

Objetivo y caso de uso

Qué construirás: Un terminal de control de acceso NFC avanzado utilizando ESP32 y PN532, que muestra información en una pantalla E-Paper de 2.9″.

Para qué sirve

  • Control de acceso en edificios mediante identificación NFC.
  • Gestión de listas de control de acceso (ACLs) para diferentes usuarios.
  • Visualización de información de acceso en tiempo real en la pantalla E-Paper.
  • Integración con sistemas de notificación mediante MQTT para alertas de acceso.

Resultado esperado

  • Tiempo de respuesta de acceso inferior a 500 ms.
  • Capacidad para gestionar hasta 1000 usuarios en la base de datos de ACLs.
  • Latencia de comunicación entre ESP32 y PN532 menor a 50 ms.
  • Actualización de la pantalla E-Paper cada vez que se registra un acceso, con un tiempo de refresco de 1 segundo.

Público objetivo: Desarrolladores avanzados; Nivel: Avanzado

Arquitectura/flujo: ESP32 <-> PN532 <-> E-Paper <-> MQTT

Nivel: Avanzado

Prerrequisitos

Sistema operativo y utilidades

  • Windows 11 23H2, macOS 14.5 (Sonoma) o Ubuntu 22.04.5 LTS (x86_64).
  • Python 3.11.6 instalado y en PATH (recomendado usar pyenv/pipx en Linux/macOS).
  • Cable micro‑USB de datos (no solo carga).

Toolchain concreta (versiones exactas)

  • PlatformIO Core 6.1.13
  • PlatformIO Platform: espressif32 @ 6.6.0
  • Framework Arduino-ESP32: 2.0.14
  • Toolchain GCC Xtensa (ESP32): 11.2.0 (xtensa-esp32-elf-gcc 11.2.0, binutils 2.37)
  • esptool.py 4.6
  • OpenOCD (para depuración opcional): 0.12.0-esp32-20230921

Comando de verificación de versiones propuestas (tras instalar PlatformIO):
– pio –version → PlatformIO Core, versión 6.1.13
– pio pkg update; pio pkg list (dentro del proyecto) → mostrará las versiones de platform/framework/toolchain usadas.
– python –version → Python 3.11.6

Controladores USB (según tu placa)

  • NodeMCU-32S suele integrar CP210x (Silicon Labs). Instalar:
  • Windows: “CP210x Universal Windows Driver” (v11 o superior).
  • macOS: no suele requerirse desde 10.15+, pero es recomendable instalar el paquete de Silicon Labs si no aparece el puerto.
  • Linux: kernel reciente incluye cp210x. Verifica permisos de /dev/ttyUSBx o /dev/tty.SLAB_USBtoUART.
  • Algunas variantes traen CH34x:
  • Windows: CH341SER.EXE (WCH).
  • Linux/macOS: soportado en kernel modernos, pero puede requerir permisos/udev.

Notas de Linux (opcional):
– Agrega una regla udev para acceso sin sudo:
– Crear /etc/udev/rules.d/99-esp32.rules con:
– SUBSYSTEM==»tty», ATTRS{idVendor}==»10c4″, ATTRS{idProduct}==»ea60″, MODE:=»0666″
– sudo udevadm control –reload-rules && sudo udevadm trigger

Materiales

  • ESP32 NodeMCU-32S (modelo exacto de placa “NodeMCU-32S”).
  • Waveshare 2.9″ E‑Paper (controlador SSD1680) + cableado Dupont hembra-hembra.
  • Módulo PN532 NFC (Waveshare o Adafruit/Genérico) con interfaz SPI habilitada.
  • Tarjetas y/o llaveros NFC tipo A (MIFARE Classic/Ultralight/NTAG).
  • Fuente de alimentación: USB 5V (PC o cargador estable de 1A); el regulador onboard del NodeMCU-32S alimentará 3V3 para PN532 y E‑Paper.
  • Opcional: protoboard, resistencias pull-up si tu PN532 lo requiere para IRQ (no usaremos IRQ en este caso), soportes/adhesivos.

Comentarios de consumo:
– Waveshare 2.9″ e‑paper: ~26 mA durante refresco activo; ~<1 mA reposo.
– PN532: 50–80 mA durante poll activo.
– NodeMCU-32S puede alimentar ambos desde 3V3 onboard con margen si la entrada 5V es estable.

Preparación y conexión

Este caso práctico usa un bus SPI compartido (VSPI del ESP32) para E‑Paper y PN532, cada uno con su propio pin CS. El SSD1680 no requiere MISO. El PN532 sí usa MISO.

  • Bus SPI (VSPI) del ESP32 NodeMCU-32S:
  • SCLK → GPIO18
  • MOSI → GPIO23
  • MISO → GPIO19 (solo PN532)
  • Pines auxiliares E‑Paper:
  • CS, DC, RST, BUSY
  • Pines PN532:
  • SS (CS)
  • (Opcional) RSTO/IRQ: no usado en este ejemplo (polling).

Tabla de mapeo de pines y alimentación:

Módulo Señal/Pin ESP32 NodeMCU-32S Notas
Waveshare 2.9″ E‑Paper (SSD1680) VCC 3V3 Alimentación 3.3V
GND GND Tierra común
DIN (MOSI) GPIO23 Datos SPI
CLK (SCLK) GPIO18 Reloj SPI
CS GPIO5 Chip Select E‑Paper
DC (Data/Command) GPIO17 Línea DC
RST GPIO16 Reset de panel
BUSY GPIO4 Estado ocupado del panel
PN532 NFC (SPI) VCC 3V3 Alimentación 3.3V (no usar 5V en lógica)
GND GND Tierra común
SCK GPIO18 Comparte bus SPI
MOSI GPIO23 Comparte bus SPI
MISO GPIO19 Entrada MISO para PN532
SS (CS) GPIO15 Chip Select PN532
LED onboard (opcional) LED GPIO2 Indicador de acceso (on: concedido; parpadeo: denegado)

Consejos:
– Usa cables cortos y firmes. Evita falsos contactos en BUSY y DC del E‑Paper, que provocan bloqueos.
– No cruces 5V con señales lógicas. Todo va a 3.3V.
– Asegura masa común entre los módulos y el ESP32.

Código completo (Arduino/ESP32 con PlatformIO) y explicación

A continuación se muestra un proyecto completo con:
– Whitelist de UIDs basada en SHA‑256 (no se guardan UIDs en claro).
– NTP para hora real vía WiFi.
– Registro de eventos en LittleFS (CSV).
– Renderizado en E‑Paper con GxEPD2 (SSD1680).
– Lógica de control de acceso (éxito/denegado) y mitigación de ghosting (refresco completo periódico).

platformio.ini

Bloquea versiones de plataforma, framework y librerías.

; platformio.ini
[env:nodemcu-32s]
platform = espressif32 @ 6.6.0
board = nodemcu-32s
framework = arduino

; Toolchain y ajustes de carga/monitor
upload_speed = 921600
monitor_speed = 115200
monitor_filters = direct, time
board_build.flash_mode = dio

; Versiones de librerías
lib_deps =
  zinggjm/GxEPD2 @ 1.5.8
  adafruit/Adafruit PN532 @ 1.3.3
  adafruit/Adafruit BusIO @ 1.14.1
  adafruit/Adafruit GFX Library @ 1.11.9

; Opcional: reducir warnings verbosos
build_flags =
  -DCORE_DEBUG_LEVEL=0

; Asegurar framework Arduino-ESP32 versión 2.0.14
platform_packages =
  framework-arduinoespressif32@~3.20014.0
  toolchain-xtensa-esp32@~11.2.0
  tool-esptoolpy@~1.40500.0

Notas:
– framework-arduinoespressif32@3.20014.0 corresponde a Arduino-ESP32 2.0.14 en nomenclatura PlatformIO.
– toolchain-xtensa-esp32 ~11.2.0 alinea con gcc 11.2.0.

src/main.cpp

#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <FS.h>
#include <LittleFS.h>

// E-Paper (SSD1680) con GxEPD2
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoBold9pt7b.h>

// PN532 (SPI)
#include <Adafruit_PN532.h>

// ===== Configuración WiFi/NTP =====
static const char* WIFI_SSID     = "TU_SSID";
static const char* WIFI_PASSWORD = "TU_PASSWORD";
static const char* NTP_SERVER    = "pool.ntp.org";
static const long  GMT_OFFSET    = 0;          // ajustar según zona horaria
static const int   DST_OFFSET    = 0;          // horario de verano si aplica

// ===== Pines (ESP32 NodeMCU-32S) =====
static const int PIN_EPD_CS   = 5;
static const int PIN_EPD_DC   = 17;
static const int PIN_EPD_RST  = 16;
static const int PIN_EPD_BUSY = 4;

static const int PIN_PN532_SS = 15;

static const int PIN_LED      = 2;  // LED onboard

// ===== Objetos globales =====
GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(GxEPD2_290(PIN_EPD_CS, PIN_EPD_DC, PIN_EPD_RST, PIN_EPD_BUSY));
Adafruit_PN532 pn532(PIN_PN532_SS);

// ===== Whitelist (hash SHA-256 hex de UIDs) =====
// Para generar hashes, ver script Python más abajo o función utilitaria.
static const char* WHITELIST_SHA256[] = {
  // Ejemplos (reemplaza con los tuyos):
  // "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  // "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
};
static const size_t WHITELIST_COUNT = sizeof(WHITELIST_SHA256)/sizeof(WHITELIST_SHA256[0]);

// ===== Utilidades =====
String bytesToHex(const uint8_t* data, size_t len) {
  static const char* hex = "0123456789abcdef";
  String out; out.reserve(len * 2);
  for (size_t i = 0; i < len; ++i) {
    out += hex[(data[i] >> 4) & 0x0F];
    out += hex[data[i] & 0x0F];
  }
  return out;
}

// SHA-256 usando mbedTLS del core ESP32
#include "mbedtls/md.h"

bool sha256_hex(const uint8_t* data, size_t len, char out_hex[65]) {
  mbedtls_md_context_t ctx;
  const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
  if (!info) return false;
  mbedtls_md_init(&ctx);
  if (mbedtls_md_setup(&ctx, info, 0) != 0) { mbedtls_md_free(&ctx); return false; }
  if (mbedtls_md_starts(&ctx) != 0) { mbedtls_md_free(&ctx); return false; }
  if (mbedtls_md_update(&ctx, data, len) != 0) { mbedtls_md_free(&ctx); return false; }
  uint8_t hash[32];
  if (mbedtls_md_finish(&ctx, hash) != 0) { mbedtls_md_free(&ctx); return false; }
  mbedtls_md_free(&ctx);
  static const char* hex = "0123456789abcdef";
  for (int i = 0; i < 32; ++i) {
    out_hex[2*i]   = hex[(hash[i] >> 4) & 0xF];
    out_hex[2*i+1] = hex[hash[i] & 0xF];
  }
  out_hex[64] = '\0';
  return true;
}

bool inWhitelist(const char* sha256_hex) {
  for (size_t i = 0; i < WHITELIST_COUNT; ++i) {
    if (strcasecmp(sha256_hex, WHITELIST_SHA256[i]) == 0) return true;
  }
  return false;
}

// Tiempo legible
String nowString() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo, 3000)) return String("1970-01-01 00:00:00");
  char buf[32];
  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
  return String(buf);
}

// Registro en LittleFS
bool appendLog(const String& line) {
  if (!LittleFS.begin(true)) return false;
  File f = LittleFS.open("/access_log.csv", FILE_APPEND);
  if (!f) return false;
  bool ok = f.print(line);
  f.close();
  return ok;
}

// Renderizado en E‑Paper
void drawAccessScreen(bool granted, const String& uid_hex, const String& timestamp, bool partial = false) {
  if (partial) {
    display.setPartialWindow(0, 0, display.width(), display.height());
  } else {
    display.setFullWindow();
  }
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.setRotation(1);
    display.setTextColor(GxEPD_BLACK);
    display.setFont(&FreeMonoBold12pt7b);

    int16_t x = 6, y = 26;
    display.setCursor(x, y);
    display.print("nfc-epaper-access-control");

    display.setFont(&FreeMonoBold9pt7b);
    y += 28;
    display.setCursor(x, y);
    display.print("UID: ");
    display.print(uid_hex);

    y += 24;
    display.setCursor(x, y);
    display.print("Time: ");
    display.print(timestamp);

    y += 30;
    display.setFont(&FreeMonoBold12pt7b);
    display.setCursor(x, y);
    if (granted) {
      display.print("ACCESS GRANTED");
    } else {
      display.print("ACCESS DENIED ");
    }

    // Pie de página
    display.setFont(&FreeMonoBold9pt7b);
    y += 28;
    display.setCursor(x, y);
    display.print("Panel: Waveshare 2.9\" (SSD1680)");
  } while (display.nextPage());
}

// Conectividad WiFi + NTP
void initWiFiNTP() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("[WiFi] Conectando a "); Serial.println(WIFI_SSID);
  unsigned long t0 = millis();
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
    if (millis() - t0 > 15000) break;
  }
  Serial.println();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("[WiFi] OK. IP: "); Serial.println(WiFi.localIP());
    configTzTime("UTC0", NTP_SERVER); // se puede ajustar con TZ
    // Espera bloqueo NTP
    struct tm timeinfo;
    for (int i = 0; i < 20; ++i) {
      if (getLocalTime(&timeinfo, 500)) break;
      delay(500);
    }
    Serial.println("[NTP] Sincronizado (si red disponible).");
  } else {
    Serial.println("[WiFi] No conectado. Continuando sin NTP.");
  }
}

// Inicialización de periféricos
void initEPaper() {
  display.init(115200);  // velocidad SPI para E‑Paper
  display.setRotation(1);
  // Primera pantalla
  drawAccessScreen(false, "----", nowString(), false);
}

void initPN532() {
  pn532.begin();
  uint32_t versiondata = pn532.getFirmwareVersion();
  if (!versiondata) {
    Serial.println("[PN532] No se detecto el PN532. Verifica cableado y SS.");
  } else {
    Serial.print("[PN532] Chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
    Serial.print("[PN532] Firmware: "); Serial.print((versiondata>>16) & 0xFF, DEC);
    Serial.print("."); Serial.println((versiondata>>8) & 0xFF, DEC);
    pn532.SAMConfig(); // modo normal, permite lectura pasiva
  }
}

void blinkDenied() {
  for (int i = 0; i < 3; ++i) {
    digitalWrite(PIN_LED, HIGH); delay(120);
    digitalWrite(PIN_LED, LOW); delay(120);
  }
}

void solidGranted() {
  digitalWrite(PIN_LED, HIGH); delay(800);
  digitalWrite(PIN_LED, LOW);
}

void setup() {
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW);
  Serial.begin(115200);
  delay(200);

  Serial.println("\n[nfc-epaper-access-control] Inicio");
  initWiFiNTP();
  if (!LittleFS.begin(true)) {
    Serial.println("[FS] LittleFS montado (formateado en primera vez si era necesario).");
  }

  initEPaper();
  initPN532();

  Serial.println("[Sistema] Listo. Acerca credencial NFC tipo A al PN532.");
}

void loop() {
  static uint32_t lastFullRefresh = 0;
  static int opCount = 0;

  uint8_t uid[8];
  uint8_t uidLength = 0;

  // Lee una tarjeta (timeout corto para mantener fluidez)
  bool success = pn532.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 50);
  if (!success) {
    // Refresco completo cada ~60s para mitigar ghosting
    if (millis() - lastFullRefresh > 60000) {
      drawAccessScreen(false, "----", nowString(), false);
      lastFullRefresh = millis();
    }
    delay(10);
    return;
  }

  String uid_hex = bytesToHex(uid, uidLength);
  char sha_hex[65];
  if (!sha256_hex(uid, uidLength, sha_hex)) {
    Serial.println("[Crypto] Error SHA-256.");
    blinkDenied();
    return;
  }

  bool granted = inWhitelist(sha_hex);

  String ts = nowString();
  // Render parcial para respuesta rápida
  drawAccessScreen(granted, uid_hex, ts, true);
  if (granted) solidGranted(); else blinkDenied();

  // Log CSV: timestamp,uid_hex,sha256_hex,granted
  String line = ts + "," + uid_hex + "," + String(sha_hex) + "," + (granted ? "1" : "0") + "\n";
  if (!appendLog(line)) {
    Serial.println("[FS] Error escribiendo /access_log.csv");
  }

  Serial.print("[NFC] UID="); Serial.print(uid_hex);
  Serial.print(" SHA256="); Serial.print(sha_hex);
  Serial.print(" Access="); Serial.println(granted ? "GRANTED" : "DENIED");

  opCount++;
  // Forzar refresco completo cada 10 operaciones para borrar ghosting
  if (opCount % 10 == 0) {
    drawAccessScreen(granted, uid_hex, ts, false);
    lastFullRefresh = millis();
  }

  delay(600); // pequeña pausa para evitar múltiples lecturas idénticas instantáneas
}

Explicación breve de partes clave:
– Whitelist mediante SHA‑256: evita exponer UIDs en claro en firmware/FS; puedes generar los hashes desde un PC y pegarlos en WHITELIST_SHA256.
– PN532 por SPI: usa el bus VSPI compartido con el E‑Paper; cada dispositivo tiene su CS (GPIO5 para E‑Paper y GPIO15 para PN532).
– E‑Paper con GxEPD2 (SSD1680): renderiza texto con fuentes GFX. Se usan actualizaciones parciales para respuesta fluida y periódicas completas para eliminar ghosting.
– NTP via WiFi: se sincroniza hora al inicio si hay red; se usa en logs y en pantalla.
– LittleFS: almacena un CSV con los registros de accesos.
– LED onboard: feedback simple de acceso concedido/denegado.

Script auxiliar (opcional) para generar SHA‑256 de UIDs

Úsalo en tu PC para convertir UIDs (en hex) a hash SHA‑256 y pegarlos en WHITELIST_SHA256.

# uid2sha256.py
import sys, binascii, hashlib

def main():
    if len(sys.argv) < 2:
        print("Uso: python uid2sha256.py <uid_hex> [<uid_hex> ...]")
        print("Ejemplo: python uid2sha256.py 04a224b9c13280")
        sys.exit(1)
    for uid_hex in sys.argv[1:]:
        uid_hex = uid_hex.lower().strip()
        try:
            uid_bytes = binascii.unhexlify(uid_hex)
        except Exception as e:
            print(f"UID inválido: {uid_hex} ({e})")
            continue
        sha = hashlib.sha256(uid_bytes).hexdigest()
        print(f"UID={uid_hex}  SHA256={sha}")

if __name__ == "__main__":
    main()

Ejemplo:
– python uid2sha256.py 04a224b9c13280
– Copia el sha256 resultante a WHITELIST_SHA256 en main.cpp.

Compilación / flash / ejecución

Usaremos PlatformIO en modo CLI para reproducibilidad total.

1) Crear proyecto
– mkdir nfc-epaper-access-control
– cd nfc-epaper-access-control
– pio project init –board nodemcu-32s

2) Sustituir/crear archivos
– Copia el contenido de platformio.ini (arriba) a ./platformio.ini
– Crea ./src/main.cpp con el código mostrado

3) Instalar dependencias y compilar
– pio run

4) Enumerar puertos serie
– pio device list
– Windows: COM3/COMx (CP210x/CH340)
– Linux: /dev/ttyUSB0 (CP210x) o /dev/ttyACM0
– macOS: /dev/tty.SLAB_USBtoUART

5) Grabar firmware
– pio run -t upload –upload-port
– Ejemplo Windows: pio run -t upload –upload-port COM5
– Ejemplo Linux/macOS: pio run -t upload –upload-port /dev/ttyUSB0

6) Abrir monitor serie
– pio device monitor -b 115200 –port

7) Verificación de paquetes (opcional)
– pio pkg list
– pio pkg show framework-arduinoespressif32
– pio pkg show platformio/toolchain-xtensa-esp32

Notas:
– Si “Failed to connect to ESP32”, mantén pulsado BOOT y suéltalo cuando empiece el upload (o pulsa EN/RESET tras iniciar el proceso).
– Asegúrate de haber editado WIFI_SSID y WIFI_PASSWORD en main.cpp (puedes dejarlo sin red; el proyecto funciona igual y solo no tendrá hora real).

Validación paso a paso

1) Alimentación y conexión:
– Conecta el NodeMCU-32S al PC.
– Verifica en la tabla de pines que E‑Paper y PN532 están conectados a 3V3 y GND correctamente, y los pines CS/DC/RST/BUSY/SS según la tabla.

2) Arranque y logs por serie:
– En el monitor (115200), debes ver:
– [nfc-epaper-access-control] Inicio
– [WiFi] Conectando a (si configurado)
– [NTP] Sincronizado (si la red responde)
– [FS] LittleFS montado…
– [PN532] Chip PN5xx / Firmware x.y
– [Sistema] Listo. Acerca credencial NFC tipo A al PN532.

3) Pantalla E‑Paper inicial:
– Debe mostrar título, UID —-, la hora (si hay NTP) y el pie “Waveshare 2.9″ (SSD1680)”.

4) Presenta una tarjeta/lavero NFC:
– Acerca a ~2–4 cm del PN532.
– En menos de 1 s, la pantalla debe actualizarse parcialmente mostrando:
– UID: en hex
– Hora
– ACCESS GRANTED si su SHA‑256 está en whitelist
– ACCESS DENIED en caso contrario
– LED en GPIO2:
– Encendido fijo ~800 ms si concedido
– 3 parpadeos cortos si denegado

5) Registro de acceso:
– Reinicia y entra en modo “FS inspect” (pequeña rutina temporal o lee con LittleFS a través de un sketch; alternativamente, añade temporalmente un bloque que imprima /access_log.csv en setup).
– Debe existir /access_log.csv con entradas tipo:
– 2025-05-01 12:34:56,04a224b9c13280,248d6a…,1

6) Prueba ghosting:
– Ojo a posibles sombras tras ~10–20 operaciones parciales seguidas.
– El firmware fuerza un refresco completo cada 10 lecturas o ~60 s para limpiar ghosting.

7) Pruebas sin WiFi:
– Si no hay red, el sistema funciona igual. La hora será “1970-01-01 00:00:00” hasta que haya NTP.

8) Prueba de whitelist:
– Genera el SHA‑256 del UID con el script Python y añádelo al arreglo WHITELIST_SHA256.
– Compila y sube. Vuelve a probar: ahora esa tarjeta debe recibir ACCESS GRANTED.

Troubleshooting (errores típicos y soluciones)

1) No aparece puerto serie / no sube firmware
– Windows: instala driver CP210x (o CH340). Cambia cable USB por uno de datos.
– Linux: agrega tu usuario a grupo dialout, udev rules, o usa sudo temporalmente.
– “Failed to connect to ESP32”: mantén BOOT presionado al iniciar upload y suelta cuando empiece, o pulsa EN/RESET justo después.
– Revisa que el switch de alimentación de la placa (si lo tuviera) esté en ON.

2) “[PN532] No se detecto el PN532”
– Verifica que el PN532 está en modo SPI (algunas placas tienen switch/puentes SEL).
– Comprueba SS en GPIO15 y el cableado SCK/MOSI/MISO compartiendo VSPI.
– Alimentación: usa 3V3 estable; evita cables flojos.
– Asegúrate de no usar IRQ si el sketch no lo configura (nuestro ejemplo usa polling).

3) E‑Paper bloqueado en “BUSY” (no actualiza)
– BUSY mal cableado (GPIO4 en este diseño). Verifica continuidad y pin correcto.
– DC/RST invertidos: revisa DC (GPIO17) y RST (GPIO16).
– Usa el modelo adecuado de GxEPD2: GxEPD2_290 corresponde a SSD1680 2.9″ 296×128.
– Alimentación insuficiente durante refresco: intenta refresco con USB directo a PC o cargador 5V 1A.

4) Ghosting persistente en la pantalla
– Aumenta frecuencia de refrescos completos (por ejemplo cada 5 operaciones).
– Evita texto en posiciones que cambian píxel a píxel en parciales excesivos.
– Llama a drawAccessScreen(…, false) después de una serie de parciales intensos.

5) “Brownout detector triggered”
– Fuente USB débil o cable demasiado largo/fino. Cambia a puerto USB de mayor potencia o cargador 5V 2A.
– Evita alimentar otros módulos de alto consumo desde el 3V3 si no es necesario.

6) No se sincroniza NTP
– Revisa SSID/clave; verifica que el firewall permite NTP/UDP 123.
– Cambia servidor NTP (por ejemplo “time.google.com”, “es.pool.ntp.org”).
– Asegura buena potencia de señal WiFi.

7) No se imprime/crea access_log.csv
– Asegúrate de LittleFS.begin(true) en setup; se formatea automáticamente si no existe.
– Comprueba espacio: aunque improbable en pruebas, evita generar archivos demasiado grandes sin depuración.

8) El UID se muestra pero nunca “GRANTED”
– Verifica que el SHA‑256 del UID está correcto:
– Cuidado con mayúsculas/minúsculas; nuestro código usa hex minúscula.
– Asegúrate de no añadir espacios/nuevas líneas al copiar el hash.
– Algunas tarjetas tienen UIDs de longitud distinta (4/7 bytes). El hash depende exactamente de los bytes y el orden; usa el script provisto.

Mejoras / variantes

  • Persistencia de whitelist:
  • Cargar/guardar desde LittleFS en JSON/CSV para no recompilar al añadir tarjetas.
  • Herramienta de administración por puerto serie o web (microservidor HTTP en el ESP32).
  • Seguridad avanzada:
  • HMAC‑SHA256 con “salt” y clave única por instalación; evita incluso ataques de diccionario sobre UIDs.
  • Generar códigos de un solo uso (OTP) mostrados temporalmente en E‑Paper tras verificación NFC.
  • UX del panel:
  • Añadir iconografía o inversos de color en respuestas (negro sobre blanco y viceversa).
  • Temporizador de autoapagado y despertar por botón (profundizar en consumo).
  • Red y backend:
  • Sincronizar logs a un servidor remoto (HTTPS con WiFiClientSecure).
  • Integración con MQTT para auditoría centralizada.
  • Hardware:
  • Añadir un relé/SSR controlado por ESP32 al conceder acceso (con las debidas protecciones).
  • Añadir buzzer piezoactivo para feedback audible (GPIO dedicado).
  • Modo offline robusto:
  • RTC externo (p.ej., DS3231) para mantener hora sin NTP.
  • Cifrado de whitelist en flash y bloqueo por “secure boot”/flash encryption (ecosistema ESP-IDF).

Checklist de verificación

  • [ ] He instalado PlatformIO Core 6.1.13 y Python 3.11.6.
  • [ ] He creado el proyecto con board = nodemcu-32s y platform = espressif32 @ 6.6.0.
  • [ ] He pegado platformio.ini con librerías y paquetes en las versiones indicadas.
  • [ ] He cableado correctamente el E‑Paper (SSD1680) a GPIO23/18/5/17/16/4 y 3V3/GND.
  • [ ] He cableado el PN532 (SPI) a GPIO23/18/19/15 y 3V3/GND, y está en modo SPI.
  • [ ] He configurado WIFI_SSID/WIFI_PASSWORD (opcional) en main.cpp.
  • [ ] He generado y añadido el SHA‑256 de al menos un UID a WHITELIST_SHA256.
  • [ ] La compilación pio run finaliza sin errores.
  • [ ] La carga pio run -t upload se completa; el monitor serie muestra el banner de inicio.
  • [ ] Al acercar un tag NFC se muestra UID y “ACCESS GRANTED/DENIED” en el E‑Paper.
  • [ ] Se registra el evento en /access_log.csv con timestamp, UID y resultado.
  • [ ] He verificado refrescos completos periódicos (ghosting controlado).

Con este caso práctico “nfc-epaper-access-control” has integrado lectura NFC con PN532, render en e‑paper Waveshare 2.9″ (SSD1680), y control de acceso con verificación criptográfica en un ESP32 NodeMCU-32S, cuidando reproducibilidad con PlatformIO y un pipeline de build/flash/monitor determinista.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es la versión recomendada de Python para el proyecto?




Pregunta 2: ¿Qué herramienta se utiliza para la verificación de versiones en PlatformIO?




Pregunta 3: ¿Cuál es la versión de PlatformIO Core recomendada?




Pregunta 4: ¿Qué controlador USB suele integrar el NodeMCU-32S?




Pregunta 5: ¿Qué comando se utiliza para actualizar los paquetes en PlatformIO?




Pregunta 6: ¿Cuál es la versión de la Toolchain GCC Xtensa para ESP32 requerida?




Pregunta 7: ¿Qué versión de OpenOCD es opcional para la depuración?




Pregunta 8: ¿Qué versión del Framework Arduino-ESP32 se debe usar?




Pregunta 9: ¿Qué archivo se debe crear para agregar una regla udev en Linux?




Pregunta 10: ¿Cuál es la versión mínima recomendada del controlador CP210x para Windows?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio