1// ═══════════════════════════════════════════════════════════
2// ADMIN SIMPLE - Solo SCAN y TOGGLE RELAY
3// Version minimalista para control basico I2C
4// ═══════════════════════════════════════════════════════════
7#include <DevLab_ICM20948.h>
8// Configuracion I2C - Wire1 con GPIO2 (SDA) y GPIO3 (SCL)
12#define I2C_FREQ 100000 // 100 kHz - SEGURO para inicialización (luego se puede subir a 400kHz)
14// ═══════════════════════════════════════════════════════════
15// COMANDOS CRÍTICOS DE SEGURIDAD (0xA0-0xAF)
16// ═══════════════════════════════════════════════════════════
17#define CMD_RELAY_OFF 0xA0 // Apagar relé PA2 y PB5
18#define CMD_RELAY_ON 0xA1 // Encender relé PA2 y PB5
19#define CMD_RELAY_TOGGLE 0xA6 // Pulso/Disparo relé PA2 y PB5
21// ═══════════════════════════════════════════════════════════
23// ═══════════════════════════════════════════════════════════
24#define CMD_RED 0x02 // NeoPixels rojos
25#define CMD_GREEN 0x03 // NeoPixels verdes
26#define CMD_BLUE 0x04 // NeoPixels azules
27#define CMD_OFF 0x05 // NeoPixels apagados + PWM OFF
28#define CMD_WHITE 0x08 // NeoPixels blancos
30// ═══════════════════════════════════════════════════════════
31// COMANDOS PWM / BUZZER
32// ═══════════════════════════════════════════════════════════
33#define CMD_PWM_OFF 0x20 // PWM OFF - Silencio
34#define CMD_PWM_25 0x21 // PWM 25% - Tono grave 200Hz
35#define CMD_PWM_50 0x22 // PWM 50% - Tono medio 500Hz
36#define CMD_PWM_75 0x23 // PWM 75% - Tono agudo 1000Hz
37#define CMD_PWM_100 0x24 // PWM 100% - Tono muy agudo 2000Hz
39// Comandos deshabilitados - Causan problemas de estabilidad I2C
42#define CMD_TONE_DO 0x25 // Tono DO (261Hz)
43#define CMD_TONE_RE 0x26 // Tono RE (294Hz)
44#define CMD_TONE_MI 0x27 // Tono MI (330Hz)
45#define CMD_TONE_FA 0x28 // Tono FA (349Hz)
46#define CMD_TONE_SOL 0x29 // Tono SOL (392Hz)
47#define CMD_TONE_LA 0x2A // Tono LA (440Hz)
48#define CMD_TONE_SI 0x2B // Tono SI (494Hz)
51#define CMD_SUCCESS 0x2C // Success - Tono positivo 800Hz
52#define CMD_OK 0x2D // OK - Confirmación 1000Hz
53#define CMD_WARNING 0x2E // Warning - Advertencia 1200Hz
54#define CMD_ALERT 0x2F // Alert - Alerta crítica 1500Hz
57// ═══════════════════════════════════════════════════════════
58// COMANDOS LECTURA DIGITAL
59// ═══════════════════════════════════════════════════════════
60#define CMD_PA4_DIGITAL 0x07 // Leer PA0 como entrada digital
61#define RESP_PA4_DIGITAL 0x09 // Respuesta PA4 digital
63// ═══════════════════════════════════════════════════════════
64// COMANDOS ADC 12-bit (PA1 interno del slave)
65// ═══════════════════════════════════════════════════════════
66#define CMD_ADC_PA1_HSB 0xD8 // Leer ADC - bits 11-8 (nibble alto)
67#define CMD_ADC_PA1_LSB 0xD9 // Leer ADC - bits 7-0
69// ═══════════════════════════════════════════════════════════
70// COMANDOS I2C ADDRESS MANAGEMENT
71// ════════════════════════════════════════════════════════════
72#define CMD_SET_I2C_ADDR 0x3D // Establecer nueva dirección I2C (se guarda en Flash)
73#define CMD_RESET_FACTORY 0x3E // Reset factory (usar UID por defecto)
74#define CMD_GET_I2C_STATUS 0x3F // Obtener estado I2C (Flash vs UID)
75#define CMD_SAVE_COLOR 0x46 // Guardar color por slot (1..3 en admin, 0..2 en esclavo)
77#define RESP_I2C_ADDR_SET 0x0D // Nueva dirección I2C establecida
78#define RESP_FACTORY_RESET 0x0E // Reset factory ejecutado (usar UID)
79#define RESP_I2C_FROM_FLASH 0x0F // I2C usando dirección desde Flash
80#define RESP_I2C_FROM_UID 0x0A // I2C usando dirección desde UID
81#define RESP_COLOR_STAGE_READY 0x15 // Etapa de save_color lista
82#define RESP_COLOR_SAVED 0x16 // Color guardado en Flash
85uint8_t found_devices[128];
86uint8_t device_count = 0;
87bool scan_loop_active = false; // Bandera para scan continuo
88uint32_t last_scan_time = 0; // Timestamp del último scan
89uint32_t scan_interval = 1000; // Intervalo entre scans (1 segundo)
90bool scan_in_progress = false; // Bandera para evitar scans simultáneos
91#define DEVICE_DELAY 30 // Delay óptimo entre dispositivos (ms)
95 uint32_t success_count;
96 uint32_t last_error_time;
98DeviceError device_errors[128];
99int device_error_count = 0;
102bool test_mode_active = false;
103uint32_t test_interval = 100; // Intervalo de test (100ms por defecto)
104uint32_t test_start_time = 0;
105uint32_t test_duration = 0;
107// PA0 Digital reading stats
108struct DigitalReadStats {
112 uint8_t last_state; // 0=LOW, 1=HIGH
113 uint32_t last_read_time;
115DigitalReadStats digital_stats[128];
116int digital_stats_count = 0;
118// Test mode para lecturas digitales
119bool read_test_active = false;
120uint32_t read_test_interval = 100;
121uint32_t read_test_start_time = 0;
122uint32_t read_test_duration = 0;
124// Flag para reimprimir menú cuando se conecta Serial
125bool menu_shown = false;
126uint32_t last_serial_check = 0;
127bool i2c_safe_mode = false; // Modo seguro si I2C está bloqueado
132 // ═══════════════════════════════════════════════════════════
133 // PASO 1: INICIALIZAR USB/CDC PRIMERO (CRÍTICO EN RP2040/RP2350)
134 // ═══════════════════════════════════════════════════════════
135 Serial.begin(115200);
137 #if defined(ARDUINO_ARCH_RP2040)
138 // RP2040/RP2350: Esperar a que USB enumere ANTES de tocar I2C
139 // Esto evita que I2C bloquee IRQs y mate el CDC
140 uint32_t t0 = millis();
141 while (!Serial && millis() - t0 < 2000) {
144 delay(200); // Margen extra de seguridad
146 Serial.println("\n[RP2040/RP2350] USB CDC listo");
151 Serial.println("\n\n");
152 Serial.println("╔═══════════════════════════════════════╗");
153 Serial.println("║ ADMIN PWM - I2C Control ║");
154 Serial.println("║ Scan + Relay + PWM/Buzzer ║");
155 Serial.println("╚═══════════════════════════════════════╝");
157 // ═══════════════════════════════════════════════════════════
158 // PASO 2: AHORA SÍ INICIALIZAR I2C (USB ya está vivo)
159 // ═══════════════════════════════════════════════════════════
160 Serial.println("\n[INFO] Inicializando I2C...");
162 // Verificar que SDA/SCL no estén bloqueados ANTES de WIRE.begin()
163 Serial.println("[CHECK] Verificando bus I2C...");
164 pinMode(I2C_SDA, INPUT_PULLUP);
165 pinMode(I2C_SCL, INPUT_PULLUP);
168 bool sda_ok = digitalRead(I2C_SDA);
169 bool scl_ok = digitalRead(I2C_SCL);
171 Serial.printf(" SDA (GPIO%d): %s\n", I2C_SDA, sda_ok ? "HIGH ✓" : "LOW ✗ BLOQUEADO");
172 Serial.printf(" SCL (GPIO%d): %s\n", I2C_SCL, scl_ok ? "HIGH ✓" : "LOW ✗ BLOQUEADO");
174 if (!sda_ok || !scl_ok) {
175 Serial.println("\n[ERROR] Bus I2C bloqueado!");
176 Serial.println("CAUSA: Dispositivo esclavo colgado o sin pull-ups");
177 Serial.println("SOLUCION:");
178 Serial.println(" 1. Desconecta todos los dispositivos I2C");
179 Serial.println(" 2. Verifica resistencias pull-up (4.7k ohm)");
180 Serial.println(" 3. Resetea el RP2350");
181 Serial.println("\n[MODO SEGURO] I2C NO inicializado");
182 i2c_safe_mode = true;
183 // NO return - continuar para procesar comandos seriales
185 Serial.println("[OK] Bus I2C libre");
186 i2c_safe_mode = false;
189 // Solo inicializar I2C si el bus está libre
190 if (!i2c_safe_mode) {
191 // Inicializar I2C con Wire1
192 // RP2040 / RP2350: configurar pines ANTES de begin()
193 // WIRE.setSDA(I2C_SDA);
194 // WIRE.setSCL(I2C_SCL);
195 WIRE.begin(I2C_SDA,I2C_SCL);
196 WIRE.setClock(I2C_FREQ);
198 if(!imu.beginI2C(ICM_ADDR,WIRE,I2C_FREQ)){
199 Serial.println(F("ERROR: beginI2C() failed"));
200 while (1) delay(200);
202 if(!imu.auxMasterEnable(0x07)){
203 Serial.println(F("ERROR: enableAuxMaster() failed"));
204 while (1) delay(200);
206 // Deshabilitado temporalmente: bloqueaba el arranque (while(1)) si el
207 // esclavo en 0x2B no respondia al registro 0x31 (valores eran para el
208 // magnetometro AK09916, no para el esclavo de relay/PWM).
210 if(!imu.auxWriteByte(0x2B, 0x31, 0x08)){
211 Serial.println(F("ERROR: auxwritebyte()) failed"));
212 while (1) delay(200);
215 Serial.println("[OK] I2C inicializado sin bloquear USB");
216 Serial.printf(" Wire1 - SDA:GPIO%d SCL:GPIO%d @ %d kHz\n", I2C_SDA, I2C_SCL, I2C_FREQ/1000);
218 } // Fin de if (!i2c_safe_mode)
219 bool mag_ok = imu.initMag();
220 Serial.printf("[TEST] initMag() = %s\n", mag_ok ? "OK" : "FAIL");
222 Serial.println("\nComandos disponibles:");
223 Serial.println(" s - Scan dispositivos I2C");
224 Serial.println(" loop - Scan continuo (cada 1 seg)");
225 Serial.println(" stop - Detener scan continuo");
226 Serial.println(" t XX - Pulso relay (XX = addr hex)");
227 Serial.println(" on XX - Encender relay");
228 Serial.println(" off XX - Apagar relay");
229 Serial.println(" help - Mostrar ayuda");
230 Serial.println("\nEjemplos:");
231 Serial.println(" s (scan de dispositivos)");
232 Serial.println(" loop (scan continuo)");
233 Serial.println(" stop (detener scan)");
234 Serial.println(" t 32 (toggle relay en 0x20)");
235 Serial.println(" off 32 (apagar relay en 0x20)");
236 Serial.println("\n");
240 // Reimprimir menú si se reconecta el Serial Monitor
241 #if defined(ARDUINO_ARCH_RP2040)
242 if (Serial && !menu_shown && millis() - last_serial_check > 1000) {
245 last_serial_check = millis();
249 last_serial_check = millis();
253 // Procesar comandos desde Serial
254 if (Serial.available()) {
255 String cmd = Serial.readStringUntil('\n');
259 if (cmd.length() > 0) {
264 // Ejecutar scan continuo si está activo
265 if (scan_loop_active && !scan_in_progress) {
266 if (millis() - last_scan_time >= scan_interval) {
268 last_scan_time = millis();
272 // Ejecutar test asíncrono si está activo
273 if (test_mode_active) {
274 if (millis() - last_scan_time >= test_interval) {
276 last_scan_time = millis();
278 // Verificar si el test debe terminar
279 if (test_duration > 0 && (millis() - test_start_time >= test_duration)) {
285 // Ejecutar test de lectura digital si está activo
286 if (read_test_active) {
287 if (millis() - last_scan_time >= read_test_interval) {
289 last_scan_time = millis();
291 // Verificar si el test debe terminar
292 if (read_test_duration > 0 && (millis() - read_test_start_time >= read_test_duration)) {
298void processCommand(String cmd) {
299 if (cmd == "s" || cmd == "scan") {
301 } else if (cmd == "loop") {
303 } else if (cmd == "stop") {
305 } else if (cmd == "menu" || cmd == "m") {
307 } else if (cmd.startsWith("test ")) {
309 } else if (cmd == "stoptest") {
312 } else if (cmd == "errors") {
314 } else if (cmd.startsWith("adc ")) {
316 } else if (cmd.startsWith("read ")) {
318 } else if (cmd == "dstats") {
320 } else if (cmd.startsWith("readtest ")) {
322 } else if (cmd == "stopread") {
324 } else if (cmd.startsWith("t ")) {
326 } else if (cmd.startsWith("on ")) {
328 } else if (cmd.startsWith("off ")) {
330 } else if (cmd.startsWith("pwm ")) {
333 // Comandos deshabilitados
334 } else if (cmd.startsWith("note ")) {
336 } else if (cmd.startsWith("alert ")) {
339 } else if (cmd == "silence" || cmd == "pwmoff") {
341 } else if (cmd.startsWith("neo ")) {
343 } else if (cmd.startsWith("red ")) {
345 } else if (cmd.startsWith("green ")) {
347 } else if (cmd.startsWith("blue ")) {
349 } else if (cmd.startsWith("white ")) {
351 } else if (cmd == "neooff") {
353 } else if (cmd.startsWith("ch ")) {
354 changeI2CAddress(cmd);
355 } else if (cmd.startsWith("save_color ")) {
356 saveColorPreset(cmd);
357 }else if (cmd == "help" || cmd == "h") {
360 Serial.println("ERROR: Comando desconocido. Usa 'help' o 'menu' para ver comandos.");
365 Serial.println("\n\n");
366 Serial.println("╔═══════════════════════════════════════╗");
367 Serial.println("║ ADMIN PWM - I2C Control ║");
368 Serial.println("║ Scan + Relay + PWM/Buzzer ║");
369 Serial.println("╚═══════════════════════════════════════╝");
370 Serial.println("[OK] I2C inicializado sin bloquear USB");
371 Serial.printf(" SDA:GPIO%d SCL:GPIO%d @ %d kHz\n", I2C_SDA, I2C_SCL, I2C_FREQ/1000);
372 Serial.println("\nComandos disponibles:");
373 Serial.println(" s / menu - Scan dispositivos / mostrar menu");
374 Serial.println(" loop - Scan continuo (cada 1 seg)");
375 Serial.println(" stop - Detener scan continuo");
376 Serial.println(" t XX - Pulso relay (XX = addr hex)");
377 Serial.println(" on XX - Encender relay");
378 Serial.println(" off XX - Apagar relay");
379 Serial.println(" pwm XX NIVEL - PWM (25, 50, 75, 100, off)");
380 Serial.println(" silence - Apagar PWM en todos");
381 Serial.println(" neo XX COLOR - NeoPixel (red/green/blue/white/off o 1/2/3/4/0)");
382 Serial.println(" ch XX YY - Cambiar dir I2C (XX=old, YY=new)");
383 Serial.println(" save_color A P R G B - Guardar color por slot (1..3)");
384 Serial.println(" help - Mostrar ayuda completa");
385 Serial.println("\nEjemplos:");
386 Serial.println(" s (scan de dispositivos)");
387 Serial.println(" menu (mostrar este menu)");
388 Serial.println(" pwm 20 50 (PWM 50% en 0x20)");
389 Serial.println(" silence (apagar PWM en todos)");
390 Serial.println(" neo 20 1 (NeoPixel rojo en 0x20)");
391 Serial.println(" save_color 20 1 0x00 0xff 0x60");
396 // Verificar si estamos en modo seguro
398 Serial.println("[ERROR] I2C en MODO SEGURO - comando no disponible");
399 Serial.println("Desconecta dispositivos I2C y resetea el dispositivo");
403 // Evitar scans simultáneos
404 if (scan_in_progress) {
405 Serial.println("⚠ Scan ya en progreso, esperando...");
409 scan_in_progress = true;
410 Serial.println("\n━━━ SCAN I2C + TOGGLE ━━━");
413 memset(found_devices, 0, sizeof(found_devices));
415 WIRE.setTimeout(50); // Timeout corto para velocidad
417 for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
419 WIRE.beginTransmission(addr);
420 uint8_t error = WIRE.endTransmission();
423 found_devices[device_count++] = addr;
424 Serial.printf(" [%02d] 0x%02X (%3d) → ", device_count, addr, addr);
426 // Enviar toggle automático SIN esperar respuesta
427 if (sendCommandFast(addr, CMD_RELAY_TOGGLE)) {
428 Serial.println("TOGGLE ✓");
430 Serial.println("TOGGLE ✗");
433 delay(DEVICE_DELAY); // Delay optimizado: 30ms
437 // auxReadByte retorna true si el dispositivo hace ACK
438 if (imu.auxReadByte(addr, 0x00, dummy)) {
439 found_devices[device_count++] = addr;
440 Serial.printf(" [%02d] 0x%02X (%3d) → ", device_count, addr, addr);
442 if (imu.auxWriteCommand(addr, CMD_RELAY_TOGGLE)) {
443 Serial.println("TOGGLE ✓");
445 Serial.println("TOGGLE ✗");
451 Serial.println("━━━━━━━━━━━━━━━━");
452 Serial.printf("Total: %d dispositivos\n\n", device_count);
454 if (device_count == 0) {
455 Serial.println("⚠ No se encontraron dispositivos I2C");
456 Serial.println(" Verifica conexiones y pull-ups\n");
459 scan_in_progress = false;
464void toggleRelay(String cmd) {
465 // Verificar si estamos en modo seguro
467 Serial.println("[ERROR] I2C en MODO SEGURO - comando no disponible");
471 String addr_str = cmd.substring(2);
474 uint8_t address = parseHex(addr_str);
476 Serial.println("ERROR: Direccion invalida");
480 if (sendCommand(address, CMD_RELAY_TOGGLE)) {
481 Serial.printf("[OK] Pulso relay en 0x%02X (10ms)\n", address);
483 Serial.printf("[FAIL] No se pudo enviar pulso a 0x%02X\n", address);
487void relayOn(String cmd) {
488 String addr_str = cmd.substring(3);
491 uint8_t address = parseHex(addr_str);
493 Serial.println("ERROR: Direccion invalida");
497 if (sendCommand(address, CMD_RELAY_ON)) {
498 Serial.printf("[OK] Relay ON en 0x%02X\n", address);
500 Serial.printf("[FAIL] No se pudo encender relay en 0x%02X\n", address);
504void relayOff(String cmd) {
505 String addr_str = cmd.substring(4);
508 uint8_t address = parseHex(addr_str);
510 Serial.println("ERROR: Direccion invalida");
514 if (sendCommand(address, CMD_RELAY_OFF)) {
515 Serial.printf("[OK] Relay OFF en 0x%02X\n", address);
517 Serial.printf("[FAIL] No se pudo apagar relay en 0x%02X\n", address);
521// ═══════════════════════════════════════════════════════════
522// LECTURA DIGITAL PA0
523// ═══════════════════════════════════════════════════════════
525void readADC(String cmd) {
526 String addr_str = cmd.substring(4);
529 uint8_t address = parseHex(addr_str);
531 Serial.println("ERROR: Direccion invalida");
536 if (readADC12(address, raw)) {
537 float voltage = raw * (3.3f / 4095.0f);
538 Serial.printf("[OK] 0x%02X - ADC: %u (%.3f V)\n", address, raw, voltage);
540 Serial.printf("[FAIL] No se pudo leer ADC en 0x%02X\n", address);
544void readDigital(String cmd) {
545 String addr_str = cmd.substring(5);
548 uint8_t address = parseHex(addr_str);
550 Serial.println("ERROR: Direccion invalida");
555 bool success = readPA0Digital(address, &state);
558 Serial.printf("[OK] 0x%02X - PA0 Digital: %s\n", address, state ? "HIGH" : "LOW");
560 Serial.printf("[FAIL] No se pudo leer PA0 digital en 0x%02X\n", address);
564bool readPA0Digital(uint8_t address, uint8_t* state) {
565 // Enviar comando de lectura PA0 digital
566 WIRE.beginTransmission(address);
567 WIRE.write(CMD_PA4_DIGITAL);
568 uint8_t error = WIRE.endTransmission();
571 updateDigitalStats(address, false, 0);
575 // Esperar procesamiento
580 uint8_t bytesRead = WIRE.requestFrom(address, (uint8_t)1);
582 if (bytesRead == 1) {
583 uint8_t response = WIRE.read();
585 // El estado digital está en el nibble alto (bit 7-4)
586 // 0xF0 = HIGH, 0x00 = LOW
587 *state = (response & 0xF0) ? 1 : 0;
589 updateDigitalStats(address, true, *state);
593 updateDigitalStats(address, false, 0);
597bool readADC12(uint8_t address, uint16_t &raw) {
598 uint8_t hi = 0, lo = 0;
600 // El slave necesita transacciones SEPARADAS: write (comando) → STOP,
601 // slave procesa + convierte ADC (~20ms), luego read independiente.
602 // auxReadByte hace write+RSTART+read combinado → el slave no alcanza
603 // a llamar HAL_I2C_Slave_Transmit → NACK.
604 // auxReadResponse hace solo [START, addr+R, 1byte, STOP].
606 if (!imu.auxWriteCommand(address, CMD_ADC_PA1_HSB)) return false;
607 delay(25); // slave: main loop + conversión ADC (HAL_ADC_PollForConversion, max 20ms)
608 if (!imu.auxReadResponse(address, hi)) return false;
610 // LSB: el slave usa valor cacheado (adc_last_read < 50ms → mismo sample que HSB)
611 if (!imu.auxWriteCommand(address, CMD_ADC_PA1_LSB)) return false;
612 delay(5); // sin conversión nueva, solo tiempo de main loop
613 if (!imu.auxReadResponse(address, lo)) return false;
615 raw = ((uint16_t)(hi & 0x0F) << 8) | lo; // 0–4095
619void updateDigitalStats(uint8_t address, bool success, uint8_t state) {
620 // Buscar dispositivo en el array
622 for (int i = 0; i < digital_stats_count; i++) {
623 if (digital_stats[i].address == address) {
629 // Si no existe, agregarlo
630 if (index == -1 && digital_stats_count < 128) {
631 index = digital_stats_count++;
632 digital_stats[index].address = address;
633 digital_stats[index].read_count = 0;
634 digital_stats[index].fail_count = 0;
635 digital_stats[index].last_state = 0;
636 digital_stats[index].last_read_time = 0;
640 digital_stats[index].read_count++;
642 digital_stats[index].fail_count++;
644 digital_stats[index].last_state = state;
646 digital_stats[index].last_read_time = millis();
650void showDigitalStats() {
651 if (digital_stats_count == 0) {
652 Serial.println("No hay estadisticas de lectura digital");
656 Serial.println("\n╔════════════════════════════════════════════════════════╗");
657 Serial.println("║ ESTADISTICAS DE LECTURA DIGITAL PA0 ║");
658 Serial.println("╠════════════════════════════════════════════════════════╣");
659 Serial.println("║ Addr │ Lecturas │ Fallos │ Tasa OK │ Estado │ Tiempo ║");
660 Serial.println("╠════════════════════════════════════════════════════════╣");
662 for (int i = 0; i < digital_stats_count; i++) {
663 DigitalReadStats* stats = &digital_stats[i];
665 float success_rate = 0.0;
666 if (stats->read_count > 0) {
667 success_rate = ((float)(stats->read_count - stats->fail_count) / stats->read_count) * 100.0;
670 uint32_t time_since = (millis() - stats->last_read_time) / 1000; // segundos
672 Serial.printf("║ 0x%02X │ %8lu │ %6lu │ %6.1f%% │ %4s │ %4lus ║\n",
677 stats->last_state ? "HIGH" : "LOW",
681 Serial.println("╚════════════════════════════════════════════════════════╝");
684// ═══════════════════════════════════════════════════════════
685// TEST DE LECTURA DIGITAL CONTINUA
686// ═══════════════════════════════════════════════════════════
688void startReadTest(String cmd) {
689 // Formato: "readtest <intervalo_ms> <duracion_ms>"
690 // Ejemplo: "readtest 100 10000" = leer cada 100ms durante 10 segundos
692 int space1 = cmd.indexOf(' ', 9);
695 Serial.println("ERROR: Formato incorrecto");
696 Serial.println("Uso: readtest <intervalo_ms> <duracion_ms>");
697 Serial.println("Ejemplo: readtest 100 10000 (leer cada 100ms por 10 seg)");
701 String interval_str = cmd.substring(9, space1);
702 String duration_str = cmd.substring(space1 + 1);
704 read_test_interval = interval_str.toInt();
705 read_test_duration = duration_str.toInt();
708 if (read_test_interval < 30) read_test_interval = 30;
709 if (read_test_interval > 1000) read_test_interval = 1000;
710 if (read_test_duration < 1000) read_test_duration = 1000;
712 // Reset contadores de lectura digital
713 digital_stats_count = 0;
714 memset(digital_stats, 0, sizeof(digital_stats));
716 read_test_active = true;
717 read_test_start_time = millis();
718 last_scan_time = millis();
720 Serial.println("\n[READ TEST MODE ACTIVADO]");
721 Serial.printf("Intervalo: %lu ms\n", read_test_interval);
722 Serial.printf("Duración: %lu ms (%.1f seg)\n", read_test_duration, read_test_duration / 1000.0);
723 Serial.println("Dispositivos a leer: ");
724 for (int i = 0; i < device_count; i++) {
725 Serial.printf(" 0x%02X\n", found_devices[i]);
727 Serial.println("Usa 'stopread' para detener antes\n");
731 read_test_active = false;
732 uint32_t elapsed = millis() - read_test_start_time;
733 Serial.println("\n[READ TEST MODE DETENIDO]");
734 Serial.printf("Tiempo transcurrido: %.2f segundos\n", elapsed / 1000.0);
738void testReadDigital() {
741 for (int i = 0; i < device_count; i++) {
742 uint8_t addr = found_devices[i];
744 readPA0Digital(addr, &state);
745 delay(read_test_interval / device_count); // Distribuir tiempo entre dispositivos
749bool sendCommand(uint8_t address, uint8_t command) {
750 /*WIRE.beginTransmission(address);
752 uint8_t error = WIRE.endTransmission();
754 if (error != 0) return false;
756 // Esperar respuesta con timeout más generoso
757 delay(20); // Dar tiempo al esclavo para procesar
758 WIRE.setTimeout(100);
759 uint8_t bytesRead = WIRE.requestFrom(address, (uint8_t)1);
760 if (bytesRead == 1) {
761 WIRE.read(); // Leer y descartar respuesta
766 return imu.auxWriteCommand(address, command);
769// Función para leer respuesta del dispositivo
770uint8_t readResponse(uint8_t address) {
771 delay(10); // Pequeño delay para que el dispositivo prepare respuesta
772 WIRE.setTimeout(100);
773 uint8_t bytesRead = WIRE.requestFrom(address, (uint8_t)1);
774 if (bytesRead == 1) {
777 return 0xFF; // Error: no se recibió respuesta
780// Versión rápida: enviar comando SIN esperar respuesta
781bool sendCommandFast(uint8_t address, uint8_t command) {
782 WIRE.beginTransmission(address);
784 uint8_t error = WIRE.endTransmission();
786 // Actualizar contador de errores
787 updateDeviceError(address, error == 0);
789 return (error == 0); // Solo verificar que se envió
792void updateDeviceError(uint8_t address, bool success) {
793 // Buscar dispositivo en el array
795 for (int i = 0; i < device_error_count; i++) {
796 if (device_errors[i].address == address) {
802 // Si no existe, agregarlo
803 if (index == -1 && device_error_count < 128) {
804 index = device_error_count++;
805 device_errors[index].address = address;
806 device_errors[index].error_count = 0;
807 device_errors[index].success_count = 0;
808 device_errors[index].last_error_time = 0;
811 // Actualizar contadores
814 device_errors[index].success_count++;
816 device_errors[index].error_count++;
817 device_errors[index].last_error_time = millis();
822void startTest(String cmd) {
823 // Formato: "test <intervalo_ms> <duracion_ms>"
824 // Ejemplo: "test 50 5000" = test cada 50ms durante 5 segundos
826 int space1 = cmd.indexOf(' ', 5);
829 Serial.println("ERROR: Formato incorrecto");
830 Serial.println("Uso: test <intervalo_ms> <duracion_ms>");
831 Serial.println("Ejemplo: test 50 5000 (test cada 50ms por 5 seg)");
835 String interval_str = cmd.substring(5, space1);
836 String duration_str = cmd.substring(space1 + 1);
838 test_interval = interval_str.toInt();
839 test_duration = duration_str.toInt();
842 if (test_interval < 30) test_interval = 30;
843 if (test_interval > 1000) test_interval = 1000;
844 if (test_duration < 1000) test_duration = 1000;
847 device_error_count = 0;
848 memset(device_errors, 0, sizeof(device_errors));
850 test_mode_active = true;
851 test_start_time = millis();
852 last_scan_time = millis();
854 Serial.println("\n[TEST MODE ACTIVADO]");
855 Serial.printf("Intervalo: %lu ms\n", test_interval);
856 Serial.printf("Duración: %lu ms (%.1f seg)\n", test_duration, test_duration / 1000.0);
857 Serial.println("Usa 'stoptest' para detener antes\n");
861 test_mode_active = false;
862 uint32_t elapsed = millis() - test_start_time;
863 Serial.println("\n[TEST MODE DETENIDO]");
864 Serial.printf("Tiempo transcurrido: %.2f segundos\n\n", elapsed / 1000.0);
868 // Verificar si estamos en modo seguro
870 Serial.println("[ERROR] I2C en MODO SEGURO - comando no disponible");
876 for (int i = 0; i < device_count; i++) {
877 uint8_t addr = found_devices[i];
878 sendCommandFast(addr, CMD_RELAY_TOGGLE);
879 delay(test_interval / device_count); // Distribuir tiempo entre dispositivos
883void showTestResults() {
884 Serial.println("\n╔═══════════════════════════════════════════════════╗");
885 Serial.println("║ RESULTADOS DE TEST I2C ║");
886 Serial.println("╚═══════════════════════════════════════════════════╝\n");
888 if (device_error_count == 0) {
889 Serial.println("No hay datos de test disponibles\n");
893 uint32_t total_success = 0;
894 uint32_t total_errors = 0;
896 Serial.println("Addr | Success | Errors | Tasa Éxito | Último Error");
897 Serial.println("------|---------|--------|------------|-------------");
899 for (int i = 0; i < device_error_count; i++) {
900 uint8_t addr = device_errors[i].address;
901 uint32_t success = device_errors[i].success_count;
902 uint32_t errors = device_errors[i].error_count;
903 uint32_t total = success + errors;
904 float rate = total > 0 ? (success * 100.0 / total) : 0;
906 total_success += success;
907 total_errors += errors;
909 Serial.printf("0x%02X | %7lu | %6lu | %6.2f%% | ",
910 addr, success, errors, rate);
913 uint32_t since_error = (millis() - device_errors[i].last_error_time) / 1000;
914 Serial.printf("%lu seg\n", since_error);
916 Serial.println("Nunca");
920 Serial.println("------|---------|--------|------------|-------------");
921 uint32_t grand_total = total_success + total_errors;
922 float overall_rate = grand_total > 0 ? (total_success * 100.0 / grand_total) : 0;
923 Serial.printf("TOTAL | %7lu | %6lu | %6.2f%% |\n\n",
924 total_success, total_errors, overall_rate);
927uint8_t parseHex(String hex_str) {
930 // Convertir de hexadecimal
932 long addr = strtol(hex_str.c_str(), &endptr, 16);
934 if (*endptr != '\0' || addr < 0x08 || addr > 0x77) {
938 return (uint8_t)addr;
941bool parseByteAuto(String value_str, uint8_t* out) {
944 long value = strtol(value_str.c_str(), &endptr, 0);
946 if (*endptr != '\0' || value < 0 || value > 0xFF) {
950 *out = (uint8_t)value;
954void startScanLoop() {
955 scan_loop_active = true;
956 last_scan_time = millis();
957 Serial.println("\n[OK] Scan continuo ACTIVADO");
958 Serial.printf(" Intervalo: %lu ms\n", scan_interval);
959 Serial.println(" Usa 'stop' para detener\n");
963 scan_loop_active = false;
964 Serial.println("\n[OK] Scan continuo DETENIDO\n");
967// ═══════════════════════════════════════════════════════════
968// FUNCIONES PWM / BUZZER
969// ═══════════════════════════════════════════════════════════
972 Serial.println("\n━━━ APAGAR PWM (TODOS) ━━━");
974 for (int i = 0; i < device_count; i++) {
975 uint8_t addr = found_devices[i];
976 if (sendCommand(addr, CMD_PWM_OFF)) {
977 Serial.printf(" [OK] 0x%02X - PWM apagado\n", addr);
980 Serial.printf(" [FAIL] 0x%02X\n", addr);
984 Serial.printf("\nTotal: %d/%d dispositivos\n", count, device_count);
987void pwmCommand(String cmd) {
988 // Formato: "pwm XX NIVEL"
989 // XX = dirección hex, NIVEL = 25, 50, 75, 100, off
991 int space = cmd.indexOf(' ', 4);
994 Serial.println("ERROR: Formato incorrecto");
995 Serial.println("Uso: pwm XX NIVEL");
996 Serial.println("Ejemplo: pwm 20 50 (PWM 50% en 0x20)");
997 Serial.println("Niveles: 25, 50, 75, 100, off");
1001 String addr_str = cmd.substring(4, space);
1002 String level_str = cmd.substring(space + 1);
1006 uint8_t address = parseHex(addr_str);
1008 Serial.println("ERROR: Direccion invalida");
1012 uint8_t pwm_cmd = CMD_PWM_OFF;
1013 String level_name = "OFF";
1015 if (level_str == "off" || level_str == "0") {
1016 pwm_cmd = CMD_PWM_OFF;
1018 } else if (level_str == "25") {
1019 pwm_cmd = CMD_PWM_25;
1020 level_name = "25% (200Hz)";
1021 } else if (level_str == "50") {
1022 pwm_cmd = CMD_PWM_50;
1023 level_name = "50% (500Hz)";
1024 } else if (level_str == "75") {
1025 pwm_cmd = CMD_PWM_75;
1026 level_name = "75% (1000Hz)";
1027 } else if (level_str == "100") {
1028 pwm_cmd = CMD_PWM_100;
1029 level_name = "100% (2000Hz)";
1031 Serial.println("ERROR: Nivel invalido. Usa: 25, 50, 75, 100, off");
1035 if (sendCommand(address, pwm_cmd)) {
1036 Serial.printf("[OK] PWM %s en 0x%02X\n", level_name.c_str(), address);
1038 Serial.printf("[FAIL] No se pudo configurar PWM en 0x%02X\n", address);
1042// ═══════════════════════════════════════════════════════════
1043// FUNCIONES NEOPIXEL
1044// ═══════════════════════════════════════════════════════════
1046void neoCommand(String cmd) {
1047 // Formato: "neo XX COLOR"
1048 // XX = dirección hex, COLOR = red/green/blue/white/off o 1/2/3/4/0
1050 int space = cmd.indexOf(' ', 4);
1053 Serial.println("ERROR: Formato incorrecto");
1054 Serial.println("Uso: neo XX COLOR");
1055 Serial.println("Ejemplo: neo 20 1 (NeoPixel rojo en 0x20)");
1056 Serial.println("Colores: red, green, blue, white, off");
1057 Serial.println("Numeros: 1=red, 2=green, 3=blue, 4=white, 0=off");
1061 String addr_str = cmd.substring(4, space);
1062 String color_str = cmd.substring(space + 1);
1065 color_str.toLowerCase();
1067 uint8_t address = parseHex(addr_str);
1069 Serial.println("ERROR: Direccion invalida");
1073 uint8_t neo_cmd = 0;
1074 String color_name = "";
1076 if (color_str == "red") {
1078 color_name = "ROJO";
1079 } else if (color_str == "1") {
1081 color_name = "ROJO";
1082 } else if (color_str == "green") {
1083 neo_cmd = CMD_GREEN;
1084 color_name = "VERDE";
1085 } else if (color_str == "2") {
1086 neo_cmd = CMD_GREEN;
1087 color_name = "VERDE";
1088 } else if (color_str == "blue") {
1090 color_name = "AZUL";
1091 } else if (color_str == "3") {
1093 color_name = "AZUL";
1094 } else if (color_str == "white") {
1095 neo_cmd = CMD_WHITE;
1096 color_name = "BLANCO";
1097 } else if (color_str == "4") {
1098 neo_cmd = CMD_WHITE;
1099 color_name = "BLANCO";
1100 } else if (color_str == "off") {
1102 color_name = "APAGADO";
1103 } else if (color_str == "0") {
1105 color_name = "APAGADO";
1107 Serial.println("ERROR: Color invalido. Usa: red, green, blue, white, off");
1108 Serial.println(" O numeros: 1=red, 2=green, 3=blue, 4=white, 0=off");
1112 if (sendCommand(address, neo_cmd)) {
1113 Serial.printf("[OK] NeoPixel %s en 0x%02X\n", color_name.c_str(), address);
1115 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1119void neoRed(String cmd) {
1120 String addr_str = cmd.substring(4);
1123 uint8_t address = parseHex(addr_str);
1125 Serial.println("ERROR: Direccion invalida");
1129 if (sendCommand(address, CMD_RED)) {
1130 Serial.printf("[OK] NeoPixel ROJO en 0x%02X\n", address);
1132 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1136void neoGreen(String cmd) {
1137 String addr_str = cmd.substring(6);
1140 uint8_t address = parseHex(addr_str);
1142 Serial.println("ERROR: Direccion invalida");
1146 if (sendCommand(address, CMD_GREEN)) {
1147 Serial.printf("[OK] NeoPixel VERDE en 0x%02X\n", address);
1149 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1153void neoBlue(String cmd) {
1154 String addr_str = cmd.substring(5);
1157 uint8_t address = parseHex(addr_str);
1159 Serial.println("ERROR: Direccion invalida");
1163 if (sendCommand(address, CMD_BLUE)) {
1164 Serial.printf("[OK] NeoPixel AZUL en 0x%02X\n", address);
1166 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1170void neoWhite(String cmd) {
1171 String addr_str = cmd.substring(6);
1174 uint8_t address = parseHex(addr_str);
1176 Serial.println("ERROR: Direccion invalida");
1180 if (sendCommand(address, CMD_WHITE)) {
1181 Serial.printf("[OK] NeoPixel BLANCO en 0x%02X\n", address);
1183 Serial.printf("[FAIL] No se pudo configurar NeoPixel en 0x%02X\n", address);
1188 Serial.println("\n━━━ APAGAR NEOPIXEL (TODOS) ━━━");
1190 for (int i = 0; i < device_count; i++) {
1191 uint8_t addr = found_devices[i];
1192 if (sendCommand(addr, CMD_OFF)) {
1193 Serial.printf(" [OK] 0x%02X - NeoPixel apagado\n", addr);
1196 Serial.printf(" [FAIL] 0x%02X\n", addr);
1200 Serial.printf("\nTotal: %d/%d dispositivos\n", count, device_count);
1203// ═══════════════════════════════════════════════════════════
1204// FUNCIÓN: CAMBIAR DIRECCIÓN I2C
1205// ═══════════════════════════════════════════════════════════
1206void changeI2CAddress(String cmd) {
1207 // Formato: "ch XX YY" donde XX=dirección actual, YY=nueva dirección
1209 int firstSpace = cmd.indexOf(' ');
1210 int secondSpace = cmd.indexOf(' ', firstSpace + 1);
1212 if (firstSpace == -1 || secondSpace == -1) {
1213 Serial.println("[ERROR] Formato: ch XX YY");
1214 Serial.println(" XX = dirección actual (hex)");
1215 Serial.println(" YY = nueva dirección (hex)");
1219 String oldAddrStr = cmd.substring(firstSpace + 1, secondSpace);
1220 String newAddrStr = cmd.substring(secondSpace + 1);
1225 // Convertir direcciones de hexadecimal
1226 uint8_t oldAddr = (uint8_t)strtol(oldAddrStr.c_str(), NULL, 16);
1227 uint8_t newAddr = (uint8_t)strtol(newAddrStr.c_str(), NULL, 16);
1230 if (oldAddr < 0x08 || oldAddr > 0x77) {
1231 Serial.printf("[ERROR] Dirección actual 0x%02X fuera de rango (0x08-0x77)\n", oldAddr);
1235 if (newAddr < 0x08 || newAddr > 0x77) {
1236 Serial.printf("[ERROR] Nueva dirección 0x%02X fuera de rango (0x08-0x77)\n", newAddr);
1240 Serial.println("\n━━━ CAMBIAR DIRECCIÓN I2C ━━━");
1241 Serial.printf("Dispositivo: 0x%02X → 0x%02X\n", oldAddr, newAddr);
1242 Serial.println("⚠️ ADVERTENCIA: Esta operación es PERMANENTE (guarda en Flash)");
1245 // PROTOCOLO CORRECTO según firmware i2c_slave/main.c:
1246 // 1. Enviar 0x3D (CMD_SET_I2C_ADDR) - activa modo "esperar nueva dirección"
1247 // 2. El firmware responde 0x0D y espera el siguiente byte como nueva dirección
1248 // 3. Enviar la nueva dirección como siguiente comando
1249 // 4. Firmware guarda en Flash y responde 0x0D
1250 // 5. Requiere reset para aplicar
1252 Serial.print("Paso 1: Activando modo cambio dirección... ");
1254 WIRE.beginTransmission(oldAddr);
1255 WIRE.write(CMD_SET_I2C_ADDR); // 0x3D
1256 uint8_t error = WIRE.endTransmission();
1259 Serial.printf("[FAIL] Error I2C: %d\n", error);
1260 Serial.println(" El dispositivo no respondió. Verifica:");
1261 Serial.println(" - Dirección correcta (usa 'scan' para confirmar)");
1262 Serial.println(" - Dispositivo conectado y funcionando");
1266 delay(50); // Delay importante: firmware necesita procesar comando
1269 uint8_t bytesRead = WIRE.requestFrom(oldAddr, (uint8_t)1);
1270 uint8_t response = 0xFF;
1271 if (bytesRead == 1) {
1272 response = WIRE.read();
1275 if (response == 0x0D) { // RESP_I2C_ADDR_SET
1276 Serial.println("[OK] Modo activado");
1278 Serial.printf("[WARN] Respuesta: 0x%02X (esperaba 0x0D)\n", response);
1279 // Continuamos, puede funcionar igual
1282 delay(50); // Delay adicional antes del siguiente comando
1284 // Paso 2: Enviar nueva dirección
1285 Serial.printf("Paso 2: Enviando nueva dirección 0x%02X... ", newAddr);
1287 WIRE.beginTransmission(oldAddr);
1288 WIRE.write(newAddr); // Nueva dirección como dato
1289 error = WIRE.endTransmission();
1292 Serial.printf("[FAIL] Error I2C: %d\n", error);
1293 Serial.println(" No se pudo completar el cambio");
1297 delay(100); // Delay largo: escritura Flash puede tomar tiempo
1299 // Leer confirmación
1300 bytesRead = WIRE.requestFrom(oldAddr, (uint8_t)1);
1302 if (bytesRead == 1) {
1303 response = WIRE.read();
1306 if (response == 0x0D) { // RESP_I2C_ADDR_SET
1307 Serial.println("[OK] Guardado en Flash");
1309 Serial.printf("[WARN] Respuesta: 0x%02X\n", response);
1313 Serial.println("╔════════════════════════════════════════════╗");
1314 Serial.println("║ CAMBIO DE DIRECCIÓN COMPLETADO ║");
1315 Serial.println("╚════════════════════════════════════════════╝");
1317 Serial.println(" IMPORTANTE - PRÓXIMOS PASOS:");
1318 Serial.println(" 1. La nueva dirección está GUARDADA EN FLASH");
1319 Serial.println(" 2. DEBES RESETEAR el dispositivo para aplicar");
1320 Serial.println(" 3. Opciones de reset:");
1321 Serial.println(" • Desconecta y reconecta el dispositivo");
1322 Serial.println(" • Usa el botón de reset físico");
1323 Serial.println(" • Cicla la alimentación");
1324 Serial.printf(" 4. Después del reset: dispositivo en 0x%02X\n", newAddr);
1326 Serial.println("Verificación:");
1327 Serial.println(" 1. Resetea el dispositivo");
1328 Serial.println(" 2. Ejecuta: scan");
1329 Serial.printf(" 3. Busca la dirección 0x%02X en la lista\n", newAddr);
1333void saveColorPreset(String cmd) {
1334 // Formato: save_color <addr> <pos> <r> <g> <b>
1338 int token_count = 0;
1340 while (i < cmd.length() && token_count < 6) {
1341 while (i < cmd.length() && cmd.charAt(i) == ' ') {
1344 if (i >= cmd.length()) {
1349 while (i < cmd.length() && cmd.charAt(i) != ' ') {
1352 tokens[token_count++] = cmd.substring(start, i);
1355 if (token_count < 6 || tokens[0] != "save_color") {
1356 Serial.println("[ERROR] Formato: save_color <addr> <pos> <r> <g> <b>");
1357 Serial.println(" pos: 1=slot rojo, 2=slot verde, 3=slot azul");
1358 Serial.println(" r/g/b: 0..255 (decimal) o 0x00..0xFF");
1362 String addr_str = tokens[1];
1363 String pos_str = tokens[2];
1364 String r_str = tokens[3];
1365 String g_str = tokens[4];
1366 String b_str = tokens[5];
1374 uint8_t addr = parseHex(addr_str);
1376 Serial.println("[ERROR] Direccion invalida (0x08-0x77)");
1380 uint8_t pos_user = 0;
1386 if (!parseByteAuto(pos_str, &pos_user) || pos_user < 1 || pos_user > 3) {
1387 Serial.println("[ERROR] Posicion invalida. Usa 1, 2 o 3");
1390 pos = (uint8_t)(pos_user - 1);
1391 if (!parseByteAuto(r_str, &r) || !parseByteAuto(g_str, &g) || !parseByteAuto(b_str, &b)) {
1392 Serial.println("[ERROR] RGB invalido. Usa 0..255 o 0x00..0xFF");
1396 Serial.println("\n━━━ SAVE COLOR PRESET ━━━");
1397 Serial.printf("Dispositivo 0x%02X, slot %u, RGB=(%u,%u,%u)\n", addr, pos_user, r, g, b);
1399 if (!sendCommand(addr, CMD_SAVE_COLOR)) {
1400 Serial.println("[FAIL] No se pudo iniciar save_color");
1405 if (!sendCommand(addr, pos)) {
1406 Serial.println("[FAIL] No se pudo enviar posicion");
1411 if (!sendCommand(addr, r)) {
1412 Serial.println("[FAIL] No se pudo enviar canal R");
1417 if (!sendCommand(addr, g)) {
1418 Serial.println("[FAIL] No se pudo enviar canal G");
1423 if (!sendCommand(addr, b)) {
1424 Serial.println("[FAIL] No se pudo enviar canal B");
1428 Serial.println("[OK] Color guardado en Flash");
1429 Serial.println("Usa comandos red/green/blue para probar los 3 slots.");
1433 Serial.println("\n╔═══════════════════════════════════════╗");
1434 Serial.println("║ COMANDOS DISPONIBLES ║");
1435 Serial.println("╚═══════════════════════════════════════╝");
1437 Serial.println("SCAN:");
1438 Serial.println(" s - Escanear dispositivos I2C");
1439 Serial.println(" loop - Scan continuo (1 seg)");
1440 Serial.println(" stop - Detener scan continuo");
1442 Serial.println("TEST I2C:");
1443 Serial.println(" test INT DUR - Test asíncrono");
1444 Serial.println(" INT = intervalo (30-1000ms)");
1445 Serial.println(" DUR = duración (ms)");
1446 Serial.println(" stoptest - Detener test y mostrar resultados");
1447 Serial.println(" errors - Mostrar estadísticas de errores");
1449 Serial.println("LECTURA DIGITAL PA0:");
1450 Serial.println(" read XX - Leer estado digital PA0");
1451 Serial.println(" dstats - Estadísticas de lecturas PA0");
1452 Serial.println(" readtest I D - Test continuo de lectura PA0");
1453 Serial.println(" I = intervalo (30-1000ms)");
1454 Serial.println(" D = duración (ms)");
1455 Serial.println(" stopread - Detener test de lectura");
1457 Serial.println("RELAY:");
1458 Serial.println(" t XX - Pulso relay PB5 (10ms)");
1459 Serial.println(" on XX - Encender relay PB5");
1460 Serial.println(" off XX - Apagar relay PB5");
1462 Serial.println("PWM / BUZZER:");
1463 Serial.println(" pwm XX NIVEL - PWM en PA2 (25, 50, 75, 100, off)");
1464 Serial.println(" silence - Apagar PWM en todos los dispositivos");
1466 Serial.println("NEOPIXEL:");
1467 Serial.println(" neo XX COLOR - Control NeoPixel (red/green/blue/white/off o 1/2/3/4/0)");
1468 Serial.println(" red XX - NeoPixel rojo");
1469 Serial.println(" green XX - NeoPixel verde");
1470 Serial.println(" blue XX - NeoPixel azul");
1471 Serial.println(" white XX - NeoPixel blanco");
1472 Serial.println(" neooff - Apagar NeoPixel en todos");
1474 Serial.println("I2C ADDRESS:");
1475 Serial.println(" ch XX YY - Cambiar dirección I2C");
1476 Serial.println(" XX = dirección actual (hex)");
1477 Serial.println(" YY = nueva dirección (hex)");
1478 Serial.println(" (se guarda en Flash, requiere reset)");
1479 Serial.println(" save_color A P R G B - Guardar color por slot en Flash");
1480 Serial.println(" P: 1=red slot, 2=green slot, 3=blue slot");
1481 Serial.println(" R/G/B: decimal o 0x00..0xFF");
1483 Serial.println("FORMATO:");
1484 Serial.println(" XX = Direccion I2C en hexadecimal");
1486 Serial.println("EJEMPLOS:");
1487 Serial.println(" s → Scan completo");
1488 Serial.println(" loop → Scan automatico cada 1s");
1489 Serial.println(" stop → Detener scan automatico");
1490 Serial.println(" test 50 10000 → Test cada 50ms por 10 seg");
1491 Serial.println(" stoptest → Detener test y ver errores");
1492 Serial.println(" errors → Ver estadísticas");
1493 Serial.println(" read 20 → Leer PA0 digital en 0x20");
1494 Serial.println(" readtest 50 10000 → Test lectura cada 50ms por 10s");
1495 Serial.println(" stopread → Detener test de lectura");
1496 Serial.println(" dstats → Ver estadísticas PA0");
1497 Serial.println(" pwm 20 50 → PWM 50% (500Hz) en 0x20");
1498 Serial.println(" silence → Apagar PWM en todos");
1499 Serial.println(" neo 20 1 → NeoPixel rojo en 0x20");
1500 Serial.println(" red 20 → NeoPixel rojo en 0x20");
1501 Serial.println(" neooff → Apagar NeoPixel en todos");
1502 Serial.println(" t 20 → Pulso en 0x20");
1503 Serial.println(" on 32 → Encender en 0x32");
1504 Serial.println(" off 20 → Apagar relay en 0x20");
1505 Serial.println(" ch 19 25 → Cambiar 0x19 a 0x25");
1506 Serial.println(" save_color 20 1 0x00 0xff 0x60 → Slot 1 personalizado");