Wiki: Mathe und Info

Unterrichtsmaterialien für Mathematik und Informatik

Benutzer-Werkzeuge

Webseiten-Werkzeuge


info:sek-ii:q4:embedded:l1-grundlagen

Grundlagen von eingebetteten Systemen

Was ist ein eingebettetes System?
Eingebettete Systeme sind aus unserem Alltag nicht mehr wegzudenken. Angefangen von der elektrischen Zahnbürste über den Toaster, Mikrowelle, Router, WLAN-Access-Point bis hin zu Steuerungsmodulen in Autos (auch "normale", nicht nur selbst fahrende).

info:sek-ii:q4:embedded:l1-anwendungen.png

Wer hätte gedacht, dass selbst unsere Bank-Karten oder der neue Personalausweis eingebettete Systeme1) sind?

Charakteristische Eigenschaften

Eingebettete Systeme sind anwendungsspezifisch, sie können also genau eine Sache richtig gut (die entsprechende Anwendung). Außerdem haben sie im Gegensatz zu Computern eine minimale Benutzerschnittstelle (z.B. nur einen Button, um die elektrische Zahnbürste zu starten und einen Drehregler, um die Rotationsgeschwindigkeit des Bürstenkopfs einzustellen). Sie zeichnen sich außerdem durch hohe Effizienz, Zuverlässigkeit und Stabilität aus, verbrauchen also so wenig Strom wie möglich, gehen (fast) nicht kaputt und tun genau das, was von ihnen will. Außerdem benötigen sie wenig Leistung / Ressourcen und sind günstig.

Architektur von eingebetteten Systemen
Eingebettete Systeme unterscheiden sich in drei Grundbausteine: Eingabegeräte (Sensoren), Verarbeitungsgeräte (Mikrocontroller) und Ausgabegeräte (Aktoren). In der Regel sind die Sensoren und Aktoren analoge Geräte, nur der Mikrocontroller ist digital, weswegen wir noch Analog-zu-Digital- und Digital-zu-Analog-Wandler benötigen. Was das genau bedeutet, sehen wir später.

info:sek-ii:q4:embedded:l1-architecture.png

Beispiel 1: Ein erstes eingebettetes System
a) Meldet euch auf Tinkercad an. Link und Nickname erhaltet ihr von eurem Lehrer.
b) Erstellt einen neuen Schaltkreis (Circuit) mit dem Namen "Eingebettete Systeme - 01 - Blinkende LED", indem ihr im Schaltkreis auf den generierten Zufallsnamen klickt:
c) Zieht folgende Komponenten auf das Board:
  • 1 Arduino Uno R3
  • 1 LED
  • 1 Resistor

Verbindet die Komponenten mit Kabeln, indem ihr die entsprechenden Pins anklickt, um das Kabel zu erstellen:

  • Linker Pin der LED mit dem oberen Pin des Resistors
  • unterer Pin des Resistors mit einem GND-Port
  • Rechter Pin der LED mit Port 2
d) Klickt oben rechts auf Code und stellt den Editor auf "Text" statt "Blöcke". Übernehmt den Code rechts.

void setup() {
  pinMode(2, OUTPUT);
}
 
void loop() {
  digitalWrite(2, HIGH);
  delay(1000);
 
  digitalWrite(2, LOW);
  delay(1000);
}
e) Klickt auf "Start Simulation". Wenn ihr alles richtig gemacht habt, sollte die LED jetzt blinken:


f) Fertig ist euer erstes eingebettetes System. Es kann genau das, was es soll, nicht mehr und nicht weniger: eine LED zum Blinken bringen.

  • Als Aktor wurde eine LED verwendet.
  • Als Mikrocontroller wurde ein Arduino verwendet.
  • Es wurden keine Sensoren verwendet.
Fachkonzept: Arduino-Programm

Für die Programmierung des Arduino gibt es einige Regeln:

  • Es gibt die Methoden setup() und loop(). Sie sind ähnlich zu den setup()- und draw()-Methoden, die ihr aus Processing-Programmen kennt.
    • setup() wird zu Beginn der Simulation einmal ausgeführt und regelt grundlegende Einstellungen, z.B. welche Pins als Eingabe- und welche als Ausgabe-Signale betrachtet werden. Mehr lesen.
    • loop() wird in einer Endlosschleife immer wieder hintereinander ausgefürt. Hier wird die Programmlogik geschrieben. Mehr lesen.
  • Mit pinMode(2, OUTPUT) teilt man dem Arduino mit, dass an Pin Nr. 2 eine Ausgabe-Komponente angeschlossen ist Mehr lesen
  • Eingabesignale können mit digitalRead(2) gelesen werden (hier von Pin Nr. 2). Mehr lesen
  • Ausgabesignale können mit digitalWrite(2, HIGH) geschrieben werden (hier auf Pin Nr. 2). Mehr lesen
  • Die Syntax von Arduino-Programmen ist die der Programmiersprache C (Siehe Referenz). Sie ist meistens sehr ähnlich zu Java.
Digitale Signale
Digitale Signale haben - wie alles digitale - genau zwei Zustände: Strom an (HIGH) und Strom aus (LOW). Mikrokontroller wie der Arduino können an ihren Pins abfragen, welcher Zustand gerade anliegt (digitalRead()) und diesen auch festlegen (digitalWrite()).
Beispiel 2 - Aktoren und Sensoren
a) Erstelle den folgenden Schaltkreis und speichere ihn als "Eingebettete Systeme - 02 - LED + Button":
  • 1 Arduino Uno R3
  • 2 Resistoren
  • 1 LED
  • 1 Button

Verbinde wie folgt:

  • Die unteren Pins der beiden Resistoren mit dem GND-Port
  • Der obere Pin des ersten Resistors mit dem linken Pin der LED
  • Der obere Pin des zweiten Resistors mit dem unteren linken Pin des Buttons
  • Der rechte Pin der LED mit Port 2 des Arduino
  • Der rechte untere Pin des Buttons mit dem 5V-Port des Arduino
  • Der linke obere Pin des Buttons mit Port 4 des Arduino
b) Übernimm den Code rechts für den Arduino.


c) Starte die Simulation. Die LED sollte nun leuchten, wenn du den Button drückst. Wenn du den Button wieder loslässt, geht auch die LED aus.

  • Als Sensor wurde ein Button verwendet, der Rest ist identisch zu Beispiel 1.
void setup() {
  pinMode(2, OUTPUT);
  pinMode(4, INPUT);
}
 
void loop() {  
  int stateButton = digitalRead(4);
 
  if (stateButton == HIGH) {
    digitalWrite(2, HIGH);
  } else {
    digitalWrite(2, LOW);
  }
 
  delay(10);
}
Weitere Sprachelemente

In Arduino-Programmen könnt ihr alle Sprachelemente verwenden, die ihr in Java kennen gelernt habt:

Aufgabe - Ampelschaltung
a) Baut aus den folgenden Komponenten eine Ampel-Schaltung und speichert sie unter "Eingebettete Systeme - 03 - Ampel":
  • 1x Arduino Uno R3
  • 3x LED (1x grün, 1x gelb, 1x rot)
  • 2x Widerstand
  • 1x Button

Verkabelt wie folgt:

  • Grüne LED an Port 4 (OUTPUT)
  • Gelbe LED an Port 3 (OUTPUT)
  • Rote LED an Port 2 (OUTPUT)
  • Button an Port 1 (INPUT)
b) Übernehmt den Code unten. Bevor ihr die Simulation startet, versuch den Code zu verstehen.
// Zuerst werden ein paar Konstanten definiert, die die verschiedenen Zustände der Ampel angeben.
const int AMPEL_AUS = 0;
const int AMPEL_GRUEN = 1;
const int AMPEL_GELB = 2;
const int AMPEL_ROT = 3;
 
//Die Variable "ampel" speichert den aktuellen Zustand.
int ampel = AMPEL_AUS;
 
void setup() {
  // Die entsprechenden Ein- und Ausgänge werden definiert
  pinMode(1, INPUT);  
 
  pinMode(2, OUTPUT); 
  pinMode(3, OUTPUT); 
  pinMode(4, OUTPUT); 
 
  // Alle drei LEDs werden ausgeschaltet
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
}
 
 
void loop() {
  // Button-Status wird ausgelesen
  int reading = digitalRead(1);
 
  // Wenn Button gedrückt
  if (reading == HIGH) {
    // Ampel auf nächsten Status setzen
    ampel++;
    if (ampel > AMPEL_ROT) ampel = AMPEL_AUS;
 
 
    switch (ampel) {
      // Wenn Ampel aus, schalte alle LEDs aus
      case AMPEL_AUS:
        digitalWrite(2, LOW);
        digitalWrite(3, LOW);
        digitalWrite(4, LOW);
        break;
      // Wenn Ampel grün, schalte die grüne LED an und alle anderen aus
      case AMPEL_GRUEN:
        digitalWrite(2, LOW);
        digitalWrite(3, LOW);
        digitalWrite(4, HIGH);
        break;
      // Wenn Ampel gelb, schalte die gelbe LED an und alle anderen aus
      case AMPEL_GELB:
        digitalWrite(2, LOW);
        digitalWrite(3, HIGH);
        digitalWrite(4, LOW);
        break;
      // Wenn Ampel rot, schalte die rote LED an und alle anderen aus
      case AMPEL_ROT:
        digitalWrite(2, HIGH);
        digitalWrite(3, LOW);
        digitalWrite(4, LOW);
        break;
    }
  }
}
c) Testet den Code. Was passiert?


Irgendwie scheint der Code nicht zu funktionieren. Es scheint eine zufällige LED angeschaltet zu werden, nicht nacheinander GRÜN → GELB → ROT → AUS → GRÜN → GELB → …

Hüpfende Knöpfe
Dass der Code oben nicht funktioniert liegt daran, wie der Arduino intern funktioniert. Die loop()-Methode läuft als Endlosschleife. Unser Beispiel ist trotz der 50 Zeilen Code sehr kurz. Es läuft also extrem schnell ab. Wenn wir nun mit unseren Wurstfingern den Button drücken, wird die loop()-Methode mehrfach durchlaufen, während der Button noch gedrückt ist, also als Status HIGH hat. Bei echter Hardware kann es bei Buttons auch passieren, dass der Button bei einmal drücken mehrfach auslöst. Das nennt man Bouncing - "Hüpfen" und sorgt für viele Probleme: der Status der Ampel wird direkt hintereinander mehrfach druchgeschaltet - Die Ampelschaltung ist so nicht nutzbar.
Debouncing
Zum Glück sind wir ja Programmierer und können dem entgegenwirken. Im Arduino gibt es die Funktion millis(), die uns die Millisekunden seit Start des Mikrocontrollers liefert (Mehr dazu). Mit ihrer Hilfe können wir eine Verzögerung einbauen - das so genannte debouncing, also dass der Status der Ampel nur geändert wird, wenn der Button 50 Millisekunden gedrückt wurde.

Dazu benötigen wir ein paar zusätzliche Variablen:

int buttonState;
int lastButtonState = LOW;
 
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

Außerdem müssen wir die loop()-Methode anpassen:

void loop() {
  int reading = digitalRead(1);
 
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }
 
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
 
      // Hier kommt der ursprüngliche Inhalt von loop() hin
    }
  }
 
  lastButtonState = reading;
}
Aufgabe 2 - Enthüpfte Ampelschaltung

a) Dupliziert euren Ampel-Schaltkreis und speichert die Kopie unter "Eingebettete Systeme - 04 - Debounce-Ampel". Passt dann den Code der Ampelschaltung an:

const int AMPEL_AUS = 0;
const int AMPEL_GRUEN = 1;
const int AMPEL_GELB = 2;
const int AMPEL_ROT = 3;
 
int ampel = AMPEL_AUS;
int buttonState;
int lastButtonState = LOW;
 
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;
 
void setup() {
  pinMode(1, INPUT);
 
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
 
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
}
 
void loop() {
  int reading = digitalRead(1);
 
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }
 
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
 
      if (reading == HIGH) {
        ampel++;
 
        if (ampel > AMPEL_ROT) ampel = AMPEL_AUS;
 
        switch (ampel) {
          case AMPEL_AUS:
            digitalWrite(2, LOW);
            digitalWrite(3, LOW);
            digitalWrite(4, LOW);
            break;
          case AMPEL_GRUEN:
            digitalWrite(2, LOW);
            digitalWrite(3, LOW);
            digitalWrite(4, HIGH);
            break;
          case AMPEL_GELB:
            digitalWrite(2, LOW);
            digitalWrite(3, HIGH);
            digitalWrite(4, LOW);
            break;
          case AMPEL_ROT:
            digitalWrite(2, HIGH);
            digitalWrite(3, LOW);
            digitalWrite(4, LOW);
            break;
        }
      }
    }
  }
 
  lastButtonState = reading;
}

b) Nun funktioniert die Ampelschaltung wie gewünscht:

Weitere Informationen

Quellen:

1)
Unsere Bankkarte hat in etwa die Rechenleistung eines PCs aus den 1980er Jahren! Siehe https://de.wikipedia.org/wiki/Contact_EMV
info/sek-ii/q4/embedded/l1-grundlagen.txt · Zuletzt geändert: 2023-01-11 15:03 von yannik.wehr