les vencimos · offlineArduino Software

Offline

Software

Referencia para escribir sketches en la placa. Se abre en este aparato, sin red y sin cuenta. No instala el IDE, no es el curso y no es el catálogo de placas. Los montajes largos están en Proyectos. El ritmo, de cero a cien, está en Enseñanza.

La placa de los ejemplos es un Arduino Uno o un Nano con ATmega328P. Pines de otras placas: catálogo de hardware, Uno R3.

IDE Arduino

El IDE 2 compila en tu ordenador. Cuando el núcleo de la placa ya está instalado, verificar no necesita red. Esta página no descarga nada.

La carpeta del sketch se llama igual que el archivo .ino. Verificar comprueba que compila. Subir lo escribe en la placa. Placa y puerto están en Herramientas. El Monitor serie tiene que ir al mismo número que Serial.begin. Si no coinciden, ves basura o silencio, no un fallo de la placa.

El .ino es C++. Antes de compilar, el IDE junta las pestañas y escribe los prototipos de las funciones. Por eso en un .ino puedes llamar a una función que declaras más abajo. En un .cpp suelto, no.

El curso abre el mismo gesto en la lección del pin, sin reinstalar el entorno: pinMode, digitalWrite y delay.

PlatformIO

Hace lo mismo que el IDE, con un archivo de proyecto y la terminal. También es local: no hace falta abrir esta página contra ningún servidor.

[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 9600

El código va en src/main.cpp y la primera línea útil es #include <Arduino.h>. Compilar: pio run. Subir: pio run -t upload. Monitor: pio device monitor. Si usas una función antes de declararla, escribe el prototipo tú: aquí nadie lo inventa por ti.

Anatomía del sketch

setup() corre una vez al arrancar. loop() corre después, una y otra vez, mientras haya alimentación. No hay un sistema que llame a tus funciones por su cuenta: o están en loop, o las llamas tú, o las enganchas a una interrupción.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500);
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
}

En el Uno, LED_BUILTIN es el pin 13. El punto y coma cierra la orden. Las llaves cierran el bloque. // comenta hasta el fin de línea. delay aquí solo enseña la forma: paraliza el loop entero. Más abajo se sustituye por millis.

digitalWrite y pinMode

pinMode(pin, OUTPUT) antes de escribir. HIGH pone la patilla a la tensión de la placa (5 V en el Uno). LOW la pone a masa. Sin pinMode, la escritura se pierde o se comporta raro.

pinMode(13, OUTPUT);
digitalWrite(13, HIGH);

Un pin de salida no es una fuente de alimentación. En el Uno, quédate muy por debajo de 20 mA por pin. Un servo, un motor o una tira de LED no se alimentan de la patilla: se conmutan, o comen de otra fuente con la masa en común. El límite físico está en el Uno del catálogo.

digitalRead

Devuelve HIGH o LOW. Un pin de entrada suelto, sin resistencia, lee ruido. Para un pulsador a masa usa INPUT_PULLUP: en reposo lees HIGH, y pulsado lees LOW. La lógica va al revés de lo que parece.

pinMode(2, INPUT_PULLUP);

void loop() {
  if (digitalRead(2) == LOW) {
    // pulsado: el botón cierra a GND
  }
}

El curso lo practica con calma en el rebote. Aquí, el mecanismo fino del rebote está en debounce.

analogRead

En el Uno el conversor es de 10 bits: devuelve de 0 a 1023. La referencia por defecto es AVCC, 5 V. 0 es 0 V y 1023 es casi 5 V. Una lectura tarda unas 100 µs. No es instantánea, pero tampoco es un delay.

int sensor = analogRead(A0);

int cabe, porque 1023 entra en 16 bits. No compares un flotante con == después de escalar: quédate en enteros o deja un margen. map reparte el intervalo, y no recorta: si el valor se sale, el resultado también. Si necesitas tope, constrain.

int luz = analogRead(A0);
int recortada = constrain(luz, 0, 1023);
int paso = map(recortada, 0, 1023, 0, 255);

El recorrido del potenciómetro, en el curso: lección del potenciómetro. Qué pin analógico tiene cada placa, en Hardware.

analogWrite es PWM

En el Uno, analogWrite no es un conversor digital-analógico. Es un pulso: un 8 bits, de 0 a 255. 0 es siempre apagado, 255 siempre encendido, 127 aproximadamente la mitad del tiempo. El ojo y un motor ven una media. Un polímetro en continua también, más o menos. Una entrada digital ve los flancos.

analogWrite(9, 180);

Pines con PWM en el Uno: 3, 5, 6, 9, 10 y 11. El 13 no lo es. La frecuencia ronda 490 Hz en 3, 9, 10 y 11, y 980 Hz en 5 y 6. En un pin sin PWM, analogWrite(0) es LOW y cualquier otro valor es HIGH. En un pin que sí tiene PWM no hace falta pinMode antes.

La librería Servo, en el Uno, se queda el temporizador de los pines 9 y 10: el PWM de esos dos deja de obedecer. tone estorba el PWM de 3 y 11.

Tipos y variables

Arduino es C++. En un AVR (Uno, Nano clásico, Mega 2560) int son 16 bits: de −32768 a 32767. En ESP32 y en placas SAMD, int son 32 bits. Un número que cabe en un sitio desborda en el otro. 40000 no cabe en un int del Uno: acaba negativo.

TipoUno / Nano 328PNota
bool1 bytecierto o falso
char / byte8 bitsbyte es sin signo, 0–255
int16 bitsen ESP32 y SAMD, 32 bits
long32 bitscabe un millis a medias: mejor unsigned long
unsigned long32 bitsel tipo de millis() y de micros()
float y double32 bits los dosen AVR no hay doble precisión. La división flotante es lenta

ATmega328P: 32 KB de flash (el gestor de arranque ocupa una parte), 2 KB de SRAM, 1 KB de EEPROM. Mega 2560: int sigue siendo de 16 bits, 256 KB de flash, 8 KB de SRAM, 4 KB de EEPROM. De otras placas no doy cifras aquí: están en el catálogo, y cambian.

const int pinLed = 13; es preferible a #define cuando quieres un tipo. #define solo sustituye texto. Una variable global vive todo el rato en esa SRAM de 2 KB. Una local vive mientras dura la función.

La clase String pide memoria en el montón y en el Uno lo fragmenta. Para un texto fijo que solo vas a imprimir, F("hola") lo deja en flash. Para un contador de tiempo, unsigned long, nunca int.

const unsigned long ESPERA = 40000UL;
unsigned long anterior = 0;

void setup() {
  Serial.begin(9600);
  Serial.println(F("listo"));
}

El sufijo UL obliga a constante sin signo y larga. Sin él, 40000 en un Uno ya no cabe en int y el compilador puede avisarte, o callar y darte un número raro.

Familias, no un solo Uno

Un sketch que va en el Uno no es un sketch universal. Lo que cambia de verdad, al copiarlo, es el ancho de int, qué significa analogWrite, dónde está el serie y si la EEPROM se escribe sola. Los pines concretos de cada placa están en el catálogo de hardware. Aquí va lo que el compilador y el lenguaje hacen distinto.

FamiliaintanalogReadanalogWriteSerie para depurarMemoria que sobrevive
Uno, Nano clásico (328P)16 bits0–1023, referencia 5 VPWM 0–255 en 3, 5, 6, 9, 10, 11Serial es el USB y también los pines 0 y 1EEPROM real, 1024 bytes. put escribe ya
Mega 256016 bits, igual0–1023PWM en 2–13 y 44–46Serial en 0/1, más Serial1 (19 RX, 18 TX), Serial2 (17 RX, 16 TX), Serial3 (15 RX, 14 TX)EEPROM real, 4096 bytes
Leonardo, Micro (32U4)16 bits0–1023PWM, y en el Leonardo también el 13Serial es solo el USB. El UART de pines es Serial1 (0 RX, 1 TX). while (!Serial) espera a que abras el monitorEEPROM real, 1024 bytes. SRAM del chip, 2,5 KB
ESP826632 bits0–1023, un solo canal. El chip aguanta unos 0–1 V; la placa de desarrollo a veces ya trae divisor0–1023, no 0–255Serial es el UART principalNo hay EEPROM de verdad. EEPROM.begin(n) y commit(), o LittleFS
ESP3232 bits0–4095 de fábrica (12 bits). analogReadResolution lo cambiaEn el núcleo 2.x, analogWrite 0–255. En el 3.x esa función no está: se usa ledcAttach. Si no compila, es el núcleo, no tu lógicaVarios UART. Serial suele ser el USB de la placa de desarrolloPreferences (mejor) o EEPROM emulada con begin y commit
SAMD (Zero, MKR)32 bitsSuele ser de 12 bits, 0–4095En varias de estas placas, A0 es un DAC: analogWrite ahí es tensión de verdad, no un pulsoSerial es el USB nativo. while (!Serial) esperaNo copies la cifra de 1024. Mírala en el catálogo de esa placa

Sugerencia: antes de portar un sketch, cambia solo la placa en el IDE y compila, sin subir. Los errores de analogWrite y de Serial1 salen ahí. Los de tensión del ADC, no: esos se ven en la mesa.

Mega: no secuestres el USB

En el Mega puedes dejar el monitor en Serial y hablar con un módulo en Serial1. Misma masa, TX con RX cruzados, y el nivel de tensión lo mira Hardware. 115200 es cómodo para depurar. 9600 es más fácil de ver a ojo.

void setup() {
  Serial.begin(115200);   // USB, el monitor
  Serial1.begin(9600);    // pines 19 RX, 18 TX
}

void loop() {
  if (Serial1.available()) {
    Serial.write(Serial1.read()); // eco al monitor, para ver qué manda el módulo
  }
}

Leonardo: el USB no es el pin 0

Si llamas a Serial.begin y escribes en el pin 0, no estás depurando: el pin 0 es Serial1. Y while (!Serial) en un Leonardo sin el monitor abierto se queda esperando para siempre. Úsalo solo si de verdad quieres esperar al ordenador.

En Leonardo y Micro existe Keyboard y Mouse. En cuanto haces Keyboard.begin(), la placa puede teclear en el ordenador que la alimenta por USB. No lo arranques en setup sin un interruptor físico que lo frene. Un sketch así, subido por error, escribe solo. El Uno no tiene estas funciones: si no compilan, es porque la placa no es un 32U4.

SPI en el Leonardo sale por el conector ICSP, no por los pines 11, 12 y 13 del Uno. Si un sketch de tarjeta SD «de Uno» no ve la tarjeta en un Leonardo, el cableado es el primero a mirar, en el catálogo.

ESP: la escritura no cae sola

#include <EEPROM.h>

void setup() {
  EEPROM.begin(16);             // reserva, no escribes aún
  byte brillo = EEPROM.read(0);
  if (brillo == 255) brillo = 128; // celda vacía, muy habitual
  EEPROM.write(0, brillo);
  EEPROM.commit();              // sin esto, al reiniciar no está
}

void loop() {}

En el Uno, commit no existe y no hace falta. Si lo copias al Uno, no compila. En el ESP8266, un loop apretado que no vuelve ni llama a yield() dispara el perro de guarda de la propia placa y rearranca. delay sí cede el turno. Un while vacío, no.

Sugerencia: en ESP32 guarda ajustes con Preferences, no con la EEPROM emulada. Menos desgaste mal contado y no depende de acordarte del commit en cada byte.

Interrupciones, según la familia

Uno y Nano clásico: pines 2 y 3, y se acabó para attachInterrupt. Mega: 2, 3, 18, 19, 20 y 21. Leonardo: 0, 1, 2, 3 y 7. En ESP hay más patillas, y también patillas que no sirven (en el 8266, la 16 no despierta igual que las demás). No enumeres de memoria un ESP: confirma el pin en Hardware y, si digitalPinToInterrupt devuelve un número negativo, ese pin no vale.

Control de flujo

== compara. = asigna. Un if (x = 1) no pregunta: mete un 1 y entra siempre. && y || unen condiciones. El curso lo abre despacio: decidir con if.

if (valor < 100) {
  digitalWrite(13, HIGH);
} else if (valor < 500) {
  digitalWrite(13, LOW);
} else {
  // por encima de 499
}
switch (paso) {
  case 0:
    break;
  case 1:
    break;
  default:
    break;
}

Sin break, el switch sigue cayendo al caso siguiente. A veces se quiere. Casi nunca.

Este for no termina. uint8_t no puede ser negativo, así que i >= 0 es siempre verdad, y al pasar de 0 salta a 255:

for (uint8_t i = 10; i >= 0; i--) {
  // bucle infinito
}

Así sí, porque la condición mira el valor antes de restar, o porque el índice es con signo y el tope es exclusivo:

for (int i = 10; i > 0; i--) {
  // i vale 10, 9, ... 1
}

while mira primero. do while hace el cuerpo y luego mira: al menos una vuelta. break sale del bucle. continue salta a la siguiente vuelta. return sale de la función.

Funciones

La firma dice qué entra, qué sale y cómo se llama. void no devuelve nada. Un parámetro normal es una copia: cambiarlo dentro no cambia al de fuera. Una referencia int & sí aliasa la variable original. Los punteros existen en el núcleo; puedes aplazarlos.

int tope(int valor, int maximo) {
  if (valor > maximo) return maximo;
  return valor;
}

void intercambia(int &a, int &b) {
  int t = a;
  a = b;
  b = t;
}

En src/main.cpp de PlatformIO, si llamas a tope antes de escribirla, declara antes int tope(int valor, int maximo);. En el .ino del IDE no hace falta.

La recursión cabe en un libro y mal en un Uno: la pila sale de esos 2 KB, compartidos con las locales. Un bucle hace el mismo trabajo.

Arrays y texto

El índice empieza en 0. Cuatro casillas se numeran 0, 1, 2 y 3. Salirte no lanza un error: escribes encima de otra variable. En el Uno eso se ve como un comportamiento absurdo, no como un mensaje claro.

const int N = 4;
int v[N] = {10, 20, 30, 40};

int suma() {
  int s = 0;
  for (int i = 0; i < N; i++) s += v[i];
  return s;
}

sizeof(v) / sizeof(v[0]) vale el número de casillas solo si v es el array, en el mismo sitio donde está declarado. Si pasas v a una función, allí ya es un puntero y sizeof no mide el array. Pasa también la longitud.

char msg[] = "hola"; // 5 bytes: h o l a y un 0 final
Serial.print(msg);

No construyas un String nuevo en cada vuelta del loop. Si el texto es fijo, F(). Si cambia, un char de tamaño conocido.

millis frente a delay

delay(1000) detiene el loop un segundo. En ese segundo no lees el botón, no atiendes el serie y no avanzas la otra tarea. delayMicroseconds también bloquea, solo que menos tiempo.

millis() devuelve unsigned long: milisegundos desde el arranque. Desborda más o menos a los 49,7 días y vuelve a 0. No hace falta reiniciar antes. La resta de dos unsigned long sigue siendo correcta al cruzar ese punto, si no la guardas en un int.

const unsigned long INTERVALO = 500;
unsigned long anterior = 0;
bool encendido = false;

void loop() {
  unsigned long ahora = millis();
  if (ahora - anterior >= INTERVALO) {
    anterior = ahora;
    encendido = !encendido;
    digitalWrite(LED_BUILTIN, encendido ? HIGH : LOW);
  }
  // aquí el botón se lee en la misma vuelta
}

Dos tareas son dos parejas anterior / intervalo, no dos delay. micros() es el mismo idea con microsegundos, y desborda más o menos a los 70 minutos. La resolución en el Uno es de 4 µs.

Simulación en esta página, no en la placa. El carril del botón es un pulsador que querrías leer mientras el LED parpadea.

LED

apagado

Botón

Pulsaciones válidas: 0
Perdidas mientras bloqueaba: 0

Con millis el pulsador responde en cualquier momento.

Rebote

Un pulsador mecánico no pasa de abierto a cerrado de una vez. Durante unos milisegundos rebota y digitalRead ve varios flancos. El curso espera un poco en la lección del rebote. Aquí no uses delay para eso: espera con millis a que la lectura lleve quieta unos 20–50 ms.

const int PIN = 2;
const unsigned long ESTABLE = 40;
bool estado = HIGH;
bool anteriorLectura = HIGH;
unsigned long cambio = 0;

void loop() {
  bool lectura = digitalRead(PIN);
  if (lectura != anteriorLectura) cambio = millis();
  if (millis() - cambio >= ESTABLE && lectura != estado) {
    estado = lectura;
    if (estado == LOW) {
      // flanco de pulsación, una sola vez
    }
  }
  anteriorLectura = lectura;
}

El pin va en INPUT_PULLUP, así que pulsado es LOW. 40 ms es un punto de partida, no una ley.

Serial

En el Uno el puerto de depuración es el mismo UART de los pines 0 y 1, compartido con el USB. Mientras depuras, no pongas ahí otro aparato. Serial.begin(9600) y el monitor, al mismo número.

void setup() {
  Serial.begin(9600);
  Serial.println(F("listo"));
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    Serial.print(F("llego: "));
    Serial.println(c);
  }
}

while (!Serial) espera a que el USB abra en placas con USB nativo, como Leonardo o Micro. En el Uno no espera: el operador ya es verdad. No lo copies al Uno creyendo que frena hasta que abras el monitor.

parseInt y readString se quedan esperando hasta el tiempo de setTimeout (un segundo por defecto). Eso es un delay disfrazado. Para no bloquear, lee de byte en byte cuando available sea mayor que cero.

En el Mega hay Serial1, Serial2 y Serial3, hardware de verdad. En el Uno solo hay uno. Otro puerto en el Uno es SoftwareSerial, peor.

Interrupciones

En el Uno y el Nano clásico, las interrupciones externas por attachInterrupt solo existen en los pines 2 y 3. El resto de placas, en el catálogo. El curso no las enseña: el rebote simple le basta. El mecanismo es este.

volatile bool pulso = false;

void isr() {
  pulso = true;
}

void setup() {
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), isr, FALLING);
}

void loop() {
  if (pulso) {
    pulso = false;
    // el trabajo lento va aquí, no en la isr
  }
}

Modos en el Uno: LOW, CHANGE, RISING, FALLING. Dentro de la rutina no hay delay ni Serial: las interrupciones están paradas, millis no avanza y delay no vuelve. La variable que toca la rutina y también el loop va con volatile. El trabajo largo se hace en el loop, avisado por un sí o un no.

El rebote también llega a la interrupción. Si cuentas flancos crudos, cuentas rebotes. Filtra en el loop con tiempo, o acepta el error.

EEPROM

En el 328P hay 1024 bytes que sobreviven a un reset y a quitar la alimentación. No sobreviven a un chip nuevo. Cada celda aguanta del orden de 100 000 escrituras. No escribas en cada vuelta del loop.

#include <EEPROM.h>

void setup() {
  unsigned int n = 0;
  EEPROM.get(0, n);
  n = n + 1;
  EEPROM.put(0, n);
}

void loop() {}

put y get mueven el objeto entero. Un unsigned int en el Uno son 2 bytes: lo siguiente empieza en la dirección 2, no en la 1. EEPROM.update escribe un byte solo si cambió. No pases de EEPROM.length(). En un ESP, la librería que se llama EEPROM suele ser una imitación sobre flash, con otro tamaño y otro desgaste. No copies la cifra de 1024.

I2C y Wire

Dos hilos para muchos dispositivos: SDA y SCL, más masa. En el Uno son A4 (SDA) y A5 (SCL). Las direcciones que usa Wire son de 7 bits. Los módulos suelen llevar ya las resistencias de pull-up. Si el bus no responde, el fallo casi siempre es dirección, masa que no es común, o SDA y SCL cruzados. El pinout de otras placas está en Hardware, no aquí.

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.println(addr, HEX);
    }
  }
}

void loop() {}

endTransmission devuelve 0 si alguien contestó. Escribir a un dispositivo: beginTransmission, write, endTransmission. Leer: requestFrom y luego read. Esta página no habla con ningún chip.

SPI

Más rápido que I2C, con un hilo de reloj, uno de ida, uno de vuelta y un cable de selección por cada periférico. En el Uno: 11 MOSI, 12 MISO, 13 SCK, y el 10 es el SS de fábrica. Aunque tu selección sea otro pin, en el Uno el 10 tiene que quedar como salida. Si el 10 es entrada y cae a LOW, el hardware SPI del AVR se cree esclavo y el maestro se calla.

#include <SPI.h>

const int CS = 10;

void setup() {
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();
}

byte transfiere(byte dato) {
  digitalWrite(CS, LOW);
  byte v = SPI.transfer(dato);
  digitalWrite(CS, HIGH);
  return v;
}

UART

Es el serie de toda la vida: un hilo de transmisión, uno de recepción, masa común, y el mismo baud en los dos extremos. En el Uno ese UART es Serial, pines 0 y 1. Un Mega añade Serial1 y siguientes. Una placa con USB nativo separa el USB de depuración de un UART de pines: no asumas que siempre son la misma cosa.

Cruzado: el TX de uno va al RX del otro. Misma masa. Voltajes compatibles: un Uno a 5 V no se conecta a pelo a un pin de 3,3 V. Eso es hardware.

SoftwareSerial

Imita un UART por software cuando el Uno ya gastó el de verdad. Es peor: se come tiempo, se lleva mal con otras interrupciones y por encima de 9600 se vuelve inestable. Solo uno puede estar escuchando. Para cambiar, listen(). No lo pongas en los pines 0 y 1.

#include <SoftwareSerial.h>

SoftwareSerial radio(4, 5); // RX, TX

void setup() {
  radio.begin(9600);
}

void loop() {
  if (radio.available()) {
    char c = radio.read();
  }
}

Esos pines 4 y 5 dejan libres el 2 y el 3 para interrupciones. Si usas el 2 o el 3 para el puerto blando, ya no los tienes limpios para attachInterrupt.

Ficheros

El Uno no tiene sistema de archivos. Lo que parece un disco es un módulo SD por SPI, o la flash de un ESP. No es la EEPROM.

SD

El pin de selección lo marca el módulo. En muchas shields es el 4 o el 10: míralo en el módulo, no lo des por hecho. El nombre, estilo 8.3. Cierra el archivo. Si se va la alimentación con el archivo abierto, se pierde lo último.

#include <SD.h>

const int CS = 4;

void setup() {
  if (!SD.begin(CS)) return;
  File f = SD.open("nota.txt", FILE_WRITE);
  if (f) {
    f.println(F("hola"));
    f.close();
  }
}

void loop() {}

Según la versión de la librería, FILE_WRITE añade al final o no. Si la tuya trae FILE_APPEND, úsalo cuando quieras añadir. No lo inventes si el compilador no lo conoce.

LittleFS

En ESP32 y ESP8266 la flash puede montarse como disco. SPIFFS es el nombre viejo. LittleFS.begin() sin argumento no borra. No llames a la variante que formatea si te importa lo que ya había.

#include <LittleFS.h>

void setup() {
  if (!LittleFS.begin()) return;
  File f = LittleFS.open("/nota.txt", "w");
  if (f) {
    f.print(F("hola"));
    f.close();
  }
}

Esto no compila en un Uno. "w" vacía el archivo al abrirlo.

RTC

Un reloj de tiempo real (el DS3231 es el habitual) lleva la hora con la placa apagada, porque tiene su pila. Se habla por I2C. La librería que usa este ejemplo se llama RTClib. Si no la tienes instalada, no compila. Esta página no la instala.

#include <Wire.h>
#include <RTClib.h>

RTC_DS3231 rtc;

void setup() {
  Wire.begin();
  if (!rtc.begin()) return;
}

void loop() {
  DateTime t = rtc.now();
  int h = t.hour();
  int m = t.minute();
}

Si begin falla, pasa el escáner I2C antes de tocar registros. El chip, la pila y el pinout son de Hardware.

Servo

attach prepara el pin. write pide un ángulo de 0 a 180 en un servo normal, no una tensión. En el Uno la librería usa el temporizador 1 y el PWM de los pines 9 y 10 deja de funcionar.

#include <Servo.h>

Servo brazo;

void setup() {
  brazo.attach(9);
}

void loop() {
  brazo.write(90);
}

No alimentes el servo desde el pin de 5 V de la placa si tiene que mover carga: la placa se reinicia por caída de tensión. Fuente aparte, masa en común con el Uno. El cableado está en Hardware.

tone

tone(pin, hercios) saca una onda cuadrada. tone(pin, hercios, milisegundos) la corta sola. noTone(pin) la para. Solo una nota a la vez en la placa. En el Uno usa el temporizador 2, así que el PWM de los pines 3 y 11 se estropea mientras suena. El pin del altavoz puede ser otro, por ejemplo el 8. El altavoz no va directo de un pin a masa sin una resistencia: eso es del montaje.

tone(8, 440, 200);

FastLED

Para tiras direccionables, tipo WS2812. El dato es un solo pin. show corta las interrupciones un instante para cumplir el tiempo del protocolo: no llames a show dentro de una rutina de interrupción, y no esperes que millis sea fino en ese margen.

#include <FastLED.h>

const int DATA = 6;
const int N = 8;
CRGB leds[N];

void setup() {
  FastLED.addLeds<WS2812B, DATA, GRB>(leds, N);
  leds[0] = CRGB::Red;
  FastLED.show();
}

A blanco pleno, cada LED puede pedir del orden de 60 mA. Ocho aún se discuten. Una tira larga, no: no sale del pin de 5 V del Uno. Fuente de 5 V externa, masa común, y el dato del Uno. Si la placa es de 3,3 V y la tira es de 5 V, a veces el dato no entra: hay que adaptar el nivel, y eso está en Hardware.

DHT

DHT11 y DHT22 se leen por un pin, a pulsos. El 11 quiere como mínimo un segundo entre lecturas. El 22, dos. Si preguntas antes, la librería devuelve NaN. Hay más de una librería llamada DHT. Este ejemplo es la clase DHT con un DHT22. Si el compilador pide Adafruit_Sensor.h, falta esa otra librería en el IDE. No se baja desde aquí.

#include <DHT.h>

DHT dht(7, DHT22);

void setup() {
  dht.begin();
  Serial.begin(9600);
}

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  if (isnan(h) || isnan(t)) return;
  delay(2000);
}

Ese delay cumple el mínimo del sensor y bloquea el loop. En un programa de verdad, el mismo margen se hace con millis. El DHT se lleva mal con interrupciones largas justo en la lectura.

PubSubClient

Es una librería MQTT. El sketch necesita una placa con red (WiFi o Ethernet) y un broker. Esta página no se conecta a nada: el ejemplo solo enseña la forma. El cliente tiene que llamar a loop a menudo. Si lo metes detrás de un delay largo, se cae la sesión. El búfer de paquete, de fábrica, anda por 256 bytes: un mensaje más grande se corta si no lo subes en la librería.

#include <PubSubClient.h>

WiFiClient red;
PubSubClient cliente(red);

void llega(char* tema, byte* datos, unsigned int n) {
  // n bytes en datos; no asumas un texto cerrado
}

void setup() {
  cliente.setServer("broker-local", 1883);
  cliente.setCallback(llega);
  cliente.connect("placa-1");
  cliente.subscribe("casa/luz");
}

void loop() {
  cliente.loop();
}

WiFiClient es de una placa con WiFi, no del Uno. Falta el WiFi.begin de esa placa, con el nombre y la clave que tú tengas, que no van escritos aquí. Publicar: cliente.publish("casa/luz", "1").

Punto de acceso

Algunas placas ESP pueden abrir su propia red, sin router. Un Uno no. La página tampoco se asocia a esa red: solo documenta la llamada. La clave, si es WPA, conviene de 8 caracteres o más. Si no, softAP puede devolver falso y no levantar nada.

#include <WiFi.h> // ESP32. En el ESP8266 el include es ESP8266WiFi.h

void setup() {
  WiFi.softAP("les-vencimos", "cambia-esto-8");
}

Quien se conecte habla con la placa, no con internet. Sigue siendo un programa en el dispositivo, no un servicio de esta página.

Actualizar por la red local

En un ESP, el IDE puede subir un sketch por la red de casa si la placa ya está en esa red y tu código llama a handle en cada vuelta. No es magia y no sale de esta página. Sin contraseña, cualquiera en esa red puede reescribir la placa. Pon una.

#include <ArduinoOTA.h>

void setup() {
  // antes: WiFi.begin con tu red, ya conectado
  ArduinoOTA.setHostname("placa");
  ArduinoOTA.setPassword("cambia-esto-8");
  ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle();
}

Si handle no se llama porque el loop está atascado en un delay, la subida falla. El Uno de toda la vida se sigue programando por el cable.

PID

Sirve para acercar una medida a un objetivo: temperatura, velocidad, posición. Tres términos. El proporcional mira el error de ahora. El integral acumula el error, para que no se quede corto. El derivativo mira si el error cambia deprisa, para no pasarte. Se calcula cada cierto tiempo con millis, no con delay.

float error = objetivo - medida;
integral = integral + error * dt;
float derivada = (error - errorAntes) / dt;
float salida = Kp * error + Ki * integral + Kd * derivada;
errorAntes = error;

dt es el tiempo desde el cálculo anterior, en las mismas unidades con las que afinaste Kp, Ki y Kd. Recorta la salida al rango real del actuador. Recorta también la integral: si no, cuando el sistema no puede llegar, el término crece sin límite y luego se pasa de largo. Hay librerías que llaman a esto Compute en cada vuelta. Los tres números son los mismos. Los valores concretos salen del proyecto, no de una tabla universal.

Dormir la placa

Dormir baja el consumo. En el Uno, el modo profundo para casi todo menos que llegue una interrupción. Si duermes sin haber preparado un despertador, solo un reset te saca. Prepara el pin 2 o el 3 antes.

#include <avr/sleep.h>

void setup() {
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), despertar, FALLING);
}

void despertar() {}

void loop() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu();
  sleep_disable();
  // sigue aquí al volver del pin 2
}

Un ESP no usa este código. Ahí el sueño profundo es otra llamada y, al despertar, muchas veces la placa arranca desde setup como si la enchufaras. No mezcles los dos. Mientras duerme, millis no es un reloj de pared.

Temporizadores

El Uno tiene tres temporizadores de hardware, y casi todos están ya cogidos:

TemporizadorQuién lo usa
0, de 8 bitsmillis, delay, micros. Si lo reprogramas, el reloj de la placa miente.
1, de 16 bitsLa librería Servo. También el PWM de los pines 9 y 10.
2, de 8 bitstone. También el PWM de los pines 3 y 11.

Para un periodo de medio segundo no toques registros: usa millis. Los registros del temporizador solo merecen la pena cuando necesitas un pulso mucho más fino y sabes qué librería vas a romper. No hay una receta de registros en esta página, a propósito.

Watchdog

Es un temporizador que reinicia la placa si tu programa deja de acordarse de él. Sirve cuando el loop se cuelga. En el Uno:

#include <avr/wdt.h>

void setup() {
  wdt_disable();
  wdt_enable(WDTO_2S);
}

void loop() {
  wdt_reset();
}

WDTO_2S son unos dos segundos. Si una vuelta tarda más y no llamas a wdt_reset, la placa rearranca. wdt_disable al entrar en setup evita heredar un perro ya armado. Si la placa se pone a reiniciar sola y ya no acepta un sketch, quita el watchdog del programa y vuelve a subir por el cable en cuanto el puerto aparezca. No es un ESP: allí el perro de guarda es otra API.

Máquina de estados

Cuando el programa es una historia (esperar, marchar, pausa, error), no la cuentes con delay ni con banderas sueltas que se pisan. Un estado y un switch dentro del loop. Cada vuelta mira en qué estado está, hace solo lo de ese estado, y decide si cambia.

enum Paso { ESPERA, MARCHA, PAUSA };
Paso paso = ESPERA;

void loop() {
  switch (paso) {
    case ESPERA:
      if (digitalRead(2) == LOW) paso = MARCHA;
      break;
    case MARCHA:
      // avanzar sin delay; el tiempo, con millis
      break;
    case PAUSA:
      break;
  }
}

El rebote del botón que cambia de estado se filtra antes, o el mismo flanco te hará entrar y salir.

Buenas prácticas

Nombra el pin una vez, arriba, con const. No dejes un 13 mágico en medio del loop. Comenta el porqué, no el qué: digitalWrite ya se lee solo.

No bloquees. Un delay en el loop apaga el resto del programa. El tiempo va con millis, una pareja por tarea. No construyas String en cada vuelta. Los textos fijos, con F().

Separa. Leer el sensor, decidir y actuar pueden ser tres funciones. La máquina de estados, si hay historia. La depuración, por el serie, se quita o se calla con un const bool cuando ya no hace falta.

Escribe en EEPROM y en la SD a propósito, no de pasada. Cierra el archivo. Deja el pin 10 del Uno como salida si usas SPI.

Depuración

Primero, que compile. El mensaje del compilador dice archivo y línea. was not declared es un nombre que no existe todavía, o un prototipo que falta en un .cpp. expected ';' o expected '}' suelen estar una línea más arriba de la que señala.

Si sube y no hace lo que crees, un Serial.println con etiqueta vale más que uno suelto: Serial.print(F("luz=")); Serial.println(luz);. El baud, igual en los dos sitios. Un LED que cambia de estado en cada vuelta del loop dice si el programa sigue vivo cuando no te fías del serie.

Si el fallo es raro, comenta la mitad. Si desaparece, está en lo que comentaste. Si sigue, está en la otra mitad. No cambies cinco cosas a la vez.

Si no sube: mira el puerto, cierra el monitor serie que lo tenga pillado, y confirma que la placa del menú es la que tienes en la mesa. Un Nano viejo a veces pide el gestor de arranque antiguo. Eso ya es de la placa, y está en Hardware.

Errores típicos

Lo que vesLo que suele ser
No compila, falta ; o }Mira la línea de encima del aviso.
Entra siempre en el ifEscribiste = en vez de ==.
Un contador se vuelve negativo cerca de 32 000int de 16 bits en el Uno. Pasa a long o a unsigned long.
El for no acabaÍndice sin signo y condición >= 0.
El LED no enciendeFalta pinMode, o el pin no es el que crees, o el circuito. El circuito, en Hardware.
El botón va al revésINPUT_PULLUP: pulsado es LOW.
El botón cuenta de tres en tresRebote. Filtro con millis.
El monitor enseña símbolosEl baud no coincide con begin.
El programa va a trompiconesHay un delay, un parseInt o una lectura DHT con espera.
I2C mudoDirección de 7 bits mal, SDA/SCL cruzados, o sin masa común. Escáner en I2C.
SPI mudo en el UnoEl pin 10 no está como salida.
El PWM del 9 y el 10 murióUsaste Servo.
El PWM del 3 y el 11 murióUsaste tone.
La placa se reinicia al mover el servoEl servo come del 5 V de la placa.
millis hace cosas rarasTocaste el temporizador 0, o guardaste la resta en un int.
NaN en el DHTLeíste demasiado pronto, o el pin no es el de datos.

Lo que ya viene en el núcleo

No son proyectos. Son cuatro herramientas que el IDE ya trae y que se usan mal por copiarlas a medias.

Texto en flash

En el Uno, Serial.println("una frase larga") copia la frase a la SRAM en el arranque. Con 2 KB se nota. F() la deja en flash.

Serial.println(F("sensor listo, esperando trama"));

Una tabla constante, en AVR, se marca PROGMEM y se lee con pgm_read_word (un int del Uno son 16 bits). En ESP32 la flash ya se ve como memoria: PROGMEM casi no cambia el resultado. No es un error dejarlo, pero tampoco es el ahorro que era en el Uno.

const int curva[] PROGMEM = {0, 12, 40, 90, 200};

int punto(int i) {
  return pgm_read_word(&curva[i]);
}

pulseIn

Mide cuánto dura un nivel, en microsegundos. Sirve para un eco de ultrasonidos o un pulso de un receptor. Bloquea hasta que acaba el pulso o hasta el tope. El tope por defecto es un segundo: eso es un delay de un segundo si el pin no se mueve. Pasa siempre el tercero.

// espera como mucho 30 ms un nivel alto; 0 significa que no llegó
unsigned long us = pulseIn(8, HIGH, 30000UL);
if (us == 0) {
  // timeout: no hay eco, o el pin está quieto
}

Sugerencia: no llames a pulseIn sin tope dentro de un loop que también tiene que leer botones. Es el mismo fallo que delay, con otro nombre.

shiftOut

Saca un byte por dos pines, uno de datos y uno de reloj, sin el hardware SPI. Va más lento y bloquea mientras salen los 8 bits. MSBFIRST manda primero el bit de más peso. El orden lo dicta el chip que escucha, no el gusto.

shiftOut(pinDatos, pinReloj, MSBFIRST, valor);

Bits

bitSet(flags, 2);          // pone a 1 el bit 2
bitClear(flags, 2);
if (bitRead(flags, 2)) { } // distinto de flags == 2

flags == 2 mira el número entero. bitRead mira un solo bit. Confundirlos enciende la salida equivocada y el resto del programa parece inocente.

Quitar interrupciones un momento

Si lees un long que también escribe una interrupción, en el Uno esa lectura no es atómica: pueden entrar a mitad y ver un número que nunca existió. La sección corta se cierra siempre. Si te dejas el noInterrupts puesto, millis se congela.

noInterrupts();
unsigned long copia = compartido;
interrupts();

Pantalla de texto

La librería LiquidCrystal habla con el controlador HD44780 en 4 bits. Viene con el IDE. El ejemplo de abajo usa los pines del sketch de referencia del IDE (RS, E, D4, D5, D6, D7). Si tu módulo es I2C, esta librería no es la suya: ese módulo es un expansor por I2C y quiere otra librería, casi siempre LiquidCrystal_I2C, que no viene de fábrica. No la bajes desde esta página.

#include <LiquidCrystal.h>

// RS, E, D4, D5, D6, D7. El orden es el del cable, no un capricho.
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() {
  lcd.begin(16, 2);     // columnas, filas. 16x2 o 20x4, la que tengas
  lcd.print(F("listo"));
}

void loop() {
  lcd.setCursor(0, 1);  // columna 0, fila 1. Empieza en cero
  lcd.print(millis() / 1000);
}

Imprimir un número variable sin limpiar deja restos del número anterior (1000 y luego 999 se lee 9990). lcd.clear() borra, y también tarda. Para un contador, escribe espacios de sobra o formatea a ancho fijo.

Sugerencia: el contraste del pin V0, si la fila existe y no se lee, es un potenciómetro, no un fallo de print. Eso está en el montaje, en Hardware.

Stepper

La librería Stepper viene con el IDE. setSpeed pide revoluciones por minuto, no un retardo. step bloquea hasta acabar los pasos: es un delay largo con otro traje. No llames a step(2000) si en ese tiempo tenías que leer una parada de emergencia.

#include <Stepper.h>

const int PASOS_POR_VUELTA = 200; // el de TU motor. No es una constante universal.
Stepper motor(PASOS_POR_VUELTA, 8, 9, 10, 11);

void setup() {
  motor.setSpeed(10); // rpm. Si es alto y el motor vibra sin girar, bájalo.
}

void loop() {
  motor.step(PASOS_POR_VUELTA); // una vuelta, y el loop no hace otra cosa mientras
}

El orden de los cuatro pines depende del driver. Si el motor vibra y no avanza, prueba el cruce de bobinas antes de subir la velocidad. Un 28BYJ-48 con reductora no son 200 pasos por vuelta de eje: la reductora multiplica, y la cifra que circula (2048) es de ese conjunto, no de un NEMA. Cuéntalo o mídelo. No lo copies de otro motor.

Sugerencia: la parada va en un pin leído entre tandas cortas de step, no después de una tanda de mil. O no es una parada.

Ajustes en ESP32

Preferences guarda en la flash del ESP32 por nombres, no por direcciones mágicas. No existe en el Uno. El segundo argumento de begin, false, abre en lectura y escritura. true es solo lectura.

#include <Preferences.h>

Preferences prefs;

void setup() {
  prefs.begin("ajustes", false);
  unsigned int brillo = prefs.getUInt("brillo", 128); // 128 si aún no hay nada
  // cuando el usuario cambie el valor, y solo entonces:
  prefs.putUInt("brillo", brillo);
  prefs.end();
}

void loop() {}

No llames a put en cada vuelta. La flash se desgasta. Un ajuste se escribe cuando cambia, como la EEPROM del Uno con update.

Sugerencia: el primer texto, "ajustes", es un cajón. Déjalo corto. Si abres dos cajones con el mismo nombre a la vez, te pisas. Uno abierto, end, y luego el otro.

Ejemplos comentados

Cuatro sketches enteros, con el comentario al lado de la decisión. No son proyectos: no hay lista de la compra ni caja. El pin físico, si dudas, está en el Uno: el 13 en D13, el 2 en D2, A4 y A5 en A4 y A5.

Dos tareas sin delay

Captura mental: el LED debería parpadear y, a la vez, el monitor debería anotar cada pulsación. Si el LED va bien pero el botón solo responde cada segundo, hay un delay escondido. Si el contador salta de 32 767 a negativo, el contador era un int del Uno.

const int PIN_LED = 13;      // D13, LED de la placa en el Uno
const int PIN_BOTON = 2;     // a GND, con INPUT_PULLUP
const unsigned long PERIODO_LED = 500;
const unsigned long PERIODO_INFORME = 2000;

unsigned long anteriorLed = 0;
unsigned long anteriorInforme = 0;
bool ledOn = false;
unsigned int pulsaciones = 0; // 16 bits bastan un rato; si crece mucho, unsigned long
bool botonAntes = HIGH;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_BOTON, INPUT_PULLUP);
  Serial.begin(9600);
}

void loop() {
  unsigned long ahora = millis();

  // Tarea 1. No toca delay: solo pregunta si ya toca cambiar el LED.
  if (ahora - anteriorLed >= PERIODO_LED) {
    anteriorLed = ahora;
    ledOn = !ledOn;
    digitalWrite(PIN_LED, ledOn ? HIGH : LOW);
  }

  // Tarea 2. Flanco, no nivel: cuenta una vez por pulsación.
  bool boton = digitalRead(PIN_BOTON);
  if (botonAntes == HIGH && boton == LOW) {
    pulsaciones = pulsaciones + 1;
  }
  botonAntes = boton;

  // Tarea 3. El informe no frena a las otras dos.
  if (ahora - anteriorInforme >= PERIODO_INFORME) {
    anteriorInforme = ahora;
    Serial.print(F("pulsaciones="));
    Serial.println(pulsaciones);
  }
}

Ese flanco aún puede contar rebotes. El filtro de 40 ms va delante de la suma, no en vez de ella.

Leer un registro por I2C

Captura mental: el escáner imprime una dirección y, aun así, la lectura sale 255 o no cambia. Casi nunca es el loop. Es el registro interno del chip, o que lees un byte cuando el aparato manda dos. El ejemplo pide un byte al registro 0 y lo imprime en hexadecimal. Sustituye la dirección por la que te dio el escáner.

#include <Wire.h>

const byte DIRECCION = 0x68; // 7 bits, la que imprime el escáner
const byte REGISTRO = 0x00;

void setup() {
  Wire.begin();           // Uno: SDA A4, SCL A5
  Serial.begin(9600);
}

void loop() {
  Wire.beginTransmission(DIRECCION);
  Wire.write(REGISTRO);                 // qué posición interna quieres
  byte err = Wire.endTransmission(false); // false: no suelta el bus, sigue un read
  if (err != 0) {
    Serial.print(F("fin de escritura="));
    Serial.println(err);                // 0 es el único éxito
    delay(1000);
    return;
  }
  Wire.requestFrom(DIRECCION, (byte)1);
  if (Wire.available()) {
    byte dato = Wire.read();
    Serial.println(dato, HEX);
  }
  delay(500); // aquí estorba a propósito: es un banco de pruebas, no un programa
}

endTransmission(false) es un repeated start. Algunos chips lo exigen. Si con true (el valor por defecto) lees basura y con false no, era eso. El significado de cada registro no está aquí: está en la hoja de ese chip, y el cableado en Hardware.

Guardar un ajuste en EEPROM

Captura mental: cada reset el brillo vuelve a cero, o al revés, la EEPROM se agota en una tarde porque escribías dentro del loop sin mirar si el valor cambió. update no escribe si el byte ya era ese. Un int del Uno ocupa dos direcciones.

#include <EEPROM.h>

const int DIR_BRILLO = 0; // ocupa 2 bytes: 0 y 1. Lo siguiente iría en 2.

int cargar() {
  int brillo = 0;
  EEPROM.get(DIR_BRILLO, brillo);
  if (brillo < 0 || brillo > 255) return 128; // celda virgen o dato roto
  return brillo;
}

void guardar(int brillo) {
  int previo = cargar();
  if (previo == brillo) return; // ni siquiera llama a put
  EEPROM.put(DIR_BRILLO, brillo);
}

void setup() {
  int brillo = cargar();
  analogWrite(9, brillo); // PWM del Uno, pin 9
  // guardar(brillo) solo cuando el usuario cambie el ajuste, no aquí en bucle
}

void loop() {}

Estados con tiempo

Captura mental: «debería esperar dos segundos y seguir», y mientras tanto el botón de parada no existe. Si la espera es un delay(2000), la parada llega tarde. El estado MARCHA apunta la hora de entrada y compara. Salir no depende de terminar la espera.

const int PIN_BOTON = 2;
const unsigned long MARCHA_MS = 2000;

enum Paso { ESPERA, MARCHA };
Paso paso = ESPERA;
unsigned long entro = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(PIN_BOTON, INPUT_PULLUP);
}

void loop() {
  bool parado = digitalRead(PIN_BOTON) == LOW;

  switch (paso) {
    case ESPERA:
      digitalWrite(LED_BUILTIN, LOW);
      if (!parado) {
        paso = MARCHA;
        entro = millis(); // se anota al entrar, no en cada vuelta
      }
      break;
    case MARCHA:
      digitalWrite(LED_BUILTIN, HIGH);
      if (parado || millis() - entro >= MARCHA_MS) {
        paso = ESPERA;
      }
      break;
  }
}

El botón aquí sigue sin filtro de rebote. Para un gesto de dos segundos casi no se nota. Para contar pulsaciones, sí: vuelve a debounce.

Órdenes por el serie, sin bloquear

Captura mental: escribes on en el monitor y no pasa nada hasta que pulsas Enter, o la placa se queda muda porque usaste readString y el loop espera un segundo entero. Aquí cada byte se guarda al llegar. Solo al recibir el salto de línea se mira la palabra. El LED y el botón de otras tareas siguen en la misma vuelta, fuera de este while.

char buf[16];
byte n = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  Serial.println(F("on / off y Enter"));
}

void orden(const char* s) {
  if (strcmp(s, "on") == 0) digitalWrite(LED_BUILTIN, HIGH);
  else if (strcmp(s, "off") == 0) digitalWrite(LED_BUILTIN, LOW);
  else {
    Serial.print(F("no entiendo: "));
    Serial.println(s);
  }
}

void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r') continue;          // el monitor a veces manda retorno y luego salto
    if (c == '\n') {
      buf[n] = 0;                     // cierra la cadena
      if (n > 0) orden(buf);
      n = 0;
    } else if (n < sizeof(buf) - 1) {
      buf[n] = c;
      n = n + 1;
    }
    // si n ya está lleno, se tiran los bytes de más hasta el Enter
  }
}

El monitor tiene que enviar el salto de línea (la opción de fin de línea del IDE, no «sin línea»). strcmp compara textos cerrados con cero. No hace falta incluir nada más: el núcleo de Arduino ya lo trae.

Qué estás viendo, en una frase

La escenaLa causa que miras primero
El LED cambia, el botón no, y el serie va a saltos de un segundo.Un delay en el loop. Pásalo a millis.
El monitor es una fila de símbolos y ni un texto reconocible.El baud del monitor no es el de Serial.begin.
Al mover el servo, el Uno rearranca y el USB hace el ruido de desconectar.El servo está comiendo del 5 V de la placa. Otra fuente, masa común. Pin de 5 V.
Un número que subía se vuelve negativo al pasar de 32 767.int en AVR. No es un fallo del sensor.
analogWrite en el 9 no hace medias tintas desde que añadiste el servo.La librería se quedó el temporizador 1. Cambia de pin PWM (5 o 6).
El escáner I2C no imprime nada.SDA/SCL cruzados, sin masa, o el módulo sin alimentación. Pines del Uno: A4, A5.
La placa reinicia sola cada dos segundos justos.El watchdog está armado y loop tarda más, o no llama a wdt_reset.

les vencimos · software arduino · offline · no recoge datos · v20261003b