Die Aufgabe der Software für den PIC-Mikrocontroller besteht beim elektronischen Adventkranz hauptsächlich in der Erzeugung sehr vieler Flackerlichter (52 mal für jede Leuchtdiode, die den Kranz bilden, und vier für die vier Flammen der Kerzen) die in einem Array (Vektor, Abbildung 3.1) abgelegt sind, und in der Erzeugung eines seriellen Datenstromes für Ausgabe dieses Arrays (Vektors) durch die Schieberegister IC2 bis IC9 (vgl. Abschnitt Abschnitt 2 (Schaltungsbeschreibung).).
Die Abbildung 3.1 zeigt wie das Array Flackerlichter[] aufgebaut ist.
Wichtig:
Die Zuordnung der Bits im Array für die vier Kerzen (bestehend aus je einem Bit für die Flamme und einem Bit für das Wachs) und für den Kranz muss der Beschaltung der Schieberegister gemäß der Schaltung in Abbildung 2.2 entsprechen. |
Daher ist in Abbildung 3.1 zu jedem Arrayelement auch das zugehörige Schieberegister angegeben. Zur Besseren Übersicht sind die Bits, die den Leuchtdioden entsprechen in deren Farben dargestellt (gelb: Flamme, rot: Wachs und grün: Kranz).
Der Kranz flackert sofort nach dem Einschalten, während eine Kerze erst nach einer Betätigung der zur Kerze zugehörigen Tasters eingeschaltet und durch einen weiteren Tastendruck wieder ausgeschaltet wird. Eine wichtige Aufgabe der Software ist daher auch das Erkennen ob, und welche Taste gedrückt wurde. Diese Aufgabe muss regelmäßig durchgeführt werden und erfolgt mit dem Unterprogramm TastenRoutine. Siehe Abschnitt 3.4.2. (Unterprogramm zur Tastenabfrage).
Manche Unterprogramme müssen zyklisch (also regelmäßig) aufgerufen werden. Dazu sind mehrere Zeitbasen notwendig. Diese werden mit Hilfe des Timer-Interrupts erzeugt (siehe Abschnitt 3.2. Hauptprogramm und Abschnitt 3.3. Interrupt-Service-Routine (ISR, Timer 0).
Die in der Schaltung vorgesehenen Jumper (JP1 und JP2) sind für zukünftige Optionen und Erweiterungen vorgesehen. In dieser Version werden diese nicht benötigt.
Zur besseren Übersicht ist der Quellcode in mehrere Dateien aufgeteilt:
Als Programmiersprache wurde hier C, und als Compiler mikroC von [mikroElektronika 2009] verwendet.
Globale Register:
Für die Erzeugung der zwei Zeitbasen (hier für 409.6µs und 10ms) sind die folgenden zwei globalen Register
notwendig:
Weitere globale Register werden auch für die Erzeugung der Flackerlichter benötigt (siehe Abschnitt 3.4.3. Konstanten, globale Variablen, Tabellen und Unterprogramme zur Erzeugung der Flackerlichter).
Portdefinitionen:
Im Allgemeinen werden bei jeder Anwendung die Ein- und Ausgangspins der diversen Hardwarekomponenten (hier
Jumper, Taster und die Schieberegister) an einem anderen Portpin verwendet. Damit dies in der Software nur an
einer Stelle berücksichtigt werden muss, befindet sich in der Software eine Portdefinition.
Portdefinition für die Jumper:
Die Jumper (JP1 und JP2) werden zwar hier noch nicht verwendet, eine Portdefinition ist aber schon
vorgesehen (Zeilen 49 und 50).
Portdefinition für die Taster und Schieberegister:
Für die Taster ist keine Portdefinitionen notwendig. Die Portdefinition für die Steuerleitungen zur Ansteuerung der Schieberegister
befindet sich in der Datei SR_74XX595.H (siehe
Abschnitt 3.4.4. Portdefinitionen und Unterprogramme zur seriellen Datenausgabe mit den Schieberegistern vom Typ 74xx595).
Konstanten:
Die Konstante KONSTZEITBASIS10MS gibt die Anzahl der notwendigen
Interrupt-Aufrufe für die 10-ms-Zeitbasis an. Hier, beim elektronischen Adventkranz, wird die
Interrupt-Service-Routine (ISR) alle 409.6$\mu$s aufgerufen, daher ergibt sich für die Konstante der Wert 24
(24 x 409.6µs = 9.83ms = ca. 10ms).
Konstanten werden auch für die Erzeugung der Flackerlichter benötigt (siehe Abschnitt 3.4.3. Konstanten, globale Variablen, Tabellen und Unterprogramme zur Erzeugung der Flackerlichter).
Tabellen:
Tabellen werden nur für die Erzeugung der Flackerlichter benötigt. Mehr dazu im
Abschnitt 3.4.3. Konstanten, globale Variablen, Tabellen und Unterprogramme zur Erzeugung der Flackerlichter.
Zuerst müssen der Mikrocontroller und die (globalen) Register für die Erzeugung der Flackerlichter initialisiert werden. Diese Aufgaben werden von den Unterprogrammen Init (Zeile 323) und Init_Flackerlichter (Zeile 326) ausgeführt.
Nun befindet sich das Hauptprogramm in einer Endlosschleife. Diese Schleife besitzt die Aufgabe ständig die so genannten Botschaftsflags abzufragen. Ist eines dieser Botschaftsflags gesetzt, so muss vom Hauptprogramm eine bestimmte Aufgabe ausgeführt werden. Diese Aufgaben sind in Form von Unterprogrammen vorhanden.
Hier die Tätigkeiten in der Endlosschleife, welche durch die Botschaftsflags ausgelöst werden:
Achtung:
Die Botschaftsflags müssen nach der Ausführung der Aufgaben wieder gelöscht werden, da diese Aufgaben sonst ununterbrochen wiederholt werden! |
Listing 3.4 zeigt das Hauptprogramm (Auszug aus Adventkranz.c).
Eine Interrupt-Service-Routine (kurz: ISR) ist im Prinzip ein Unterprogramm, welches aber im Gegensatz zu normalen Unterprogrammen "unvorhergesehen" aufgerufen wird. Hier, beim Timer-0-Interrupt jedes Mal wenn der Timer 0 überläuft, also von 255 auf 0 wechselt. Würde zum Beispiel ein RB-Interrupt verwendet werden, so würde bei jeder Pegeländerung von RB4 bis RB7 ein Interrupt auftreten und die entsprechende ISR wird ausgeführt. Eine ISR sollte so kurz wie möglich sein.
Ein wichtiger Punkt bei einer ISR ist, dass das w-Register (Working- oder Arbeitsregister) und das STATUS-Register in andere Register zwischengespeichert werden müssen, falls diese Register in der ISR ihre Registerinhalte verändern. Der Grund dafür ist, dass eine ISR eben unvorhergesehen aufgerufen wird, und die angesprochenen Register unter Umständen zu diesen Zeitpunkten gerade benötigte Werte enthalten. Nach Ausführung der ISR springt diese zwar wieder genau an die Stelle zurück, wo sie war, bevor der Interrupt auftauchte, aber mit einem möglicherweise falschen Wert im w-Register (bzw. STATUS-Register). Das Zwischenspeichern des w-Register bzw. des STATUS-Registers wird häufig auch als PUSH bezeichnet. Das Wiederherstellen von w-Register und STATUS-Register nennt man POP. Diese Aufgaben werden vom mikroC-Compiler automatisch erledigt, so dass sich der Programmierer darüber keine Gedanken machen muss.
Woher weiß das Programm, dass ein Interrupt aufgerufen werden muss? Dazu gibt es für jede Interruptquelle ein Kontroll-Flag. Dies wird vom Controller gesetzt wenn dieser Interrupt auftritt. (Vorausgesetzt, dass diese Interruptquelle freigegeben wurde). Damit aber die ISR nicht ständig aufgerufen wird, muss dieses Bit in der ISR wieder gelöscht werden.
Nun aber zur projektspezifischen Timer 0-ISR. Diese hat lediglich die Aufgabe eine Zeitbasis für 409.6µs und eine zweite für ca. 10ms zu erzeugen. Damit eine Zeit von 10ms entsteht muss die ISR 24-mal aufgerufen werden (24 x 409.6µs = 9.83ms). Bei jedem ISR-Aufruf muss daher ein Zählregister (hier: ZaehlerZeitbasis10ms) um 1 vermindert werden. Besitzt es danach den Wert 0, so ist eine Zeit von ca. 10ms vergangen. Nun wird das Botschaftsflag FlagZeitbasis10ms im Register FlagISRHP gesetzt, und das Zählregister muss mit dem Wert 24 neu geladen werden. Der Wert 24 wird hier durch die Konstante KONSTZEITBASIS10MS ersetzt.
Anmerkung:
Die 10ms-Zeitbasis wird für das regelmäßige Abfragen der Taster benötigt, während die 409.6$µs für das erzeugen von Flackerlicht benötigt
wird.
Die Zeit von 409.6µs ergibt sich, weil als Taktquelle ein 20-MHz-Quarz (X1) verwendet wird, und weil der Vorteiler (VT) mit dem Wert 1:8 (TMR0 Rate, Zeile 120 im Unterprogramm Init) geladen wird. Für diese Zeit gilt:
Listing 3.5 zeigt die kurze Interrupt-Service-Routine (Auszug aus Adventkranz.c).
Die insgesamt 10 Unterprogramme lassen sich wie folgt einteilen:
Das Unterprogramm Init dient zur Initialisierung des Mikrocontroller und muss daher am Beginn des Hauptprogramms aufgerufen werden.
Da die Interrupt-Service-Routine (ISR) zyklisch (alle 409.6µs) aufgerufen wird, ist eine entsprechende Zeitbasis notwendig. Diese wird mit Hilfe eines Timer-Interrupts erzeugt. Für die Definition der Zeitbasis ist hier das mikrocontrollerinterne Funktions-Register OPTION zuständig. Damit bei einer PIC-Taktfrequenz von 20MHz eine Zeitbasis von 409.6µs erzeugt wird, muss das Register OPTION mit dem binären Wert bxxxx0010 geladen werden (Zeile 126). Das Zählregister für diese Zeitbasis (Funktions-Register TMR0) muss gelöscht werden (Zeile 124).
Die beiden Ports (beim PIC16F87 die Ports A und B) müssen gemäß der Beschaltung nach Abbildung 2.1 entweder als Eingang oder als Ausgang definiert werden (Zeilen 155 und 165).
Achtung:
Der PIC16F87 verfügt über analoge Komparatoren. Diese werden bei diesem Projekt nicht verwendet und müssen daher deaktiviert werden (Zeilen 177 und 178). |
Das Zählregister für die 10ms-Zeitbasis muss initialisiert werden (Zeile 181).
Zum Schluss werden der Timer0-Interrupt und der globale Interrupt freigegeben (Zeile 184) [1]. Für das Freigeben der Interrupts ist hier das Register INTCON zuständig. Je nach benötigten Interrupts müssen die Freigabebits (im Englischen: Enable) gesetzt werden. (Hier das Freigabebit T0IE (Bit 5) für den Timer 0.) Zusätzlich muss auch die globale Interruptfreigabe GIE (General Interrupt Enable, Bit 7) gesetzt werden. Er ist sozusagen der Hauptschalter, der Interrupts ermöglicht. Der Timer-0-Interrupt ist nun eingeschaltet und sorgt hier für eine 409.6-µs-Zeitbasis.
Listing 3.6 zeigt das Unterprogramm zur Initialisierung des PIC16F87 (Auszug aus der Datei Adventkranz.c).
Das Unterprogramm TastenRoutine hat die Aufgabe regelmäßig zu prüfen, ob und welche Taste gedrückt wurde. Wurde eine Taste gedrückt die dazugehörige Kerze ein- bzw. ausschalten. Dieses Unterprogramm wird daher zyklisch (ca. alle 10ms) vom Hauptprogramm aufgerufen.
Die Taster sind nach Abbildung 2.1 an den folgenden Portpins angeschlossen:
Vorgehensweise:
Der Rückgabewert dieses Unterprogramms beinhaltet nun für jede Kerze den Status ob die Kerze eingeschaltet oder ausgeschaltet ist.
Anmerkungen:
Listing 3.7 zeigt das Unterprogramm zur Tastenabfrage (Auszug aus Adventkranz.c).
Das Prinzip zur Erzeugung von Flackerlicht ist im Abschnitt 1 (Grundlegendes zur Erzeugung von Flackerlicht) sehr ausführlich erklärt. Hier erfolgen nun die Besonderheiten bei diesem Projekt.
Ein wichtiger Punkt bei der Erzeugung mehrerer Flackerlichter ist, dass der Eindruck entsteht, dass die Leuchtdioden unabhängig voneinander flackern. Dies wird erreicht, indem für jede Leuchtdiode eine eigene Zählvariablen verwendet wird. Diese Zählvariable muss hier als globale Variable ausgeführt werden.
Sämtlicher Quellcode, der für die Erzeugung der Flackerlichter notwendig ist (also Konstanten, globale Variablen, Tabellen und Unterprogramme) befindet sich in einer eigenen Datei Namens Flackerlicht.C (Quellcode) bzw. Flackerlicht.H (Header).
Konstanten:
Gemäß Abschnitt 1 sind für die Erzeugung der Flackerlichter die beiden Konstanten KONST_PWM_OBERGRENZE
(beinhaltet den Wert 32) und KONST_GLEICHHEIT (beinhaltet den Wert 5) notwendig. Diese werden in der
Header-Datei Flackerlicht.H definiert (Listing 3.8):
Globale Variablen:
Damit die vielen Leuchtdioden unabhängig voneinander flackern sind für jede Leuchtdiode zwei globale Variablen notwendig (Listing 3.9).
Diese Variablen sind hier als Array definiert und entsprechen den Registern TabFlackerlicht und
PWM_LED aus Abschnitt 1.
Tabellen:
Für die Erzeugung der vielen Flackerlichter sind eine Reihe von Tabellen (Listing 3.10) notwendig. Die erste Tabelle
(TabFlackerlicht, Zeilen 27 bis 45) beinhaltet die Werte für die Puls-Weiten-Modulation. Diese Tabelle wurde
bereits im Abschnitt 1 ausführlich diskutiert.
Die weiteren Tabellen sind notwendig damit die Flackerlichter für die vier Kerzen und für den Kranz mit Hilfe von Schleifen schnell und
einfach ausgeführt werden können.
Die Tabellen TabKerzeTabstart (Zeilen 49 bis 52) und TabKranzTabstart
(Zeilen 54 bis 60) geben für jede Leuchtdiode den Startwert für die Tabelle TabFlackerlicht an. Dadurch
erscheint der Eindruck, dass die Leuchtdioden unabhängig voneinander flackern. In Wirklichkeit ist der Flackerzyklus für jede Leuchtdiode
gleich, sie sind nur zeitlich verschoben.
Die Tabelle TabKerzeInLEDZustand (Zeilen 64 bis 67) gibt an, in welchem Array-Elemet (vom Array
Flackerlichter) sich die Kerze mit dem Index i befindet. Dies ist notwendig, weil die Flackerlichter für die
Kerzen mit Hilfe einer Schleife erfolgt. Da die Bit-Nummern für die vier Kerzen gleich sind [2]
ist für die Bitinformation keine Tabelle notwendig. (Siehe auch Unterprogramm FlackerlichtKerzen, Listing 3.12.
Die Tabellen TabKranzInLEDZustand (Zeilen 69 bis 75) und TabKranzInLEDZustandBit
(Zeilen 77 bis 83) geben an, welches Array-Element und welches Bit der Leuchtdioden für den Kranz mit dem Index i entspricht.
Unterprogramme:
Für die Erzeugung der Flackerlichter sind drei Unterprogramme notwendig, wobei ein Unterprogramm die Flackerlichter für die vier Kerzen
erzeugt, während die Flackerlichter für den Kranz von einem anderen Unterprogramm erzeugt werden. Dies ist auch sinnvoll, da der Kranz von
Beginn an flackert, während das Flackern der Kerzen davon abhängt, ob die Kerze ein- oder ausgeschaltet ist.
Ein weiteres Unterprogramm initialisiert die globalen Variablen, die für die Erzeugung der Flackerlichter notwendig sind.
Unterprogramm Init_Flackerlichter:
Dieses kurze Unterprogramm hat nur die Aufgabe die Zählregister mit den Anfangspositionen zu initialisieren. Listing 3.11 zeigt dieses
kurze Unterprogramm.
Anmerkung:
Dieses Unterprogramm wird im Hauptprogramm nach dem Initialisieren des Mikrocontrollers aufgerufen (Siehe Hauptprogramm, Listing 3.4,
Zeile 326).
Unterprogramm Flackerlicht_Kerzen:
Dieses Unterprogramm erzeugt das Flackern (Puls-Weiten-Modulation, PWM) für die Kerzen, indem es die Bits 0 (Flamme) und 1 (Wachs) im
übergebenen Array mit Hilfe der Tabelle TabKerzeInLEDZustand setzt oder zurücksetzt.
Es entspricht vom Prinzip her dem Listing aus Abschnitt 1 (Grundlegendes zur Erzeugung von Flackerlicht).
Die wesentlichen Unterschiede gegenüber Abschnitt 1 sind:
Dieses Unterprogramm benötigt als Übergabeparameter einen Zeiger auf das Array und ein Register, welches angibt welche Kerze ein- bzw. ausgeschaltet ist. (Anmerkung: Dieses Register entspricht dem Rückgabewert vom Unterprogramm TastenRoutine.)
Wichtig:
Dieses Unterprogramm muss im Hauptprogramm regelmäßig (zyklisch) aufgerufen werden (Siehe Hauptprogramm, Listing 3.4, Zeile 333). |
Listing 3.12 zeigt die Realisierung von diesem Unterprogramm.
Unterprogramm Flackerlicht_Kranz:
Dieses Unterprogramm erzeugt das Flackern (Puls-Weiten-Modulation) für den Kranz, indem es die Bits im übergebenen Array mit Hilfe der
Tabellen TabKranzInLEDZustand und TabKranzInLEDZustandBit setzt oder
rücksetzt.
Auch dieses Unterprogramm entspricht vom Prinzip her dem Listing aus Abschnitt 1 (Grundlegendes zur Erzeugung von Flackerlicht). Der wesentliche Unterschied gegenüber Abschnitt 1 ist nur, dass dieses Unterprogramm "gleichzeitig" 52 Flackerlichter erzeugt. Daher die Verwendung von Schleifen.
Als Übergabeparameter ist hier nur ein Zeiger auf das Array notwendig.
Wichtig:
Auch dieses Unterprogramm muss im Hauptprogramm regelmäßig (zyklisch) aufgerufen werden (Siehe Hauptprogramm, Listing 3.4, Zeile 334). |
Listing 3.13 zeigt die Realisierung von diesem Unterprogramm.
Die Ansteuerung der Leuchtdioden erfolgt bei diesem Projekt mit Hilfe von Schieberegistern. Die softwaretechnische Ansteuerung der Schieberegister erfolgt mit Hilfe von zwei Unterprogrammen, die in einer eigenen Datei (hier SR_74XX595.C) vorhanden sind.
Protokoll zur Kommunikation mit den Schieberegistern:
Die Abbildung 3.2 zeigt das sehr einfache Protokoll zur Kommunikation mit den Schieberegistern vom Typ 74xx595, und die Reihenfolge, in
welcher die Array-Elemente in die Schieberegister geschoben werden müssen, damit die "richtigen" Leuchtdioden angesteuert werden.
Beim Übertragen der einzelnen Bits muss die Steuerleitung Port_Enable low sein. Erst nachdem alle Bits gesendet wurden muss ein kurzer High-Impuls (Ladeimpuls) erfolgen. Erst bei diesem Ladeimpuls übernehmen die Schieberegister die Daten.
Wichtig:
Das 8-Bit-Datenregister welches für das Schieberegister, dass vom Mikrocontroller am "weitesten entfernt" ist, zuständig ist (hier IC9), muss als erstes gesendet werden. Hier muss daher das Array-Element mit dem Index 7 (für IC9) als erstes gesendet werden. |
Portdefinitionen:
Für die Kommunikation mit den Schieberegistern sind drei Portleitungen des Mikrocontrollers notwendig. Diese werden in der Datei
SR_74XX595.H gemäß der Schaltung nach Abbildung 2.1 definiert.
Listing 3.14 zeigt die Portdefinitionen für die Kommunikation mit den Schieberegistern.
Unterprogramme:
Die Kommunikation mit den Schieberegistern erfolgt mit den beiden Unterprogrammen SR_74xx595_SendData und
SR_74xx595_SendByte.
Unterprogramm SR_74xx595_SendData:
Dieses Unterprogramm erzeugt dass Protokoll gemäß Abbildung 3.2. Zunächst muss die Steuerleitung Port_Enable
gelöscht werden, dann nacheinander die Datenbits (im Array SendArray[]) mit dem Unterprogramm
SR_74xx595_SendByte in die Schieberegister schieben. Nach dem letzten Datenbit einen Ladeimpuls für die
Übernahme in die Schieberegister erzeugen.
Die Übergabeparameter sind das zu "schiebende" Daten-Array (SendArray[]) und die Größe des Daten-Array (AnzahlDaten).
Listing 3.15 zeigt die Realisierung dieses Unterprogramms.
Hilfs-Unterprogramm SR_74xx595_SendByte:
Dieses Hilfs-Unterprogramm gibt mit Hilfe einer Schleife das MSB (Bit 7) am Datenausgang aus, erzeugt einen Takt und schiebt alle Bits
um eine Stelle weiter.
Achtung:
Dieses Hilfsprogramm darf nur vom Unterprogramm SR_74xx595_SendData aufgerufen werden. Daher befindet sich der zugehörige Funktionsprototyp nicht in der Header-Datei SR_74XX595.H sondern in der Quelldatei SR_74XX595.C. Wird dieses Unterprogramm in einem anderen Unterprogramm oder im Hauptprogramm aufgerufen, so sollte der Compiler einen Fehler, oder zumindest eine Warnung ausgeben! |
Listing 3.16 zeigt die Realisierung dieses Unterprogramms.
Zum Schluss noch drei kurze Unterprogramme, die für das Setzen (SetBit, Listing 3.17) bzw. für das Löschen (ClrBit, Listing 3.18) und für das Prüfen von Bits in Registern (isBitSet, Listing 3.19) zuständig sind. Diese drei Unterprogramme sind notwendig, da auf einzelne Bits nicht mit Registern zugegriffen werden kann. Diese sind hier aber durch die Verwendung von Schleifen notwendig.
Eine weitere Erläuterung zu diesen Unterprogrammen ist hier nicht notwendig.
Diese drei Unterprogramme befinden sich in der Datei myBitOperationenV2.C.
Ein kleiner Nachteil beim mikroC-Compiler ist, dass die Konfigurationsbits in der IDE gesetzt werden müssen. Ich persönlich würde sie lieber im Quellcode mit einer geeigneten Anweisung setzen.
Hier sind die notwendigen Einstellungen für den PIC16F87 für dieses Projekt:
1 Der PIC16F87 besitzt 12 Interruptquellen. Bei diesem Projekt wird aber nur der Timer-0-Interrupt benötigt.
2 Bit 0: Flamme, Bit 1: Wachs, siehe Abbildung 3.1