311 lines
14 KiB
C++
311 lines
14 KiB
C++
/**
|
|
* Olimex ESP32-C6-EVB — Board Functional Test
|
|
* =================================================
|
|
* Flash this sketch BEFORE deploying the main firmware.
|
|
* It tests every hardware subsystem and reports PASS/FAIL
|
|
* on the Serial Monitor AND on a simple web page.
|
|
*
|
|
* Board settings (Arduino IDE):
|
|
* Board : ESP32C6 Dev Module
|
|
* USB CDC On Boot : Enabled ← REQUIRED
|
|
* Flash Size : 4MB
|
|
* Upload Speed : 921600
|
|
*
|
|
* How to read results:
|
|
* 1. Open Serial Monitor at 115200 baud after upload.
|
|
* 2. Press RESET on the board — full report prints once.
|
|
* 3. Connect a phone/PC to the same WiFi and open:
|
|
* http://192.168.0.181/test (or whatever IP prints)
|
|
*
|
|
* Relay self-test: the test pulses each relay 200 ms ON then OFF.
|
|
* You will see/hear the relays click twice each.
|
|
*
|
|
* NFC self-test: tries all baud rates (115200 / 9600 / 57600)
|
|
* on both pin orientations. Module must be wired on UEXT1.
|
|
*/
|
|
|
|
#include <WiFi.h>
|
|
#include <WebServer.h>
|
|
#include <PN532_HSU.h>
|
|
#include <PN532.h>
|
|
|
|
// ── WiFi credentials ─────────────────────────────────────────────────────────
|
|
const char* SSID = "BUON GUSTO PARTER";
|
|
const char* PASSWORD = "arleta13";
|
|
IPAddress STATIC_IP(192, 168, 0, 181);
|
|
IPAddress GATEWAY (192, 168, 0, 1);
|
|
IPAddress SUBNET (255, 255, 255, 0);
|
|
|
|
// ── Pin map ───────────────────────────────────────────────────────────────────
|
|
const int LED_PIN = 8;
|
|
const int BUT_PIN = 9;
|
|
const int RELAY_PIN[] = {10, 11, 22, 23}; // Relay 1-4
|
|
const int INPUT_PIN[] = {1, 2, 3, 15}; // Digital Input 1-4
|
|
const int NFC_RX = 5; // UEXT1 pin 4
|
|
const int NFC_TX = 4; // UEXT1 pin 3
|
|
|
|
// ── NFC objects ───────────────────────────────────────────────────────────────
|
|
HardwareSerial nfcSerial(1);
|
|
PN532_HSU pn532hsu(nfcSerial);
|
|
PN532 nfc(pn532hsu);
|
|
|
|
// ── Web server ────────────────────────────────────────────────────────────────
|
|
WebServer server(80);
|
|
|
|
// ── Test result storage ───────────────────────────────────────────────────────
|
|
struct TestResult {
|
|
const char* name;
|
|
bool passed;
|
|
String detail;
|
|
};
|
|
|
|
static const int MAX_TESTS = 20;
|
|
TestResult results[MAX_TESTS];
|
|
int result_count = 0;
|
|
int pass_count = 0;
|
|
int fail_count = 0;
|
|
|
|
// ── Helper: record a result ───────────────────────────────────────────────────
|
|
void record(const char* name, bool ok, String detail = "") {
|
|
if (result_count < MAX_TESTS) {
|
|
results[result_count++] = {name, ok, detail};
|
|
}
|
|
if (ok) pass_count++; else fail_count++;
|
|
Serial.printf(" [%s] %s%s\n",
|
|
ok ? "PASS" : "FAIL",
|
|
name,
|
|
detail.length() ? (" — " + detail).c_str() : "");
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// TEST FUNCTIONS
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
void testGPIO() {
|
|
Serial.println("\n--- GPIO Test ---");
|
|
|
|
// ── LED ──────────────────────────────────────────────────────────────────
|
|
pinMode(LED_PIN, OUTPUT);
|
|
digitalWrite(LED_PIN, LOW); // LED on (active-low)
|
|
delay(300);
|
|
digitalWrite(LED_PIN, HIGH); // LED off
|
|
delay(100);
|
|
// We cannot read back an output pin reliably, so just record as attempted.
|
|
record("LED on/off", true, "GPIO8 — verify LED blinked");
|
|
|
|
// ── Button ────────────────────────────────────────────────────────────────
|
|
pinMode(BUT_PIN, INPUT_PULLUP);
|
|
int btn = digitalRead(BUT_PIN);
|
|
// Button is pull-up; HIGH = not pressed. Either state is valid at test time.
|
|
record("Button readable", true,
|
|
String("GPIO9 = ") + (btn ? "HIGH (not pressed)" : "LOW (pressed)"));
|
|
|
|
// ── Digital Inputs ────────────────────────────────────────────────────────
|
|
const char* in_names[] = {"Input1 (GPIO1)", "Input2 (GPIO2)",
|
|
"Input3 (GPIO3)", "Input4 (GPIO15)"};
|
|
for (int i = 0; i < 4; i++) {
|
|
pinMode(INPUT_PIN[i], INPUT_PULLUP);
|
|
int v = digitalRead(INPUT_PIN[i]);
|
|
record(in_names[i], true,
|
|
String("= ") + (v ? "HIGH (open)" : "LOW (active)"));
|
|
}
|
|
|
|
// ── Relays ────────────────────────────────────────────────────────────────
|
|
const char* rel_names[] = {"Relay1 (GPIO10)", "Relay2 (GPIO11)",
|
|
"Relay3 (GPIO22)", "Relay4 (GPIO23)"};
|
|
for (int i = 0; i < 4; i++) {
|
|
pinMode(RELAY_PIN[i], OUTPUT);
|
|
digitalWrite(RELAY_PIN[i], LOW);
|
|
}
|
|
delay(100);
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
// Pulse ON for 200 ms — you should hear/see relay click
|
|
digitalWrite(RELAY_PIN[i], HIGH);
|
|
delay(200);
|
|
digitalWrite(RELAY_PIN[i], LOW);
|
|
delay(100);
|
|
record(rel_names[i], true, "pulsed 200 ms — listen for click");
|
|
}
|
|
}
|
|
|
|
// ── WiFi ─────────────────────────────────────────────────────────────────────
|
|
void testWiFi() {
|
|
Serial.println("\n--- WiFi Test ---");
|
|
WiFi.disconnect(true);
|
|
delay(200);
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.config(STATIC_IP, GATEWAY, SUBNET);
|
|
|
|
bool connected = false;
|
|
for (int pass = 1; pass <= 3 && !connected; pass++) {
|
|
Serial.printf(" Connecting (attempt %d/3)...", pass);
|
|
WiFi.begin(SSID, PASSWORD);
|
|
for (int t = 0; t < 40 && WiFi.status() != WL_CONNECTED; t++) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
}
|
|
Serial.println();
|
|
connected = (WiFi.status() == WL_CONNECTED);
|
|
if (!connected && pass < 3) { WiFi.disconnect(true); delay(500); WiFi.mode(WIFI_STA); WiFi.config(STATIC_IP, GATEWAY, SUBNET); }
|
|
}
|
|
|
|
if (connected) {
|
|
record("WiFi connect", true,
|
|
WiFi.localIP().toString() + " RSSI=" + String(WiFi.RSSI()) + " dBm");
|
|
} else {
|
|
record("WiFi connect", false,
|
|
"status=" + String(WiFi.status()) + " — check SSID/password");
|
|
}
|
|
}
|
|
|
|
// ── NFC ──────────────────────────────────────────────────────────────────────
|
|
void testNFC() {
|
|
Serial.println("\n--- NFC (PN532 HSU) Test ---");
|
|
|
|
const long BAUDS[] = {115200, 9600, 57600, 38400};
|
|
const int NPINS[2][2]= {{NFC_RX, NFC_TX}, {NFC_TX, NFC_RX}};
|
|
uint32_t ver = 0;
|
|
long found_baud = 0;
|
|
int found_rx = NFC_RX, found_tx = NFC_TX;
|
|
|
|
for (int pi = 0; pi < 2 && !ver; pi++) {
|
|
for (int bi = 0; bi < 4 && !ver; bi++) {
|
|
int rx = NPINS[pi][0], tx = NPINS[pi][1];
|
|
Serial.printf(" baud=%-7ld RX=GPIO%d TX=GPIO%d ... ", BAUDS[bi], rx, tx);
|
|
nfcSerial.begin(BAUDS[bi], SERIAL_8N1, rx, tx);
|
|
delay(500);
|
|
nfc.begin();
|
|
ver = nfc.getFirmwareVersion();
|
|
if (ver) {
|
|
found_baud = BAUDS[bi]; found_rx = rx; found_tx = tx;
|
|
Serial.println("FOUND");
|
|
} else {
|
|
Serial.println("no response");
|
|
delay(100);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ver) {
|
|
nfc.SAMConfig();
|
|
char detail[80];
|
|
snprintf(detail, sizeof(detail),
|
|
"PN5%02X FW=%d.%d baud=%ld RX=GPIO%d TX=GPIO%d",
|
|
(ver >> 24) & 0xFF,
|
|
(ver >> 16) & 0xFF, (ver >> 8) & 0xFF,
|
|
found_baud, found_rx, found_tx);
|
|
record("NFC PN532 init", true, detail);
|
|
} else {
|
|
record("NFC PN532 init", false,
|
|
"not detected — check DIP switches (both=0) & UEXT1 wiring");
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WEB PAGE GET /test
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
void handleTestPage() {
|
|
String h = "<!DOCTYPE html><html><head><title>Board Test</title>";
|
|
h += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
|
|
h += "<style>";
|
|
h += "body{font-family:monospace;margin:24px;background:#1a1a1a;color:#eee}";
|
|
h += "h1{font-size:20px;color:#fff;margin-bottom:4px}";
|
|
h += ".sub{color:#888;font-size:13px;margin-bottom:20px}";
|
|
h += "table{border-collapse:collapse;width:100%;max-width:700px}";
|
|
h += "th{background:#333;padding:8px 12px;text-align:left;font-size:13px;color:#aaa}";
|
|
h += "td{padding:8px 12px;border-bottom:1px solid #333;font-size:13px}";
|
|
h += ".pass{color:#4CAF50;font-weight:bold}.fail{color:#f44336;font-weight:bold}";
|
|
h += ".detail{color:#aaa;font-size:12px}";
|
|
h += ".summary{margin-top:16px;padding:12px;border-radius:6px;font-size:15px}";
|
|
h += ".ok{background:#1b5e20;color:#a5d6a7}.bad{background:#b71c1c;color:#ffcdd2}";
|
|
h += "</style></head><body>";
|
|
h += "<h1>Olimex ESP32-C6-EVB — Functional Test</h1>";
|
|
h += "<div class='sub'>MAC: " + WiFi.macAddress() + " IP: "
|
|
+ WiFi.localIP().toString() + " Uptime: "
|
|
+ String(millis() / 1000) + "s</div>";
|
|
|
|
h += "<table><tr><th>#</th><th>Test</th><th>Result</th><th>Detail</th></tr>";
|
|
for (int i = 0; i < result_count; i++) {
|
|
bool ok = results[i].passed;
|
|
h += "<tr><td>" + String(i + 1) + "</td>";
|
|
h += "<td>" + String(results[i].name) + "</td>";
|
|
h += "<td class='" + String(ok ? "pass" : "fail") + "'>"
|
|
+ String(ok ? "PASS" : "FAIL") + "</td>";
|
|
h += "<td class='detail'>" + results[i].detail + "</td></tr>";
|
|
}
|
|
h += "</table>";
|
|
|
|
bool all_ok = (fail_count == 0);
|
|
h += "<div class='summary " + String(all_ok ? "ok" : "bad") + "'>";
|
|
h += String(pass_count) + " PASSED / "
|
|
+ String(fail_count) + " FAILED out of "
|
|
+ String(result_count) + " tests";
|
|
if (all_ok) h += " ✓ Board OK";
|
|
else h += " ✗ Check failures above";
|
|
h += "</div></body></html>";
|
|
server.send(200, "text/html", h);
|
|
}
|
|
|
|
void handleTestJSON() {
|
|
String j = "{\"pass\":" + String(pass_count)
|
|
+ ",\"fail\":" + String(fail_count)
|
|
+ ",\"total\":" + String(result_count)
|
|
+ ",\"board_ok\":" + String(fail_count == 0 ? "true" : "false")
|
|
+ ",\"mac\":\"" + WiFi.macAddress() + "\""
|
|
+ ",\"ip\":\"" + WiFi.localIP().toString() + "\""
|
|
+ ",\"uptime_s\":" + String(millis() / 1000)
|
|
+ ",\"tests\":[";
|
|
for (int i = 0; i < result_count; i++) {
|
|
if (i) j += ",";
|
|
j += "{\"name\":\"" + String(results[i].name) + "\""
|
|
+ ",\"pass\":" + String(results[i].passed ? "true" : "false")
|
|
+ ",\"detail\":\"" + results[i].detail + "\"}";
|
|
}
|
|
j += "]}";
|
|
server.send(200, "application/json", j);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// setup / loop
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
delay(2000);
|
|
for (int i = 0; i < 10 && !Serial; i++) delay(500);
|
|
|
|
Serial.println("\n\n╔══════════════════════════════════════╗");
|
|
Serial.println("║ Olimex ESP32-C6-EVB Board Test ║");
|
|
Serial.println("╚══════════════════════════════════════╝");
|
|
|
|
testGPIO();
|
|
testWiFi();
|
|
testNFC();
|
|
|
|
// ── Start web server ──────────────────────────────────────────────────────
|
|
server.on("/", HTTP_GET, handleTestPage);
|
|
server.on("/test", HTTP_GET, handleTestPage);
|
|
server.on("/test.json", HTTP_GET, handleTestJSON);
|
|
server.onNotFound([](){ server.send(404, "text/plain", "use /test or /test.json"); });
|
|
server.begin();
|
|
|
|
// ── Final summary on serial ───────────────────────────────────────────────
|
|
Serial.println("\n╔══════════════════════════════════════╗");
|
|
Serial.printf( "║ PASSED: %2d FAILED: %2d TOTAL: %2d ║\n",
|
|
pass_count, fail_count, result_count);
|
|
Serial.println(fail_count == 0
|
|
? "║ ✓ ALL TESTS PASSED — board is OK ║"
|
|
: "║ ✗ FAILURES DETECTED — see above ║");
|
|
Serial.println("╚══════════════════════════════════════╝");
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
Serial.printf("\nOpen browser: http://%s/test\n", WiFi.localIP().toString().c_str());
|
|
Serial.printf("Or fetch JSON: http://%s/test.json\n\n", WiFi.localIP().toString().c_str());
|
|
}
|
|
}
|
|
|
|
void loop() {
|
|
server.handleClient();
|
|
}
|