Die Nutzung der RS232-Hardware (USART)


zurück zu Input/Output , PIC-Grundlagen , PIC-Prozessoren , Elektronik , Homepage

Allgemeines
Initialisierung
Daten Empfangen
Daten Senden
Beispiel


Allgemeines

Die serielle Schnittstelle der PIC16F87x /16F62x ist eine universelle Schnittstelle für asyncrone und syncrone Datenübertragung (USART). Am interessantesten für den Bastler ist die normale asyncrone RS232-Schnittstelle mit der sich der PIC z.B. an die COM-Schnittstellen eines Personalcomputers anschließen lässt.
Eine Beschreibung der RS232-Schnittstelle befindet sich hier.

Für ein RS232-Interface benötigt man folgende Pins des PIC:

RS-232 mit Treiber Außerdem muss an diese Pins ein externer RS232-Treiberschaltkreis (z.B. MAX232) angeschlossen werden, der die Invertierung und Pegelwandlung von TTL auf 12V vornimmt. 
Treiber: Billiglösung Bei kurzen Verbindungsleitungen kann der Treiber-IC auch durch zwei Transistoren ersetzt werden, da viele RS232-Empfänger auch mit +5V/0V anstelle von +12V/-12V zurechtkommen. 
Ganz ohne Zusatzhardware geht es aber nicht.
RS-232 mit Treiber und Handshake Wenn man Hardwarehandshake benutzen möchte, muss man auch die Handshakeleitungen von 12V auf 5V anpassen. Die RS232-Schnittstelle des PIC hat aber keine speziellen Pins für Handshake. Folglich muss man zwei beliebige I/O-Pins dafür einsetzen, und die Harwarehandshakeleitungen per Software setzen bzw. abfragen.

In der Praxis wird auf Hardwarehandshake meistens verzichtet.
nach oben

Initialisierung

Um die RS232-Schnittstelle nutzen zu können, muss sie initialisiert werden. Dazu müssen einzelne Bits in drei Steuerregistern gesetzt werden

Nachfolgend sind die möglichen Einstellungen für den normalen RS232-Mode (8 Bit, keine Parität) gezeigt::
 

TXSTA: TRANSMIT STATUS AND CONTROL REGISTER (ADDRESS 98h):


bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
Name: CSRC TX9 TXEN SYNC - BRGH TRMT TX9D
Wert: - 0 1 0 - 0 oder 1 - 0

 
 
RCSTA: RECEIVE STATUS AND CONTROL REGISTER (ADDRESS 18h):


bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
Name: SPEN RX9 SREN CREN ADDEN FERR OERR RX9D
Wert: 1 0 - 1 0 - - -

   
SPBRG: BAUD RATE GENERATOR (ADDRESS 99h):

bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 2 bit 0
ein Wert von 0 bis 255


TRISC:  RATE DATA DIRECTION REGISTER (ADDRESS 87h):


bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
Wert: 1 1 - - - - - -

 
 

Für eine normale RS232-Funktion wird das Register TXSTA mit 0x20 oder 0x24 und das Register RCSTA mit 0x90 beschrieben:

; USART initialisieren
    BSF      STATUS,RP0        ; Bank1
    MOVLW    0x20              ; Sender: RS232
    MOVWF    TXSTA             ;
    BCF      STATUS,RP0        ; Bank 0
    MOVLW    0x90              ; Empfänger: RS232
    MOVWF    RCSTA             ;

Die einzige variable Größe ist die Baudrate. Sie wird mit dem 8-Bit Wert im Register SPBRG und dem Bit BRGH im Register TXSTA eingestellt. In Abhängigkeit vom Takt des PIC und der gewünschten Baudrate ergeben sich folgende Werte für SPBRG bei BRGH=0 bzw BRGH=1::

Die Einstellung BRGH=0 eignet sich vor allem für niedrige Baudraten. Die in der Tabelle mit * gekennzeichneten Werte sollten vermieden werden, da bei ihnen unzulässig hohe Abweichungen von den gewünschten Baudraten auftreten. Das kann zu Übertragungsfehlern führen.
 

Baudrateneinstellung mit SPBRG (BRGH=0)


20 MHz 16 MHz 10 MHz 8 MHz 4 MHz
2 400 Baud 129 103
64
51 25
9 600 Baud 31 25 15 12 6*
19 200 Baud 15 12 7 6 2*
28 800 Baud 9* 8 4* 4 1*
33 600 Baud 8 6* 4* 3* -
57 600 Baud 4* 3 2* - -

Die Einstellung BRGH=1 eignet sich vor allem für hohe Baudraten. Die in der Tabelle mit * gekennzeichneten Werte sollten vermieden werden, da bei ihnen unzulässig hohe Abweichungen von den gewünschten Baudraten auftreten. Das kann zu Übertragungsfehlern führen.
 

Baudrateneinstellung mit SPBRG (BRGH=1)


20 MHz 16 MHz 10 MHz 8 MHz 4 MHz
2 400 Baud - -
255
204
103
9 600 Baud 129 103 64 51 25
19 200 Baud 64 51 31 25 12
28 800 Baud 42 33 21 16 8
33 600 Baud 36 29 19 14 6*
57 600 Baud 20 16 10 8 -

Eine Baudrate von 19 200 Baud lässt sich bei einem Takt von 20 MHz also einstellen, indem man SPBRG mit 15 beschreibt, und BRGH auf 0 setzt:

; USART Baudrate einstellen
    BSF      STATUS,RP0        ; Bank1
    MOVLW    D'15'             ; Set Baud rate 19,2 kBPS bei 20 MHz
    MOVWF    SPBRG
    BCF      TXSTA, BRGH       ; BRGH=0
    BCF      STATUS,RP0        ; Bank 0

Ebendso könnte man SPBRG mit 64 beschreiben und BRGH auf 1 setzen.


Außerdem müssen die Pins TX und RX  im TRIS-Register als Eingänge definiert sein (auch TX !). Beim PIC16F876 müssen also die Bits 6 und 7 des TRISC-Registers gesetzt sein. Das ist aber auch die Grundstellung (default) dieser Bits, so dass sie in aller Regel nicht extra gesetzt werden müssen. Der folgende Abschnitt ist also meist überflüssig:

; USART initialisieren
    BSF      STATUS,RP0        ; Bank1
    BSF      TRISC,7           ; RX-RS232 für USART-Modul
    BSF      TRISC,6           ; TX-RS232 für USART-Modul
    BCF      STATUS,RP0        ; Bank 0

nach oben


Daten Empfangen

Das Empfangen von Daten kann man mit Polling (ständiges abfragen) oder durch einen Interrupt organisieren. Polling ist einfacher zu programmieren, verbraucht aber viel Rechenzeit.

Polling
Immer wenn das serielle Port ein Byte empfangen hat, setzt es das Bit RCIF im Register PIR1 (Adresse 0Ch). Dieses Bit kann man in einer Schleife abfragen. Ist das Bit gesetzt, liest man das empfangene Byte aus dem Register RCREG aus. RCIF geht dann von allein wieder auf 0. Die folgende Routine pollt, und schreibt ein empfangenes Zeichen nach 'Zeichen':

RS232in
    btfss   PIR1,RCIF        ; sind Daten da ?
    goto    RS232in          ; nein, noch keine Daten da
    movfw   RCREG            ; RS232-Register auslesen
    movwf   Zeichen          ; und in den Speicher nach 'Zeichen' schreiben

Interrupt
Der RS232-Empfänger kann einen Interrupt auslösen, wenn er ein Byte empfangen hat. Damit fordert er dazu auf, das empfangene Byte abzuholen. Sollen Interrupts genutzt werden, müssen Interrupts generell erlaubt werden (GIE=1) und im speziellen muss der Empfängerinterrupt freigeschaltet werden (RCIE=1 ).

;Interrupts vorbereiten
    BSF     STATUS,RP0         ; Bank1
    BSF     PIE1,RCIE          ; Enable receive interrupts
    BCF     STATUS,RP0         ; Bank 0
    clrf    PIR1               ; alle Interruptflags löschen
    clrf    PIR2
    BSF     INTCON,GIE         ; generell Interrupts erlauben
    BSF     INTCON,PEIE        ; Interrupts von Peripheriegeräten erlauben

Eine Interruptroutine die das empfangene Zeichen in das Register 'Zeichen' kopiert könnte wie folgt aussehen:

    org  4                   ; Interrupt beginnt immer bei Adresse 4
int
    movwf   w_temp           ; w und STATUS retten
    swapf   STATUS,w
    movwf   status_temp

;RS232-Empfänger-Interrupt?
    btfss   PIR1,RCIF
    goto    intEnde          ; Interrupt kam von wo anders

    movfw   RCREG            ; RS232-Register auslesen
    movwf   Zeichen          ; und in den Speicher nach 'Zeichen' schreiben
    bcf     PIR1,RCIF        ; interrupt-Flag löschen

intEnde                      ; gerettete w und STATUS wieder zurückschreiben
    swapf   status_temp,w
    movwf   STATUS
    swapf   w_temp,f
    swapf   w_temp,w
    retfie

nach oben


Daten Senden

Man sendet Zeichen, indem man sie in das Register TXREG schreibt. Dort holt sie sie Hardware ab, und schickt sie über die RS232-Leitung. Bevor man das nächste Zeichen nach TXREG schreiben kann, muss man aber warten, bis das vorherige Zeichen gesendet wurde. Für dieses Warten gibt es zwei Methoden.
Das Senden von Daten kann man mit Polling (ständiges abfragen) oder durch einen Interrupt organisieren. Polling ist einfacher zu programmieren, verbraucht aber viel Rechenzeit.

Polling
Immer wenn das serielle Port bereit ist, ein neues Byte zu senden, setzt es das Bit TXIF im Register PIR1 (Adresse 0Ch). Dieses Bit kann man in einer Schleife abfragen. Ist das Bit gesetzt, schreibt man das nächste zu sendende Byte in das Register TXREG der USART. TXIF geht von allein wieder auf 0. Die folgende Routine pollt, und das nächste Zeichen von 'Zeichen' zum RS232-Sender:

RS232out
    btfss   PIR1,TXIF        ; ist Sender leer ?
    goto    RS232out         ; nein, noch nicht leer
    movfw   Zeichen          ; nächstes Byte holen
    movwf   TXREG            ; und in den RS232-Sender schreiben

Interrupt
Der RS232-Sender kann einen Interrupt auslösen, wenn er bereit ist, ein neues Byte zu senden. Damit fordert er dazu auf, ihm das nächste Byte zu übereben. Sollen Interrupts genutzt werden, müssen Interrupts generell erlaubt werden (GIE=1) und im speziellen muss der Senderinterrupt freigeschaltet werden ( RCIE=1).

;Interrupts vorbereiten
    BSF     STATUS,RP0         ; Bank1
    BSF     PIE1,TXIE          ; Enable transmitter interrupt
    BCF     STATUS,RP0         ; Bank 0
    clrf    PIR1               ; alle Interruptflags löschen
    clrf    PIR2
    BSF     INTCON,GIE         ; generell Interrupts erlauben
    BSF     INTCON,PEIE        ; Interrupts von Peripheriegeräten erlauben

Eine Interruptroutine die das zu sendende Zeichen aus dem Register 'Zeichen' in den Sender schreibt, könnte wie folgt aussehen:

    org  4                   ; Interrupt beginnt immer bei Adresse 4
int
    movwf   w_temp           ; status retten
    swapf   STATUS,w
    movwf   status_temp

;RS232-Sender-Interrupt?
    btfss   PIR1,TXIF
    goto    intEnde          ; Interrupt kam von wo anders

    movfw   Zeichen          ; nächstes Byte aus 'Zeichen' lesen
    movwf   TXREG            ; und in den Sender schreiben
    bcf     PIR1,TXIF        ; interrupt-Flag löschen

intEnde                      ; geretteten Status wieder zurückschreiben
    swapf   status_temp,w
    movwf   STATUS
    swapf   w_temp,f
    swapf   w_temp,w
    retfie

++ACHTUNG FALLE++

Wird eine Menge Daten ohne Pause gesendet, und als einzige Bremse das TXIF-Flag abgefragt, so kann es am Empfänger der Daten zum Verschlucken von Zeichen kommen, da der Empfänger die Lücke zwischen 2 benachbarten Zeichen nicht erkennt. In diesem Fall empfehle ich nicht TXIF sonder besser TRMT im Register  TXSTA abzufragen. Dieses Bit wird erst gesetzt, wenn das zu sendende Byte komplett herausgeschoben ist. TXSTA liegt in der Bank1 !

nach oben

Beispiel

Das folgende Beispielprogramm für einen mit 20 MHz getakteten PIC16F876 liest serielle Daten mit 19 200 Baud ein und zeigt sie am PortB an. Jedes Byte wird darüberhinaus um 1 erhöht und wieder ausgesendet.

;**************************************************************
;*
;* Pinbelegung
;*  ----------------------------------
;*  PORTA:   0
;*      1
;*      2
;*      3
;*      4
;*  PORTB:   0 LED
;*      1 LED
;*      2 LED
;*      3 LED
;*      4 LED
;*      5 LED
;*      6 LED
;*      7 LED
;*  PORTC:   0
;*      1
;*      2
;*      3
;*      4
;*      5
;*      6 RS-232-Ausgang zum Treiber
;*      7 RS-232-Eingang vom Treiber
;*
;**************************************************************
;
; sprut (zero) Bredendiek 06/2001
;
; RS-232-ECHO:
;
; 16F876 empfängt RS232 Code
; der empfangene Ccode wird am PORTB ausgegeben
; danach wird der code um 1 erhöht und mit RS232 zurückgesendet
;
; Prozessortakt:  20 MHz
; RS232-Baudrate: 19 200 Baud
;
;**************************************************************
; Includedatei für den 16F84 einbinden

    list      p=16f876
    #include <P16f876.INC>

    ERRORLEVEL      -302    ;SUPPRESS BANK SELECTION MESSAGES

;**********************************************************
; Variablennamen vergeben

w_temp        equ   0x20
status_temp   equ   0x21
Zeichen       equ   0x22
DatenSindDa   equ   0x24

    org 0
    goto     init

;**********************************************************
; Interruptroutine für RS232-Empfang

    org      4             ; Interrupt beginnt immer bei Adresse 4
int
    movwf    w_temp        ; status retten
    swapf    STATUS,w
    movwf    status_temp

;RS232-Empfänger-Interrupt?
    btfss    PIR1,RCIF
    goto     intEnde       ; Interrupt kam von wo anders

    movfw    RCREG         ; RS232-Register auslesen
    movwf    Zeichen       ; und in den Speicher nach 'Zeichen' schreiben
    movwf    PORTB         ; Zeichen am PortB anzeigen
    bsf      DatenSindDa,0 ; Kennzeichen für gültige Daten setzen
    bcf      PIR1,RCIF     ; interrupt-Flag löschen

intEnde                    ; geretteten Status wieder zurückschreiben
    swapf    status_temp,w
    movwf    STATUS
    swapf    w_temp,f
    swapf    w_temp,w
    retfie

;**********************************************************
; Anfangsinitialisierung

init
    clrf     DatenSindDa

    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

; USART initialisieren
    BSF      STATUS,RP0    ; Bank1
    MOVLW    0x20          ; Sender: RS232
    MOVWF    TXSTA         ;
    BCF      STATUS,RP0    ; Bank 0
    MOVLW    0x90          ; Empfänger: RS232
    MOVWF    RCSTA         ;

; USART Baudrate einstellen
    BSF      STATUS,RP0    ; Bank1
    MOVLW    D'15'         ; Set Baud rate 19,2 kBPS bei 20 MHz
    MOVWF    SPBRG
    BCF      TXSTA, BRGH   ; BRGH=0
    BCF      STATUS,RP0    ; Bank 0

;Interrupts vorbereiten
    BSF      STATUS,RP0    ; Bank1
    BSF      PIE1,RCIE     ; Enable receive interrupts
    BCF      STATUS,RP0    ; Bank 0
    clrf     PIR1          ; alle Interruptflags löschen
    clrf     PIR2
    BSF      INTCON,GIE    ; generell Interrupts erlauben
    BSF      INTCON,PEIE   ; Interrupts von Peripheriegeräten erlauben

;**********************************************************
; Hauptprogrammschleife

Main
    btfss    DatenSindDa, 0 ; wurde was empfangen?
    goto     Main           ; nein

RS232out
    btfss    PIR1,TXIF     ; ist Sender leer ?
    goto     RS232out      ; nein, noch nicht leer
    incf     Zeichen,w     ; um 1 erhöht nach w
    movwf    TXREG         ; und in den RS232-Sender schreiben
    clrf     DatenSindDa
    goto     Main

    end

nach oben

zurück zu Input/Output , PIC-Grundlagen , PIC-Prozessoren , Elektronik , Homepage
Autor: sprut
erstellt: 03.07.2001
letzte Änderung: 14.07.2012