Arduino programmieren lernen: Variablen, Schleifen, Funktionen
Arduino programmieren lernen: setup und loop, int, long, byte, Überlauf, const, #define, if-else, switch-case, Schleifen, Arrays, Funktionen mit LED-Beispielen.
Arduino programmieren heißt, einem Arduino-Board in der Sprache C/C++ zu sagen, was es tun soll: Du schreibst, welche LED wann leuchtet, was beim Drücken eines Tasters passiert und wie eine Zahl vom Sensor ausgewertet wird, und das Board wiederholt diese Anweisungen tausende Male pro Sekunde. Die gute Nachricht: Fast jeder Sketch, den du findest, vom Blinken einer LED bis zum Roboterauto, besteht aus denselben rund zehn Bausteinen. Das sind setup() und loop(), Variablen, Konstanten, Operatoren, if-else, switch-case, for- und while-Schleifen, Arrays und Funktionen.
In dieser Anleitung lernst du diese Bausteine nacheinander kennen, jeden mit einem echten LED- oder Taster-Beispiel. Alle Beispiele laufen auf derselben einfachen Schaltung und wurden für den Arduino Uno kompiliert und getestet. Die Stellen, an denen Einsteiger am häufigsten hängen bleiben, also Überlauf von Variablen, die Verwechslung von = und ==, ein vergessenes break und ein Zähler, der bei jedem Durchlauf neu startet, werden extra erklärt. In den letzten Abschnitten siehst du außerdem, wie dieselbe Logik auf dem ESP32 funktioniert und wie sie in MicroPython auf dem Raspberry Pi Pico aussieht.

Was ist Arduino-Programmierung und welche Sprache wird verwendet?
Arduino-Code basiert auf C++; eine eigene "Arduino-Sprache" gibt es nicht. Der Arduino-Core ergänzt C++ um fertige Funktionen wie pinMode(), digitalWrite() und delay(). Die Datei, die du schreibst, heißt Sketch und hat die Endung .ino. Wenn du auf Überprüfen klickst, fügt die Arduino IDE oben die Header-Datei Arduino.h ein und erzeugt die Funktionsdeklarationen selbst, und der Compiler übersetzt den Code in Maschinencode, den der Prozessor versteht. Der Hochladen-Button schickt das Ergebnis dann per USB an den Bootloader des Boards. Falls du die IDE noch nicht installiert hast, beginne mit der Anleitung Arduino IDE installieren.
Beim Schreiben von Code gibt es ein paar Grundregeln. Wenn du sie gleich am Anfang lernst, verschwindet die Hälfte der Compilerfehler, die du sonst in den ersten Tagen sehen würdest:
| Regel | Richtig | Wenn du sie brichst |
|---|---|---|
| Jede Anweisung endet mit einem Semikolon | digitalWrite(13, HIGH); | expected ';' before '}' token |
| Groß- und Kleinschreibung zählt | pinMode(13, OUTPUT); | 'pinmode' was not declared in this scope |
| Codeblöcke stehen in geschweiften Klammern | void loop() { ... } | Zu jeder { gehört eine } |
| Einzeiliger Kommentar | // diese Zeile wird nicht ausgeführt | Kommentare werden nicht kompiliert, sie dienen der Erklärung |
| Mehrzeiliger Kommentar | /* ... */ | Ohne Abschluss wird alles darunter zum Kommentar |
| Text in doppelten, ein Zeichen in einfachen Anführungszeichen | "Hallo" und 'A' | Vertauschte Anführungszeichen ergeben falsche Werte oder Fehler |
Die Schaltung für alle Beispiele
Alle Beispiele laufen auf einer Schaltung. Schließe sechs LEDs an die Pins D8–D13 an, jede über einen 220-Ω-Vorwiderstand; das kurze Bein (Kathode) jeder LED geht an GND. Den passenden Widerstand für deine LED kannst du mit dem LED-Vorwiderstandsrechner prüfen. Verbinde ein Bein des Tasters mit D2 und das diagonal gegenüberliegende Bein mit GND. Da der Code INPUT_PULLUP verwendet, brauchst du keinen externen Widerstand: Der Pin liest HIGH, wenn der Taster losgelassen ist, und LOW, wenn er gedrückt ist. Mehr dazu findest du in Taster am Arduino anschließen und Was ist eine LED?.
| Bauteil | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| 6 LEDs (je mit 220 Ω, Kathode an GND) | D8, D9, D10, D11, D12, D13 | GPIO 18, 19, 21, 22, 23, 25 | GP14, GP13, GP12, GP11, GP10, GP9 |
| Taster (anderes Bein an GND) | D2 | GPIO 4 | GP2 |
| LED auf dem Board | D13 (die mit L beschriftete LED) | GPIO 2 (bei den meisten DevKits) | GP25 (beim Pico W "LED") |
| Serielle Geschwindigkeit | 9600 Baud | 115200 Baud | Thonny-Shell |
setup() und loop(): das Grundgerüst jedes Sketches
Jeder Arduino-Sketch hat zwei Pflichtfunktionen. setup() läuft nur ein einziges Mal, wenn das Board startet oder der Reset-Knopf gedrückt wird; Vorbereitungen wie Pin-Einstellungen und Serial.begin() gehören hierher. loop() wiederholt sich danach endlos. Im Hintergrund hat der Arduino-Core eine versteckte main()-Funktion: Sie bereitet zuerst die Hardware vor, ruft dann setup() einmal auf und anschließend loop() in einer Endlosschleife immer wieder. Der Bereich ganz oben in der Datei, außerhalb dieser beiden Funktionen, ist der globale Bereich; hier definierst du Konstanten und Variablen, die von überall erreichbar sein sollen.
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}Lade den Code hoch und öffne den seriellen Monitor mit 9600 Baud: Du siehst einmal "setup() done" und danach jede Sekunde eine steigende Durchlaufnummer, während die eingebaute LED im selben Takt blinkt. Die beiden delay(500) in loop() verlängern jeden Durchlauf auf etwa eine Sekunde; eine loop() ganz ohne Wartezeit und serielle Ausgabe kann auf dem Uno hunderttausende Male pro Sekunde laufen. Wie du den seriellen Monitor optimal nutzt, erfährst du in der Anleitung Arduino Serial Monitor.
Variablen und Datentypen: int, long, float, bool, byte
Eine Variable ist eine benannte Schachtel im Speicher, die einen Wert aufbewahrt. Beim Definieren schreibst du zuerst den Datentyp, dann den Namen und auf Wunsch einen Startwert, zum Beispiel int counter = 0;. Der Typ legt fest, wie viele Bytes die Schachtel belegt und welchen Zahlenbereich sie aufnehmen kann. Der Arduino Uno hat nur 2 KB RAM, deshalb lohnt sich die Wahl des richtigen Typs. Variablennamen beginnen mit einem Buchstaben oder Unterstrich und dürfen weder Leerzeichen noch Sonderzeichen wie ä, ö, ü oder ß enthalten.
| Typ | Größe beim Uno | Wertebereich | Typische Verwendung |
|---|---|---|---|
| bool | 1 Byte | true oder false | Ist die LED an, wurde der Taster gedrückt |
| byte | 1 Byte | 0 – 255 | Pinnummer, PWM-Helligkeit |
| char | 1 Byte | -128 – 127 | Ein einzelnes Zeichen: 'A' |
| int | 2 Byte | -32.768 – 32.767 | Zähler, Ergebnis von analogRead() |
| unsigned int | 2 Byte | 0 – 65.535 | Nur positive Zahlen |
| long | 4 Byte | -2.147.483.648 – 2.147.483.647 | Große Ganzzahlen |
| unsigned long | 4 Byte | 0 – 4.294.967.295 | Zeitmessung mit millis() |
| float | 4 Byte | Etwa ±3,4 × 10^38, 6–7 Stellen Genauigkeit | Temperatur, Spannung, Mittelwerte |
| double | 4 Byte (beim Uno gleich wie float) | Wie float | Beim ESP32 8 Byte |
Ein paar praktische Regeln ersparen dir viel Ärger. analogRead() liefert eine Zahl von 0 bis 1023, ein int reicht also aus. millis() dagegen liefert einen unsigned long; speicherst du die Zeit in einer int-Variable, läuft der Wert nach 32,7 Sekunden über und dein Timing bricht zusammen. Auf dem Uno werden float-Berechnungen in Software ausgeführt, sie sind daher langsamer als Ganzzahl-Rechnungen, und nach 6–7 Stellen ist das Ergebnis nicht mehr zuverlässig; vergleiche zwei float-Werte deshalb nie mit ==. Wenn du einen Typ willst, dessen Größe auf jedem Board gleich bleibt, nimm Typen mit der Größe im Namen wie uint8_t, int16_t und uint32_t.
Überlauf (Overflow): Was passiert, wenn eine Variable ihre Grenze überschreitet?
Wenn du eine größere Zahl speichern willst, als der Typ fassen kann, meldet der Arduino keinen Fehler; die Zahl springt wie ein Kilometerzähler wieder an den Anfang. Beim Uno fasst ein int höchstens 32.767, addierst du 1, wird daraus -32.768. Ein byte springt von 255 auf 0. Eine noch tückischere Falle ist das Rechnen mit Konstanten: Ein Ausdruck wie 60 * 1000 wird zuerst als int berechnet und läuft über, selbst wenn das Ergebnis in einer long-Variable landet. Der folgende Sketch gibt all diese Fälle im seriellen Monitor aus, und seine loop() zeigt einen byte-Überlauf an einer LED:
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}| Codezeile | Ausgabe beim Uno | Ausgabe bei ESP32 und Pico | Warum? |
|---|---|---|---|
| a = 32767 + 1 | -32768 | 32768 | int ist beim Uno 2 Byte groß, bei den anderen 4 Byte |
| 60 * 1000 | -5536 | 60000 | Konstanten werden zuerst als int berechnet |
| 60 * 1000L | 60000 | 60000 | Das Suffix L macht die Rechnung zu long |
| 5 / 2 | 2.00 | 2.00 | Die Division zweier Ganzzahlen ergibt eine Ganzzahl |
| 5 / 2.0 | 2.50 | 2.50 | Hat eine Zahl Nachkommastellen, hat auch das Ergebnis welche |
Beobachte die LED an D9: Ihre Helligkeit steigt langsam von 0 bis 255, dann macht die Zeile brightness++ aus dem Wert 0 statt 256, und die LED geht plötzlich aus. So siehst du einen Überlauf am einfachsten mit eigenen Augen. D9 ist einer der mit ~ markierten PWM-Pins des Uno, deshalb funktioniert analogWrite() dort; PWM wird ausführlich in Was ist PWM? erklärt. Auch das Zurückspringen von millis() nach etwa 49,7 Tagen ist ein Überlauf, der in richtig geschriebenem Code keine Probleme macht; wie das geht, zeigt die Anleitung millis() statt delay().
const und #define: Werte, die sich nie ändern
Für Zahlen, die sich während des Programms nicht ändern, etwa Pinnummern oder Schwellwerte, gibt es zwei Möglichkeiten, ihnen einen Namen zu geben. Eine mit const definierte Konstante hat einen Typ, hält sich an die Regeln des Gültigkeitsbereichs, und wenn du sie versehentlich ändern willst, stoppt dich der Compiler mit assignment of read-only variable 'LED_PIN'. #define ist dagegen eine Präprozessor-Anweisung: Noch vor dem Kompilieren ersetzt sie jedes BUTTON_PIN im Code durch 2. Sie hat keinen Typ, und in der Zeile stehen weder Semikolon noch Gleichheitszeichen. Bei einfachen Zahlen belegt keine der beiden Varianten RAM, weil der Compiler den Wert direkt in den Code einsetzt. Heute wird const bevorzugt, und Konstantennamen in GROSSBUCHSTABEN zu schreiben ist eine verbreitete Gewohnheit. Das folgende Beispiel nutzt beides: Die LED leuchtet, solange der Taster gedrückt ist.
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}Operatoren: Arithmetik, Vergleich und Logik
Mit Operatoren rechnest du mit Variablen, vergleichst sie und verknüpfst Bedingungen. Fast alle Operatoren, die du in alltäglichen Projekten brauchst, stehen in dieser Tabelle:
| Gruppe | Operator | Bedeutung | Beispiel |
|---|---|---|---|
| Arithmetik | + - * / | Die vier Grundrechenarten | x = a * 2; |
| Rest | % | Rest einer Division (nur bei Ganzzahlen) | 7 % 3 ergibt 1 |
| Zuweisung | = += -= *= /= | Zuweisen, dazuzählen, abziehen… | total += 5; |
| Erhöhen, verringern | ++ -- | Um 1 erhöhen oder verringern | i++; |
| Vergleich | == != < > <= >= | Ergebnis ist true oder false | if (x >= 10) |
| Logik | && || ! | UND, ODER, NICHT | if (a && !b) |
| Bedingung | ? : | Kurzes if-else | x > 0 ? HIGH : LOW |
| Bitweise | & | ^ ~ << >> | Arbeitet mit den Bits einer Zahl | 1 << 3 ergibt 8 |
Am häufigsten verwechselt werden die Paare = und == sowie & und &&. Ein einfaches Gleichheitszeichen weist einen Wert zu, ein doppeltes vergleicht. Ein einfaches & arbeitet mit den Bits von Zahlen, && fragt dagegen, ob beide Bedingungen wahr sind. Die Wahrheitstabellen von UND, ODER und NICHT kannst du im Werkzeug Logikgatter ausprobieren; die bitweisen Operatoren werden ausführlich in der Anleitung Zahlensysteme und Bitoperationen erklärt. Das folgende Beispiel zählt, wie oft der Taster gedrückt wurde, und schaltet die LED nach einer ungeraden Anzahl von Tastendrücken ein:
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}Der Trick ist die Bedingung pressed && !lastPressed: Sie bedeutet "jetzt gedrückt UND im vorherigen Durchlauf nicht gedrückt". So steigt die Zahl nicht weiter, solange du den Taster festhältst, und jeder Tastendruck wird nur einmal gezählt. presses % 2 liefert den Rest der Division durch 2: 1 bei ungeraden, 0 bei geraden Zahlen. Der Operator ? : in der letzten Zeile wählt anhand dieses Ergebnisses HIGH oder LOW.
if, else if und else: Entscheidungen treffen
if führt den Code in den geschweiften Klammern aus, wenn die Bedingung in den runden Klammern wahr ist. Ist sie falsch, läuft der else-Block, sofern es einen gibt. Brauchst du mehr Möglichkeiten, fügst du dazwischen else if ein. Die Bedingungen werden von oben nach unten geprüft, und sobald der Block der ersten wahren Bedingung gelaufen ist, werden die übrigen übersprungen. In C++ gilt jeder Wert ungleich null als wahr, if (counter) bedeutet also "wenn der Zähler nicht null ist". Ein einfaches if-else-Beispiel hast du schon im const-Abschnitt gesehen. Jetzt schreiben wir eines, das einen kurzen von einem langen Tastendruck unterscheidet:
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}Sobald der Taster gedrückt wird, merkt sich der Code mit millis() die Zeit, wartet in einer while-Schleife, bis der Taster losgelassen wird, und berechnet die Zeit dazwischen. Drücke unter 50 ms gelten als Störung durch das mechanische Prellen des Tasters, und return beendet diesen Durchlauf von loop() vorzeitig. Ein Druck unter 1 Sekunde schaltet die LED ein, ein längerer schaltet sie aus. Die Reihenfolge der Bedingungen ist wichtig: Stünde die Prüfung held < 1000 ganz oben, würde auch ein Störimpuls von 30 ms als "kurzer Druck" zählen.
switch-case: zwischen vielen Fällen wählen
Wenn du je nach Wert einer Variable viele verschiedene Dinge tun willst, ist switch-case übersichtlicher als eine lange else-if-Kette. switch vergleicht den Wert in der Klammer mit jedem case-Label und beginnt beim passenden mit der Ausführung. Jeden case solltest du mit break beenden, sonst läuft das Programm einfach in den Code des nächsten case weiter. Passt kein case, läuft der default-Block. switch funktioniert nur mit Ganzzahltypen wie int, byte, char und enum; für String, float oder Bereiche wie "zwischen 10 und 20" brauchst du if. Im folgenden Beispiel schaltet jeder Tastendruck die Lampe in den nächsten Modus: aus, an, langsam blinken, schnell blinken.
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}Die Zeile mode = (mode + 1) % 4 zählt den Modus 0, 1, 2, 3 hoch und setzt ihn dank des Operators % bei 4 wieder auf 0. Beachte, dass wir zum Blinken kein delay() benutzen: Der Ausdruck (millis() / 500) % 2 wechselt alle 500 ms zwischen 0 und 1, so reagiert der Taster jederzeit, während die LED blinkt. Die erwachsene Version dieses Modus-Ansatzes heißt Zustandsautomat; wie man ihn bei Ampeln und Menüs einsetzt, zeigt die Anleitung Zustandsautomat mit Arduino.
for-, while- und do-while-Schleifen
Schleifen führen einen Codeblock immer wieder aus. Die Klammer einer for-Schleife hat drei Teile: den Start (int i = 0), die Bedingung zum Weitermachen (i < 6) und den Schritt nach jedem Durchlauf (i++). for ist ideal, wenn du weißt, wie oft wiederholt werden soll. while läuft, solange die Bedingung wahr ist, und prüft sie vor jedem Durchlauf; das passt zu Wartezeiten, deren Länge du vorher nicht kennst. do-while prüft die Bedingung erst am Ende und läuft deshalb mindestens einmal. Innerhalb einer Schleife verlässt break die Schleife sofort, und continue überspringt den Rest des aktuellen Durchlaufs und macht mit dem nächsten weiter. Da loop() selbst schon eine Endlosschleife ist, brauchst du darin meist kein while (true).
| Schleife | Wann wird die Bedingung geprüft? | Wann verwenden? | Beispiel |
|---|---|---|---|
| for | Vor jedem Durchlauf | Wenn die Anzahl der Wiederholungen feststeht | for (int i = 0; i < 6; i++) |
| while | Vor jedem Durchlauf | Beim Warten auf ein Ereignis | while (digitalRead(2) == HIGH) |
| do-while | Nach jedem Durchlauf | Wenn sie mindestens einmal laufen muss | do { ... } while (x < 10); |
Das folgende Beispiel erzeugt mit sechs LEDs einen Knight-Rider-Effekt. Die for-Schleife in setup() macht alle sechs Pins in einer Zeile zu Ausgängen, und die while-Schleife in loop() wartet, bis der Taster gedrückt wird. Solange du den Taster hältst, läuft das Licht von D8 nach D13 und zurück:
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}Dass die Rückwärtsschleife bei LAST_LED - 1 beginnt und nur läuft, solange der Pin größer als FIRST_LED ist, ist Absicht: Die LEDs an den Enden leuchten nicht zweimal hintereinander auf, und die Bewegung wirkt flüssig. Geschwindigkeitsregelung, einen sich füllenden Effekt und weitere Muster findest du in der Anleitung Arduino Lauflicht und Knight Rider.
Arrays: viele Werte unter einem Namen
Ein Array speichert mehrere Werte desselben Typs geordnet unter einem einzigen Namen. Die Zeile const byte LEDS[] = {8, 9, 10, 11, 12, 13}; erzeugt ein Array mit sechs Elementen. Auf die Elemente greifst du mit eckigen Klammern zu, und gezählt wird ab 0: LEDS[0] ist 8, LEDS[5] ist 13. Die Länge eines Arrays lässt sich nach der Definition nicht mehr ändern. Lässt du die Anzahl der Elemente mit sizeof(LEDS) / sizeof(LEDS[0]) berechnen, statt sie von Hand einzutragen, passen sich deine Schleifen automatisch an, wenn du einen neuen Pin hinzufügst. Ein Index außerhalb des Arrays, etwa LEDS[6], führt zu keinem Fehler; er liest oder überschreibt eine andere Variable im Speicher. Achte deshalb darauf, dass der Index zwischen 0 und der Anzahl der Elemente minus eins bleibt. Das folgende Beispiel macht aus den sechs LEDs einen elektronischen Würfel:
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) liefert eine Zahl von 1 bis 6, weil die obere Grenze nicht zum Ergebnis gehört. Genauso viele LEDs leuchten: Zeigt der Würfel 4, gehen die vier LEDs von LEDS[0] bis LEDS[3] an. Die Zeile randomSeed(analogRead(A0)) nutzt das elektrische Rauschen am unbeschalteten Pin A0 als Startwert; ohne sie würde das Board nach jedem Einschalten dieselbe Zahlenfolge erzeugen. Noch ein Vorteil von Arrays: Verlegst du die LEDs auf andere Pins, musst du nur die erste Zeile ändern.
Funktionen schreiben: Code in Teile zerlegen
Wenn du dieselbe Aufgabe an mehreren Stellen im Programm erledigst, mach daraus eine Funktion. Eine Funktion besteht aus einem Rückgabetyp, einem Namen, Parametern in runden Klammern und einem Rumpf in geschweiften Klammern. Funktionen ohne Rückgabewert haben den Typ void; Funktionen mit Rückgabewert geben ihr Ergebnis mit return zurück. Parameter werden als Kopie übergeben, eine Änderung des Parameters in der Funktion ändert also nicht die Variable an der Aufrufstelle. Die Arduino IDE erzeugt die Deklarationen für Funktionen in .ino-Dateien selbst, du kannst eine Funktion also auch unterhalb von loop() schreiben. In .cpp-Dateien oder in PlatformIO musst du eine Funktion dagegen deklarieren, bevor du sie benutzt. Gib Funktionen Namen, die sagen, was sie tun, etwa blinkLed oder isPressed.
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() bekommt den Pin, die Anzahl der Blinker und die Geschwindigkeit als Parameter, deshalb kannst du dieselbe Funktion für eine andere LED und ein anderes Tempo wiederverwenden. isPressed() gibt einen bool zurück und hält die Logik "gedrückt = LOW" von INPUT_PULLUP an einer einzigen Stelle fest; schließt du den Taster später anders an, änderst du nur diese Zeile. Das delay() in der Funktion hält das Programm aber trotzdem an. Für Funktionen, die mehrere Dinge gleichzeitig erledigen, brauchst du ein Timing auf Basis von millis().
Globale, lokale und static-Variablen
Wo eine Variable sichtbar ist, nennt man ihren Gültigkeitsbereich (Scope). Globale Variablen werden außerhalb der Funktionen definiert, leben während des ganzen Programms und lassen sich von überall lesen und ändern. Lokale Variablen werden in einer Funktion oder einem Block aus geschweiften Klammern definiert, bei jedem Durchlauf dieses Blocks neu angelegt und verschwinden an seinem Ende. Genau daher kommt die häufigste Einsteigerfrage: "Warum bleibt mein Zähler immer bei 1?" Schreibst du static vor eine lokale Variable, erhält sie ihren Startwert nur beim ersten Mal und behält ihren Wert zwischen den Durchläufen, ist aber weiterhin nur innerhalb dieser Funktion sichtbar.
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}Drückst du den Taster dreimal, zeigt der serielle Monitor die Zeilen 1 1 1, 2 1 2 und 3 1 3: Die globalen und static-Zähler steigen weiter, der lokale Zähler bleibt dagegen immer bei 1, weil er in jedem Durchlauf von loop() wieder bei 0 beginnt. Definierst du eine lokale Variable mit demselben Namen wie eine globale, verdeckt die lokale die globale, und innerhalb dieser Funktion erreichst du die globale Variable gar nicht. Globale Variablen, die von einer Interrupt-Funktion (ISR) geändert werden, müssen zusätzlich als volatile deklariert werden; Details findest du in der Anleitung Arduino Interrupts.
Arduino programmieren auf dem ESP32: gleiche Sprache, andere Größen
Wenn du den ESP32 mit der Arduino IDE programmierst, nutzt du dieselbe Sprache C++ und dieselben Befehle, es gibt aber ein paar Unterschiede. Weil der ESP32 ein 32-Bit-Prozessor ist, ist int 4 Byte groß und fasst Werte von -2.147.483.648 bis 2.147.483.647; die Rechnung 32767 + 1 aus dem Überlauf-Beispiel ergibt also 32768. double ist 8 Byte groß, analogRead() liefert Werte von 0 bis 4095, und die Pins arbeiten mit 3,3 V. Mit Serial.printf() kannst du formatiert ausgeben. random() nutzt standardmäßig den Hardware-Zufallszahlengenerator, deshalb brauchst du die Zeile randomSeed() nicht. Das folgende Programm vereint alles, was du in diesem Artikel gelernt hast: ein Array, eine Funktion, switch-case und einen Taster, der den Modus wechselt. Die allgemeinen Eigenschaften des Boards findest du in Was ist der ESP32?.
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}Um dieses Programm auf dem Uno laufen zu lassen, musst du nur das Array LEDS auf {8, 9, 10, 11, 12, 13} und BUTTON_PIN auf 2 ändern und die Zeile mit Serial.printf(), das es auf dem Uno nicht gibt, durch Serial.print() ersetzen. Genau das ist der Vorteil, wenn du Pinnummern ganz oben als Konstanten und Arrays definierst. Beachte auch, dass der Block von case 1 in geschweiften Klammern steht; weil darin die Variable step definiert wird, sind diese Klammern beim ESP32 Pflicht.
Raspberry Pi Pico: dieselbe Logik in MicroPython
Den Raspberry Pi Pico kannst du in C++ programmieren, indem du der Arduino IDE das RP2040-Boardpaket hinzufügst; dann funktioniert der Code aus diesem Artikel, sobald du die Pinnummern anpasst, und int ist ebenfalls 4 Byte groß. Am häufigsten wird der Pico aber mit MicroPython genutzt. In Python schreibst du keine Datentypen, Blöcke werden durch Einrückung statt durch geschweifte Klammern festgelegt, am Zeilenende steht kein Semikolon, und Ganzzahlen laufen nicht über, sondern wachsen so weit wie nötig. Der folgende Code ist die MicroPython-Version des elektronischen Würfels. Zur Einrichtung siehe Was ist der Raspberry Pi Pico?, zu den Details der Sprache MicroPython-Grundlagen.
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)| Konzept | Arduino (C++) | MicroPython |
|---|---|---|
| Teil, der einmal läuft | setup() | Der Code vor der Zeile while True: |
| Teil, der sich endlos wiederholt | loop() | while True: |
| Variable | int x = 5; | x = 5 |
| Konstante | const int PIN = 2; | PIN = const(2) (from micropython import const) |
| Bedingung | if / else if / else | if / elif / else |
| Mehrfachauswahl | switch-case | if / elif-Kette |
| Zählschleife | for (int i = 0; i < 6; i++) | for i in range(6): |
| Array | byte a[] = {1, 2, 3}; | a = [1, 2, 3] |
| Funktion | void blink(int pin) { ... } | def blink(pin): |
| Logik | && || ! | and or not |
| Kommentar | // Kommentar | # Kommentar |
Projektideen für diese Grundlagen
- Ampel: Baue mit Konstanten, delay() und if eine Kreuzung mit Fußgängertaster; die Schritt-für-Schritt-Anleitung findest du unter Arduino Ampel.
- Knight Rider und Lauflicht: Erzeuge mit einer for-Schleife und einem Array Dutzende verschiedene Lichtmuster.
- Elektronischer Würfel: Bring den Würfel mit Array, random() und einer Funktion von sechs LEDs auf eine 7-Segment-Anzeige; dazu passt die Anleitung 7-Segment-Anzeige.
- Reaktionstest: Lass eine LED zu einem zufälligen Zeitpunkt aufleuchten und miss mit unsigned long und millis(), wie schnell du den Taster drückst.
- Lampe mit Modi: Aus, Dauerlicht, Atmen und Blitz über einen einzigen Taster mit switch-case; für eine farbige Version wirf einen Blick auf RGB-LED verwenden.
- Codeschloss: Sammle die gedrückten Tasten in einem Array und vergleiche sie mit dem gespeicherten Code; Details stehen in der Anleitung Keypad am Arduino.
Häufige Fehler und ihre Lösungen
| Fehler | Symptom | Lösung |
|---|---|---|
| = in einem Vergleich | Der if-Block läuft immer | Zum Vergleichen == verwenden |
| ; oder = in der #define-Zeile | expected ')' before ';' token | Als #define LED_PIN 13 schreiben |
| Befehl falsch geschrieben | 'pinmode' was not declared in this scope | Auf Groß- und Kleinschreibung achten, z. B. pinMode |
| Umlaut im Variablennamen | stray '\303' in program | In Namen nur englische Buchstaben, Ziffern und _ verwenden |
| 60 * 1000 als int berechnen | Das Ergebnis ist -5536 | 60 * 1000L oder 60000UL schreiben |
| millis() in einem int speichern | Das Timing bricht nach 32 Sekunden zusammen | unsigned long verwenden |
| Ganzzahldivision | 5 / 2 ergibt 2 | 5 / 2.0 schreiben oder in float umwandeln |
| break am Ende eines case vergessen | Der nächste case läuft mit | Jeden case mit break beenden |
| Außerhalb eines Arrays schreiben | Zufällige Werte, Abstürze | Index zwischen 0 und Anzahl der Elemente - 1 halten |
| Zähler lokal definieren | Der Zähler bleibt immer bei 1 | Global oder static machen |
Wenn du diese Bausteine beherrschst, ist der nächste Schritt, die meistgenutzten Befehle kennenzulernen: Die Arduino-Befehlsliste erklärt alles von pinMode() bis attachInterrupt() mit Beispielen. In der BlueGrays-App findest du außerdem Projekte wie LED-Blinken, LED-Steuerung per Taster, Knight Rider, Ampel und Würfel Schritt für Schritt mit Schaltplänen und fertigem Code, sodass du jede hier gelernte Struktur an einer echten Schaltung ausprobieren kannst.
Häufige Fragen
In welcher Programmiersprache wird Arduino programmiert?
Arduino-Code basiert auf C++. Der Arduino-Core ergänzt die Sprache um fertige Funktionen wie pinMode(), digitalWrite() und delay(), und der Code steht in Dateien mit der Endung .ino. Boards wie der ESP32 und der Raspberry Pi Pico lassen sich auf Wunsch auch mit MicroPython programmieren.
Wie fange ich am besten an, Arduino programmieren zu lernen?
Installiere zuerst die Arduino IDE und lade das Beispiel Blink hoch, das die eingebaute LED blinken lässt. Probiere danach der Reihe nach setup() und loop(), Variablen, if-else, Schleifen, Arrays und Funktionen aus, jeweils mit einer LED- oder Taster-Schaltung. Jedes neue Thema in einem kleinen Projekt anzuwenden, bleibt viel besser hängen als reines Lesen.
Was ist der Unterschied zwischen int und long beim Arduino?
Beim Arduino Uno ist ein int 2 Byte groß und fasst Zahlen von -32.768 bis 32.767, ein long ist 4 Byte groß und reicht bis etwa ±2,1 Milliarden. Eine Zahl, die diese Grenze überschreitet, springt ohne Fehlermeldung um, deshalb nimmt man für große Werte wie millis() long oder unsigned long. Beim ESP32 und Pico ist auch int 4 Byte groß.
Was ist der Unterschied zwischen const und #define beim Arduino?
Eine mit const definierte Konstante hat einen Typ, hält sich an den Gültigkeitsbereich, und der Compiler meldet einen Fehler, wenn du sie ändern willst. #define ist eine Textersetzung vor dem Kompilieren, hat keinen Typ und führt zu unerwarteten Fehlern, wenn ein Semikolon am Ende steht. Bei einfachen Zahlen belegen beide kein RAM, const wird aber meist bevorzugt, weil es sicherer ist.
Was ist der Unterschied zwischen for- und while-Schleife beim Arduino?
Eine for-Schleife fasst Start, Bedingung und Schritt in einer Zeile zusammen und wird verwendet, wenn die Zahl der Wiederholungen feststeht, zum Beispiel um sechs LEDs nacheinander einzuschalten. Eine while-Schleife prüft nur eine Bedingung und läuft, solange diese wahr ist; sie eignet sich für Situationen unbekannter Dauer wie das Warten auf einen Tastendruck.
Kann man Arduino mit Python programmieren?
8-Bit-Boards wie der Arduino Uno können kein Python ausführen und werden deshalb in C++ programmiert. Der ESP32 und der Raspberry Pi Pico lassen sich dagegen mit MicroPython programmieren. Außerdem kann ein Python-Programm auf deinem Computer einen Uno indirekt steuern, indem es über die serielle Schnittstelle mit ihm kommuniziert.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
LED Blinken
Das allererste Hallo der Elektronik: eine einzelne LED an- und ausschalten. Der Arduino schaltet die LED in kurzen Abständen ein und aus, das Licht blinkt also schnell. So siehst du ganz einfach, wie

LED mit Taster schalten (Push)
Wie bei einer Türklingel: Das Licht leuchtet, solange du drückst, und geht aus, wenn du loslässt. Der Arduino horcht ständig auf den Taster; solange er gedrückt ist, leuchtet die LED, und sobald du de

LED mit Taster ein- und ausschalten
Einmal drücken und das Licht geht an, noch einmal drücken und es geht aus, genau wie der Lichtschalter in deinem Zimmer. Der Arduino bemerkt jeden Druck und schaltet die LED in den jeweils anderen Zus

Knight-Rider-Lauflicht
Das klassische Lauflicht, das hin und her wandert: Ein Lichtpunkt gleitet über sechs LEDs von einem Ende zum anderen und kommt wieder zurück. Der Arduino lässt jede LED eine Zehntelsekunde leuchten, d

Ampel
Eine winzige Nachbildung der Ampel, die du jeden Tag auf der Straße siehst. Rot leuchtet zwei Sekunden, Gelb blitzt kurz auf, Grün leuchtet zwei Sekunden, dann geht es über Gelb wieder zurück zu Rot,

ESP32 LED-Steuerung mit Drucktaste (Push)
Die LED leuchtet auf, wenn die Taste gedrückt wird und geht aus, wenn sie losgelassen wird. Zeigt, wie der digitale Eingangspin des ESP32 und der interne Pull-up-Widerstand verwendet werden. Dank des INPUT_PULLUP-Modus ist kein externer Widerstand erforderlich.

Raspberry Pi Pico Taster Toggle LED (GP14 + GP2)
Taster + LED — nächste Stufe! 🔁 Anders als #155, du musst den Taster nicht halten. Drücken und loslassen → LED an. Nochmal drücken → LED aus (toggle). Gleiche Schaltung wie #155, aber großer Codeunterschied. Lerne Edge Detection, State Tracking und Debounce.

Würfel-Projekt
Schluss mit verschwundenen Würfeln beim Brettspiel: Bei jedem Druck auf den Knopf erscheint auf der 7-Segment-Anzeige eine zufällige Zahl von 1 bis 6. Jeder Druck liefert genau ein Ergebnis, und das E
Weitere Artikel
BlueGrays-App
Diese Schaltung Schritt für Schritt auf dem Handy nachbauen
Schaltplan, Code und Teileliste auf einem Bildschirm. Über 160 Arduino-, ESP32- und Pico-Projekte zum Nachbauen.
Mit dem Handy scannen
Richte die Kamera auf den Code, dann öffnet sich der App-Store deines Handys.
oder im Store öffnen

