Elektronik-Projekt: Temperatur-Feuchtigkeits-Modul (Version 1)


zurück zu Elektronik, Homepage


4. Softwarebeschreibung

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.

nach oben

4.1. Portdefinitionen, Strukturen, externe Register und Konstanten

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:

nach oben

4.2. Hauptprogramm

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:

nach oben

4.3. ISR (Timer 0)

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:

Formel

Daraus ergibt sich folgender Zusmmenhang:

Formel

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.

nach oben

4.4. Unterprogramme

Die insgesamt 17 Unterprogramme lassen sich folgendermaßen einteilen:

nach oben

4.4.1. INIT

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.

nach oben

4.4.2. Unterprogramme zur Kommunikation mit dem Feuchtigkeitssensor

Zur Kommunikation mit dem Feuchtigkeitssensor sind folgende Unterprogramme notwendig:

Nun aber zu den einzelnen Unterprogrammen im Detail:

nach oben

4.4.2.1. Unterprogramm FEUCHTE_START

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.).

Start-Sequenz
nach oben

4.4.2.2. Unterprogramm FEUCHTE_CONNECTIONRESET

Aufgabe:
9 SCK-Zyklen, während DATA = 1, anschließend eine Startbedingung (vgl. Abschnitt 2.4.5.).

Connection-Reset-Sequenz
nach oben

4.4.2.3. Unterprogramm FEUCHTE_SCHREIBEN

Aufgabe:
Ein Byte (also eine Anweisung) an den Sensor übergeben und die Bestätigung vom Sensor empfangen.

Vorgehensweise:

nach oben

4.4.2.4. Unterprogramm FEUCHTE_LESEN

Aufgabe:
Ein Byte vom Sensor lesen und eine Bestätigung zurückgeben, wenn ack=1

Vorgehensweise:

nach oben

4.4.2.5. Unterprogramm FEUCHTE_STATUSSCHREIBEN

Aufgabe:
Statusregister des Sensors beschreiben

Vorgehensweise:

nach oben

4.4.2.6. Unterprogramm FEUCHTE_STATUSLESEN

Aufgabe:
Statusregister des Sensors und Checksumme auslesen

Vorgehensweise:

nach oben

4.4.2.7. Unterprogramm FEUCHTE_MESSUNG

Aufgabe:
Einen Feuchtigkeits- oder Temperatur-Messzyklus starten

Vorgehensweise:

nach oben

4.4.2.8. Unterprogramm FEUCHTE_LINEARISIERUNG

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)

nach oben

4.4.3. Unterprogramme für die LCD-Ansteuerung

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:

nach oben

4.4.3.1. Unterprogramm LCD_INIT

Aufgabe:
Dieses Unterprogramm initialisiert das LC-Display

Vorgehensweise:

nach oben

4.4.3.2. Unterprogramm LCD_BEFEHL

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.

nach oben

4.4.3.3. Unterprogramm LCD_ZEICHEN

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.

nach oben

4.4.3.4. Unterprogramm LCD_BUSY

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:

nach oben

4.4.3.5. Unterprogramm LCD_STRING

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.

nach oben

4.4.3.6. Unterprogramm VERZ100US

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).

nach oben

4.4.4. Weitere Unterprogramme

4.4.4.1. Unterprogramm AUSGABE

Aufgabe:
Die berechnete Temperatur und Luftfeuchtigkeit am LC-Display ausgeben.

Vorgehensweise:

nach oben

4.4.4.2. Unterprogramm AUSGABEBCD

Aufgabe:
Den übergeben Wert in BCD-Form umwandeln und am Display ausgeben.

nach oben


zurück zu Elektronik, Homepage

Autor: Buchgeher Stefan
Erstellt: 9. Jänner 2005
Letzte Änderung: