Gta Robotik Programmierung

GTA-Robotik/Programmierung am Hans-Erlwein-Gymnasium in Dresden

/assets/img/HEG.jpg

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)

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 und das minimalistische Programm

Zur Installation der Arduino IDE siehe Artikel “Arduino IDE unter Linux einrichten”.

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;
}

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.

Hier ist ein 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
}

In der Wartezeit kann man nichts anderes machen. Wie soll man hier also 2 LEDs arithmisch blinken lassen?

Schaltplanfrage: Muss man jede LED mit einem eigenen Widerstand absichern (Bild links), oder kann man sich einen Widerstand sparen (Bild rechts)?

Schaltungsvarianten mit 2 LEDs

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.

Aufgabenstellung: Erzeuge folgendes Blinkmuster!

  1. beide LEDs aus
  2. 300 ms warten
  3. Rote LED an
  4. 200 ms warten
  5. Blaue LED an
  6. 500 ms warten

… und bei 1. wiederholen

Lösungsansatz: Die Funktion millis() benutzen, um die verstrichene Zeit zu erfassen. Beim Überschreiten der jeweiligen Wartezeiten die geforderte Aktion ausführen. Vorschlag: Schrittkette mit 3 Stufen umsetzen (keine LED an, nur Rot an, Rot und Blau an).

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().

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()

… je LED zwei Variablen, dazu noch die lastMillis -> 3 Variablen je LED…

LEDFade - Klassenprogrammierung

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
98
99
100
101
102
// 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 delayMs, int stepSize) {
    // Membervariablen initialisieren
    m_pin = pin;
    m_direction = direction;
    m_value = value;
    m_delayMs = delayMs;
    m_stepSize = stepSize;
    m_lastMillis = 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; 

    // Gegen Overflow schützen (millis() werden nach einer Weile zurückgesetzt)
    if (millis() < m_lastMillis)
      m_lastMillis = millis();

    // LED erst aktualisieren, wenn die geforderte Wartezeit überschritten wurde
    if (millis() < m_lastMillis + m_delayMs) 
      return; // not yet there, skip this

    m_lastMillis = millis();

    // 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_delayMs            = 50;   // Wartezeit zwischen Anpassungen in ms
  int   m_value              = 0;    // Aktuelle Helligkeit
  int   m_stepSize           = 1;    // Schrittgröße
  unsigned long m_lastMillis = 0;    // Letzter Aktualisierungszeitpunkt
};


// Variablen für die beiden LEDs
LEDFade led1;
LEDFade led2;

void setup() {
  // die Klassenvariablen mit den jeweiligen Parametern initialisieren
  
  // PIN 10: anfänglich aufblenden, Startwert 0, 250 ms Wartezeit, und
  //         25er Schritte machen
  led1.init(10, true, 0, 250, 25);  
  
  // PIN 11 etwas anders konfiguriert
  led2.init(11, 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).

Ansteuerung mehrerer LEDs (Output-PINS) und Bitmasken

Aufgabenstellung: Schreibe eine Klasse, welche die Ansteuerung mehrerer LEDs kapselt.

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);
}

Einführung Bitfelder und Bitmasken

Lösungsansatz:

Felder von AN/AUS Eigenschaften kann man 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 mehr Speicher (mind. 1 Byte pro LED Zustand), und das gleichzeitige Umschalten der Zustände mehrerer LEDs braucht mehrere Zuweisungen. Alternativ kann man Bitfelder benutzen.

  • Eine Dezimalzahl bzw. 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
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
29
30
31
32
33
// Die zu schreibende Klasse LEDstates
class LEDstates {
  // TODO : GTA Teilnehmer
};


// Klassename LEDstates, wir erstellen eine Klassenvariable LEDs
LEDstates LEDs;

void setup() {
  int NUM_PINS  =5;
  int pins[NUM_PINS];
  pins[0] = 3;
  pins[1] = 4;
  pins[2] = 5;
  pins[3] = 6;
  pins[4] = 7;
  
  // 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);
   }
}

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

Mareks TransistorSchaltung (PIN-Extender)

Es gibt ein Modul, welches als Pin-Extender funktioniert, erklärt in diesem PCF8575 Tutorial.