Das Ampelprogramm

Zu guter Letzt müssen wir noch das Programm schreiben, das unsere Ampel steuern wird. Unsere Ampel wird eine ganz einfache Ausführung sein. Sie wird nur die verschiedenen Phasen, in der sich die Ampel befinden kann, nacheinander in einer Endlosschleife durchlaufen.

Ersteinmal sollten wir uns ins Gedächtnis rufen, in welchen Phasen sich eine Ampel befinden kann:

Die Ampel könnte auf ROT geschalten sein. Im Anschluss daran wird die Ampel in eine Phase wechseln, in der ROT und GELB leuchten. Dann kommt die - heiß begehrte - GRÜN Phase. Endet diese, so leuchtet an der Ampel nur GELB auf. Anschließend ist die Ampel wieder auf ROT geschalten und das Spiel beginnt von neuem.

Hier erstmal der Code:

#include <avr/io.h>
#include <util/delay.h>
 
// _delay_ms muss wissen, wie schnell der Controller getaktet ist.
// Umso genauer der Wert hier stimmt, umso genauer werden auch
// die Zeitabschnitte von _delay_ms stimmen!
#ifndef F_CPU
#warning "F_CPU war noch nicht definiert, jetzt mit 18686400 definiert"
#define F_CPU 18686400UL     /* Quarz mit 18.6864 Mhz  */
#endif
 
/*
PB4 = rote LED
PB3 = gelbe LED
PB0 = gruene LED
*/
 
 
// Eine Hilfsfunktion, um eine gegebene Anzahl an Sekunden zu warten
// _delay_ms kann nur eine bestimmte Zeit warten, deshalb
// muss es öfters aufgerufen werden!
void wait_seconds(char s)
{
  // Warte s Sekunden
  for(; s > 0; s--)
  {
    // Warte 1 Sekunde
    char ms = 0;
    for(; ms < 20; ms++)
    {
      _delay_ms(50);
    }
  }
}
 
 
// Das Hauptprogramm
int main (void)
{
  // PB4, PB3 und PB0 werden als Ausgänge definiert
  DDRB = 0x0 | (1 << DDB4) | (1 << DDB3) | (1 << DDB0);
 
  // Solange der Controller an ist
  while(1)
  {
    // ROT PHASE
    // rote LED an
    PORTB |= (1 << PB4);
 
    // 5 Sekunden in dieser Phase bleiben ...
    wait_seconds(5);
 
 
 
 
    // ROT-GELB PHASE
    // gelbe LED zusätzlich an
    PORTB |= (1 << PB3);
 
    // 2 Sekunden in dieser Phase bleiben ...
    wait_seconds(2);
 
 
 
 
    // GRÜN PHASE
    // rote und gelbe LED aus
    PORTB &= ~ ((1 << PB4) | (1 << PB3));
    // grüne LED an
    PORTB |= (1 << PB0);
 
    // 5 Sekunden grün ... da muss man sich beeilen! ;)
    wait_seconds(5);
 
 
 
 
    // GELB PHASE
    // grüne LED aus
    PORTB &= ~(1 << PB0);
    // gelbe LED an
    PORTB |= (1 << PB3);
 
    // 2 Sekunden gelb ... genug Zeit, um noch über die Ampel zu kommen!
    wait_seconds(2);
 
 
 
 
    // gelbe LED aus, wird in der ROT PHASE ja nicht gebraucht
    PORTB &= ~(1 << PB3);
  }
 
  // Hier kommen wir nie an
  return 0;
}

Der Code ist nicht viel komplexer als der Code für unser erstes Programm. Wichtigste Neuerung ist wohl die Funktion _delay_ms, die den Controller eine gegebene Zeit (in Millisekunden) warten lässt. Dieser Funktion kann man nicht beliebig große Werte übergeben, da sonst intern ein Zähler überläuft. Um also eine längere Zeit (wie z.B. ein paar Sekunden) zu warten muss man die Funktion also in einer Schleife aufrufen, um insgesamt die gewollte Zeit zu warten.

Damit diese Funktion auch richtig funktioniert, muss ihr bekannt sein, wie „schnell“ der Controller läuft. Der ATTINY13 läuft mit ungefähr 20 MHz, in diesem Beispiel sagen wir einfach, er liefe mit 18.6864 MHz. Je genauer letzterer Wert mit der tatsächlichen Geschwindigkeit übereinstimmt, desto genauer funktioniert auch _delay_ms.

Zu Testzwecken ist es vielleicht ganz interessant zu sehen, wie sich die Ampel verhält, wenn der Wert von F_CPU z.B. halbiert oder verdoppelt wird ;) Die F_CPU zu ändern kann auch zu bösen Nebenwirkungen führen, wenn man mal UART (das ist die serielle Schnittstelle), SPI oder TWI nutzen will!

Das Programm wird exakt so wie unser Testprogramm kompiliert, gelinkt und zum Controller übertragen! Sobald es übertragen ist, sollte die Ampel auch schon ihren Dienst verrichten.

Es kann zu Problemen kommen, wenn der ISP Adapter noch angeschlossen ist. Falls also nichts funktioniert, erstmal den ISP Adapter von der Schaltung trennen.