Fundamentos · 20 min de lectura

Programación Arduino desde cero: variables, bucles y funciones

Programación Arduino desde cero: setup y loop, int, long, byte, desbordamiento, const, #define, if-else, switch-case, bucles, arrays y funciones con LED.

La programación Arduino consiste en decirle a una placa Arduino qué hacer usando el lenguaje C/C++: tú escribes qué LED se enciende y cuándo, qué pasa al pulsar un botón y cómo se interpreta el número que llega de un sensor, y la placa repite esas instrucciones miles de veces por segundo. La buena noticia es que casi todos los programas que verás, desde hacer parpadear un LED hasta un coche robot, se construyen con la misma decena de piezas básicas. Son setup() y loop(), las variables, las constantes, los operadores, if-else, switch-case, los bucles for y while, los arrays y las funciones.

En esta guía aprenderás estas piezas una a una, cada una con un ejemplo real de LED o pulsador. Todos los ejemplos funcionan en el mismo circuito sencillo y se han compilado y probado para Arduino Uno. Los puntos donde más se atascan los principiantes, es decir, el desbordamiento de variables, confundir = con ==, olvidar un break y un contador que se reinicia en cada vuelta, se explican por separado. En las últimas secciones verás también cómo funciona la misma lógica en el ESP32 y cómo se escribe en MicroPython en la Raspberry Pi Pico.

Placa Arduino Uno y sus pines digitales
Arduino Uno: todos los ejemplos de este artículo usan un pulsador en el pin D2 y seis LED en los pines D8–D13. Gracias al LED integrado en D13, para los primeros ejemplos ni siquiera necesitas cables.

¿Qué es la programación Arduino y qué lenguaje usa?

El código de Arduino se basa en C++; no existe un "lenguaje Arduino" aparte. El núcleo de Arduino añade a C++ funciones listas para usar como pinMode(), digitalWrite() y delay(). El archivo que escribes se llama sketch y tiene la extensión .ino. Cuando pulsas Verificar, el Arduino IDE añade al principio del archivo la cabecera Arduino.h y genera por sí mismo las declaraciones de las funciones, y el compilador convierte el código en lenguaje máquina que entiende el procesador. Después, el botón Subir envía ese resultado por USB al bootloader de la placa. Si todavía no has instalado el IDE, empieza por la guía cómo instalar Arduino IDE.

Al escribir código hay unas pocas reglas básicas. Si las aprendes desde el principio, desaparece la mitad de los errores de compilación que verías en tus primeros días:

ReglaForma correctaSi no la cumples
Cada instrucción termina en punto y comadigitalWrite(13, HIGH);expected ';' before '}' token
Mayúsculas y minúsculas importanpinMode(13, OUTPUT);'pinmode' was not declared in this scope
Los bloques de código van entre llavesvoid loop() { ... }Cada { necesita su }
Comentario de una línea// esta línea no se ejecutaLos comentarios no se compilan, sirven para explicar
Comentario de varias líneas/* ... */Si no lo cierras, todo lo de abajo se vuelve comentario
Texto entre comillas dobles, un carácter entre comillas simples"Hola" y 'A'Mezclar las comillas da un valor erróneo o un error

El circuito de los ejemplos

Todos los ejemplos funcionan en un solo circuito. Conecta seis LED a los pines D8–D13, cada uno con una resistencia de 220 Ω; la pata corta (cátodo) de cada LED va a GND. Puedes comprobar el valor de la resistencia para tu LED con la calculadora de resistencia para LED. Conecta una pata del pulsador a D2 y la pata opuesta en diagonal a GND. Como el código usa INPUT_PULLUP, no hace falta ninguna resistencia externa: el pin lee HIGH con el pulsador suelto y LOW cuando está pulsado. Encontrarás más detalles en cómo conectar un pulsador a Arduino y ¿qué es un LED?.

ComponenteArduino UnoESP32Raspberry Pi Pico
6 LED (cada uno con 220 Ω, cátodo a GND)D8, D9, D10, D11, D12, D13GPIO 18, 19, 21, 22, 23, 25GP14, GP13, GP12, GP11, GP10, GP9
Pulsador (la otra pata a GND)D2GPIO 4GP2
LED de la placaD13 (el LED marcado con L)GPIO 2 (en la mayoría de DevKit)GP25 ("LED" en la Pico W)
Velocidad del puerto serie9600 baudios115200 baudiosShell de Thonny
No conectes nunca un LED sin resistencia: pueden dañarse tanto el LED como el pin del Arduino. Si al pinchar el pulsador en la protoboard usas dos patas del mismo lado, puede que ya estén unidas por dentro y el pulsador se comporte como si nunca se pulsara; por eso usar las patas en diagonal es lo más seguro. Puedes consultar los números de pin de tu placa en la herramienta Pinout.

setup() y loop(): el esqueleto de todo programa

Todo sketch de Arduino tiene dos funciones obligatorias. setup() se ejecuta una sola vez, al encender la placa o al pulsar el botón de reset; aquí van los preparativos como la configuración de los pines y Serial.begin(). loop(), en cambio, se repite para siempre cuando termina setup(). Por detrás, el núcleo de Arduino tiene una función main() oculta: primero prepara el hardware, luego llama una vez a setup() y después llama a loop() una y otra vez dentro de un bucle infinito. La zona de la parte superior del archivo, fuera de estas dos funciones, es la zona global; ahí se definen las constantes y las variables a las que hay que acceder desde cualquier parte.

Arduino (C++)
1// 1) Global alan: sabitler ve değişkenler / Global area: constants and variables
2const int LED_PIN = 13;        // dahili LED / built-in LED
3unsigned long passCount = 0;   // loop() kaç kez döndü / how many times loop() has run
4
5// 2) setup(): açılışta bir kez çalışır / runs once at power-up or reset
6void setup() {
7  pinMode(LED_PIN, OUTPUT);
8  Serial.begin(9600);
9  Serial.println("setup() bitti / setup() done");
10}
11
12// 3) loop(): sonsuza kadar tekrar eder / repeats forever
13void loop() {
14  passCount++;                       // her turda 1 artır / add 1 on every pass
15  Serial.print("Tur / Pass: ");
16  Serial.println(passCount);
17
18  digitalWrite(LED_PIN, HIGH);       // LED'i yak / LED on
19  delay(500);                        // 500 ms bekle / wait 500 ms
20  digitalWrite(LED_PIN, LOW);        // LED'i söndür / LED off
21  delay(500);
22}

Sube el código y abre el Monitor Serie a 9600 baudios: verás una vez "setup() done" y después un número de vuelta que aumenta cada segundo, mientras el LED integrado parpadea al mismo ritmo. Los dos delay(500) dentro de loop() alargan cada vuelta a un segundo más o menos; un loop() sin esperas ni impresiones por el puerto serie puede dar cientos de miles de vueltas por segundo en un Uno. Para sacarle todo el partido al Monitor Serie, echa un vistazo a la guía Arduino Serial Monitor.

Variables y tipos de datos: int, long, float, bool, byte

Una variable es una caja con nombre en la memoria que guarda un valor. Para definirla escribes primero su tipo, luego su nombre y, si quieres, un valor inicial, por ejemplo int counter = 0;. El tipo decide cuántos bytes ocupa la caja y qué rango de números puede guardar. El Arduino Uno solo tiene 2 KB de RAM, así que elegir bien el tipo importa. Los nombres de variable empiezan por una letra o un guion bajo y no pueden llevar espacios, tildes ni la ñ.

TipoTamaño en el UnoRangoUso típico
bool1 bytetrue o false¿Está el LED encendido?, ¿se pulsó el botón?
byte1 byte0 – 255Número de pin, brillo PWM
char1 byte-128 – 127Un solo carácter: 'A'
int2 bytes-32.768 – 32.767Contadores, resultado de analogRead()
unsigned int2 bytes0 – 65.535Solo números positivos
long4 bytes-2.147.483.648 – 2.147.483.647Números enteros grandes
unsigned long4 bytes0 – 4.294.967.295Medir tiempo con millis()
float4 bytesAprox. ±3,4 × 10^38, 6–7 cifras de precisiónTemperatura, tensión, promedios
double4 bytes (en el Uno igual que float)Igual que floatEn el ESP32 ocupa 8 bytes

Unas cuantas reglas prácticas te ahorrarán muchos problemas. analogRead() devuelve un número de 0 a 1023, así que con un int basta. millis(), en cambio, devuelve un unsigned long; si guardas el tiempo en una variable int, el valor se desborda a los 32,7 segundos y tu temporización deja de funcionar. En el Uno las operaciones con float se hacen por software, por lo que son más lentas que las de enteros, y a partir de 6–7 cifras el resultado ya no es fiable; por eso no compares dos float con ==. Si quieres un tipo que tenga el mismo tamaño en cualquier placa, usa los que llevan el tamaño en el nombre, como uint8_t, int16_t y uint32_t.

Desbordamiento (overflow): ¿qué pasa cuando una variable supera su límite?

Si intentas guardar un número mayor del que cabe en su tipo, Arduino no da ningún error; el número vuelve a empezar como el cuentakilómetros de un coche. En el Uno un int guarda como máximo 32.767, y si le sumas 1 se convierte en -32.768. Un byte pasa de 255 a 0. Una trampa más traicionera son las cuentas con constantes: una expresión como 60 * 1000 se calcula primero como int y se desborda, aunque el resultado se guarde en una variable long. El siguiente sketch muestra todos estos casos en el Monitor Serie, y su loop() enseña un desbordamiento de byte en un LED:

Arduino (C++)
1const int LED_PIN = 9;        // PWM destekli pin (~9) / PWM-capable pin (~9)
2byte brightness = 0;          // byte: 0-255
3
4void setup() {
5  pinMode(LED_PIN, OUTPUT);
6  Serial.begin(9600);
7
8  int a = 32767;              // Uno'da int'in en büyük değeri / largest int on an Uno
9  a = a + 1;                  // taşma: -32768 olur / overflow: becomes -32768
10  Serial.println(a);
11
12  long wrong = 60 * 1000;     // int ile hesaplanır, taşar: -5536 / computed as int, overflows: -5536
13  long right = 60 * 1000L;    // L eki hesabı long yapar: 60000 / the L suffix makes it long math: 60000
14  Serial.println(wrong);
15  Serial.println(right);
16
17  float half = 5 / 2;         // tam sayı bölmesi: 2.00 / integer division: 2.00
18  float half2 = 5 / 2.0;      // ondalıklı bölme: 2.50 / floating-point division: 2.50
19  Serial.println(half);
20  Serial.println(half2);
21}
22
23void loop() {
24  analogWrite(LED_PIN, brightness);   // parlaklığı uygula / apply the brightness
25  brightness++;                       // 255'ten sonra 0'a döner / wraps to 0 after 255
26  delay(10);
27}
Línea de códigoResultado en el UnoResultado en ESP32 y Pico¿Por qué?
a = 32767 + 1-3276832768int ocupa 2 bytes en el Uno y 4 bytes en los demás
60 * 1000-553660000Las constantes se calculan primero como int
60 * 1000L6000060000El sufijo L hace que la cuenta sea long
5 / 22.002.00La división de dos enteros da un entero
5 / 2.02.502.50Si un número tiene decimales, el resultado también

Fíjate en el LED de D9: su brillo sube despacio de 0 a 255, luego la línea brightness++ convierte el valor en 0 en lugar de 256 y el LED se apaga de golpe. Es la forma más fácil de ver un desbordamiento con tus propios ojos. D9 es uno de los pines PWM del Uno marcados con ~, por eso analogWrite() funciona ahí; el PWM se explica a fondo en ¿qué es PWM?. Que millis() vuelva a cero a los 49,7 días más o menos también es un desbordamiento, y no causa problemas si el código está bien escrito; verás cómo hacerlo en la guía millis() en lugar de delay().

En el Arduino IDE abre Archivo > Preferencias y pon las Advertencias del compilador (Compiler warnings) en "Más" o "Todas". Así, cuando escribas una expresión que se desborda como 60 * 1000, el compilador mostrará el aviso integer overflow in expression y detectarás el fallo antes de que el código llegue a la placa.

const y #define: valores que nunca cambian

Para los números que no cambian mientras se ejecuta el programa, como los números de pin o los umbrales, hay dos maneras de darles nombre. Una constante definida con const tiene tipo, respeta las reglas de ámbito y, si intentas cambiarla por error, el compilador te para con assignment of read-only variable 'LED_PIN'. #define, en cambio, es una directiva del preprocesador: antes de compilar sustituye cada BUTTON_PIN del código por 2. No tiene tipo, y en esa línea no se ponen ni punto y coma ni signo igual. Con números sencillos ninguna de las dos gasta RAM, porque el compilador coloca el valor directamente en el código. Hoy se prefiere const, y escribir los nombres de las constantes en MAYÚSCULAS es una costumbre muy extendida. El siguiente ejemplo usa las dos: el LED se enciende mientras mantienes pulsado el botón.

Arduino (C++)
1#define BUTTON_PIN 2          // önişlemci: derlemeden önce metin değişimi / preprocessor: text swap before compiling
2const int LED_PIN = 13;       // tipi olan, salt okunur sabit / typed, read-only constant
3
4void setup() {
5  pinMode(LED_PIN, OUTPUT);
6  pinMode(BUTTON_PIN, INPUT_PULLUP);   // dahili pull-up: basılınca LOW / internal pull-up: LOW when pressed
7}
8
9void loop() {
10  if (digitalRead(BUTTON_PIN) == LOW) {   // buton basılı mı? / is the button pressed?
11    digitalWrite(LED_PIN, HIGH);          // evet: LED yanar / yes: LED on
12  } else {
13    digitalWrite(LED_PIN, LOW);           // hayır: LED söner / no: LED off
14  }
15}
Si escribes #define LED_PIN 13;, el punto y coma pasa a formar parte del valor y la línea pinMode(LED_PIN, OUTPUT) se convierte en pinMode(13;, OUTPUT). El compilador muestra expected ')' before ';' token; la nota in expansion of macro 'LED_PIN' que aparece debajo indica que el problema surge donde se usa la constante, pero la línea que tienes que corregir es la del #define. Escribir #define LED_PIN = 13 también da error: expected primary-expression before '=' token.

Operadores: aritméticos, de comparación y lógicos

Los operadores te permiten hacer cuentas con las variables, compararlas y combinar condiciones. Casi todos los que usarás en tus proyectos del día a día están en esta tabla:

GrupoOperadorSignificadoEjemplo
Aritméticos+ - * /Las cuatro operacionesx = a * 2;
Resto%Resto de una división (solo con enteros)7 % 3 da 1
Asignación= += -= *= /=Asignar, sumar, restar…total += 5;
Incremento, decremento++ --Sumar 1 o restar 1i++;
Comparación== != < > <= >=El resultado es true o falseif (x >= 10)
Lógicos&& || !Y, O, NOif (a && !b)
Condicional? :if-else abreviadox > 0 ? HIGH : LOW
A nivel de bits& | ^ ~ << >>Opera sobre los bits de un número1 << 3 da 8

Las dos parejas que más se confunden son = y == y & y &&. Un signo igual asigna un valor; dos signos comparan. Un solo & trabaja con los bits de los números, mientras que && pregunta si las dos condiciones son verdaderas. Puedes probar las tablas de verdad de Y, O y NO en la herramienta puertas lógicas, y los operadores de bits se explican a fondo en la guía sistemas de numeración y operaciones con bits. El siguiente ejemplo cuenta cuántas veces se ha pulsado el botón y enciende el LED tras un número impar de pulsaciones:

Arduino (C++)
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4int presses = 0;               // toplam basış sayısı / total number of presses
5bool lastPressed = false;      // önceki turdaki durum / state on the previous pass
6
7void setup() {
8  pinMode(LED_PIN, OUTPUT);
9  pinMode(BUTTON_PIN, INPUT_PULLUP);
10  Serial.begin(9600);
11}
12
13void loop() {
14  bool pressed = (digitalRead(BUTTON_PIN) == LOW);   // karşılaştırma: true ya da false / comparison: true or false
15
16  if (pressed && !lastPressed) {     // şimdi basılı VE önceden değildi: yeni basış / pressed now AND not before: new press
17    presses++;                       // 1 artır / increment by 1
18    Serial.print("Basış / Presses: ");
19    Serial.println(presses);
20    delay(50);                       // basit debounce / simple debounce
21  }
22  lastPressed = pressed;
23
24  // Kalan (%) ile tek mi çift mi? / odd or even using the remainder (%)
25  digitalWrite(LED_PIN, (presses % 2 == 1) ? HIGH : LOW);
26}

La clave está en la condición pressed && !lastPressed: significa "pulsado ahora Y no pulsado en la vuelta anterior". Así el número no sigue subiendo mientras mantienes el botón y cada pulsación se cuenta una sola vez. presses % 2 da el resto de dividir el número entre 2: 1 para los impares y 0 para los pares. El operador ? : de la última línea elige HIGH o LOW según ese resultado.

if, else if y else: tomar decisiones

if ejecuta el código entre llaves cuando la condición entre paréntesis es verdadera. Si es falsa, se ejecuta el bloque else, si lo hay. Cuando necesitas más opciones, añades else if en medio. Las condiciones se comprueban de arriba abajo y, en cuanto se ejecuta el bloque de la primera verdadera, el resto se ignora. En C++ cualquier valor distinto de cero se considera verdadero, así que if (counter) significa "si el contador no es cero". Ya vimos un ejemplo sencillo de if-else en la sección de const. Ahora vamos a escribir uno que distingue una pulsación corta de una larga:

Arduino (C++)
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4void setup() {
5  pinMode(LED_PIN, OUTPUT);
6  pinMode(BUTTON_PIN, INPUT_PULLUP);
7  Serial.begin(9600);
8}
9
10void loop() {
11  if (digitalRead(BUTTON_PIN) == LOW) {             // basıldı / pressed
12    unsigned long start = millis();
13    while (digitalRead(BUTTON_PIN) == LOW) {         // bırakılana kadar bekle / wait until released
14    }
15    unsigned long held = millis() - start;          // basılı kalma süresi (ms) / hold time (ms)
16
17    if (held < 50) {
18      return;                                       // 50 ms'den kısa: parazit, yok say / under 50 ms: bounce, ignore
19    } else if (held < 1000) {
20      digitalWrite(LED_PIN, HIGH);                  // kısa basış: LED yak / short press: LED on
21      Serial.println("Kısa basış / Short press");
22    } else {
23      digitalWrite(LED_PIN, LOW);                   // uzun basış: LED söndür / long press: LED off
24      Serial.println("Uzun basış / Long press");
25    }
26  }
27}

En cuanto se pulsa el botón, el código guarda el momento con millis(), espera en un bucle while hasta que se suelta y calcula el tiempo transcurrido. Las pulsaciones de menos de 50 ms se consideran ruido del rebote mecánico del pulsador, y return termina antes de tiempo esa vuelta de loop(). Una pulsación de menos de 1 segundo enciende el LED; una más larga lo apaga. El orden de las condiciones importa: si hubieras puesto primero la comprobación held < 1000, un rebote de 30 ms también contaría como "pulsación corta".

Escribir if (mode = 2) es el error clásico: esta línea no compara nada, asigna 2 a mode y, como el resultado no es cero, la condición siempre es verdadera. Otro fallo traicionero es poner un punto y coma justo después del paréntesis, como en if (x == 5);: el if termina con una instrucción vacía y el bloque de abajo se ejecuta siempre, se cumpla o no la condición.

switch-case: elegir entre muchos casos

Si vas a hacer muchas cosas distintas según el valor de una variable, switch-case se lee mejor que una larga cadena de else if. switch compara el valor entre paréntesis con cada etiqueta case y empieza a ejecutar desde la que coincide. Debes terminar cada case con break; si no, el programa sigue y ejecuta también el código del siguiente case. Si no coincide ninguno, se ejecuta el bloque default. switch solo funciona con tipos enteros como int, byte, char y enum; para String, float o rangos como "entre 10 y 20" necesitas if. En el siguiente ejemplo cada pulsación pasa la lámpara al siguiente modo: apagada, encendida, parpadeo lento y parpadeo rápido.

Arduino (C++)
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4byte mode = 0;                 // 0 kapalı, 1 açık, 2 yavaş, 3 hızlı / 0 off, 1 on, 2 slow, 3 fast
5bool lastPressed = false;
6
7void setup() {
8  pinMode(LED_PIN, OUTPUT);
9  pinMode(BUTTON_PIN, INPUT_PULLUP);
10}
11
12void loop() {
13  bool pressed = (digitalRead(BUTTON_PIN) == LOW);
14  if (pressed && !lastPressed) {
15    mode = (mode + 1) % 4;      // 0 → 1 → 2 → 3 → 0
16    delay(50);                  // basit debounce / simple debounce
17  }
18  lastPressed = pressed;
19
20  switch (mode) {
21    case 0:                                        // kapalı / off
22      digitalWrite(LED_PIN, LOW);
23      break;
24    case 1:                                        // sürekli açık / always on
25      digitalWrite(LED_PIN, HIGH);
26      break;
27    case 2:                                        // 500 ms'de bir değiş / toggle every 500 ms
28      digitalWrite(LED_PIN, (millis() / 500) % 2 ? HIGH : LOW);
29      break;
30    case 3:                                        // 100 ms'de bir değiş / toggle every 100 ms
31      digitalWrite(LED_PIN, (millis() / 100) % 2 ? HIGH : LOW);
32      break;
33    default:                                       // beklenmeyen değer: başa dön / unexpected value: reset
34      mode = 0;
35      break;
36  }
37}

La línea mode = (mode + 1) % 4 hace avanzar el modo por 0, 1, 2, 3 y, gracias al operador %, lo devuelve a 0 al llegar a 4. Fíjate en que no usamos delay() para parpadear: la expresión (millis() / 500) % 2 cambia entre 0 y 1 cada 500 ms, así el botón responde al instante mientras el LED parpadea. La versión más elaborada de este enfoque de "modos" se llama máquina de estados; puedes ver cómo se usa en proyectos como semáforos y menús en la guía máquina de estados con Arduino.

Si vas a definir una variable dentro de un case, mete el código de ese case entre llaves: case 1: { byte step = 3; ... break; }. Sin llaves, el Uno lo compila solo con una advertencia, pero el ESP32 da el error jump to case label y el código no llega a compilar.

Bucles for, while y do-while

Los bucles ejecutan un bloque de código una y otra vez. Los paréntesis de un bucle for tienen tres partes: el inicio (int i = 0), la condición para seguir (i < 6) y el paso tras cada vuelta (i++). for es ideal cuando sabes cuántas veces quieres repetir. while sigue mientras su condición sea verdadera y la comprueba antes de cada vuelta, por lo que encaja con esperas cuya duración no conoces de antemano. do-while comprueba la condición al final, así que siempre se ejecuta al menos una vez. Dentro de un bucle, break sale de él inmediatamente y continue se salta el resto de la vuelta actual y pasa a la siguiente. Como loop() ya es un bucle infinito, normalmente no necesitas escribir while (true) dentro.

Bucle¿Cuándo se comprueba la condición?¿Cuándo usarlo?Ejemplo
forAntes de cada vueltaCuando sabes cuántas repeticiones hayfor (int i = 0; i < 6; i++)
whileAntes de cada vueltaPara esperar a que ocurra algowhile (digitalRead(2) == HIGH)
do-whileDespués de cada vueltaCuando debe ejecutarse al menos una vezdo { ... } while (x < 10);

El siguiente ejemplo crea un efecto coche fantástico con seis LED. El bucle for de setup() pone los seis pines como salida en una sola línea, y el bucle while de loop() espera hasta que se pulsa el botón. Mientras lo mantengas pulsado, la luz va de D8 a D13 y vuelve:

Arduino (C++)
1const int FIRST_LED = 8;       // ilk LED pini / first LED pin
2const int LAST_LED = 13;       // son LED pini / last LED pin
3const int BUTTON_PIN = 2;
4
5void setup() {
6  for (int pin = FIRST_LED; pin <= LAST_LED; pin++) {
7    pinMode(pin, OUTPUT);      // 6 pini tek döngüyle çıkış yap / make 6 pins outputs with one loop
8  }
9  pinMode(BUTTON_PIN, INPUT_PULLUP);
10}
11
12void loop() {
13  while (digitalRead(BUTTON_PIN) == HIGH) {
14    // butona basılana kadar burada bekle / wait here until the button is pressed
15  }
16
17  for (int pin = FIRST_LED; pin <= LAST_LED; pin++) {      // ileri: 8 → 13 / forward: 8 → 13
18    digitalWrite(pin, HIGH);
19    delay(80);
20    digitalWrite(pin, LOW);
21  }
22  for (int pin = LAST_LED - 1; pin > FIRST_LED; pin--) {   // geri: 12 → 9 / back: 12 → 9
23    digitalWrite(pin, HIGH);
24    delay(80);
25    digitalWrite(pin, LOW);
26  }
27}

Que el bucle de vuelta empiece en LAST_LED - 1 y siga solo mientras el pin sea mayor que FIRST_LED es una decisión a propósito: los LED de los extremos no se encienden dos veces seguidas y el movimiento se ve fluido. Para regular la velocidad, un efecto de llenado y más patrones, consulta la guía efecto coche fantástico y luces secuenciales con Arduino.

El bucle for (byte i = 0; i < 256; i++) no termina nunca: un byte guarda como máximo 255, así que i vuelve a 0 antes de llegar a 256 y la condición se cumple siempre. Del mismo modo, un bucle while que espera una condición que nunca llega, por ejemplo un sensor con el cable suelto, bloquea el programa para siempre.

Arrays: muchos valores con un solo nombre

Un array guarda varios valores del mismo tipo, en orden, bajo un único nombre. La línea const byte LEDS[] = {8, 9, 10, 11, 12, 13}; crea un array de seis elementos. A los elementos se accede con corchetes y se cuenta desde 0: LEDS[0] vale 8 y LEDS[5] vale 13. La longitud de un array no puede cambiar después de definirlo. Si dejas que sizeof(LEDS) / sizeof(LEDS[0]) calcule el número de elementos en lugar de escribirlo a mano, tus bucles se adaptan solos cuando añades un pin nuevo al array. Un índice fuera del array, como LEDS[6], no da error; lee o estropea otra variable de la memoria. Por eso debes asegurarte de que el índice esté entre 0 y el número de elementos menos uno. El siguiente ejemplo convierte los seis LED en un dado electrónico:

Arduino (C++)
1const byte LEDS[] = {8, 9, 10, 11, 12, 13};               // LED pinleri / LED pins
2const byte LED_COUNT = sizeof(LEDS) / sizeof(LEDS[0]);    // eleman sayısı: 6 / number of elements: 6
3const byte BUTTON_PIN = 2;
4
5void setup() {
6  for (byte i = 0; i < LED_COUNT; i++) {     // indeks 0'dan başlar / index starts at 0
7    pinMode(LEDS[i], OUTPUT);
8  }
9  pinMode(BUTTON_PIN, INPUT_PULLUP);
10  randomSeed(analogRead(A0));                // boştaki A0'ın gürültüsüyle tohumla / seed from noise on the unconnected A0
11}
12
13void loop() {
14  if (digitalRead(BUTTON_PIN) == LOW) {
15    byte dice = random(1, 7);                // 1 ile 6 arası / between 1 and 6
16    for (byte i = 0; i < LED_COUNT; i++) {
17      digitalWrite(LEDS[i], (i < dice) ? HIGH : LOW);   // ilk 'dice' kadar LED yanar / the first 'dice' LEDs light up
18    }
19    delay(300);                              // basılı tutarsan zar dönmeye devam eder / keeps rolling while held
20  }
21}

random(1, 7) genera un número del 1 al 6, porque el límite superior no se incluye en el resultado. Se encienden tantos LED como indique: si sale un 4, se encienden los cuatro LED de LEDS[0] a LEDS[3]. La línea randomSeed(analogRead(A0)) usa como valor inicial el ruido eléctrico del pin A0, que está sin conectar; sin ella, la placa generaría la misma secuencia de números cada vez que se encendiera. Otra ventaja de los arrays: si pasas los LED a otros pines, solo tienes que cambiar la primera línea.

Escribir funciones: dividir el código en partes

Si haces la misma tarea en varios sitios del programa, conviértela en una función. Una función tiene un tipo de retorno, un nombre, unos parámetros entre paréntesis y un cuerpo entre llaves. Las funciones que no devuelven ningún valor son de tipo void; las que sí lo hacen entregan su resultado con return. Los parámetros se pasan como copia, de modo que cambiar un parámetro dentro de la función no cambia la variable del lugar desde el que se llamó. El Arduino IDE genera por sí mismo las declaraciones de las funciones de los archivos .ino, así que también puedes escribir una función debajo de loop(). En archivos .cpp o en PlatformIO, en cambio, tienes que declarar la función antes de usarla. Pon a tus funciones nombres que digan lo que hacen, como blinkLed o isPressed.

Arduino (C++)
1const byte LED_PIN = 13;
2const byte BUTTON_PIN = 2;
3
4// Değer döndürmeyen (void) fonksiyon / function that returns nothing (void)
5void blinkLed(byte pin, byte times, int ms) {
6  for (byte i = 0; i < times; i++) {
7    digitalWrite(pin, HIGH);
8    delay(ms);
9    digitalWrite(pin, LOW);
10    delay(ms);
11  }
12}
13
14// bool döndüren fonksiyon / function that returns a bool
15bool isPressed(byte pin) {
16  return digitalRead(pin) == LOW;      // INPUT_PULLUP: basılı = LOW / pressed = LOW
17}
18
19void setup() {
20  pinMode(LED_PIN, OUTPUT);
21  pinMode(BUTTON_PIN, INPUT_PULLUP);
22  blinkLed(LED_PIN, 3, 100);           // açılışta 3 hızlı yanıp sönme / 3 quick blinks at start-up
23}
24
25void loop() {
26  if (isPressed(BUTTON_PIN)) {
27    blinkLed(LED_PIN, 2, 250);         // basınca 2 yavaş yanıp sönme / 2 slow blinks per press
28  }
29}

blinkLed() recibe como parámetros el pin, el número de parpadeos y la velocidad, así que puedes reutilizar la misma función para otro LED y otra velocidad. isPressed() devuelve un bool y guarda en un solo lugar la lógica "pulsado = LOW" de INPUT_PULLUP; si algún día conectas el botón al revés, solo cambias esa línea. Eso sí, el delay() de dentro de la función sigue deteniendo el programa. Para funciones que hagan varias cosas a la vez necesitas una temporización basada en millis().

Variables globales, locales y static

El lugar donde se puede ver una variable se llama su ámbito. Las variables globales, definidas fuera de las funciones, viven durante todo el programa y se pueden leer y cambiar desde cualquier parte. Las variables locales, definidas dentro de una función o de un bloque entre llaves, se crean de nuevo cada vez que se ejecuta ese bloque y desaparecen al terminar. De aquí viene la queja más común de los principiantes: "mi contador siempre se queda en 1". Si pones static delante de una variable local, solo toma su valor inicial la primera vez y conserva su valor entre vueltas, aunque sigue siendo visible solo dentro de esa función.

Arduino (C++)
1const byte LED_PIN = 13;
2const byte BUTTON_PIN = 2;
3
4int globalCount = 0;            // global: her yerden görünür, değerini korur / global: visible everywhere, keeps its value
5
6void setup() {
7  pinMode(LED_PIN, OUTPUT);
8  pinMode(BUTTON_PIN, INPUT_PULLUP);
9  Serial.begin(9600);
10}
11
12void loop() {
13  int localCount = 0;           // yerel: her turda yeniden 0 olur / local: back to 0 on every pass
14  static int staticCount = 0;   // static yerel: değerini korur, yalnız burada görünür / static local: keeps its value, visible only here
15
16  if (digitalRead(BUTTON_PIN) == LOW) {
17    globalCount++;
18    localCount++;
19    staticCount++;
20    Serial.print(globalCount);
21    Serial.print(' ');
22    Serial.print(localCount);
23    Serial.print(' ');
24    Serial.println(staticCount);                   // 3. basışta: 3 1 3 / on the 3rd press: 3 1 3
25    digitalWrite(LED_PIN, (staticCount % 2) ? HIGH : LOW);
26    delay(300);
27  }
28}

Si pulsas el botón tres veces, el Monitor Serie muestra las líneas 1 1 1, 2 1 2 y 3 1 3: los contadores global y static siguen subiendo, mientras que el local siempre se queda en 1 porque vuelve a empezar desde 0 en cada vuelta de loop(). Si defines una variable local con el mismo nombre que una global, la local tapa a la global y dentro de esa función nunca llegas a tocar la variable global. Las variables globales que cambia una función de interrupción (ISR) deben declararse además como volatile; encontrarás los detalles en la guía interrupciones en Arduino.

Mantén las variables lo más locales posible: usa static para los contadores que solo necesita una función y global para los valores que comparten varias funciones. La línea Global variables use … bytes of dynamic memory que muestra el IDE al compilar indica cuánta RAM ocupan tus variables globales. En el Uno pueden aparecer comportamientos inestables cuando pasa del 75 por ciento; tienes soluciones en la guía memoria insuficiente en Arduino.

Programar Arduino en el ESP32: el mismo lenguaje, otros tamaños

Cuando programas el ESP32 con el Arduino IDE usas el mismo lenguaje C++ y los mismos comandos, pero hay algunas diferencias. Como el ESP32 es un procesador de 32 bits, int ocupa 4 bytes y guarda valores de -2.147.483.648 a 2.147.483.647, así que 32767 + 1 en el ejemplo de desbordamiento da 32768. double ocupa 8 bytes, analogRead() devuelve valores de 0 a 4095 y los pines trabajan a 3,3 V. Con Serial.printf() puedes imprimir con formato. random() usa por defecto el generador de números aleatorios por hardware, así que no hace falta la línea randomSeed(). El siguiente programa reúne todo lo que has aprendido en este artículo: un array, una función, switch-case y un botón que cambia de modo. Para conocer las características generales de la placa, mira ¿qué es el ESP32?.

Arduino (C++)
1// ESP32: aynı dil, farklı pinler / ESP32: same language, different pins
2const byte LEDS[] = {18, 19, 21, 22, 23, 25};            // güvenli GPIO'lar / safe GPIOs
3const byte LED_COUNT = sizeof(LEDS) / sizeof(LEDS[0]);
4const byte BUTTON_PIN = 4;                                // diğer bacağı GND'ye / other leg to GND
5
6byte mode = 0;                  // 0 kapalı, 1 yürüyen ışık, 2 hepsi yanıp söner / 0 off, 1 chaser, 2 all blink
7bool lastPressed = false;
8
9void setAll(bool on) {
10  for (byte i = 0; i < LED_COUNT; i++) {
11    digitalWrite(LEDS[i], on ? HIGH : LOW);
12  }
13}
14
15void setup() {
16  Serial.begin(115200);
17  for (byte i = 0; i < LED_COUNT; i++) {
18    pinMode(LEDS[i], OUTPUT);
19  }
20  pinMode(BUTTON_PIN, INPUT_PULLUP);
21  Serial.printf("int = %d bayt / bytes\n", (int)sizeof(int));   // ESP32'de 4 / 4 on the ESP32
22
23  int a = 32767;
24  a = a + 1;
25  Serial.println(a);                                          // ESP32'de 32768, taşma yok / 32768 on the ESP32, no overflow
26}
27
28void loop() {
29  bool pressed = (digitalRead(BUTTON_PIN) == LOW);
30  if (pressed && !lastPressed) {
31    mode = (mode + 1) % 3;      // 0 → 1 → 2 → 0
32    delay(50);
33  }
34  lastPressed = pressed;
35
36  switch (mode) {
37    case 0:
38      setAll(false);
39      break;
40    case 1: {                                                 // case içinde değişken için süslü parantez / braces for a variable inside a case
41      byte step = (millis() / 120) % LED_COUNT;               // sıradaki LED / LED whose turn it is
42      for (byte i = 0; i < LED_COUNT; i++) {
43        digitalWrite(LEDS[i], (i == step) ? HIGH : LOW);
44      }
45      break;
46    }
47    case 2:
48      setAll((millis() / 300) % 2);                           // 300 ms'de bir hepsi / all of them every 300 ms
49      break;
50  }
51}

Para ejecutar este programa en un Uno solo tienes que cambiar el array LEDS a {8, 9, 10, 11, 12, 13} y BUTTON_PIN a 2, y sustituir la línea de Serial.printf(), que el Uno no tiene, por Serial.print(). Esa es justo la ventaja de definir los números de pin como constantes y arrays al principio. Fíjate también en que el bloque de case 1 va entre llaves; como dentro se define la variable step, esas llaves son obligatorias en el ESP32.

Raspberry Pi Pico: la misma lógica en MicroPython

Puedes programar la Raspberry Pi Pico en C++ añadiendo al Arduino IDE el paquete de placas RP2040; en ese caso el código de este artículo funciona en cuanto cambias los números de pin, y int también ocupa 4 bytes. Aun así, lo más habitual es usar la Pico con MicroPython. En Python no escribes el tipo de las variables, los bloques se marcan con la sangría en lugar de con llaves, no hay punto y coma al final de la línea y los enteros no se desbordan, sino que crecen todo lo necesario. El siguiente código es la versión en MicroPython del dado electrónico. Para la instalación, consulta ¿qué es la Raspberry Pi Pico?, y para los detalles del lenguaje, fundamentos de MicroPython.

MicroPython
1from machine import Pin
2import time, random
3
4leds = [Pin(n, Pin.OUT) for n in (14, 13, 12, 11, 10, 9)]   # liste = dizi / list = array
5button = Pin(2, Pin.IN, Pin.PULL_UP)                        # basılınca 0 / reads 0 when pressed
6
7def show(count):                                            # fonksiyon / function
8    for i, led in enumerate(leds):                          # for döngüsü / for loop
9        led.value(1 if i < count else 0)
10
11show(0)                                                     # setup() karşılığı: bir kez çalışır / like setup(): runs once
12while True:                                                 # loop() karşılığı / like loop()
13    if button.value() == 0:                                 # buton basılı mı? / is the button pressed?
14        show(random.randint(1, 6))                          # 1-6 arası zar at / roll a number from 1 to 6
15        time.sleep_ms(300)
ConceptoArduino (C++)MicroPython
Parte que se ejecuta una vezsetup()El código antes de la línea while True:
Parte que se repite siempreloop()while True:
Variableint x = 5;x = 5
Constanteconst int PIN = 2;PIN = const(2) (from micropython import const)
Condiciónif / else if / elseif / elif / else
Selección múltipleswitch-caseCadena de if / elif
Bucle de conteofor (int i = 0; i < 6; i++)for i in range(6):
Arraybyte a[] = {1, 2, 3};a = [1, 2, 3]
Funciónvoid blink(int pin) { ... }def blink(pin):
Lógica&& || !and or not
Comentario// comentario# comentario

Ideas de proyectos con estas bases

  • Semáforo: monta un cruce con botón para peatones usando constantes, delay() e if; la guía paso a paso está en semáforo con Arduino.
  • Coche fantástico y luces secuenciales: crea decenas de patrones de luz distintos con un bucle for y un array.
  • Dado electrónico: lleva el dado de 6 LED a un display de 7 segmentos con un array, random() y una función; para ello mira la guía display de 7 segmentos.
  • Test de reacción: enciende un LED en un momento aleatorio y mide con un unsigned long y millis() lo rápido que pulsas el botón.
  • Lámpara con modos: apagada, fija, respiración y destello desde un solo botón con switch-case; para una versión en color, echa un vistazo a cómo usar un LED RGB.
  • Cerradura con clave: guarda las teclas pulsadas en un array y compáralas con la clave almacenada; tienes los detalles en la guía teclado matricial con Arduino.

Errores frecuentes y cómo solucionarlos

ErrorSíntomaSolución
Usar = en una comparaciónEl bloque if se ejecuta siempreUsa == para comparar
Poner ; o = en la línea #defineexpected ')' before ';' tokenEscríbelo como #define LED_PIN 13
Escribir mal un comando'pinmode' was not declared in this scopeCuida mayúsculas y minúsculas, como en pinMode
Tildes o ñ en el nombre de una variablestray '\303' in programUsa solo letras inglesas, números y _ en los nombres
Calcular 60 * 1000 como intEl resultado es -5536Escribe 60 * 1000L o 60000UL
Guardar millis() en un intLa temporización falla a los 32 segundosUsa unsigned long
División entera5 / 2 da 2Escribe 5 / 2.0 o convierte a float
Olvidar el break al final de un caseTambién se ejecuta el siguiente caseTermina cada case con break
Escribir fuera de un arrayValores aleatorios, cuelguesMantén el índice entre 0 y el número de elementos - 1
Definir un contador como localEl contador siempre se queda en 1Hazlo global o static
Cuando te salga un error de compilación, lee solo la primera línea de error y corrígela; la mayoría de los errores de las líneas siguientes son consecuencia del primero. Lo que significa cada mensaje lo encontrarás en la guía errores de compilación en Arduino. Si no tienes una placa a mano, también puedes probar el código en una simulación de Tinkercad.

Una vez que domines estas piezas, el siguiente paso es conocer los comandos más usados: la lista de comandos de Arduino explica con ejemplos todo, desde pinMode() hasta attachInterrupt(). En la app BlueGrays también encontrarás proyectos como encender un LED, controlar un LED con un pulsador, el coche fantástico, un semáforo y un dado, paso a paso con esquemas de circuito y código listo para usar, para que pruebes en un circuito real cada estructura que has aprendido aquí.

Preguntas frecuentes

¿Con qué lenguaje de programación se programa Arduino?

El código de Arduino se basa en C++. El núcleo de Arduino añade al lenguaje funciones listas para usar como pinMode(), digitalWrite() y delay(), y el código se escribe en archivos con extensión .ino. Placas como el ESP32 y la Raspberry Pi Pico también se pueden programar con MicroPython si lo prefieres.

¿Por dónde empiezo a aprender programación Arduino?

Primero instala el Arduino IDE y sube el ejemplo Blink, que hace parpadear el LED integrado. Después prueba, por este orden, setup() y loop(), las variables, if-else, los bucles, los arrays y las funciones, cada uno con un circuito de LED o pulsador. Usar cada tema nuevo en un pequeño proyecto hace que se te quede mucho mejor que solo leerlo.

¿Qué diferencia hay entre int y long en Arduino?

En el Arduino Uno un int ocupa 2 bytes y guarda números de -32.768 a 32.767, mientras que un long ocupa 4 bytes y llega hasta unos ±2.100 millones. Un número que supera ese límite vuelve a empezar sin dar error, por eso para valores grandes como millis() se usa long o unsigned long. En el ESP32 y la Pico, int también ocupa 4 bytes.

¿Qué diferencia hay entre const y #define en Arduino?

Una constante definida con const tiene tipo, respeta el ámbito y el compilador da error si intentas cambiarla. #define es una sustitución de texto que se hace antes de compilar, no tiene tipo y provoca errores inesperados si termina en punto y coma. Con números sencillos ninguna de las dos gasta RAM, pero se suele preferir const porque es más seguro.

¿Qué diferencia hay entre el bucle for y el bucle while en Arduino?

El bucle for reúne en una línea el inicio, la condición y el paso, y se usa cuando sabes cuántas veces quieres repetir, por ejemplo para encender seis LED uno tras otro. El bucle while solo comprueba una condición y sigue mientras sea verdadera, por lo que encaja con situaciones de duración desconocida, como esperar a que se pulse un botón.

¿Se puede programar Arduino con Python?

Las placas de 8 bits como el Arduino Uno no pueden ejecutar Python, por eso se programan en C++. En cambio, el ESP32 y la Raspberry Pi Pico sí se pueden programar con MicroPython. Además, un programa en Python que se ejecute en tu ordenador puede controlar un Uno de forma indirecta comunicándose con él por el puerto serie.

Ver todos los proyectos
Parpadeo de LED
#1Principiante

Parpadeo de LED

El primer hola de la electrónica: encender y apagar un solo LED. El Arduino enciende y apaga el LED a intervalos cortos, así que la luz parpadea rápido. Ves de la forma más sencilla cómo la placa cont

4.6(179)
978
Encender un LED con pulsador (Push)
#9Principiante

Encender un LED con pulsador (Push)

Como un timbre: la luz se enciende mientras pulsas y se apaga cuando sueltas. El Arduino escucha el botón todo el rato; mientras está pulsado enciende el LED, y en cuanto levantas el dedo lo apaga. Co

4.9(112)
198
Encender y apagar un LED con un botón
#10Principiante

Encender y apagar un LED con un botón

Pulsas el botón una vez y la luz se enciende, lo pulsas otra vez y se apaga, igual que el interruptor de tu habitación. El Arduino nota cada pulsación y cambia el LED al estado contrario, esperando un

4.8(100)
162
Efecto coche fantástico con LED
#6Intermedio

Efecto coche fantástico con LED

La clásica luz que va y viene: un punto de luz recorre seis LED de un extremo al otro y luego regresa. El Arduino enciende cada LED una décima de segundo antes de pasar al siguiente, da la vuelta en c

4.8(104)
196
Semáforo
#3Principiante

Semáforo

Una copia en miniatura del semáforo que ves cada día en la calle. El rojo se queda dos segundos, el amarillo brilla un instante, el verde se queda dos segundos y luego vuelve al rojo pasando otra vez

4.8(129)
367
Control de LED con pulsador en ESP32 (Push)
#104Principiante

Control de LED con pulsador en ESP32 (Push)

El LED se enciende cuando se pulsa el botón y se apaga cuando se suelta. Muestra cómo se utiliza el pin de entrada digital del ESP32 y la resistencia pull-up interna. Gracias al modo INPUT_PULLUP, no se necesita ninguna resistencia externa.

4.6(36)
55
Raspberry Pi Pico Botón Toggle LED (GP14 + GP2)
#156Principiante
Premium

Raspberry Pi Pico Botón Toggle LED (GP14 + GP2)

¡Botón + LED — siguiente nivel! 🔁 A diferencia de #155, no necesitas mantener el botón presionado. Presiona y suelta → LED encendido. Presiona de nuevo → LED apagado (toggle). Mismo circuito que #155, gran diferencia de código. Aprende edge detection, state tracking y debounce.

0.0(0)
9
Proyecto Dados
#79Intermedio
Premium

Proyecto Dados

Se acabaron los dados perdidos en los juegos de mesa: cada vez que pulsas el botón aparece en la pantalla de 7 segmentos un número al azar del 1 al 6. Cada pulsación da un solo resultado, que también

5.0(48)
67