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:
- langsame, nicht zeitkritische Aufgaben, Polling
- regelmäßige, zeitgesteuerte Aufgaben, Timer
- 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