PIC-Lernbeispiel: Lauflicht


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


Ein Lauflicht mit 8 LEDs am Port B.

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

Es eignet sich z.B. die 16F84-Testplatine oder 
die 16F84-Miniplatine mit einer LED-Platine am Port B.


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

  1. Port B 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 B eine Position nach links schieben. Falls dabei die "1" aus dem MSB hinausgeschoben landet es im Carry-Flag, und wird mit dem nächsten Schieben wieder in das LSB hineingeschoben
  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 PortB befinden sich im Register TRISB. Um PortB als Ausgang nutzen zu können, müssen also alle 8 Bit des TRISB-Registers auf 0 gesetzt werden.

TRISB befindet sich in der Bank1, deshalb muss vor dem Zugriff auf  TRISB in diese Bank umgeschaltet werden, danach schaltet man in die Bank 0 zurück. Die Bankumschaltung erfolgt durch setzen/löschen des Bits RP0 im Register STATUS.
 
        bsf     STATUS, RP0     ; auf Bank 1 umschalten
        movlw   B'00000000'     ; PortB alle output
        movwf   TRISB
        bcf     STATUS, RP0     ; auf Bank 0 schalten
        clrf    PORTB           ; alle LEDs ausschalten

Um am Port B ein Lauflicht zu erzeugen muss man an diesem Port einen Spannungspegel ausgeben. Jedes Pin kann entrweder 0V oder 5V ausgeben. Im obrigen Stromlaufplan sieht man, das die LEDs über einen Widerstand an den Pins des PORTB  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 PortB ausgegebenen Spannungspegel stellt man ein, indem man die zugehörigen 8 Bits des Registers PORTB setzt (1) oder löscht (0). Eine 1 bewirkt 5V am zugehörigen Pin, eine 0 bewirkt 0V.

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

        bsf     PORTB,0         ; LED an RB0 einschalten

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

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

Loop
        call    Wait            ; Wartezeit
        rlf     PORTB,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 PORTB herausgeschoben worden, erschein sie einen rlf-Befehl später wieder im Bit 0 des PORTB-Registers. (wird durch das C-Flag hindurch geschoben.)


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ät. 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 4 MHz ausgelegt.

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

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

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

Die 9 Zyklen lange Warteschleife besteht aus 6 NOP-Befehlen, einem DCFSZ-Befehl und einem GOTO-Befehl. Der DCFSZ-Befehl dient gleichzeitig der Zählung der Schleifen. Nach 110 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, mu die 1-ms-Routine mehr oder weniger Befehle/Schleifen enthalten. Nachfolgende Tabelle gibt eine Orientierung:
 
PIC-Takt
Schleifenlämge
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   .110           ; Zeitkonstante für 1ms
        movwf   loops2
Wai2    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   .110           ; Zeitkonstante für 1ms
        movwf   loops2
Wai2    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


Tip zum PIC16F62x

Wer dem moderneren 16F627 oder 16F628 anstelle des 16F84 einsetzen möchte, der achte bitte darauf, da beim Brennen 'LV-programming enable' nicht aktiviert ist. Ansonsten funktioniert RB4 nicht!
Man darf also nicht die Configuration aus dem HEX-File benutzen, sondern mu die Configuration im Brennprogramm manuell einstellen. Dabei sollte auch ein eventuell aktiviertes Codeprotecton abgeschaltet werden.


Programmlisting

Das Assembler-Programm ist auch in nachfolgender Tabelle zu sehen. Den überflüssigen Kommentar am Anfang habe ich grau gefärbt, damit man sich auf den eigentlichen Code konzentrieren kann.
 
        list p=16f84
;**************************************************************
;*
;* Pinbelegung
;*      ---------------------------------- 
;*      PORTA:  0 
;*              1 
;*              2 
;*              3 
;*              4 
;*      PORTB:  0 LED 
;*              1 LED
;*              2 LED 
;*              3 LED 
;*              4 LED 
;*              5 LED 
;*              6 LED 
;*              7 LED 
;*
;**************************************************************
;
; sprut (zero) Bredendiek 05/2000 (mod. am 16.01.2002) 
;
; Lauflicht am Port B 
;
; Taktquelle: 4MHz
;
;**************************************************************
; Includedatei für den 16F84 einbinden
;
        #include <P16f84.INC>
;
; Configuration festlegen
;
; bis 4 MHz: Power on Timer, kein Watchdog, XT-Oscillator
        __CONFIG        _PWRTE_ON & _WDT_OFF & _XT_OSC
;
; ab 4 MHz: Power on Timer, kein Watchdog, HS-Oscillator
;       __CONFIG        _PWRTE_ON & _WDT_OFF & _HS_OSC
;
;**************************************************************
; Variablennamen vergeben

loops   Equ     0x22            ; Zähler für Warteschleife 
loops2  Equ     0x23            ; Zähler für Warteschleife 

;**************************************************************
; los gehts mit dem Programm
; Port B auf Ausgabe stellen

Init
        bsf     STATUS, RP0     ; auf Bank 1 umschalten
        movlw   B'00000000'     ; PortB alle output
        movwf   TRISB
        bcf     STATUS, RP0     ; auf Bank 0 zurückschalten
        clrf    PORTB           ; alle LEDs ausschalten

; 1. LED einschalten 

        bsf     PORTB,0         ; LED an RB0 einschalten

; Lauflicht

Loop
        call    Wait            ; Wartezeit
        rlf     PORTB,f         ; laufen zur nächsten LED
        goto    Loop 
 

;**********************************************************
; Warteschleife 250 ms

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

Wai
        movlw   .110           ; Zeitkonstante für 1ms
        movwf   loops2
Wai2    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


zurück zu Lernbeispiele , PIC-Prozessoren , Elektronik , Homepage
Autor: sprut
erstellt: 2000
letzte Änderung 16.01.2004