Die Aufgabe der PIC-Software besteht hier hauptsächlich aus der Kommunikation mit dem Sensor und der Kommunikation mit dem LC-Display.
Die Kommunikation mit dem Sensor ist ausgiebig im Abschnitt
2.4. (Protokoll) beschrieben. Aufgabe der Software ist es
nun, dieses Protokoll umzusetzen. Weiters die Linearisierung und Temperaturkompensation gemäß Abschnitt
2.6. Diese Aufgaben werden von mehreren Unterprogrammen
ausgeführt. Diese Unterprogramme werden im Abschnitt 4.4.2. beschrieben.
Zum Schluss müssen die Daten noch für den Anwender sichtbar gemacht werden. Zur Ausgabe wird ein
LC-Display mit 2 Zeilen zu je 16 Zeichen verwendet. Abschnitt 4.4.3. beschreibt
die Unterprogramme die dazu notwendig sind.
Das Messen der Luftfeuchtigkeit und der Temperatur erfolgt alle 10 Sekunden. Dafür ist eine Zeitbasis von 10 Sekunden notwendig. Diese Zeitbasis erfolgt mit Hilfe eines Timer-Interrupts. (siehe Abschnitt 4.3.).
Als Programmiersprache wurde hier C, und als Compiler CC5X gewählt.
Portdefinitionen:
Im Allgemeinen werden bei jeder Anwendung die Eingangs- und Ausgangspins 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 den Feuchtigkeitssensor:
Für den Feuchtigkeitssensor besteht diese Portdefinition aus den folgenden 3 Parametern:
Achtung: Wird für DATA der Port A verwendet, so muss für TRIS_DATA das zum Port A zugehörige TRIS-Register definiert werden.
Bei diesem Projekt ergeben sich aufgrund der Hardwarebeschaltung folgende Definitionen:
bit DATA @ PORTA.0;
bit TRIS_DATA @ TRISA.0;
bit SCK @ PORTA.1;
Portdefinition für das LC-Display:
Für das LC-Display besteht diese Portdefinition aus den folgenden Parametern:
Bei diesem Projekt ergeben sich aufgrund der Hardwarebeschaltung folgende Definitionen:
#pragma char LCD_DATA @ PORTB
#pragma char LCD_DATA_TRIS @ TRISB
#pragma char LCD_CTRL @ PORTB
#pragma char LCD_CTRL_TRIS @ TRISB
bit LCD_RS @ LCD_CTRL.1;
bit LCD_RW @ LCD_CTRL.2;
bit LCD_E @ LCD_CTRL.3;
Strukturen und externe Register:
Die externen Register sind in einer Struktur (struct Sensor) zusammengefasst und beinhalten die
Rohdaten vom Sensor und die daraus berechnete Temperatur und Luftfeuchtigkeit.
struct Sensor
{
unsigned char feuchte_lo; // Rohwert vom Sensor (Low-Byte der Feuchte)
unsigned char feuchte_hi; // Rohwert vom Sensor (High-Byte der Feuchte)
unsigned char temperatur_lo; // Rohwert vom Sensor (Low-Byte der Temperatur)
unsigned char temperatur_hi; // Rohwert vom Sensor (High-Byte der Temperatur)
long temperatur_lin; // linearisierter Temperaturwert
long feuchte_lin; // linearisierter Feuchtewert
unsigned long feuchte_komp; // kompensierten Feuchtewert
};
Weitere externe Register:
Konstanten (allgemein):
Konstanten für den Feuchtigkeitssensor:
Konstanten für das LC-Display:
Aufgaben des Hauptprogramms:
Zuerst müssen der Mikrocontroller, das LC-Display und der Feuchtigkeitssensor initialisiert werden.
Diese Tätigkeiten werden von den Unterprogrammen INIT, LCD_INIT, FEUCHTE_CONNECTIONRESET und
FEUCHTE_STATUSSCHREIBEN ausgeführt.
Anschließend erfolgt die Ausgabe des "Begrüßungstextes" mit der Version am LC-Display.
Danach wird der Timer0-Interrupt und der globale Interrupt freigegeben. Dafür ist das Register
INTCON zuständig. Je nach benötigten Interrupts werden die entsprechenden Freigabebits (im Englischen:
Enable) gesetzt. Wird ein Interrupt verwendet so muss zusätzlich zum verwendeten Interrupt auch die
globale Interruptfreigabe GIE (General Interrupt Enable) gesetzt werden. Er ist sozusagen der
Hauptschalter, der Interrupts ermöglicht. Der Timer0-Interrupt ist jetzt eingeschaltet. Er sorgt
hier für eine 4-ms-Zeitbasis.
Nun befindet sich die Software 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. Hier, bei diesem Projekt ist nur ein Botschaftsflag relevant. Und zwar das Botschaftsflag FLAG10SEK (für die 10-Sekunden Zeitbasis).
Tätigkeiten/Unterprogramme, die alle 10 Sekunden durchgeführt werden müssen:
Eine ISR (Interrupt Service Routine) 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 daher so kurz wie möglich sein.
Ein weiterer 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 in der ISR ihren Registerinhalt 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 Widerherstellen von w-Register und STATUS-Register nennt man POP.
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 ist). 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 1
Sekunde und eine zweite für 10 Sekunde zu erzeugen. Damit eine Zeit von einer Sekunde entsteht muss die
ISR 250-mal aufgerufen werden (250 x 4ms = 1000ms = 1 Sekunde). Bei jedem ISR-Aufruf muss also ein
Zählregister um 1 vermindert werden. Besitzt es danach den Wert 0, so ist eine Sekunde vergangen. Nun
wird das Botschaftsflag FLAG1SEK im Register FLAGSISRHP gesetzt, und das Zählregister muss mit dem Wert
250 neu geladen werden. Der Wert 250 wird hier durch die Konstante KONSTISR1SEK ersetzt.
Die Zeitbasis für 10 Sekunden wird genauso, wie die für 1 Sekunde erzeugt: Jedes Mal wenn eine Sekunde
vergangen ist, wird also ein Zählregister um 1 vermindert werden. Besitzt es danach den Wert 0, so ist sind
10 Sekunde vergangen. Nun wird das Botschaftsflag FLAG10SEK im Register FLAGSISRHP gesetzt, und das
Zählregister muss mit dem Wert 10 neu geladen werden. Der Wert 10 wird hier durch die Konstante
KONSTISR10SEK ersetzt.
Die ISR wird, wie schon mehrmals erwähnt, alle 4ms aufgerufen Diese 4ms ergeben sich folgendermaßen: TMR0 wird mit dem Wert 0 geladen – es dauert also 256 Taktzyklen bis das Register wieder den Wert 0 besitzt, der Vorteiler besitzt den Wert 16 (vgl. Unterprogramm INIT, Abschnitt. 4.4.1). Der Taktzyklus ergibt sich aus dem verwendeten Quarz (X1). Dieser ist bei der PIC-Familie wie folgt definiert:
![]()
Daraus ergibt sich folgender Zusmmenhang:

Also ein ISR-Aufruf alle 4000µs, was gleichbedeutend mit 4ms ist.
Anmerkung:
Die 1-Sekunden-Zeitbasis wird hier nur für die Erzeugung der 10-Sekunden-Zeitbasis benötigt.
Die insgesamt 17 Unterprogramme lassen sich folgendermaßen einteilen:
Dieses Unterprogramm dient zur Initialisierung des Mikrocontrollers.
Da die Interrupt-Service-Routine (ISR) zyklisch (alle 4ms) 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 4,096MHz eine Zeitbasis von 4ms erzeugt wird, muss das Register OPTION mit dem binären Wert b‘xxxx0011‘ geladen werden. Das Zählregister für dies Zeitbasis (Funktions-Register TMR0, in Registerseite 0) muss gelöscht werden.
Achtung: Das Register OPTION beinhaltet auch ein Flag zum aktivieren der mikrocontrollerinterne Pull-Up-Widerstände am Port B. Bei diesem Projekt befindet sich am Port B ein LC-Display. Die mikrocontrollerinternen Pull-Up-Widerstände müssen daher deaktiviert werden. Bit 1 des Registers OPTION muss daher logisch 1 sein. Das Register OPTION muss daher mit dem binären Wert b’10000011’ geladen werden.
Port A und Port B (für den Sensor und für das LC-Display) müssen als Ausgang definiert werden.
Weiters müssen einige Register vorbelegt (initialisiert) werden.
Zur Kommunikation mit dem Feuchtigkeitssensor sind folgende Unterprogramme notwendig:
Nun aber zu den einzelnen Unterprogrammen im Detail:
Aufgabe:
Dieses Unterprogramm erzeugt die Startbedingung für die Kommunikation mit dem Feuchtigkeitssensor.
Zur Erinnerung, die Startbedingung ist wie folgt definiert (vgl. Abschnitt 2.4.1.).
Aufgabe:
9 SCK-Zyklen, während DATA = 1, anschließend eine Startbedingung (vgl. Abschnitt
2.4.5.).
Aufgabe:
Ein Byte (also eine Anweisung) an den Sensor übergeben und die Bestätigung vom Sensor empfangen.
Vorgehensweise:
Aufgabe:
Ein Byte vom Sensor lesen und eine Bestätigung zurückgeben, wenn ack=1
Vorgehensweise:
Aufgabe:
Statusregister des Sensors beschreiben
Vorgehensweise:
Aufgabe:
Statusregister des Sensors und Checksumme auslesen
Vorgehensweise:
Aufgabe:
Einen Feuchtigkeits- oder Temperatur-Messzyklus starten
Vorgehensweise:
Aufgabe:
Aus den vom Sensor empfangenen Roh-Daten die Temperatur und die Feuchtigkeit berechnen. Anschließend
muss der Feuchtigkeitswert linearisiert und mit der Temperatur kompensiert werden.
Formeln: (8/12-Bit-Mode):
Formeln: (12/14-Bit-Mode)
Zur Ansteuerung eines alphanumerischen LC-Displays sind 5 Unterprogramme notwendig:
Mit einem weiteren (optionalen) Unterprogramm kann eine ganze Zeichenkette am LC-Display ausgegeben werden. Dieses Unterprogramm heißt LCD_STRING.
Nun aber zu den einzelnen Unterprogrammen im Detail:
Aufgabe:
Dieses Unterprogramm initialisiert das LC-Display
Vorgehensweise:
Aufgaben:
Dieses Unterprogramm sendet einen Befehl nibbleweise an das LC-Display.
Vorgehensweise:
Anmerkung:
Dieses Unterprogramm unterscheidet sich zum Unterprogramm LCD_ZEICHEN nur
dadurch, dass hier die Register-Select-Steuerleitung gelöscht ist, da mit diesem
Unterprogramm Befehle an das LC-Display übertragen werden.
Aufgaben:
Dieses Unterprogramm sendet ein Zeichen, welches am LC-Display ausgegeben wird,
nibbleweise an das LC-Display.
Vorgehensweise:
Anmerkung:
Dieses Unterprogramm unterscheidet sich zum Unterprogramm LCD_BEFEHL nur
dadurch, dass hier die Register-Select-Steuerleitung gesetzt ist, da mit diesem
Unterprogramm am LC-Display auszugebende Zeichen an das LC-Display
übertragen werden.
Aufgabe:
Dieses Unterprogramm prüft das Busy-Flag des LC-Displays, und verlässt es erst wenn
das Busy-Flag low ist, also wenn das LC-Display bereit für einen Befehl oder für ein
auszugebendes Zeichen ist.
Vorgehensweise:
Aufgabe:
Dieses Unterprogramm sendet einen String Zeichen für Zeichen an das LC-Display.
Vorgehensweise:
Anmerkung:
Eine Zeichenkette (engl. String) wird in C mit einem so genannten Nullbyte abgeschlossen.
Aufgabe:
Dieses Unterprogramm erzeugt eine Zeitverzögerung. Der Übergabeparameter VerzZeit
gibt dabei an, wie oft eine Zeitverzögerung von 100µs erfolgen soll. Der
mögliche Zeitverzögerungsbereich liegt hier daher im Bereich von 100µs und 25,5ms
(=255 x 100us).
Aufgabe:
Die berechnete Temperatur und Luftfeuchtigkeit am LC-Display ausgeben.
Vorgehensweise:
Aufgabe:
Den übergeben Wert in BCD-Form umwandeln und am Display ausgeben.