Allgemeines
Initialisierung
Daten Empfangen
Daten Senden
Beispiel
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:
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. |
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. |
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. |
Um die RS232-Schnittstelle nutzen zu können, muss sie initialisiert werden. Dazu müssen einzelne Bits in drei Steuerregistern gesetzt werden
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 |
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 | - | - | - |
bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 2 | bit 0 |
ein Wert von 0 bis 255 |
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.
20 MHz | 16 MHz | 10 MHz | 8 MHz | 4 MHz | |
2 400 Baud | 129 | 103 |
|
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.
20 MHz | 16 MHz | 10 MHz | 8 MHz | 4 MHz | |
2 400 Baud | - | - |
|
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
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
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 !
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 ERRORLEVEL -302 ;SUPPRESS BANK SELECTION MESSAGES ;**********************************************************
w_temp
equ
0x20 org 0
;**********************************************************
org
4
;
Interrupt
beginnt
immer bei Adresse 4 ;RS232-Empfänger-Interrupt?
movfw
RCREG ; RS232-Register
auslesen intEnde
;
geretteten
Status
wieder zurückschreiben ;**********************************************************
init
bsf
STATUS, RP0 ; auf Bank 1 umschalten ; USART initialisieren ; USART Baudrate einstellen
;Interrupts vorbereiten ;**********************************************************
Main RS232out end |