Hier ist die Seite der GTA-Robotik/Programmierung am Hans-Erlwein-Gymnasium in Dresden.

Auf dieser Seite sammeln wir mal die Themen, Projekte und kleinen Erfolge aus dem Ganztagesangebot - auch mal als Referenz zum Nachlesen.
Inhaltsverzeichnis
- Robotik und Mikrocontrollerprogrammierung (Arduino)
- 3D Programmierung mit OpenGL
Robotik und Mikrocontrollerprogrammierung (Arduino)
Mit dem kleinen Arduino Uno, diversen LEDs und weiteren Bauteilen kann man schon eine Menge über Elektrotechnik und Programmierung lernen. Hier sind ein paar Notizen aus der AG.
Arduino-IDE selbst installieren
Zur Installation der Arduino IDE auf dem eigenen Linux-PC siehe Artikel “Arduino IDE unter Linux einrichten”. Auf den neuen Schul-PCs ist die Arduino-IDE bereits vorinstalliert.
Billige Arduino-Klone aus Fernost (zumeist in den Starterkits enthalten) verwenden den CHP341-Chip für die serielle Kommunikation. Dafür fehlt auf den Schul-PCs derzeit noch der Treiber. Diese Arduino-Klone kann man also nur auf unseren GTA-Linux-Notebooks ansteuern.
Arduino-Simulatoren
Obwohl das Zusammenstecken einer Schaltkreises und Ausprobieren mit echter Hardware natürlich am meisten Spaß macht, kann man viele Sache auch im Simulator online testen. Ich verwende hier die Webseite wokwi.com.
Arduino IDE und ein minimalistisches Programm
In der Arduino IDE wird das folgende minimalistische Programm (dort Sketch genannt) angezeigt und nachfolgend von uns befüllt:
1
2
3
4
5
6
7
8
9
void setup() {
// Diese Funktion setup() wird nur einmal aufgerufen.
// Hier kommt die Initialisierung rein.
}
void loop() {
// Diese Funktion wird kontinuierlich hintereinander aufgerufen.
// Hier kommt die gesamte Regelungslogik/das Ausführungsprogramm rein.
}
Dabei führt der Mikrocontroller im Hintergrund sowas wie das nachfolgende minimalistische C-Programm aus:
1
2
3
4
5
6
7
8
9
10
11
int main() {
setup(); // einmal aufrufen
// unendliche Schleife
while (true) {
loop(); // immer wieder neu die loop()-Funktion aufrufen
}
return 0;
}
Die Grundlagen
Die Abschnitte in diesem Kapitel beschreiben die absoluten Grundlagen der Mikrokontrollerprogrammierung.
LEDs und Schrittketten
Hier ist ein typisches Anfänger-Beispiel mit delay():
1
2
3
4
5
6
7
8
9
10
11
void setup() {
// PIN 9 als Spannungsausgang konfigurieren
pinMode(9, OUTPUT);
}
void loop() {
digitalWrite(9, HIGH); // Spannung auf +5V setzen (LED geht an)
delay(1000); // eine Sekunde warten
digitalWrite(9, LOW); // Spannung auf 0 setzen, Verbindung mit Ground (LED geht aus)
delay(1000); // eine Sekunde warten
}
Hier haben wir also eine kleine Schrittkette mit 2 Schritten programmiert:
- Schritt 1: LED ist an
- Schritt 2: LED is aus
Erweitern wir das Beispiel nun um eine 2. LED.
Schaltplanfrage: Muss man jede LED mit einem eigenen Widerstand absichern (Bild links), oder kann man sich einen Widerstand sparen (Bild rechts)?

Lösung: im Prinzip geht die 2. Schaltung auch, da die Dioden ein “Verkehrtherumfließen” des Stroms verhindern. Wenn man aber beim Einbau die 2. LED aus Versehen verkehrt herum einbaut und dann LED 1 auf HIGH zieht und LED 2 auf LOW, dann fließt ein Kurzschlussstrom durch beide LEDs und brutzelt diese weg (und den Arduino möglicherweise gleich mit). Daher sollte man jede LED mit einem eigenen Widerstand absichern.
Aufgabe: Erweitert das Programm so, dass es 2 LEDs, angeschlossen an PIN 8 und 9 blinken lässt. Dabei sollen die LEDs 1 Sekunde an und 300 ms aus sein.
Lösung: Wokwi-Simulator-Lösung mit Schaltung
Programmierung ohne Wartepausen
Wichtig für komplexere Steuerungsprogramme ist, dass man kontinuierlich Sensorwerte auslesen und entsprechende Behandlungsroutinen ausführen kann. Das geht aber nicht, wenn man - wie in fast allen einfachen Beispielprogrammen - mit der delay() Funktion arbeitet. Während der Wartezeit kann man nämlich nichts anderes anderes machen. Wie soll man hier also 2 LEDs arithmisch blinken lassen, so dass man zeitgleich noch anderes machen kann?
Die Grundidee ist, dass man sich vom Mikrokontroller die aktuelle Zeit (in Millisekunden) geben lässt und nur bei Erreichen/Überschreiten eines bestimmten Zeitpunkts eine Aktion ausführt. Das Programm oben könnte dann so aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Variable hält Zeitpunkt, an dem als nächstes was gemacht werden soll
unsigned long nextActiveMillis;
// Variable hält aktuellen Zustand der LEDs (aktuellen Schritt in der Schrittkette)
bool LEDsOn;
void setup() {
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
nextActiveMillis = millis(); // wir beginnen sofort mit der ersten Aktion
LEDsOn = false; // anfänglich sind die LEDs aus
}
void loop() {
// prüfen, ob wir aktiv werden müssen
if (millis() >= nextActiveMillis) {
// welchen Schritt führen wir als nächstes aus
if (LEDsOn) {
// LEDs sind an, schalte sie aus
digitalWrite(8, LOW);
digitalWrite(9, LOW);
LEDsOn = false;
// warte 300 ms bis zur nächsten Aktion
nextActiveMillis = millis() + 300;
}
else {
// LEDs sind aus, schalte sie an
digitalWrite(8, HIGH);
digitalWrite(9, HIGH);
LEDsOn = true;
// warte 1000 ms bis zur nächsten Aktion
nextActiveMillis = millis() + 1000;
}
}
// hier könnten wir jetzt noch anderes Zeug machen, während wir auf das Eintreten des
// nächsten Schaltzustandes warten
}
Aufgabenstellung: Erzeuge folgendes Blinkmuster!
- beide LEDs aus
- 300 ms warten
- Rote LED an
- 200 ms warten
- Blaue LED an
- 500 ms warten
… und bei 1. wiederholen
Lösungsansatz: Statt der Boolischen Variable mit 2 Zuständen (an/aus) braucht man nun 3 Zustände. Also kann man bspw. eine Integervariable dafür verwenden und so eine Schrittkette mit 3 Stufen umsetzen (0 = keine LED an, 1 = nur Rot an, 2 = Rot und Blau an).
Man kann statt if-Abfragen auch die Switch-Anweisung verwenden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// welchen Schritt führen wir als nächstes aus
switch (LEDstep) {
case 0 :
// beide ausschalten
digitalWrite(8, LOW);
digitalWrite(9, LOW);
// warte 300 ms bis zur nächsten Aktion
nextActiveMillis = millis() + 300;
break;
case 1:
// ROT anschalten, BLAU bleibt aus
digitalWrite(9, HIGH);
nextActiveMillis = millis() + 200;
break;
case 2:
// BLAU auch anschalten
digitalWrite(8, HIGH);
nextActiveMillis = millis() + 500;
break;
}
Das macht es etwas übersichtlicher.
LEDs langsam ein-/ausblenden
Dafür verwendet man die PWM-Pins (Pulse-Width-Modulation). Je nachdem, wie lange der Spannungspegel innerhalb des Duty-Cycle hochgezogen ist, umso höher ist die augenscheinliche Analogspannung am PIN Ausgang. Man setzt diesen mit analogeWrite().
Man muss dafür die mit ~ gekennzeichneten PWM-Pins benutzen. In den folgenden Beispielen verwenden wir deshalb Pins 9 und 10! Siehe auch das PINOUT Diagramm:
Hier ist das Beispielprogramm zum ein- und ausblenden einer an Pin 9 angeschlossenen LED:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int brightness = 0; // aktuelle Helligkeit (0..255)
int stepSize = 5; // Wieviele Helligkeitsschritte pro Anpassung verändern
void loop() {
// Helligkeit/PWM-Spannung am PIN 9 setzen
analogWrite(9, brightness);
// Helligkeit stufenweise ändern
brightness = brightness + stepSize;
// Beim Unterschreiten oder Überschreiten der Grenzen die Richtung tauschen
if (brightness <= 0 || brightness >= 255) {
stepSize = -stepSize;
}
// 30 Millisekunden warten
delay(30);
}
Auch hier verhindert der delay() Aufruf andere gleichzeitig stattfindende Aktionen.
LEDFade - 2 LEDs in Loop mit millis()
Im nächsten Schritt sollen 2 LEDs (wie im Kapitel oben) gleichzeitig ein- und ausgeblendet werden. Dazu soll wieder die millis()-Funktion verwendet werden. Man braucht also 2 nextActiveMillis Variablen und auch separate Helligkeitszustände je LED.
Die Lösung könnte beispielsweise so aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Variable hält Zeitpunkt, an dem als nächstes was gemacht werden soll
unsigned long nextActiveMillis_ROT;
unsigned long nextActiveMillis_BLAU;
// Aktualisierungsinterval in ms
int delay_ROT = 50;
int delay_BLAU = 500;
// aktuelle Helligkeit (0..255)
int brightness_ROT = 0;
int brightness_BLAU = 0;
// Wieviele Helligkeitsschritte pro Anpassung verändern
int stepSize_ROT = 5;
int stepSize_BLAU = 25; // größere Schritte als bei ROT machen
void setup() {
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
// Anfangszustand herstellen: beide aus
digitalWrite(9, LOW);
digitalWrite(10, LOW);
nextActiveMillis_ROT = millis(); // wir beginnen sofort mit der ersten Aktion
nextActiveMillis_BLAU = millis(); // wir beginnen sofort mit der ersten Aktion
}
void loop() {
// prüfen, ob wir ROT ändern
if (millis() >= nextActiveMillis_ROT) {
// Helligkeit stufenweise ändern
brightness_ROT = brightness_ROT + stepSize_ROT;
// auf Wertebereich beschränken
if (brightness_ROT > 255)
brightness_ROT = 255;
if (brightness_ROT < 0)
brightness_ROT = 0;
// neue Helligkeit (Spannungswert) setzen
analogWrite(9, brightness_ROT);
// Beim Unterschreiten oder Überschreiten der Grenzen die Richtung tauschen
if (brightness_ROT <= 0 || brightness_ROT >= 255)
stepSize_ROT = -stepSize_ROT;
nextActiveMillis_ROT = millis() + delay_ROT;
}
// prüfen, ob wir BLAU ändern
if (millis() >= nextActiveMillis_BLAU) {
// Helligkeit stufenweise ändern
brightness_BLAU = brightness_BLAU + stepSize_BLAU;
// auf Wertebereich beschränken
if (brightness_BLAU > 255)
brightness_BLAU = 255;
if (brightness_BLAU < 0)
brightness_BLAU = 0;
// neue Helligkeit (Spannungswert) setzen
analogWrite(10, brightness_BLAU);
// Beim Unterschreiten oder Überschreiten der Grenzen die Richtung tauschen
if (brightness_BLAU <= 0 || brightness_BLAU >= 255)
stepSize_BLAU = -stepSize_BLAU;
nextActiveMillis_BLAU = millis() + delay_BLAU;
}
// hier könnten wir jetzt noch anderes Zeug machen, während wir auf das Eintreten des
// nächsten Schaltzustandes warten
}
In dieser Variante wird sowohl das Aktualisierungsinterval, z.B. in Variable delay_ROT, definiert als auch die Helligkeitsschritte je Anpassung stepSize_ROT. Dadurch faded die rote LED gleichmäßig ein- und aus und die blaue LED ändert sich sichtbar in diskreten Schritten.
Aufgabe: Schaltung nachbauen und im Programm die Parameter delay_xxx und stepSize_xxx ändern und den Einfluss anschauen.
LEDFade - Klassenprogrammierung
Im letzten Beispiel stellen wir fest, dass die Variablen alle irgendwie doppelt da sind. Wenn man mal 10 LEDs ansteuern will, wird das schnell unübersichtlich. Man kann aber Variablen, die irgendwie zu einem Objekt (hier der LED) dazugehören, in einer Klasse gruppieren. Die gesamte Programmlogik des Ein-/Ausblendes einer LED wird nun in eine Klasse verschoben.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Klasse, welche die Funktionalität für das Ein-/Ausblenden einer LED kapselt
class LEDFade {
public:
void init(int pin, bool direction, int value, int delay, int stepSize) {
// Membervariablen initialisieren
m_pin = pin;
m_direction = direction;
m_value = value;
m_delay = delay;
m_stepSize = stepSize;
m_nextActiveMillis = millis();
pinMode(m_pin, OUTPUT); // Als Ausgabe-PIN festlegen
}
// Diese Funktion setzt einen neuen Helligkeitswert für die LED
void setValue(int value) {
if (value < 0)
value = 0;
if (value > 255)
value = 255;
m_value = value;
// Quadratische Funktion verwenden, um gleichmäßig die
// Helligkeit der LED zu erhöhen
// v(x) = x² * 255 / 255² = x²/255
analogWrite(m_pin, m_value*m_value/255);
}
void fade() {
// Fehlerbehandlung
if (m_pin == 0)
return;
// LED erst aktualisieren, wenn die geforderte Wartezeit überschritten wurde
if (millis() < m_nextActiveMillis)
return; // not yet there, skip this
m_nextActiveMillis = millis() + m_delay;
// wenn wir die Helligkeit erhöhen
if (m_direction) {
m_value += m_stepSize;
// am oberen Ende begrenzen und Richtung wechseln
if (m_value >= 255) {
m_value = 255;
m_direction = false; // ab jetzt dunkler werden
}
}
else {
// sonst, wenn wir die Helligkeit reduzieren
m_value -= m_stepSize;
// am unteren Ende begrenzen und Richtung wechseln
if (m_value <= 0) {
m_value = 0;
m_direction = true; // ab jetzt heller werden
}
}
// Neuen Helligkeitswert setzen
setValue(m_value);
}
private:
int m_pin = 0; // PIN der LED
bool m_direction = true; // true -> Helligkeit erhöhen
int m_delay = 50; // Wartezeit zwischen Anpassungen in ms
int m_value = 0; // Aktuelle Helligkeit
int m_stepSize = 1; // Schrittgröße
unsigned long m_nextActiveMillis = 0; // Nächster Aktualisierungszeitpunkt
};
// Variablen für die beiden LEDs
LEDFade led1;
LEDFade led2;
void setup() {
// die Klassenvariablen mit den jeweiligen Parametern initialisieren
// PIN 9: anfänglich aufblenden, Startwert 0, 250 ms Wartezeit, und
// 25er Schritte machen
led1.init(9, true, 0, 250, 25);
// PIN 10 etwas anders konfiguriert
led2.init(10, false, 255, 30, 5);
}
// die Hauptschleife ist nun sehr schön aufgeräumt und minimalistisch
void loop() {
led1.fade();
led2.fade();
}
Um die Helligkeit möglichst linear anzupassen, verwenden wir den im Artikel “LED Helligkeit linear regeln” beschriebenen Trick).
Hier ist das Beispiel im Simulator
Aufgabe: Die ganze Logik rund um nextActiveMillis lässt sich auch in eine eigene Klasse packen. Erstelle eine Klasse TimedAction mit einer Memberfunktion isActive(), welche dann true zurückliefert, wenn man den nächsten aktiven Zeitpunkt erreicht hat. Diese Klasse soll dann von der Klasse LEDFade verwendet werden.
Lösung: Die Klasse TimedAction könnte so aussehen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Klasse, welche die "bin ich jetzt dran" Funktion kapselt
class TimedAction {
public:
void init(int delay) {
m_delay = delay;
m_nextActiveMillis = millis();
}
void reset() {
m_nextActiveMillis = millis();
}
bool isActive() {
if (millis() < m_nextActiveMillis)
return false; // Aktiv-Zeitpunkt noch nicht erreicht
// nächsten Zeitpunkt festlegen
m_nextActiveMillis = millis() + m_delay;
return true; // Jetzt sind wir aktiv
}
private:
unsigned long m_nextActiveMillis = 0; // Nächster Aktualisierungszeitpunkt
int m_delay = 50; // Wartezeit zwischen Anpassungen in ms
};
Die Klasse hat drei Member-Funktionen init(delay), reset() und isActive(). Letztere wird in jeder Ausführung von loop() direkt oder indirekt aufgerufen und kümmert sich um das Erkennen des aktiven Moments.
Komplettes Beispiel im Simulator.
Integer Overflow Protection
Wenn der Arduino sehr lange läuft, dann ist irgendwann der 32-bit Datentyp unsigned long nicht mehr groß genug, die Anzahl der Millisekunden seit dem Start zu zählen. Der Maximalwert ist 4.294.967.295, entspricht also ca. 4.294.967 s und damit 49 Tagen und 17 Stunden. Danach läuft die Zahl über und beginnt wieder bei 0. Für die Klasse TimedAction bedeutet das, dass in der Zeile:
1
m_nextActiveMillis = millis() + m_delay;
beispeilsweise bei m_nextActiveMillis = 4294967200 und m_delay = 100 dann m_nextActiveMillis = 4 herauskommt (= 4294967300 - 4294967296, wobei 4294967296 = 2^32 ist).
Daraufhin würde in den darauffolgenden 100 Aufrufen von bool isActive() jeweils der nächste Zeitpunkt als erreicht betrachtet werden und der Test ständig wahr sein.
Um dieses Problem zu verhindern, kann man die Klasse anpassen und sich statt des nächsten Zeitpunkts den bisherigen Zeitpunkt merken:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Klasse, welche die "bin ich jetzt dran" Funktion kapselt
class TimedAction {
public:
void init(int delay) {
m_delay = delay;
m_lastActiveMillis = millis();
}
void reset() {
m_lastActiveMillis = millis();
}
bool isActive() {
// Schutz vor Integerüberlauf. In diesem Fall wäre millis() < m_lastActiveMillis
if (millis() < m_lastActiveMillis) {
m_lastActiveMillis = millis(); // Letzten Aufrufzeitpunkt zurücksetzen
return;
}
if (millis() < m_lastActiveMillis + m_delay)
return false; // Aktiv-Zeitpunkt noch nicht erreicht
// letzten Zeitpunkt festlegen
m_lastActiveMillis = millis();
return true; // Jetzt sind wir aktiv
}
private:
unsigned long m_lastActiveMillis = 0; // Letzter Aktualisierungszeitpunkt
int m_delay = 50; // Wartezeit zwischen Anpassungen in ms
};
Bei dieser Implementierung können zwar immer noch Überläufe auftreten:
- wenn die Addition
m_lastActiveMillis + m_delayüberläuft, kommt da ein Wert kleiner alsmillis()raus, und die Prüfbedingung wird nicht aktiv, ist also nicht kritisch. Irgendwann läuft auchmillis()über, und das wird dann wie folgt behandelt. - wenn
millis()überläuft, bevorm_lastActiveMillis + m_delayerreicht ist, dann wird das mit einer zusätzlichen Prüfung abgefangen und der letzte Zeitpunkt zurückgesetzt. Dieser Fall könnte z.B. wie folgt eintreten:m_nextActiveMillis = 4294967200undm_delay = 50, also in Summe 4294967250.millis()liefert nun in einem Aufruf vonactive()4294967220 zurück. Die Bedingung ist nicht erfüllt, die Funktion kehrt zurück. Soweit, so gut. Nun wird im Arduino-Programm eine andere längere Operation durchgeführt und der nächste Aufruf findet dann erst 100 Millisekunden später statt. Dann ist abermillis()schon übergelaufen und liefert z.B. 80 zurück. Ohne die Prüfbedingungmillis() < m_lastActiveMilliswürde nun die Funktionactive()ewig lange aufgerufen werden, bis dann irgendwann malmillis() > 42949672500wird.
Fortgeschrittene Themen
Ansteuerung mehrerer LEDs (Output-PINS) und Bitmasken
Aufgabenstellung: Schreibe eine Klasse, welche die Ansteuerung mehrerer LEDs kapselt. Dabei soll der Zustand aller LEDs durch eine einzige Zahl festgelegt werden (Stichwort: Bitmaske).
Beispielverwendung der Klasse:
1
2
3
4
5
6
void loop() {
leds.set(5); // schalte nur LED 1 und 3 an
delay(30);
leds.set(2); // schalte nur LED 2 an
delay(30);
}
Bitfelder und Bitmasken
Wenn man mehrere boolische AN/AUS Größen verwalten will (bspw. die Zustände verschiedener LEDs) kann man das in C-Arrays definieren, bspw.:
1
2
3
4
5
6
7
8
9
10
const int NUM_LEDS = 10;
bool LEDstates[NUM_LEDS];
// alle LEDS ausschalten
for (int i=0; i<NUM_LEDS; ++i)
LEDstates[i] = false;
// LED 1 und 3 einschalten (ACHTUNG: 0-basierte Nummerierung)
LEDstates[0] = true;
LEDstates[2] = true;
Allerdings braucht man dafür unnötig viel Speicher (mind. 1 Byte pro LED Zustand), und das gleichzeitige Umschalten der Zustände mehrerer LEDs braucht mehrere Zuweisungen. Da man aber den AN/AUS-Zustand bereits mit einem einzelnen Bit definieren kann, reicht eine 8-Bit Variable für 8 unterschiedliche Zustände aus.
Alternativ kann man Bitfelder benutzen.

- Beispielsweise wird die Dezimalzahl 6 wird als Binärzahl b0110 dargestellt (siehe auch Beispiele im Foto oben). Jedes Bit der Zahl steht für einen der AN/AUS-Zustände.
- Man kann über logische Operationen nun herausfinden, welches Bit gesetzt ist
- Eine Bitmaske ist dabei eine Zahl, bei der in der binären Darstellung ganz bestimmte Bits gesetzt sind
Man braucht nun Funktionen um:
- den Zustand einzelner Bits an-/auszuschalten
- zu prüfen, ob ein einzelnes oder mehrere Bits angeschaltet sind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Prüfen, ob ein Bit gesetzt ist
int zahl = 5;
int bitmaske_bit_0 = 1;
int bitmaske_bit_1 = 2;
int bitmaske_bit_2 = 4;
if ( (zahl & bitmaske_bit_0) != 0) {
// bit 0 gesetzt
}
if ( (zahl & bitmaske_bit_1) != 0) {
// bit 1 gesetzt
}
if ( (zahl & bitmaske_bit_2) != 0) {
// bit 2 gesetzt
}
Wie kann man Bitmasken mit jeweils nur einem gesetzten Bit an einer bestimmten Stelle generieren?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void setLEDs(int ledstates) {
// ledstates = Ein Bitfeld, in dem die Bits der anzuschaltenden LEDs gesetzt sind
// Alle LEDs durchlaufen und endsprechend ein/ausschalten
for (int i=0; i<NUM_LEDS; ++i) {
// Bitmaske für die ite LED über Shift-Operator << generieren
int bitmask = (1 << i); // Bitshift links
// Testen, ob Bit gesetzt
bool on = ( (ledstates & bitmask) > 0);
// LED ein/ausschalten
digitalWrite( m_pin[i], on);
}
}
Hier ist noch ein umfangreicheres Beispiel für das Arbeiten mit Bitfeldern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <iostream>
#include <iomanip>
#include <string>
// Dez Binär Hex
// 0 0 0
// 1 1 1
// 2 10 2
// 3 11 3
// 4 100 4
// 5 101 5
// 6 110 6
// 7 111 7
// 8 1000 8
// 9 1001 9
// 10 1010 A
// 11 1011 B
// 12 1100 C
// 13 1101 D
// 14 1110 E
// 15 1111 F
// 16 10000 10
std::string bitfield(
unsigned int i,
unsigned int nBits = 8)
{
// generate bitfield for number i
std::string bits(nBits, '0');
for (unsigned int b = 0; b < nBits; ++b) {
int bitmask = (1 << b);
// 0000001
// 0000001 << 2 -> 0000100
// 0000100 >> 1 -> 0000010
// i = 10001001
//
// 10001001
// & 01111000 AND
// --------
// 00001000
//
// 10001001
// | 01111000 OR
// --------
// 11111001
//
// 10001001
// ^ 01111000 XOR
// --------
// 11110001
if ((i & bitmask))
bits[nBits - b - 1] = '1';
else
bits[nBits - b - 1] = '0';
}
return bits;
}
int main()
{
// write table
std::cout << std::left << std::setw(10) << "Dezimal"
<< std::left << std::setw(10) << "Binär"
<< std::left << std::setw(10) << "Hexadezimal" << std::endl;
for (unsigned int i = 0; i < 128; ++i) {
std::cout << std::left << std::setw(10) << std::dec << i
<< std::left << std::setw(10) << bitfield(i)
<< std::left << std::setw(10) << std::hex << i << std::endl;
}
std::cout << "------------" << std::endl;
// bitshift rotation
unsigned int d = 664;
for (unsigned int i=0; i<32; ++i) {
// was ist das höherwertige Bit
unsigned int bitmask = (1 << 31);
bool bitIsSet = d & bitmask;
// links shift
d = d << 1;
// falls höchstes Bit gesetzt war, wieder als niedrigstes setzen
if (bitIsSet)
d = d | 1;
std::cout << bitfield(d, 32) << std::endl;
}
return 0;
}
Mehrere LEDs bequem mit einer Klasse ansteuern
Programmieraufgabe: Für eine bestimmte Anzahl von PINs am Arduino soll nun eine Klasse diese Funktionalität kapseln. Gewünschte Verwendung wie im nachfolgenden Beispiel:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Die zu schreibende Klasse LEDstates
class LEDstates {
// TODO : GTA Teilnehmer
};
// Klassename LEDstates, wir erstellen eine Klassenvariable LEDs
LEDstates LEDs;
const int NUM_PINS = 5;
int PINS[NUM_PINS] = {3, 4, 5, 6, 7};
void setup() {
// Klassenobjekt LEDs konfigurieren -> Anzahl der PINS/LEDS wird übergeben
// und der C-Array mit den PIN IDs
LEDs.setup(NUM_PINS, PINS);
// alle LEDs ausschalten (quasi wie die Funktion setLEDs() oben programmiert)
LEDs.set(0);
}
void loop() {
int permutations = 1 << LEDs.count(); // = 2^NUM_LEDS
for (int i=0; i<permutations; ++i) {
LEDs.set(i);
delay(30);
}
}
Lösung:: Hier ist eine Beispielimplementierung für die Klasse LEDstates:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Die Klasse LEDstates, welche mehrere digitale Pins parallel verwalten und schalten kann
class LEDstates {
public:
void setup(int count, int pins[]){
m_count = count;
m_pins = pins;
// Pinmode auf OUTPUT stellen
for (int i = 0; i<count; ++i) {
pinMode( pins[i], OUTPUT);
}
}
int count() { return m_count; }
void set(int ledstates) {
// ledstates = Ein Bitfeld, in dem die Bits der anzuschaltenden LEDs gesetzt sind
// Alle LEDs durchlaufen und endsprechend ein/ausschalten
for (int i=0; i<m_count; ++i) {
// Bitmaske für die i-te LED über Shift-Operator << generieren
int bitmask = (1 << i); // Bitshift links
// Testen, ob Bit gesetzt
bool isOn = ( (ledstates & bitmask) > 0);
// LED ein/ausschalten
digitalWrite( m_pins[i], isOn);
}
}
private:
int m_count = 0; // Anzahl der PINS
int *m_pins; // Zeiger auf Integer-Array mit PIN Nummern
};
Ein komplette Beispiel findet ihr hier im Simulator
Projekte und Projektideen
Die HEG Schaufensterinstallation
Das Projekt nimmt langsam Gestalt an. Folgende Komponenten sind vorgesehen:
- Helligkeitssensor (damit kann man das Projekt aktivieren, z.B. wenn man mit dem Handy-Flash drauf leuchtet)
- 3 “Charge” LEDs, die zum Aktivieren der Schaltung nacheinander aufgeblendet werden
- 8 Blink-LEDs für Anzeigen (“binäres Zählen”, random blinking, Knight-Raider-Modus etc.)
- LCD 16x2 Display für Laufschrift und Animation
Die aktuelle Code-Version ist hier:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Einbinden unserer Hilfsklassen
#include "LEDstates.h"
#include "LEDFade.h"
#include "TimedAction.h"
class Charge {
public:
Charge(LEDFade * fade1, LEDFade * fade2, LEDFade * fade3) :
m_fade1(fade1),
m_fade2(fade2),
m_fade3(fade3)
{
}
void reset() {
m_timer.reset();
m_counter = 1;
// 1 heißt "Charge hat begonnen" - wenn wir auf 0
// zurückfallen, ist Charge-Modus wieder aus
}
void updateLEDBrightness() {
// fade 1 - wenn counter zwischen 0 ... 255
// fade 2 - wenn counter zwischen 256 ... 511
// fade 3 - wenn counter zwischen 512 ... 767
if (m_counter > 255) {
m_fade1->setValue(255); // komplett an
}
else {
m_fade1->setValue(m_counter);
}
if (m_counter > 511) {
m_fade2->setValue(255); // komplett an
}
else {
int value = m_counter - 256;
if (value < 0)
value = 0; // falls counter < 256 ist
m_fade2->setValue(value);
}
int value = m_counter - 512;
if (value < 0)
value = 0; // falls counter < 512 ist
m_fade3->setValue(value);
}
void updateCharge(bool lightIsOn) {
// sind wir dran mit aktualisieren?
if (!m_timer.isActive())
return;
if (lightIsOn) {
// maxvalue = 3*256 - 1
if (++m_counter > 767) {
m_counter = 767; // Counter bei Maximalwert stoppen
m_hasReachedMaximum = true;
}
}
else {
if (--m_counter < 0) {
m_counter = 0; // Counter bei Maximalwert stoppen
m_hasReachedMaximum = true;
}
}
updateLEDBrightness();
}
bool isFullyCharged() const {
return m_hasReachedMaximum && m_counter > 0;
}
int m_counter = 0;
private:
TimedAction m_timer;
// Zeiger auf fade-Klassenobjekte (werden extern erzeugt und hier nur benutzt)
LEDFade *m_fade1;
LEDFade *m_fade2;
LEDFade *m_fade3;
bool m_hasReachedMaximum = false;
};
// Zustände unserer Steuerung
enum {
S_IDLE, // In diesem Modus wird nur eine LED gefaded
S_CHARGE, // In diesem Zustand werden die Charge-LEDs auf-/abgeblendet
S_ON_MODE1 // Schaltung läuft im Modus 1 los (Willkommen im LCD Display)
// TODO : weitere Zustände hinzubauen
};
// ** KONSTANTEN/PIN MAPPING **
// Inputs
int BRIGHTNESS_PIN = A0;
// Outputs
// PWM Pins
int CHARGE_PIN1 = 9;
int CHARGE_PIN2 = 10;
int CHARGE_PIN3 = 11;
// Pins für die LEDs
int LED_PINS[8] = { 2, 4, 7, A1, A2, A3, A4, A5};
// ** VARIABLEN **
// Zur Ansteuerung der dimmbaren Charge-LEDs
LEDFade fade1;
LEDFade fade2;
LEDFade fade3;
// Controller für die ganze Charge-Logik
Charge charge(&fade1, &fade2, &fade3);
// Klasse für die 8 LEDs
LEDstates leds;
// Aktueller Zustand der Schaltung
int state = S_IDLE;
// Variablen für verschiedene Modi
TimedAction onModeTimer;
int onModeCounter;
int ledNumberCounter = 0;
// Timer für Debug-Ausgaben durch die serielle Schnittstelle
TimedAction serialTimer;
void setup() {
fade1.init(CHARGE_PIN1, true, 0, 25, 5); // Konfiguriert zum langsam pulsieren
fade2.init(CHARGE_PIN2, true, 0, 10, 5);
fade3.init(CHARGE_PIN3, true, 0, 10, 5);
pinMode(BRIGHTNESS_PIN, INPUT);
leds.setup(8, LED_PINS);
leds.set(0);
serialTimer.init(500); // alle 500 ms eine Meldung über USB
Serial.begin(115200);
}
// ** Hauptschleife, wird ständig durchlaufen **
void loop() {
// Den Helligkeitssensor lesen wir immer aus.
int brightnessValue = analogRead(BRIGHTNESS_PIN);
// wenn Helligkeit größer als 900, dann ist das Licht an
bool lightIsOn = (brightnessValue > 900);
// Je nachdem in welchem Modus die Schaltung ist, machen wir verschiedene Sachen.
switch (state) {
case S_IDLE: // Rote LED pulsieren lassen und auf Licht-An warten
fade1.fade();
if (lightIsOn) {
state = S_CHARGE; // Licht ist an - wir wechseln in den Zustand 'Charge'
charge.reset();
}
break;
case S_CHARGE: // Charge-LEDs werden heller (oder ohne Licht wieder dunkel)
charge.updateCharge(lightIsOn);
// sind wir zurück auf 0 gefallen, gehen wir wieder in den Modus IDLE
if (charge.m_counter == 0)
state = S_IDLE;
// sind wir vollkommen aufgeladen, gehen wir in den Modus ON_MODE1
if (charge.isFullyCharged()) {
state = S_ON_MODE1;
onModeCounter = 0; // wir beginnen mit LED-Stufe 0
onModeTimer.init(100);
}
break;
case S_ON_MODE1: // in diesem Modus schalten wir alle LEDs schnell
// hintereinander ein und aus und dann das LCD-Display an
if (onModeTimer.isActive()) {
++onModeCounter;
if (onModeCounter % 2) { // ungerade Zahl?
leds.set(255);
}
else {
leds.set(0);
}
if (onModeCounter == 20) {
// for now fall back to idle mode
state = S_IDLE;
}
}
break;
}
// Debug-Feedback
if (serialTimer.isActive()) {
Serial.print(brightnessValue);
Serial.print(",");
Serial.print(state);
Serial.print(",");
Serial.println(charge.m_counter);
}
}
Die in den vorangehenden Kapiteln vorgestellten Klassen LEDFade, LEDstates und TimedAction sind als Include-Dateien eingebunden. Diese Dateien sind im Quelltextarchiv enthalten:
Mareks Transistorschaltung
- mittels 2 Output-Pins können insgesamt 4 Zustände geschaltet werden
- die Transistorschaltung sorgt dafür, dass die jeweils ausgewählte LED angeschaltet wird
- so kann man 4 LEDs mit nur 2 PINs schalten

Es gibt ein Modul, welches als Pin-Extender funktioniert, erklärt in diesem PCF8575 Tutorial.
Projekt: Digitale Sanduhr - 3D Drucker + LED Matrix
Anleitung/Beschreibung zum Projekt LED Sanduhr gibt’s auf https://www.instructables.com/LED-HOURGLASS-USING-ARDUINO

3D Programmierung mit OpenGL
Shader und Speicherbereiche
In diesem Beispiel werden Vertex Array Objects (VAO), Vertex Buffer Objects (VBO) verwendet, um eine Fläche zu zeichnen.

Index-Puffer
Erweiterung des “rotierenden Würfel”-Beispiels um einen Index-Puffer. Nun werden nur noch die Koordinaten der Eckpunkte im Vertexpuffer gehalten, und die 6 Seitenflächen via Elementpuffer/Indexpuffer gezeichnet.

Normalenvektoren und Phong Shader
Erweiterung um Normalenvektoren - wird für die Lichtreflektionsberechnung benutzt.
