Tienes un struct definido arriba del sketch, una función que lo recibe como parámetro, y el compilador responde con alguno de estos errores:
error: 'Sensor' was not declared in this scope
error: variable or field 'leerSensor' declared void
error: 'Sensor' does not name a type
El tipo está declarado. El problema no es tu código, sino algo que el IDE de Arduino hace con los archivos .ino antes de compilar.
La causa: los prototipos automáticos
En C++, una función tiene que estar declarada antes de usarse. Para que no tengas que escribir esas declaraciones (los prototipos), el IDE de Arduino las genera solo: lee el .ino, encuentra todas tus funciones y escribe sus prototipos en un punto del archivo, justo antes de la primera definición de función que encuentra.
Si una función usa un tipo tuyo y ese tipo se define después de ese punto, el prototipo generado lo usa antes de que exista. De ahí el error.
Este sketch falla:
#include <Arduino.h>
void setup() { // primera función: aquí se insertan los prototipos
Serial.begin(115200);
}
struct Sensor { // el tipo aparece después
uint8_t pin;
float valor;
};
void leerSensor(Sensor &s) { // su prototipo quedó arriba, antes de Sensor
s.valor = analogRead(s.pin);
}
void loop() {}
El caso difícil: una macro que es una función
Este me costó más encontrarlo. En el núcleo de Arduino para ESP32, SET_LOOP_TASK_STACK_SIZE() (la macro para dar más pila a loop()) se expande a una definición de función. Si la pones antes de un #include que define tus tipos, el IDE la toma como la primera función e inserta ahí todos los prototipos:
#include <WiFi.h>
SET_LOOP_TASK_STACK_SIZE(16 * 1024); // MAL: se expande a una función
#include "tipos.h" // define struct Nodo
void procesar(Nodo &n) { /* ... */ } // error: 'Nodo' was not declared
Visualmente no hay ninguna función antes del #include, por eso es tan confuso.
Solución 1: los tipos en un archivo .h
La más limpia. Crea una pestaña nueva en el IDE llamada tipos.h, mueve ahí tus struct y enum, e inclúyela al principio del sketch:
// tipos.h
#pragma once
#include <Arduino.h>
struct Sensor {
uint8_t pin;
float valor;
};
// sketch.ino
#include "tipos.h"
void leerSensor(Sensor &s) {
s.valor = analogRead(s.pin);
}
Solución 2: todos los tipos antes de la primera función
Si prefieres un solo archivo, define los tipos arriba del todo, antes de setup() y de cualquier otra función.
Solución 3: las macros que son funciones, después de los #include
#include <WiFi.h>
#include "tipos.h"
SET_LOOP_TASK_STACK_SIZE(16 * 1024); // BIEN: después de todos los #include
Solución 4: escribe tú el prototipo
Si declaras el prototipo a mano después del tipo, el IDE no genera el suyo para esa función:
struct Sensor { uint8_t pin; float valor; };
void leerSensor(Sensor &s); // prototipo propio
Cómo confirmar que es esto
Activa la salida detallada al compilar (Archivo → Preferencias → Mostrar salida detallada durante: compilación). El IDE muestra la ruta del .cpp que genera a partir de tu sketch; ábrelo y verás dónde quedaron los prototipos.
Si el proyecto crece, deja de pelear con esto: pasa la lógica a archivos .cpp y .h normales, donde el IDE no genera nada y C++ funciona como siempre. Es lo que hago en el firmware del ESP32 de la guía del ESP32.
Este error es uno de los 13 errores reales al construir domótica con ESP32 y AWS IoT.
Preguntas frecuentes
¿Pasa también con PlatformIO?
No con archivos .cpp: PlatformIO no genera prototipos y tienes que declararlos tú. Si usas un .ino en PlatformIO, aplica la misma conversión que el IDE de Arduino.
¿Y con enum o typedef?
Igual. Cualquier tipo propio usado en la firma de una función (parámetros o tipo de retorno) puede provocar el error.
¿Por qué a veces funciona y a veces no?
Depende de dónde quede la primera función. Mover una función de sitio, añadir una macro o reordenar los #include cambia el punto de inserción, y el mismo código pasa de compilar a fallar.