1.3 Zustandsdiagramm

Benötigte Hilfsmittel: TGI-Formelsammlung, Software Umlet, Anleitung zu Umlet:

Zustandsdiagramm für Lüftersteuerung

PA1PA6PA10
An <-> Auslangsamerschneller

Ein Lüfter soll mit den Tasten PA1 (An/Aus) PA6 (Lüfter langsamer) und PA10 (Lüfter schneller) gesteuert werden. Es gibt 4 Lüfterstufen 0..3. Bei Stufe 0 leuchtet nur LC0 an PC0 als Betriebsanzeige.
Die letzte Lüfterstufe soll beim Ausschalten gemerkt werden und beim Anschalten wieder eingestellt sein. Bei ausgeschaltetem Lüfter leuchtet keine LED und die Taster PA6 und PA10 sind ohne Funktion.
Bei der Verwendung von digitalRead(..) müsste Prellen berücksichtigt und auf das Loslassen der Tasten gewartet werden. Deshalb steht die Funktion buttonCheck() zur prellfreien Erkennung von Tastenbetätigungen zur Verfügung.

StufePC3PC2PC1PC0
Aus0000
Stufe 00001
Stufe 10011
Stufe 20111
Stufe 31111
Anzeige der Lüfterstufen
Zustandsdiagramm
Zustandsdiagramm (mit SuS entwickeln)

Aufzählungsdatentyp enum

Die Zustände müssen codiert werden. Der Quellcode ist besser lesbar wenn statt Zahlen 0,1… für zustand aussagekräftige Namen vergeben werden. Mit dem Aufzählungsdatentyp enum ist dies möglich. Es kann sogar ein eigener wiederverwendbarer Datentyp mittels typedef definiert werden. Die Symbole {AN,AUS} sollten aber Groß geschrieben werden.

enum {AUS,AN} zustand=AUS; // Aufzaehlungstyp
//typedef enum {AUS,AN} ZustandTyp;  // Aufzaehlungstyp
//ZustandTyp zustand;

Möglicher Code für Zustandsdiagramm der Lüftersteuerung

void setup() {
  pinMode(PC0,OUTPUT);
  pinMode(PC1,OUTPUT);
  pinMode(PC2,OUTPUT);
  pinMode(PC3,OUTPUT);
  pinMode(PA1,INPUT_PULLDOWN);
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
}

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(5);                      // 5ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}

enum {AUS,AN} zustand=AUS; // Aufzaehlung der Zustände
int stufe = 0;

void ausgebenStufe(){     // Luefterstufen als Leuchtband ausgeben
  int ausgabe=1;
  for(int i=0; i<stufe; i++){
    ausgabe = (ausgabe<<1)+1;
  }
  GPIOC->ODR = ausgabe;
}
void ausschaltenLED(){
  GPIOC->ODR = 0;
}
void minusLuefter(){
  if(stufe>0) stufe--;
}
void plusLuefter(){
  if(stufe<3) stufe++;
}
void loop() {
  buttonCheck();
  switch (zustand){.           // je nach Zustand
    case AUS:                  // Zustand Aus
      if(buttonEnter&(1<<1)){  // PA1 gedrueckt?
        ausgebenStufe();
        zustand = AN;
      }
      break;
    case AN:                   // Zustand An
      if(buttonEnter&(1<<1)){  // PA1 gedrueckt?
        ausschaltenLED();
        zustand = AUS;
      }
      if(buttonEnter&(1<<6)){  // PA6 gedrueckt?
        minusLuefter();
        ausgebenStufe();
      }
      if(buttonEnter&(1<<10)){ // PA10 gedrueckt?
        plusLuefter();
        ausgebenStufe();
      }
      break;
  }
}

Lösung verschönern mit #define

Es gibt noch “hässliche” Ausdrücke wie buttonEnter&(1<<1) im Projekt. Vor dem Kompilieren kann der C-Prä-Prozessor Texte ersetzen. Z.B T_ANAUS durch buttonEnter&(1<<1). D.h. wir könnten das Zustandsdiagramm und das Programm damit lesbarer machen! Hier mein Vorschlag: Das T_ steht für Taste. Die Ersetzungen sollten Groß geschrieben werden.

#define T_ANAUS buttonEnter&(1<<1)
#define T_LANGSAMER buttonEnter&(1<<6)
#define T_SCHNELLER buttonEnter&(1<<10)

Verschönern Sie das Zustandsdiagramm und den Quellcode damit!

Lösung Zustandsdiagramm
Schöneres Zustandsdiagramm
Schöneres Zustandsdiagramm
Lösung Programmcode
void setup() {
  pinMode(PC0,OUTPUT);
  pinMode(PC1,OUTPUT);
  pinMode(PC2,OUTPUT);
  pinMode(PC3,OUTPUT);
  pinMode(PA1,INPUT_PULLDOWN);
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
}

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(5);                      // 5ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}

#define T_ANAUS buttonEnter&(1<<1)
#define T_LANGSAMER buttonEnter&(1<<6)
#define T_SCHNELLER buttonEnter&(1<<10)

enum {AUS,AN} zustand=AUS; // Aufzaehlungstyp
int stufe = 0;

void ausgebenStufe(){     // Luefterstufen ausgeben
  int ausgabe=1;
  for(int i=0; i<stufe; i++){
    ausgabe = (ausgabe<<1)+1;
  }
  GPIOC->ODR = ausgabe;
}
void ausschaltenLED(){
  GPIOC->ODR = 0;
}
void minusLuefter(){
  if(stufe>0) stufe--;
}
void plusLuefter(){
  if(stufe<3) stufe++;
}
void loop() {
  buttonCheck();
  switch (zustand){
    case AUS:          // Zustand Aus
      if(T_ANAUS){     // AnAus gedruekt?
        ausgebenStufe();
        zustand = AN;
      }
      break;
    case AN:          // Zustand An
      if(T_ANAUS){    // Taste AnAus gedruekt?
        ausschaltenLED();
        zustand = AUS;
      }
      if(T_LANGSAMER){  // Taste langsamer gedrueckt?
        minusLuefter();
        ausgebenStufe();
      }
      if(T_SCHNELLER){ // Taste schneller gedrueckt?
        plusLuefter();
        ausgebenStufe();
      }
      break;
  }
}

Lösung verschönern mit isEnter()

Wem die #define -Lösung nicht gefällt könnte sich ja eine Hilfsfunktion isEnter(n:int):boolean wobei n die Pin-Nummer des Tasters ist ausdenken:

boolean isEnter(int n){
  return buttonEnter&(1<<n);
}

Einschalten auch mit Tasten PA6 und PA10

Der Lüfter soll nun auch mit den Tasten PA6 und PA10 eingeschaltet werden können. Modifizieren Sie das Zustandsdiagramm und das Programm entsprechend, verwenden Sie diesmal isEnter() zur Tastenabfrage.

Lösung Zustandsdiagramm
Zustandsdiagramm
Zustandsdiagramm

Autoblinker

PA1PA6PA10
Nach linksNach rechtsWarnblinker
Tasterbelegung

Wenn PA1 gedrückt wird soll sich ein Leuchtband von LC0 bis LC7 aufbauen mit Zeitabstand LEUCHTDAUER. Bei Betätigen von PA6 wird das Leuchtband von LC7 bis LC0 aufgebaut. Solange PA10 gedrückt ist sollen alle LED blinken mit Zeitabstand BLINKDAUER.
Diesmal wird die Arduino-Funktion digitalRead(..) verwendet. Hier Code und Zustandsdiagramm zum Start:

void setup() {
  pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(PA1,INPUT_PULLDOWN);
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
}
enum {AUS,LINKS,RECHTS,WARNBLINKEN} zustand=AUS; // Aufzaehlungstyp
#define LEUCHTDAUER 70
#define BLINKDAUER 400
Zustandsdiagramm Autoblinker

Wenn alle Tasten gedrückt werden sollte der Warnblinker Vorrang haben.
Entwickeln Sie aus dem Zustandsdiagramm den restlichen Code. Bin gespannt ob das Experiment klappt 🤠.

Lösung Programmcode
void setup() {
  pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(PA1,INPUT_PULLDOWN);
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
}

enum {AUS,LINKS,RECHTS,WARNBLINKEN} zustand=AUS; // Aufzaehlungstyp
#define LEUCHTDAUER 70
#define BLINKDAUER 400
void loop() {
  switch (zustand){
    case AUS:                   // Zustand Aus
      delay(BLINKDAUER);
      if(digitalRead(PA1)){     // Links blinken?
        zustand = LINKS;
      }
      if(digitalRead(PA6)){     // Rechts blinken?
        zustand = RECHTS;
      }
      if(digitalRead(PA10)){    // Warnblinken?
        zustand = WARNBLINKEN;
      }
      break;
    case LINKS:                 // Zustand Links
      if(GPIOC->ODR>=0xff){     // Alle leuchten?
        zustand = AUS;
        GPIOC->ODR = 0;
      }
      else{
        GPIOC->ODR = (GPIOC->ODR<<1)+1;
        delay(LEUCHTDAUER);
      }
      break;
    case RECHTS:               // Zustand rechts
      if(GPIOC->ODR>=0xff){    // Alle leuchten?
        zustand = AUS;
        GPIOC->ODR = 0;
      }
      else{
        GPIOC->ODR = (GPIOC->ODR>>1)+0x80;
        delay(LEUCHTDAUER);
      }
      break;
    case WARNBLINKEN:          // Zustand Warnblinken
      GPIOC->ODR = 0xff;
      delay(BLINKDAUER);
      GPIOC->ODR = 0;
      zustand = AUS;
      break;
  }
}

Warum ist ein Entprellen diesmal nicht notwendig?

UpDown-Zähler mit 7Segmentanzeige

Mit den Tasten PA6 (Up) und PA1 (Down) soll eine Zahl auf der 7-Segmentanzeige ausgegeben werden.

Zustandsdiagramm
Zustandsdiagramm

Beim Eintritt in den Chillen-Zustand muss wegen enter/ausgeben() ausgeben() aus den Transitionen setup(), von StufeUp und StufeDown aufgerufen werden. Kriegt man das eleganter hin, ohne ausgebenEiner() drei mal aufrufen zu müssen? Wie wäre es mit einem Zustand CHILLEN_ENTER, der dann in den Zustand CHILLEN wechselt?

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PC11,OUTPUT); // Einer
    pinMode(PC12,OUTPUT); // Zehner
    pinMode(PA1,INPUT_PULLDOWN);
    pinMode(PA6,INPUT_PULLDOWN);
}

int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung
int stufe=0;

void ausgebenEiner(){
  GPIOC->ODR = bcd_7seg[stufe] | (1<<11); // Einer einschalten
}

enum {CHILLEN_ENTER,CHILLEN,STUFEUP,STUFEDOWN} zustand=CHILLEN_ENTER;
void loop(){
  switch(zustand){
    case CHILLEN_ENTER:
      ausgebenEiner();
      zustand = CHILLEN;
      break;
    case CHILLEN:
      if(digitalRead(PA6) && stufe<9){
        stufe++;
        zustand = STUFEUP;
        delay(5); // Entprellen
      }
      if(digitalRead(PA1) && stufe>0){
        stufe--;
        zustand = STUFEDOWN;
        delay(5); // Entprellen
      }
      break;
    case STUFEUP:
      if(!digitalRead(PA6)){
        zustand = CHILLEN_ENTER;
        delay(5);
      }
      break;
    case STUFEDOWN:
      if(!digitalRead(PA1)){
        zustand = CHILLEN_ENTER;
        delay(5);
      }
      break;
  }
}

Zweistellige Ausgabe

Die Segmentanzeigen werden im Zeitmultiplex betrieben. Einerstelle ausgeben, warten, Zehnerstelle ausgeben, warten und wieder von vorne und das die ganze Zeit. Ersetze enter/ausgebenEiner() durch do/ausgeben().

#define AZEIT 20 // 20ms Stelle anzeigen, Flackern ist sichtbar
void ausgeben(){
  GPIOC->ODR = bcd_7seg[stufe%10] | (1<<11); // Einer einschalten
  delay(AZEIT);
  GPIOC->ODR = bcd_7seg[stufe/10] | (1<<12); // Zehner einschalten
  delay(AZEIT);
}
Zustandsdiagramm
Zustandsdiagramm

Beim Drücken einer Taste bleibt die Ausgabe allerdings stehen. Auch bei StufeUp und StufeDown sollte ausgegeben werden.

Zustandsdiagramm
Zustandsdiagramm

Da es eh für alle Zustände gilt, kann ausgeben() auch vor der Switch-Anweisung aufgerufen werden.

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PC11,OUTPUT); // Einer
    pinMode(PC12,OUTPUT); // Zehner
    pinMode(PA1,INPUT_PULLDOWN);
    pinMode(PA6,INPUT_PULLDOWN);
}

int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung
int stufe=0;

#define AZEIT 10 
void ausgeben(){
  GPIOC->ODR = bcd_7seg[stufe%10] | (1<<11); // Einer einschalten
  delay(AZEIT);
  GPIOC->ODR = bcd_7seg[stufe/10] | (1<<12); // Zehner einschalten
  delay(AZEIT);
}

enum {CHILLEN,STUFEUP,STUFEDOWN} zustand=CHILLEN;
void loop(){
  switch(zustand){
    ausgeben();
    case CHILLEN:
      if(digitalRead(PA6) && stufe<99){
        stufe++;
        zustand = STUFEUP;
        delay(5); // Entprellen
      }
      if(digitalRead(PA1) && stufe>0){
        stufe--;
        zustand = STUFEDOWN;
        delay(5); // Entprellen
      }
      break;
    case STUFEUP:
      if(!digitalRead(PA6)){
        zustand = CHILLEN;
        delay(5);
      }
      break;
    case STUFEDOWN:
      if(!digitalRead(PA1)){
        zustand = CHILLEN;
        delay(5);
      }
      break;
  }
}

Garagentorsteuerungssimulation

Die Simulation einer Garagentorsteuerung sollen Sie entwickeln. Der Zustand des Tors wird durch Leuchtband L0..L6 angezeigt. Wenn das Tor auf ist, leuchtet nur L0, ist es zu ist leuchten L0..L6. L7 soll später blinken, wenn sich das Tor bewegt. Zunächst wird nur der Schlüsselschalter mit PA1 (zu) und PA6 (auf) und L0..L6 berücksichtigt.

Ausgabe- und Eingabe-Belegung
Ausgabe- und Eingabe-Belegung
Schlüsselschalter
Schlüsselschalter
PA1PA6PA10
ZuAufHandsender
Tasterbelegung

Teil 1: Schlüsselschalter steuert Tor, noch kein Blinken und kein Handsender

Die Taster sind nicht entprellt! Durch Betätigen von PA1 fährt das Tor zu, ein kurzes Betätigen reicht, um die Fahrt in Gang zu setzen. Dabei wird in der Simulation nach der Zeit LEUCHTDAUER jeweils eine weitere LED eingeschaltet, bis L0..L6 leuchten, dann stoppt das Tor. Entsprechend der Ablauf zum Öffnen des Tors mit PA6. Wird während des Laufs der Schalter in die Gegenrichtung bewegt, stoppt das Tor auch. Hier ein wenig Quelltext-Vorgabe:

int tor = 1;
enum {STOPP,ZU,AUF} zustand=STOPP; // Aufzaehlungstyp
#define LEUCHTDAUER 300

void setup() {
  pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(PA1,INPUT_PULLDOWN); // Zu
  pinMode(PA6,INPUT_PULLDOWN); // Auf
  pinMode(PA10,INPUT_PULLDOWN);// Handsender
  GPIOC->ODR = tor;
}

void loop() {
}

Erstellen Sie ein Zustandsdiagramm und entwickeln Sie daraus das Programm. Verwenden Sie zunächst digitalRead(). Ich bin auf Ihre Lösungen gespannt!

Naives Zustandsdiagramm
Zustandsdiagramm
Zustandsdiagramm
Naiver Code dazu
int tor = 1;
enum {STOPP,ZU,AUF} zustand=STOPP; // Aufzaehlungstyp
#define LEUCHTDAUER 300

void setup() {
  pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(PA1,INPUT_PULLDOWN); // Zu
  pinMode(PA6,INPUT_PULLDOWN); // Auf
  pinMode(PA10,INPUT_PULLDOWN);// Handsender
  GPIOC->ODR = tor;
}

void loop() {
  switch (zustand){         // in Abhaengigkeit des Zustands
      case STOPP:
        if(digitalRead(PA1)){  // Zu?
          zustand=ZU;
        }
        if(digitalRead(PA6)){  // Auf?
          zustand=AUF;
        }
        break;
      case ZU:
        if(digitalRead(PA6) || tor>=0b1000000){  // Auf oder Tor zu
          zustand=STOPP;
        }
        else{                               // do Aktionen
          tor = (tor<<1) + 1;
          GPIOC->ODR = tor;
          delay(LEUCHTDAUER);
        }
        break;
      case AUF:
        if(digitalRead(PA1) || tor<=0b1){ // Zu oder Tor offen
          zustand=STOPP;
        }
        else{                               // do Aktionen
          tor = tor>>1;
          GPIOC->ODR = tor;
          delay(LEUCHTDAUER);
        }
      break;
    }
}

Teil 2: Handsender verwenden können

Durch Betätigen von PA10 (Handsender) soll das Tor in den jeweils nächsten Zustand schalten:
ZU->STOPP->AUF->STOPP->ZU->STOPP usw.
Um das Prellen in den Griff zu kriegen und eine “schönere” Lösung zu erhalten ist dieser Code-Schnipsel vorgegeben. Erweitern Sie Ihr Zustandsdiagramm entsprechend und testen Sie ihre Lösung.

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(7);                      // 7ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}

#define T_ZU (buttonEnter&1<<1) // PA1
#define T_AUF (buttonEnter&1<<6) // PA6
#define T_HAND (buttonEnter&1<<10) // PA10

typedef enum {STOPP,ZU,AUF} ZustandTyp;  // Aufzaehlungstyp
ZustandTyp zustand = STOPP;
ZustandTyp letzteRichtung = ZU;
Mögliches Zustandsdiagramm
Zustandsdiagramm
Zustandsdiagramm

Teil 3: Besser reagieren und mit Blinken

Zwei Dinge sollen besser werden, durch das Delay für die Leuchtdauer werden manche Tastendrücke nicht wahr genommen. Beim Lauf des Tors soll L7 blinken. Ein Unterprogramm fahren() soll L7 mit 100ms Delay umschalten und nach 4 Zyklen jeweils eine LED des Leuchtbandes ein- bzw. ausschalten. Modifizieren Sie das Zustandsdiagramm und entwickeln Sie die Lösung.

Mögliches Zustandsdiagramm
Zustandsdiagramm
Zustandsdiagramm
Möglicher Quellcode
int tor = 1;
#define LEUCHTDAUER 100

void setup() {
  pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
  GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
  pinMode(PA1,INPUT_PULLDOWN); // Zu
  pinMode(PA6,INPUT_PULLDOWN); // Auf
  pinMode(PA10,INPUT_PULLDOWN);// Handsender
  GPIOC->ODR = tor;
}

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(7);                      // 7ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}

#define T_ZU (buttonEnter&1<<1) // PA1
#define T_AUF (buttonEnter&1<<6) // PA6
#define T_HAND (buttonEnter&1<<10) // PA10

typedef enum {STOPP,ZU,AUF} ZustandTyp;  // Aufzaehlungstyp
ZustandTyp zustand = STOPP;
ZustandTyp letzteRichtung = ZU;

void fahren(){
  static int blinken =0;
  blinken++;
  if(blinken%4==0){
    if(zustand == ZU){
      tor = (tor<<1) + 1;
    }
    else{
      tor = tor>>1;
    }
    GPIOC->ODR = tor;
  }
  if(blinken%2) GPIOC->BSRR = 1<<7;
  else GPIOC->BSRR = 1<<7+16;
  delay(LEUCHTDAUER);
}

void loop() {
  buttonCheck();
  switch (zustand){         // in Abhaengigkeit des Zustands
      case STOPP:
        if(T_ZU){  // Zu?
          zustand=ZU;
        }
        if(T_AUF){  // Auf?
          zustand=AUF;
        }
        if(T_HAND){
          if(letzteRichtung==ZU){
            zustand=AUF;
            letzteRichtung=AUF;
          }
          else{
            zustand=ZU;
            letzteRichtung=ZU;
          }
        }
        break;
      case ZU:
        if(T_HAND || T_AUF || tor>=0b1000000){  // Auf oder Tor zu
          zustand=STOPP;
          GPIOC->BSRR = 1<<7+16; // L7 aus
        }
        else{                               // do Aktionen
          fahren();
        }
        break;
      case AUF:
        if(T_HAND || T_ZU || tor<=0b1){ // Zu oder Tor offen
          zustand=STOPP;
          GPIOC->BSRR = 1<<7+16; // L7 aus
        }
        else{                               // do Aktionen
          fahren();
        }
      break;
    }
}