2. Iteration, Entwurf

In der zweiten Projektiteration realisieren wir schrittweise den Wecker. Wie gehabt notieren wir zuerst alle Programmschritte als Kommentar im Quellcode

//----------------------------------------------------------------------
// Titel     : Entwurf für den myAVR C++ Wecker www.avr-cpp.de
//----------------------------------------------------------------------
// Funktion  : Wecker mit Echtzeituhr, Temperaturanzeige und PC-Frontend

Es hilft, sich die verwendeten Komponenten zu vergegenwärtigen und diese zu benennen.

Jede identifizierte Komponente repräsentiert ein Objekt, für welches wir hoffentlich eine fertige Treiberklasse vorfinden.

// Schaltung : PortD = LCD, PortB.0 = Taste, PortB.1 = LCD-Backlight
//             PortB.2 = Speaker, PortC.0 = Lichtsensor
//             PortC.4 und PortC.5 = I2C (TWI/SCL/SDA)
//----------------------------------------------------------------------
class Application : public Controller
{
	// Instanzen der Komponenten anlegen
	// ----------------------------------------------------------
	// Button für Klicken = Schlummerfunktion, Halten = Alarm aus 
	// Lichtsensor, Dunkel -> LCD Beleuchtung an 
	// LCD für Anzeige Uhrzeit, Temperatur und Alarm mit
	// Lautsprecher für Wecksignal/-melodie ausgeben
	// Temperatursensor für Raumtemperatur ermitteln
	// Batteriegestützte Echtzeituhr
	// UART für Verbindung zum PC-Programm mit einem
 
        // Variablen und Daten
        // ----------------------------------------------------------
	// Zwischenspeicher für Texteausgaben
	// Zwischenspeicher für Kommandos vom PC-Programm
	// Signal wenn neue Weckzeit empfangen wurde
	// Signal wenn neue Uhrzeit empfangen wurde
	// EEPROM zum sichern der Weckzeiten auch bei Stromausfall
	// SRAM gepufferte EEPROM Variablen für Weckzeiten
	// Dauer des Weckalarms in Sekunden

Der Systemstart sollte schon ein bisschen sexy sein. Neben der Initialisierung der Geräte zeigen wir eine kleine Begrüßung für den Nutzer an und empfangen diesen mit ein paar netten Tönen. So etwas ist nicht nur Spielerei sondern signalisiert dem Anwender akustisch und optisch das Hochfahren und die Arbeitsbereitschaft des Systems.

	// wird einmalig beim Einschalten des Systems ausgeführt    
	public: void onStart()
	{
		// Initialisierungssequenz
		// -------------------------------------------------------- 
		// Variablen initialisieren		
		// Komponenten initialisieren
		// Taste initialisieren auf B.0
		// Lichstsensor initialisieren auf C.0
		// Temperatursensor initialisieren Adresse 0x90
		// Echtzeituhr initialisieren Adresse 0xD0
		// UART initialisieren auf 9600 Bqaud
 
		// Begrüßungssequenz nach Programmieren, Reset oder Power ON
		// ---------------------------------------------------------  
		// LCD.Licht an
		// LCD "Willkommen beim kleinen Projekt");
		// Begrüßungstonfolge
		// einen Moment fürs Auge stehen lassen
		// LCD wieder aufräumen	
	}

Wir wollen für unseren Wecker zwischen drei Arten der Verarbeitung unterscheiden:

  1. langsame, nicht zeitkritische Aufgaben, Polling
  2. regelmäßige, zeitgesteuerte Aufgaben, Timer
  3. unregelmäßige Ereignisse, Event

Die zeitunkritischen Aufgaben mit niedriger Priorität, also jene, die auch mal eine Sekunde Verzögerung haben dürfen ohne dass die Funktion des Systems beeinträchtigt wird, bringen wir in onWork unter. Wir erinnern uns, diese Methode wird zyklisch aus der mainloop des Framework aufgerufen.

	// wird wiedeholend aus der "MainLoop" des AppKernel aufgerufen
	// hier alles was Zeit hat verarbeiten
	public: void onWork()
	{
		// Anzeigezyklus 
		// Echtzeituhr auslesen
		// Zeit auf LCD anzeigen
		// Zeit an PC senden
 
		// Temperatursensor auslesen
		// Temperatur auf LCD anzeigen
		// Temperatur an PC senden
 
		// Beleuchtungslogik
		// WENN zu dunkel DANN
			// LCD.Licht an
		// SONST		
			// LCD.Licht aus
 
		// Wecklogik		
		// WENN Wecker gestellt ist
		// UND WENN Weckzeit erreicht DANN
		// 		alarmDauer = 2 Minuten
		// Alarmzustand auf LCD anzeigen
 
		// WENN vom PC neue Uhrzeit empfangen DANN
			// Echtzeituhr stellen
 
		// WENN vom PC neue Weckzeit empfangen DANN
		    // Wecker stellen
 
		// Pause, Zeit fürs Auge
	}

Die Aufgaben, welche in einem regelmäßigen zeitgenauen Zyklus ausgeführt werden sollen, bringen wir in einem geeigneten Timerereignis unter. Der Wecker soll sekundengenau das Wecksignal starten.

	// einmal pro Sekunde nachschauen ob Weckmelodie neu gestartet werden muss     
	public: void onTimer1s()
	{
 
		// WENN Alarm
			// alarmDauer runter zählen
			// WENN kein Schlummermodus und keine Weckmelodie spielt DANN
				// Weckmelodie spielen
	}		

Aktionen des Anwenders sind unregelmäßig und spontan. Wir reagieren auf solche Umweltereignisse eben genau so, ereignisorientiert.

	// Ereignisbehandlung für Daten vom PC oder Tastenaktivität
	public: void onEvent(const Object& sender, uint8 data)
	{
		// WENN Daten vom PC ankommen
			// Daten zwischenspeichern			
			// WENN Kommando T = Time
				// Signal neue Zeit
			// ODER WENN  Kommando A = Alarm
				// Siganl neue Weckzeit
 
		// WENN Taste betätigt wurde DANN
			// Lautsprecher aus			
			// WENN Taste nur geklickt wurde
				// Schlummermodus 
			// WENN Tatse lange gehalten wird DANN
				// Alarm aus
	}
} app;

Programmierung

Schauen wir noch mal in Ruhe über diesen Entwurf. Auch die Programmierung sollten wir in kurzen Iterationen ausführen und uns immer wieder Zwischenergebnisse anschauen.

class Application : public Controller
{
	protected: Button 	taste;		  // Klicken = Schlummerfunktion, Halten = Alarm aus 
	protected: AnalogDevice lichtsensor;	  // Dunkel -> LCD Beleuchtung an 
	protected: LcdMK2 	anzeige;	  // Anzeige Uhrzeit, Temperatur und Alarm
	protected: SoundChanelB lautsprecher;	  // Wecksignal/-melodie ausgeben
	protected: I2C_LM75 	temperatursensor; // Raumtemperatur ermitteln
	protected: I2C_DS1307 	echtzeituhr;      // Batteriegestützte Echtzeituhr
	protected: Uart		konsole;	  // Verbindung zum PC-Programm
	protected: String	buffer;		  // Zwischenspeicher für Kommandos vom PC-Programm
	protected: bool		neuerAlarm;	  // true wenn neue Weckzeit empfangen wurde
	protected: bool		neueZeit;	  // true wenn neue Uhrzeit empfangen wurde
	protected: String	text;	          // Zwischenspeicher für Texteausgaben
	protected: Eeprom 	eeprom; 	  // Initialisiert EEPROM-Zugriff
	protected: Eeprom_uint8	weckStunde;	  // SRAM gepufferte EEPROM Variable 
	protected: Eeprom_uint8	weckMinute;	  // SRAM gepufferte EEPROM Variable
	protected: Eeprom_uint8	weckSekunde;	  // SRAM gepufferte EEPROM Variable
	protected: uint8 	alarmDauer;	  // Dauer des Weckalarms in Serunden

Sie können jetzt schon mal den Kompiler anwerfen, um zu schauen, ob alle Komponenten gefunden wurden und ob sich vielleicht doch ein Tippfehler eingeschlichen hat. Bis hier passiert noch nichts Sichtbares bei unserem Wecker. Die folgende Initialisierungssequenz soll der erste Zwischenschritt in der Programmierung sein.

	// wird einmalig bein Einschalten des Systems ausgeführt    
	public: void onStart()
	{
		// Initialisierungssequenz 
		// weckStunde=weckMinute=weckSekunde=0xFF nach Brennen
		alarmDauer = 0;			  // Alarm aus
		neueZeit   = false;		  // noch keine neue Uhrzeit vom PC empfangen 
		neuerAlarm = false;		  // noch keine neue Weckzeit vom PC empfangen
		taste.config(portB,bit0);	  // Taste initialisieren 
		lichtsensor.config(0);		  // Lichstsensor initialisieren
		temperatursensor.config(0x90);	  // Temperatursensor initialisieren
		echtzeituhr.config(0xD0);	  // Echtzeituhr initialisieren
		konsole.config(9600);		  // UART initialisieren
 
		// Begrüßungssequenz nach Programmieren, Reset oder Power ON
		anzeige.backLightOn();	
		anzeige.writeLine(1,"Willkommen beim");
		anzeige.writeLine(2,"kleinen Projekt");
		// Begrüßungstonfolge
		lautsprecher.play(FLASHSTR("a.a.a.ddd.ccc"));
		// einen Moment fürs Auge stehen lassen
		waitMs(3000);
		anzeige.clear();
		anzeige.backLightOff();
	}

Übersetzen Sie das Programm und übertragen Sie dieses auf den Controller. Es sollte jetzt beim Systemstart für drei Sekunden die Begrüßung auf dem Display erscheinen und eine kurze Tonfolge erklingen.

	// wird wiedeholend aus der "MainLoop" des AppKernel aufgerufen
	// hier alles was Zeit hat verarbeiten
	public: void onWork()
	{
		// Anzeigezyklus 
		echtzeituhr.formatTimeToString(text,"%h:%m:%s Uhr");
		anzeige.writeLine(1,text);
		konsole.sendString(text+"|");
 
		temperatursensor.formatGradToString(text,1);
		anzeige.writeLine(2, text);
		anzeige.write("\xdf" "C");
		konsole.sendString(text + "°C\n");
 
		// Beleuchtungslogik
		if (lichtsensor.getValue8()>150)
			anzeige.backLightOff();
		else		
			anzeige.backLightOn();
 
		// Wecklogik		
		if (weckSekunde<60)
		{
			// Weckzyklus starten?
			if ( alarmDauer  == 0 &&
				 weckStunde  == echtzeituhr.hours &&	 
				 weckMinute  == echtzeituhr.minutes &&
				 weckSekunde == echtzeituhr.seconds )
			{
				// Weckzeit 120s
				alarmDauer = 120;
			}
			anzeige.setPos(2,10);
			if (alarmDauer>120)
				anzeige.write("SNOOZE ");
			if (alarmDauer>0)
				anzeige.write("ALARM  ");
			else
				anzeige.write("*      ");
		}
		else
			anzeige.write("       ");
 
		// wenn vom PC neue Uhrzeit empfangen -> Echtzeituhr stellen
		if ( neueZeit )
		{
			// Format: 13:44:00T
			neueZeit = false;
			echtzeituhr.setTime(buffer);
			buffer = "";
		}
 
		// wenn vom PC neue Weckzeit empfangen -> Wecker stellen
		if ( neuerAlarm )
		{
			// Format: 06:30:00A
			neuerAlarm = false;
			echtzeituhr.convertTime(buffer);	
			weckStunde = echtzeituhr.hours;
			weckMinute = echtzeituhr.minutes;
			weckSekunde= echtzeituhr.seconds;
			buffer = "";
		}
 
		// Pause, Zeit fürs Auge
		waitMs(1000);
	}

Übersetzen Sie das Programm und übertragen Sie dieses auf den Controller. Es sollte jetzt beim Systemstart für drei Sekunden die Begrüßung auf dem Display erscheinen und eine kurze Tonfolge erklingen. Danach wird die Uhrzeit angezeigt, welche von der Echtzeituhr ausgelesen wurde. Beachten Sie jedoch, dass die Echtzeituhr noch nicht gestellt wurde. Im nächsten Schritt realisieren wir die Weckmelodie.

	// einmal pro Sekunde nachschauen ob Weckmelodie neu gestartet werden muss     
	public: void onTimer1s()
	{
		if (alarmDauer>0)
		{
			alarmDauer--;
			if (alarmDauer<=120 && !lautsprecher.isPlaying())
				lautsprecher.play(
				  FLASHSTR("vfff,,fff,,fff,,dd,a,ff.dd,a,ffff.."
				           "CCC,,CCC,,CCC,,DD,C,ff.dd,a,ffff...") );
		}
	}

Der Wecker und die Uhr sind noch nicht gestellt. Es ist so nicht möglich die Weckfunktion zu testen. Ergänzen sie die Initialisierungssequenz vorläufig um folgende Zeilen:

	// Echtzeituhr stellen
	echtzeituhr.setTime("13:44:00T");
	// Wecker stellen
	echtzeituhr.convertTime("13:45:00A");	
	weckStunde = echtzeituhr.hours;
	weckMinute = echtzeituhr.minutes;
	weckSekunde= echtzeituhr.seconds;

Übersetzen Sie das Programm und übertragen Sie dieses auf den Controller. Nach der vorgegeben Zeit sollte jetzt die Weckmelodie erklingen. Jetzt möchten wir den Wecker natürlich noch anhalten.

	// Ereignisbehandlung für Daten vom PC oder Tastenaktivität
	public: void onEvent(const Object& sender, uint8 data)
	{
		if (sender == konsole)
		{
			buffer+=(char)data;
			if (data=='T')
				neueZeit=true;
			else if (data=='A')
				neuerAlarm=true;
		}
		else if (sender == taste)
		{
			lautsprecher.stop();			
			if (data == Button::Click)
				alarmDauer = 120 + 60;      // 120s Alarmzeit + 60s Snooze
			else if (data==Button::HoldStart)
				alarmDauer = 0;    	    // Alarm aus
		}
	}
} app;

Übersetzen Sie das Programm und übertragen Sie dieses auf den Controller. Der Wecker hat jetzt seine komplette Funktionalität. Die Datenübertragung vom PC zum Stellen des Weckers und der Echtzeituhr wurden ebenfalls schon implementiert. Es fehlt nur noch das PC-Programm, um Wecker und Uhr zu stellen und die aktuelle Uhrzeit anzuzeigen.

2. Iteration, Test

Für den Test der gesamten Funktionalität können wir als PC-Backend unser Controlcenter verwenden. Die Datenübertragung erfolgt mit 9600 Baud und hat folgendes Format:

  • hh:mm:ssT, das abschließende T=Time ist das Kommando mit der gegeben Zeit die Uhr zu stellen
  • hh:mm:ssA, das abschließende A=Alarm ist das Kommando mit der gegeben Zeit den Wecker zu stellen

Weiter im Projekt