PIC-Lernbeispiel: Lauflicht

mit PIC18F8720

zurück zu 18F-Lernbeispiele , PIC-Prozessoren , Elektronik , Homepage


Ein Lauflicht mit 8 LEDs am Port D.
und um mit ganz großen Kanonen auf ganz kleine Spatzen zu schießen, nehme ich ein PIC18F8720.

Schaltung
 
Wer anstelle des großen 18F8720 lieber den 18F242 benutzen möchte, der wechsle bitte auf dieses Beispiel.

Alle acht Pins des Port D werden mit Leuchtdioden versehen, die über 1 kOhm Widerständen mit Masse verbunden sind. Damit zeigen die LEDs die Ausgangspegel des Port D an.

Es eignet sich dafür das PIC18F-TQFP-Demo-Board.


Programmablauf
Um am Port D ein Lauflicht zu erzeugen muss man:

  1. Port D in Ausgabe-Mode schalten
  2. Alle Bits bis auf das LSB auf "0" setzen, das LSB aber auf "1" (eine LED ist an)
  3. eine feste Zeit in einer Warteschleife warten
  4. Alle Bits des Port D eine Position nach links schieben. Falls dabei die "1" aus dem MSB hinausgeschoben landet es im LSB.
  5. ab Schritt 3 wird alles wiederholt

Mit einem Port LEDs ansteuern
(hier ist eine detaillierte Beschreibung der I/O-Pins)
Port-Pins sind entweder als Eingänge oder als Ausgänge nutzbar. Nach dem Einschalten des PIC (oder einem Reset) sind alle Port-Pins als Eingänge konfiguriert. Um sie als Ausgänge nutzbar zu machen, muss man sie zunächst auf die Ausgangsfunktion umschalten. Dazu muss man bestimmte Steuerbits setzen bzw. löschen. Jedes Port-Pin hat so ein Steuerbit. Ist es auf '1' gesetzt, dann funktioniert das Pin als Eingang, ist es aber auf '0' gesetzt, so ist es ein Ausgang.

Die 8 Steuerbits für die 8 Pins des PortD befinden sich im Register TRISD. Um PortD als Ausgang nutzen zu können, müssen also alle 8 Bit des TRISD-Registers auf 0 gesetzt werden.
 
        clrf    TRISD           ; PortD alle output
        clrf    PORTD           ; alle LEDs ausschalten

Um am Port D ein Lauflicht zu erzeugen muss man an diesem Port einen Spannungspegel ausgeben. Jedes Pin kann entweder 0V oder 5V ausgeben. Im obrigen Stromlaufplan sieht man, das die LEDs über einen Widerstand an den Pins des PORTD  und mit dem anderen Anschluss mit Masse verbunden sind. Gibt ein Pin 0V aus, dann leuchtet die LED nicht. Gibt das Pin aber 5V aus, dann fließt ein Strom durch den Widerstand und die LED nach Masse, wodurch die LED leuchtet. Die von den 8 Pins des PortD ausgegebenen Spannungspegel stellt man ein, indem man die zugehörigen 8 Bits des Registers PORTD setzt (1) oder löscht (0). Eine 1 bewirkt 5V am zugehörigen Pin, eine 0 bewirkt 0V.

Im der letzten Zeile des obigen Listings werden mit einem clrf-Befehl alle Bits des PORTD auf 0 gesetzt, anfangs sind also alle LEDs aus. Will man die LED am Pin RD0 einschalten, so muss man das Bit0 von PORTD auf 1 setzen:
 
; 1. LED einschalten 

        bsf     PORTD,0         ; LED an RD0 einschalten

Um ein Lauflicht zu erzeugen, verschiebt man diese 1 nun innerhalb des Registers PORTD mit Hilfe des Rotierbefehls rlncf.
Dadurch wandert die 1 in PORTD jedesmal um eine Stelle nach links, und es leuchtet jedesmal eine andere LED auf:

00000001
00000010
00000100
...
01000000
10000000
00000001
...
 
; Lauflicht

Loop
        call    Wait            ; Wartezeit
        rlncf   PORTD,f         ; laufen zur nächsten LED
        goto    Loop 

Das Verschieben erfolgt in einer Endlosschleife, die durch eine Warteroutine (Wait) soweit abgebremst wird, dass ein Durchlauf der Schleife ca. 1/4 Sekunde dauert. Ist nach 7 Schleifendurchläufen die 1 links aus dem Register PORTD heraus geschoben worden, erscheint sie wieder im Bit 0 des PORTD-Registers.


Configuration
Wenn man dem Brennprogramm P18 keine Config-Einstellungen vorschreibt, dann verwendet es die Microchip-Standardeinstellungen, und die sind nicht gerade praxistauglich. folgende Probleme kommen da auf einen zu:

Deshalb sollte man gleich im HEX-File eine andere Config einbauen. Das sieht dann so aus:
 
;Configuration bits
    CONFIG    OSC = HS     ; HS 20 MHz
    CONFIG    PWRT = ON    ; power up timer on
    CONFIG    BOR = OFF    ; brown out detect off
    CONFIG    WDT = OFF    ; watchdog off
    CONFIG    LVP = OFF    ; lvp off


Warteschleife
Immer wieder erreichen mich eMails, in denen nach Warteschleifen gefragt wird.Oft benötigt man in einem Programm eine Wartezeit, die sich mit Software-Warteschleifen einfach realisieren lässt. Sicher wäre die Nutzung des Hardware-Timers 'professioneller', es wäre aber auch das typische Schießen mit Kanonen auf Spatzen.

Ich realisiere die meisten Verzögerungen im Programm mit Hilfe einer 1-Millisekunden-Warteschleife. Das ist also eine Unterprogramm, das genau 1 ms lang läuft, und dann zum rufenden Programm zurückkehrt. Mit Hilfe einer Schleife rufe ich dieses 1-ms-Warteprogramm vielfach auf - für 1/4 Sekunde rufe ich es z.B. 250 mal.

Im nachfolgenden Fenster ist eine 1-ms-Routine zu sehen. Sie heißt 'Wai' und ist für einen PIC-Takt von 20 MHz ausgelegt.

Fast alle Befehle eines PIC dauern einen Zyklus lang, wobei ein Zyklus 4 externen PIC-Takten entspricht. Ein 20-MHz-PIC schafft also 5 Millionen Befehle pro Sekunde, da in einer Sekunde 5 Millionen Zyklen bzw. 20 Millionen externe Takte stattfinden. Ein Zyklus ist hier 0,2 Mikrosekunde (0,2 µs) lang.
Ein Programm, das 1 Millisekunde verzögern soll, muss also 5000 Zyklen lang sein. Ein einfaches Beispiel wäre eine einfache Folge von 5000 NOP-Befehlen. Eleganter ist es aber, eine kurze Schleife vielfach durchlaufen zu lassen.
Wenn man eine Schleife aufbaut, die 20 Zyklen (also auch 4 µs) lang ist, und diese Schleife 249 mal durchläuft, dann dauert das 4980 Zyklen - das sind 0,996 ms. Da fehlen noch 4 µs zur vollen Millisekunde, aber auch der Sprung zur Warteroutine und zurück wird einige Mikrosekunden dauern, so das eine 996-µs-Warteschleife in der Praxis 1 ms verzögert.
 
;**********************************************************
; Warteschleife 1 ms für einen 20-MHz-PIC-Takt

Wai
        movlw   .249          ; Zeitkonstante für 1ms
        movwf   loops2

Wai2    nop 
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        decfsz  loops2, F      ; 1 ms vorbei?
        goto    Wai2           ; nein, noch nicht
       .
        .
        .

Die 20 Zyklen lange Warteschleife besteht aus 17 NOP-Befehlen, einem DCFSZ-Befehl und einem GOTO-Befehl. Der DCFSZ-Befehl dient gleichzeitig der Zählung der Schleifen. Nach 249 Schleifen wird der GOTO-Befehl übersprungen, und die 1-ms-Warteroutine verlassen.
Diese 1-ms-Kernroutine ist taktfrequenzabhängig. Wird der PIC mit einem höheren oder niedrigeren Takt versorgt, muss die 1-ms-Routine mehr oder weniger Befehle/Schleifen enthalten. Nachfolgende Tabelle gibt eine Orientierung:
 
PIC-Takt
Schleifenlänge
Anzahl der Schleifendurchläufe
4 MHz
9 Zyklen
110 Durchläufe
8 MHz
9 Zyklen
221 Durchläufe
10 MHz
10 Zyklen
249 Durchläufe
20 MHz
20 Zyklen
249 Durchläufe

Dementsprechend ist also der in loops2 zu ladende Wert für die Schleifenanzahl zu verändern, und eventuell auch ein zusätzlicher NOP-Befehl einzufügen.

Da in der Praxis normalerweise längere Verzögerungen als 1 ms benötigt werden, ist die 1ms-Warteschleife in eine weitere Schleife eingebettet, die in folgendem Fenster ergänzt wurde:
 
;**********************************************************
; Warteschleife 250 ms
Wait250
        movlw   D'250'          ; 250 ms Pause
        movwf   loops 

Wai
        movlw   .249           ; Zeitkonstante für 1ms
        movwf   loops2

Wai2    nop 
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        decfsz  loops2, F      ; 1 ms vorbei?
        goto    Wai2           ; nein, noch nicht
                               ;
        decfsz  loops, F       ; 250 ms vorbei?
        goto    Wai            ; nein, noch nicht
        retlw   0              ; das Warten hat ein Ende

Bevor die eigentliche 1-ms-Routine gestartet wird, wird die Zelle 'loops' mit einer Zahl zwischen 1 und 255 geladen. So oft wird die 1-ms-Schleife dann durchlaufen. In diesem Beispiel wird 'loops' mit 250 beschrieben, folglich wird die gesamte Warteroutine 'Wait250' insgesamt 250 ms = 1/4 s dauern.

Ein Aufruf der Routine mit ' CALL Wait250' führt also zu einer Wartezeit von 250 ms, danach läuft das Programm weiter.

Will man 10 ms oder 100 ms verzögern, lädt man loops mit dem Wert 10 bzw. 100, und springt dann zu Wai:
 
;**********************************************************
; Warteschleife 10 ms
Wait10
        movlw   D'10'          ; 10 ms Pause
        movwf   loops 
        goto    Wai

;**********************************************************
; Warteschleife 100 ms
Wait10
        movlw   D'100'         ; 100 ms Pause
        movwf   loops 
        goto    Wai

;**********************************************************
; Warteschleife 250 ms
Wait250
        movlw   D'250'          ; 250 ms Pause
        movwf   loops 

Wai
        movlw   .249           ; Zeitkonstante für 1ms
        movwf   loops2

Wai2    nop 
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        decfsz  loops2, F      ; 1 ms vorbei?
        goto    Wai2           ; nein, noch nicht
                               ;
        decfsz  loops, F       ; 250 ms vorbei?
        goto    Wai            ; nein, noch nicht
        retlw   0              ; das Warten hat ein Ende


Programmlisting

Das Assembler-Programm ist auch in nachfolgender Tabelle zu sehen. Den überflüssigen Kommentar habe ich grau gefärbt, damit man sich auf den eigentlichen Code konzentrieren kann.
 
;******************************************************************************
; sprut (zero) Bredendiek 02/2006 
;
; Lauflicht am Port D
;
; Taktquelle: 20 MHz
;
;******************************************************************************
;
; Pinbelegung
;       ---------------------------------- 
;       PORTD:  0 LED 
;               1 LED 
;               2 LED 
;               3 LED 
;               4 LED 
;               5 LED 
;               6 LED 
;               7 LED 
;
;******************************************************************************

       LIST P=18F8720          ;der Typ des Prozessors
        #include <P18F8720.INC> ;processor-spezifische Variablendefinitionen

;******************************************************************************
;Configuration bits
    CONFIG      OSC = HS             ; HS 20 MHz
    CONFIG      PWRT = ON            ; power up timer on
    CONFIG      BOR = OFF            ; brown out detect off
    CONFIG      WDT = OFF            ; watchdog off
    CONFIG      LVP = OFF            ; lvp off

;******************************************************************************
;Variable definitions

                CBLOCK  0x000
                loops 
                loops2
                ENDC

;******************************************************************************
;Reset vector
; hier beginnt der Prozessor beim Reset

                ORG     0x0000
                goto    Main            ;go to start of main code

;******************************************************************************
;Start of main program

Main:
; Port D kmnfigurieren
; alle Pins sind output
        clrf    TRISD
        clrf    PORTD           ; alle LEDs ausschalten

; 1. LED einschalten 
        bsf     PORTD,0         ; LED an RD0 einschalten

; Lauflicht
Loop
        call    Wait            ; Wartezeit
        rlncf   PORTD,f         ; laufen zur nächsten LED
       goto    Loop 

;******************************************************************************
; Warteschleife 250 ms
; 20 MHZ
; 20 Zyklen pro loop
; 249 loops

Wait
        movlw   D'250'          ; 250 ms Pause
        movwf   loops 

Wai
        movlw   .249           ; Zeitkonstante für 1ms
        movwf   loops2
Wai2    nop
        nop
        nop
        nop
        nop
        nop 
        nop
        nop
        nop
        nop
        nop
        nop 
        nop
        nop
       nop
        nop
        nop
        decfsz  loops2, F      ; 1 ms vorbei?
        goto    Wai2           ; nein, noch nicht
                               ;
        decfsz  loops, F       ; 250 ms vorbei?
        goto    Wai            ; nein, noch nicht
        retlw   0              ; das Warten hat ein Ende

;******************************************************************************
;End of program

                END
 


zurück zu 18F-Lernbeispiele , PIC-Prozessoren , Elektronik , Homepage
Autor: sprut
erstellt: 2006
letzte Änderung 24.09.2007