In diesem Teil des PIC18-Tutorials wollen wir uns mehr mit den erweiterten Modulen des PIC beschäftigen. Mit “erweitert” habe ich keine bestimmte Kategorie oder etwa eine Beschreibung der nachfolgenden Module im Sinn. Vielmehr sind es die Dinge, die einem als blutigen Anfänger möglicherweise zunächst etwas zusammenzucken lassen. Ihr werdet jedoch (ganz im Gegenteil) im Laufe des Artikels feststellen: Alles halb so wild 😉 Der I2C-Bus (Inter-Integrated-Curcuit) sowie das Seriel-Peripheral-Interface (SPI) sind wirklich keine Hexerei. Und über das EUSART ( UART ) Interface werdet ihr nur müde schmunzeln, da es so trivial zu konfigurieren ist.
Capture/Compare Modul
Mit dem Capture/Compare Modul eines PIC lassen sich verschiedene Aufgaben bewältigen. Mit einem Capture-Eingang lässt sich beispielsweise der genaue Zeitpunkt bestimmen, an dem ein Signal am Pin CCPx
eintrifft. Und der Compare-Modus ist quasi das Pferd von hinten aufgezogen: Hier lässt sich am CCPx
zu einem exakt bestimmten Zeitpunkt ein Signal (oder Flanke) erzeugen. Der PWM-Modus hingegen eignet sich zum Erstellen eines pulsweiten modulierten Signals (PWM). Während der Capture- und Compare-Modus den TIMER1/3
oder 5
verwenden arbeitet der PWM-Modus mit dem TIMER2/4
oder 6
zusammen. Der Unterschied zwischen CCP
und ECCP
besteht lediglich darin, dass der ECCP
seine Funktion an mehreren GPIO-Pins bereitstellen kann.
Pulsweitenmodulation (PWM)
Jeder PIC18 und teilweise auch manche PIC16 besitzen das Capture, Compare, PWM (CCP
) Modul mit dem man zusammen mit dem TIMER2/4
oder 6
ein pulsweitenmoduliertes Signal (kurz PWM) erzeugen kann. Ich möchte euch hier kurz erklären wie ihr das Modul hierzu konfigurieren müsst. Der Timer wird mit ¼ des am PIC angeschlossenen Quarz-Taktes gespeist und kann zusätzlich noch über einen Vorteiler in drei Stufen geteilt werden (1
, 4
oder, 16
). Für den PWM-Mode sind folgende Register von Bedeutung: Das Register CCPRxL
, das TMRx
und das PRx
. Während der Timer immer bei 0
zu zählen beginnt, wird der aktuelle Zählerstand (TMRx
) immer mit dem Wert in PRx
und CCPR1x
verglichen. Wenn der Zählerstand des Timers dem Wert in CCPRxL
entspricht, dann wird der CCPx
Ausgang (Pin) gelöscht, also auf low
gesetzt. Wenn der Zählerwert TMRx
dem Wert in PRx
entspricht, dann wird der Ausgang auf high
gesetzt und der Timer beginnt wieder von 0
an zu zählen.
Quelle: Datenblatt des PIC18F45K22
Es handelt sich hier um eine 10 Bit PWM. Der TIMER2/4/6
und die PRx
sowie CCPRxL
Register sind jedoch nur 8 Bit breit. Daher bekommt das CCPRxL
Register zwei zusätzliche Bits spendiert eines anderen Registers spendiert. Diese befinden sich im CCPxCON
Register (siehe Bit 4
und 5
). Die Periode lässt sich hingegen nur mit 8 Bit einstellen, das Tastverhältnis mit 10 Bit. Der Pin an dem das PWM-Signal ausgegeben werden soll, muss von euch als Ausgang konfiguriert werden (zugehöriges TRIS
Bit löschen).
Vorgehensweise:
- Timer einstellen
- Periode einstellen
- Tastverhältnis einstellen
- PWM aktivieren
Formel zum Errechnen der Periodendauer
:
Formel zum Errechnen von PRx
:
Formel zum Errechnen des Tastverhältnisses
:
Durch variieren von CCPRxL
+ CCPxCON<4:5>
könnt ihr die PWM bzw. den Tastgrad des Signals verändern. So lässt sich zum Beispiel die Hintergrundbeleuchtung für ein LC-Display in Abhängigkeit der Umgebungshelligkeit regeln.
Capture Funktion
Mit der Capture-Funktion des CCP-Moduls eines PIC kann man die exakte Zeit messen, bis ein Pegelwechsel am CCPx
Pin eintritt. Das nachfolgende Blockschaltbild verdeutlicht die Funktion.
Quelle: Datenblatt des PIC18F13K22
Für die Funktion wird der TIMER1/3
oder 5
benötigt. Der Timer zählt die Zeit bis das Ereignis am GPIO eintritt. Natürlich muss auch der Timer hierfür entsprechend konfiguriert werden. Wenn alles richtig eingestellt ist, kann man mit dem Timer messen, wann sich etwas am CCPx
GPIO tut. Wenn es zum Beispiel zu einer steigenden Flanke am Pin kommt, wird der Zählerwert des gewählten Timers in den Registern CCPRxH
und CCPRxL
gesichert. Welchen Timer man verwenden möchte kann man in den Registern CCPTMRS0
und CCPTMRS1
wählen. Wenn ein Capture, also das Sichern des Zählerwertes des Timers stattgefunden hat, wird das Flag CCPxIF
im Register PIR1/2
oder 4
gesetzt. Dies kann einen Interrupt auslösen. Den CCPx
Pin, den Ihr für diese Funktionalität verwenden möchtet, muss als Eingang konfiguriert werden (zugehöriges Bit im TRIS
Register setzen).
Für das Standard CCP
Modul (nicht ECCP
) ist das folgende Register entsprechend zu setzen:
CCPxCON (Standard CCPx Control Register)
BIT 7 | BIT 6 | BIT 5…4 | BIT 3…0 | ||||
---|---|---|---|---|---|---|---|
– | – | DCxB<1:0> | CCPxM<3:0> |
CCPxM<3:0> (ECCPx Mode Select bits)
Mit diesen Bits wird eingestellt in welchem Modus sich das CCP
Modul befinden soll und ihr könnt einstellen auf welche Ereignisse das Modul reagieren soll: 0100
: Jede fallende Flanke 0101
: Jede steigende Flanke 0110
: Jede 4. steigende Flanke 0111
: Jede 16. steigende Flanke. Alle anderen Einstellungen sind nicht für den Capture Modus bestimmt.
DCxB<1:0> (PWM Duty Cycle Least Significant bits)
Diese Bits werden im Capture Modus nicht benutzt.
Compare Funktion
Das Compare-Modul ermöglicht es einem ein Signal bzw. einen Flankenwechsel am Pin CCPx
zu einem bestimmten Zeitpunkt zu erzeugen. Der GPIO Pin, der für diese Aufgabe verwendet werden soll, muss als Ausgang konfiguriert werden (das zugehörige Bit im TRIS
Register muss gelöscht werden).
Quelle: Datenblatt des PIC18F13K22
Das Modul arbeitet mit dem TIMER1/3
oder 5
. In den CCPRx
Registern wird ein entsprechender Vorgabewert eingetragen. Wenn der Zählerwert des Timers diesen Wert erreicht, wird der Flankenwechsel am Pin CCPx ausgeführt. So sind die Einstellungen zum Compare Modus (am Beispiel des PIC18F45K22
) für ein stanard CCP
(nicht ECCP
) Modul einzustellen:
CCPxCON (Standard CCPx Control Register)
BIT 7 | BIT 6 | BIT 5…4 | BIT 3…0 | ||||
---|---|---|---|---|---|---|---|
– | – | DCxB<1:0> | CCPxM<3:0> |
CCPxM<3:0> (ECCPx Mode Select bits)
Mit diesen Bits wird eingestellt in welchem Modus sich das CCP
Modul befinden soll. Zusätzlich könnt ihr einstellen auf welche Ereignisse das Modul reagieren soll: 1000
: Pin geht nach Eintreten von Low
auf High
(zusätzlich wird CCP1IF
gesetzt) 1001
: Pin geht nach Eintreten des Ereignisses von High
auf Low
(zusätzlich wird CCP1IF
gesetzt) 1010
: Der Ausgang wird nicht verändert (jedoch wird CCP1IF
gesetzt) 1011
: Der Ausgang wird nicht verändert (jedoch wird CCP1IF
gesetzt und zusätzlich der Timer auf 0
zurückgesetzt).
DCxB<1:0> (PWM Duty Cycle Least Significant bits)
Diese Bits werden im Compare Modus nicht benutzt.
Der Captuer Modus kann natürlich auch einen Interrupt erzeugen. Hierbei wird das Flag CCPxIF
im PIR1/2
oder 4
Register gesetzt. Sofern dieser Interrupt sowie das entsprechende globale Interrupt-Enable Bit gesetzt ist, springt der PIC in die ISR. Das CCPxIF
Bit dient das Flag zur Identifikation der Interrupt-Quelle.
Auswahl des Timers
Wie ihr sicherlich bereits gemerkt habt, könnt ihr jeweils verschiedene Timer für das Moduls verwenden. Ihr müsst natürlich angeben welchen Timer der PIC für welche Funktion nutzen soll. Die Wahl über den zu verwendenden Timer, wird in den Registern CCPTMRS0
und CCPTMRS1
getroffen. Hinweis: Je nach verwendetem PIC findet die Zuweisung alternativ direkt im TxCON
Register des jeweiligen Timers statt (und nicht im CCPTMRSx
).
CCPTMRS0
Bit 7…6 | – | Bit 4…3 | – | Bit 1…0 | |||
---|---|---|---|---|---|---|---|
C3TSEL<1:0> | – | C2TSEL<1:0> | – | C1TSEL<1:0> |
Nun schauen wir uns die einzelnen Bits des Registers an.
CxTSEL<1:0> (CCPx Timer Selection bits)
Mit diesen Bits wird dem CCP-Modul ein Timer zugewiesen. Dabei reicht das x
von 1-5
. Hier einmal am Beispiel für das CCP1
:
00
: Capture/Compare: TIMER1
, PWM: TIMER2
01
: Capture/Compare: TIMER3
, PWM: TIMER4
10
: Capture/Compare: TIMER5
, PWM: TIMER6
11
: –
CCPTMRS1 (PWM Timer Selection Control Register 1)
BIT 7…4 | BIT 3…2 | BIT 1…0 | |||||
---|---|---|---|---|---|---|---|
– | C5TSEL<1:0> | C4TSEL<1:0> |
Die Bits haben hier die selbe Bedeutung, wie auch schon im CCPTMRS0
Register (siehe oben). Nur, dass es hier um die Zuweisung für die CCP-Module 4
und 5
geht.
Der Stack
Die PIC18 Familie besitzt einen 32 Level großen Hardware Stack (vgl. PIC16 verfügen lediglich über einen 8 Level Stack). Der Stack ist ein sogenannter Stapelspeicher, der das “hopping” im Programm überhaupt erst ermöglicht.
Im Stack wird bei einem Sprung zu einer Funktion/Unterprogramm die Adresse gesichert von der aus gesprungen wurde (die Rücksprungadresse). Damit bei vollständiger Abarbeitung des Unterprogramms oder der Funktion wieder an diese Adresse zurück gesprungen werden kann. Dabei ist auch die Sichtbarkeit von Variablen zu beachten. Angenommen ihr habt eine globale Variable namens foo
. Nun springt ihr in eine Funktion und definiert hier eine neue Variable, die ebenfalls foo
heißt. Dann ist die globale Variable für die Zeit in der Funktion unsichtbar bleibt aber erhalten und ist bei Rückkehr in die obere Ebene des Stack wieder sichtbar.
Inter-Integrated Circuit (I2C)
Sobald euer PIC über ein Master-Synchronous-Serial-Port-Modul (kurz MSSP
) verfügt, könnt ihr damit einen I2C-Bus betreiben. Somit kann dann ein gewünschtes Bauteil (ADC, EEPROM, Display …), das selbstverständlich ebenfalls den I2C-Bus verwenden kann, angesprochen werden. Der I2C Bus besteht aus zwei Leitungen: Serial Clock (SCL
) und Serial Data (SDA
). Damit der Bus unabhängig von allen Teilnehmern benutzt werden kann, werden die beiden Busleitungen mit Pullup-Widerständen gegen Vcc
gezogen. Zusätzlich sind die Teilnehmer sind mit Open-Collector-Ausgängen an den Bus angeschlossen. Auf diese Weise werden Kurzschlüsse vermieden.
Quelle: nxp.com
Die Namen der Leitungen sind Programm: Auf der SCL
Leitung wird der Takt des Bus übertragen, welcher ausschließlich vom Master erzeugt wird. Der Slave hat lediglich die Möglichkeit den Master-Clock gegen Masse zu ziehen, um zum Beispiel zu signalisieren, dass er mehr Zeit benötigt um seine Aufgaben zu erledigen (wird auch clock stretching genannt). Auf der SDA
Leitung werden entsprechend die Daten zum Slave (schreibend) oder vom Slave (lesend) übertragen.
Die einzelnen Bits eines Bytes die auf dem Bus egal ob lesend oder schreibend übertragen werden, fangen immer mit dem MSB
(höchstwertigstes Bit) an. Während die SCL
Leitung im High-Zustand ist, darf sich der Pegel auf der SDA
Leitung nicht mehr ändern (dies würde sonst als Befehl interpretiert). Ein Bit wird mit einem SCL
Impuls übertragen, dazu später mehr. Ihr müsst dem PIC mitteilen mit welcher Taktgeschwindigkeit ihr den IC2 Bus betreiben möchten. Dazu ist es notwendig das Register SSPADD
mit einem passenden Teilerwert zu beschreiben. Dieser errechnet sich wie folgt:
Beispiel anhand eines SCL-Taktes von 50 kHz
und einem 8 MHz
Quarz als Taktgeber für den PIC:
Die Pullup-Widerstände an SDA
und SCL
dürfen unter keinen Umständen fehlen. Andernfalls kann keine Kommunikation zustande kommen, da wie bereits erwähnt, die Teilnehmer am Bus die Signale aktiv nur gegen GND
ziehen können, nicht jedoch gegen Vcc
(um Kurzschlüsse zu vermeiden).
Der I2C-Bus wird im wesentlichen in drei verschiedenen Geschwindigkeiten betrieben: 3,4 Mbit/s
(Highspeed), 400 kbit/s
(Fast) und mit 100 kbit/s
(Standard). Dabei gilt es auch immer im Datenblatt der verwendeten Slaves zu beachten, welche Taktgeschwindigkeiten diese unterstützen. Zum Beispiel ist es bei einem DS1307 nicht empfehlenswert den Takt oberhalb von 100 kHz
(Standard) zu betreiben, da dies bereits die absolute Grenze bei diesem Baustein ist.
Start / Stop
Die nachfolgende Grafik zeigt ein Start
und Stop
Sequenz auf dem I2C-Bus:
Quelle: nxp.com
Jede Aktion auf dem Bus wird vom Master über die I2C Start
Sequenz initiiert. Dazu muss sich der Master vergewissern, dass der Bus derzeit frei (SCL
und SDA
sind high) ist und beginnt dann mit seiner Arbeit. Streng genommen ist das eigentlich nur bei Multi-Master-Systemen notwendig. Für eine Start-Sequenz zieht der Master das Potential von SDA
auf low
und zwar während SCL
auf high
ist. In umgekehrter Reihenfolge entspricht das einer Stop
Sequenz.
Neben Start
und Stop
gibt es noch die Restart
Sequenz. Ein Restart
kann immer dann ausgeführt werden, wenn man auch eine Stop
Sequenz ausführen dürfte. Verwendet wird der Restart
um den Bus nach dem Beenden des aktuellen Transfers zu (be)halten. Vom Effekt her hat der Restart
die selbe Auswirkung wie ein Start
. Man spart sich also lediglich eine Stop
Sequenz (statt Stop
, Start
sendet man einfach nur Restart
). Die Restart
Sequenz verhält sich auf dem Bus identisch wie die Start
Sequenz (SDA
von high
nach low
während SCL
auf high
).
Adressierung
Hat ein Master erst einmal eine Start
Sequenz auf dem Bus eingeleitet, werden alle am Bus angeschlossenen Slaves hellhörig und wollen nun wissen an wen sich der Master richtet. Dafür überträgt der Master nun eine Adresse, die eindeutig zu einem der am Bus angeschlossenen Slaves passt. Alle anderen, deren Adresse nicht mit der ausgesandten übereinstimmt, interessieren sich nun nicht mehr für die folgenden Daten. Die Adresse der Slaves, also die, die vom Master gesendet wird ist anders als die Datenpakete lediglich 7 Bit
lang. Das fehlende 8. Bit des Adressbytes ist ein Richtungsbit. Dieses Bit sagt aus ob der Master Daten zum Slave schicken möchte 0
oder ob er Daten vom Slave haben möchte 1
. Da die Adresse auf 7 Bit
begrenzt ist folgt, dass sich maximal 128 Teilnehmer (selten gibt es einen 10 Bit Modus) am Bus befinden können. Das Richtungsbit ist stets das niederwertigste Bit.
Quelle: nxp.com
An dieser Stelle ein Hinweis (aus Erfahrung): Achtet in Datenblättern immer genau darauf ob die Slave-Adresse eines Bauteils in 7
oder 8 Bit
Schreibweise angegeben ist. Leider ist die Schreibweise nicht immer gleich und man wundert sich, warum ein Bauteil nicht entsprechend auf das Zusenden einer Adresse reagiert (fehlendes ACK
Signal, siehe nächstes Unterkapitel). Nehmen wir hierzu ein Beispiel: Ein Bauteil reagiere auf folgende Adresse 01010010
(schreiben) bzw. auf 01010011
(lesen). Das entspricht der 8 Bit Adresse 0x62
(schreiben) bzw. 0x63
(lesen). In einigen Datenblättern würde jedoch nicht 0x62
/ 0x63
sondern stattdessen einfach nur 0x92
stehen. Hier wurde dann das 0. Bit des Bytes nicht mitgezählt (0101001
) – also aufpassen 🙂0
Acknowledge (ACK)
Immer nachdem 8 Bits übertragen wurden folgt ein ACK
oder NACK
(not acknowledge). Dabei handelt es sich also um das 9. Bit einer Übertragung. Das ACK
Bit ist low
aktiv. Die nachfolgende Grafik zeigt ein ACK
auf dem I2C Bus, siehe 9. Bit.
Quelle: nxp.com
Beispiel: Wenn sich ein Slave durch Übereinstimmung der Adresse angesprochen fühlt quittiert er dieses mit einer ACK
Sequenz (Acknowledge). Die Datenpakete, die dem Adressierungsbyte folgen müssen ebenfalls mit einem ACK
vom Slave bestätigt werden. Folgt auf ein gesendetes Byte ein NACK
ist die Übertragung fehlgeschlagen und der Master muss die Übertragung dieses Bytes wiederholen. Ein ACK
sieht auf dem Bus so aus: Der Master überträgt mit 8 Impulsen auf SCL
die einzelnen Bits des Bytes auf SDA
(angefangen mit MSB
) und erzeugt im Anschluss einen 9. Impuls auf SCL
. Wenn der Zustand beim 9. Takt einen low
Zustand auf SDA
aufweist (vom Slave auf low
gezogen, nicht vom Master), dann ist es ein ACK
, der Slave ist einverstanden. Ist der Zustand hingegen high
, so ist es ein NACK
, der Slave ist nicht einverstanden.
Betrachten wir ein weiteres Beispiel: Wenn ein Master Daten von einem Slave erhalten möchte, prüft er zunächst ob der Bus frei ist. Wenn er frei ist, sendet er ein Start
gefolgt von der 7 Bit langen Adresse des Slaves (schreibend, also ist Bit #0 = 0
) den er ansprechen möchte. Der Slave antwortet mit ACK
. Daraufhin sendet der Master i.d.R. eine Adresse eines Registers, das er beim Slave auslesen möchte (das hat nichts mit den I2C-Adressen zu tun). Der Slave quittiert wieder mit ACK
. Der Master beendet die Kommunikation mit Stop
oder führt ein Restart
durch. Nun schickt er erneut die (I2C-)Adresse des Slaves. Aber nun mit der Angabe, dass er lesen möchte, also ist das Bit #0
diesmal eine 1
. Der Slave antwortet wieder mit ACK
, woraufhin der Slave damit beginnt die Daten, die sich an der Adresse befinden, die ihm der Master zuvor mitgeteilt hat, über den Bus an den Master zu übermitteln (der Master gibt dabei immer den Takt vor). Je nachdem wie viele folgende Register der Master auslesen will quittiert er mit ACK
(…noch ein Byte mehr lesen). Wenn der Master genug gelesen hat, beendet er mit einem NACK
(das reicht, ich brauche kein weiteres Byte) und folgender Stop-Sequenz.
Hier mal eine Aufzeichnung einer I2C-Kommunikation:
Offensichtlich wird hier ein Teilnehmer mit der Adresse 0xA0
(8 Bit Schreibweise der Adresse) angesprochen, es sollen Daten von ihm gelesen werden (das 9. Bit ist eine 1
). Der angesprochene antwortet mit ACK
. Danach sieht man, dass der angesprochene Teilnehmer damit beginnt Daten an den Anfragenden auszugeben.
Serial Peripheral Interface (SPI)
Das Serial Peripheral Interface, kurz SPI
, ist eine serielle synchrone Datenübertragung zwischen zwei ICs. Das Interface nutzt 3+1
Leitungen: Da wäre zunächst die Taktleitung SCK
. Die Daten werden über Master out Slave in MOSI
bzw. Master in Slave out MISO
vom/zum Master zum/vom Slave übertragen. Neben diesen drei Leitungen wird weiterhin eine Chip-Select CS
Leitung benötigt, die expliziert einen Chip zur Datenkommunikation auswählt. So können mehrere Teilnehmer (ein Master + diverse Slaves) zusammen an SCK
, MOSI
und MISO
angeschlossen sein. Der Master wählt dann nur noch über das CS
(jeder Slave hat sein eigenes CS
) mit welchem der Slaves er gerade sprechen möchte. Nachfolgend eine Grafik zur Veranschaulichung. Hinweis: Abweichende Bezeichnungen CS = SS
, MOSI = SDO
, MISO = SDI
und SCL = SCLK
.
Quelle: Datenblatt des PIC18FK22
Übertragungsprinzip
Zur Übertragung der Daten wird zwischen vier Modi unterschieden. Die Übertragungsmodi unterscheiden sich in Clock Polarität CPOL
und Clock Phase CPHA
. Beim PIC haben diese beiden Parameter die Bezeichnung CKP
für die Clock Polarität und CKE
für die Phase. Die nachfolgende Grafik zeigt den Mode 2 mit CPOL=CLP=1
was bedeutet, dass der Pegel des Clock invertiert und somit im Idle Zustand high
statt low
ist und CPHA=CKE=0
was bedeutet, dass die Daten bei der leading edge
gültig sind und dementsprechend bei trailing edge
neu gesetzt werden. Bedeutung von leading
und trailing edge
: Bei leading edge
wird die erste Flanke nach dem Idle Zustand gemeint. Wenn der Clock also im Idle Zustand den Pegel low
hat, ist dies die steigende Flanke. Bei der trailing edge
ist entsprechend die zweite Flanke gemeint. Dies ist, wenn der Clock im Idle Zustand low
ist demzufolge die fallende Flanke 🙂
Quelle: Datenblatt des PIC18FK22
Gut zu sehen in der Abbildung ist, dass eine Datenübertragung mit einem Write auf das SSPxBUF
Register beginnt. Bytes die ihr auf dem SPI Bus senden möchtet, schreibt ihr in dieses Register. Der PIC kümmert sich dann im Anschluss um den Rest, siehe “Write to SSPxBUF” in der Abbildung. Da die Abbildung den Mode 2
zeigt, werden alle Bits des Bytes, das wir gerade in das SSBxBUF
Register geschrieben haben, der Reihe nach (beginnend mit dem MSB
) mit jeder steigender Taktflanke auf MOSI
ausgegeben. Parallel liest der PIC Daten auf MISO
ein. Wenn ein vollständiges Byte empfangen wurde, wird das SSPxIF
Bit gesetzt, was einen Interrupt auslösen kann, sofern dieser mit dem zugehörigen IE
(z.B. SSP1IE
) sowie den globalen Interrupt-Enable Bits eingeschaltet wurde. Die zuvor gezeigte Grafik ist ein Ausschnitt aus dieser Grafik, die alle vier SPI-Modi zeigt.
Konfiguration beim PIC
In diesem Unterkapitel möchte ich kurz auf die Konfiguration beim PIC eingehen. Um den SPI Bus zu betreiben nutzen wir beim Master Synchronous Serial Port (MSSP
) Modul. Je nachdem ob ein MSSP
Modul gleichzeitig I2C
und SPI
bedienen kann, müssen wir das Modul entsprechend auf SPI
konfigurieren. Die Einstellung nehmen wir im SSPxCON
Register vor. Ich möchte dies hier einmal am Beispiel des PIC18F13K22 durchspielen:
Bit 7 | Bit 0 | ||||||
WCOL | SSPOV | SSPEN | CKP | SSPM3 | SSPM2 | SSPM1 | SSPM0 |
Wir möchten zunächst unseren Fokus auf die Bits SSPM0
bis SSPM3
legen. Mit diesen vier Bits wählen wir den Betriebsmodus des MSSP
Moduls aus:
0000
…0101
: SPI Mode (Slave/Master,SS
oder frei wählbarerGPIO
alsCS
, Clock Einstellung)0110
…1111
: I2C Mode (Slave/Master,7
oder10
Bit Adresse, Clock Einstellung)
Die weiteren Bits des SSPxCON
Registers:
WCOL
(Write Collision Detect): Dieses Bit wird gesetzt, wenn während einer noch laufenden Übertragung bereits ein neues Byte in SSPBUF
geschrieben wird.
SSPOV
(Receive Overflow Indicator): Dieses Bit ist der Indikator für ein eventuell aufgetretenen Überlaufen des Empfangspuffers. Nachdem ein Byte empfangen wurde muss dieses aus dem Puffer (SSPBUF
) ausgelesen werden. Wenn das nicht erfolgt, wird SSPOV
gesetzt wenn ein weiteres Byte empfangen wurde. Außerdem wird der Inhalt des Puffers nicht mehr aktualisiert, solange das Bit gesetzt ist. Das Bit muss manuell wieder gelöscht werden. Wichtig: Der Überlauf kann nur im Slave Modus auftreten.
SSPEN
(Synchronous Serial Port Enable): Mit diesem Bit wird das Modul ein- bzw. ausgeschaltet.
CKP
(Clock Polarity Select): Mit diesem Bit wird die Clock Polarität gewählt. Es ist neben CKE
(im SSPxSTAT
Register) eines der zwei Bits, die den SPI Modus einstellen.
Hier nochmal eine Tabelle, die bei der Konfiguration des SPI-Modus helfen soll:
Mode | CKP / CPOL (Polarität) | CKE / CPHA (Phase) | Verhalten der Signale |
0 |
0 |
0 |
![]() low im Idle Zustand und die Daten sind bei der steigenden leading Taktflanke gültig. |
1 |
0 |
1 |
![]() low im Idle Zustand und die Daten sind bei der fallenden trailing Taktflanke gültig. |
2 |
1 |
0 |
![]() high im Idle Zustand und die Daten sind bei der fallenden leading Taktflanke gültig. |
3 |
1 |
1 |
![]() high im Idle Zustand und die Daten sind bei der steigenden trailing Taktflanke gültig. |
Übertragung
Die nachfolgende Abbildung zeigt den Ablauf einer einfachen Datenübertragung eines Bytes mit Hilfe des SPI Bus beim PIC Mikrocontroller. Zunächst einmal ist es wichtig die GPIOs, die für das MSSP
bzw. das SPI
benötigt werden korrekt einzustellen. Sowohl SCL
(SCLK
) als auch SDO
(MOSI
) sind als Ausgang zu konfigurieren (TRIS
bit löschen). SDI
(MISO
) muss hingegen als Eingang (TRIS
bit setzen) konfiguriert werden. Achtet dabei auch darauf, dass ein als Eingang konfigurierter GPIO oftmals default als analoger Eingang eingestellt ist. Gegebenenfalls müsst ihr den GPIO zusätzlich noch digital umstellen, sofern der GPIO zusätzlich über die Anlaogfunktion verfügt (siehe ANSEL
Register).
Im Anschluss wird das MSSP
Modul entsprechend der Wünsche konfiguriert (Master/Slave, Mode, Taktgeschwindigkeit …). Wenn das erledigt ist, können bereits Daten gesendet werden (vorausgesetzt das Modul arbeitet als Master). Zum Aussenden eines Bytes wird dieses in das SSPBUF
Register geschrieben. Der PIC beginnt daraufhin mit dem Senden des Bytes (siehe auch Abbildung aus dem Datenblatt weiter oben). Sobald der PIC das aktuelle Byte vollständig übertragen, wird das Buffer Full Status BF
Bit im SSPxSTAT
Register gesetzt. Nun muss das zeitgleich zum Senden gelesene Byte (gelesen über SDI
bzw. MISO
) ebenfalls vom SSPBUF
Register gelesen werden (unabhängig davon ob einen die gelesenen Daten interessieren oder nicht).
Die serielle Schnittstelle (EUSART/UART)
Das EUSART-Modul (Enhanced Universal Synchronous Asynchronous Reseiver Transmitter) bietet die Möglichkeit eine einfache serielle Verbindung aufzubauen. Das Modul kann entweder als synchrones Modul im Halb-Duplex-Verfahren oder aber als wesentlich interessanteres asynchrones Modul im Voll-Duplex-Verfahren eingesetzt werden. Der asynchrone Betrieb ist sehr beliebt um beispielsweise eine Verbindung zum COM-Port eines Computers herzustellen, besser bekannt unter dem Begriff “RS232”. Wer jetzt sagt: “Eine serielle Schnittstelle RS232-Schnittstelle an modernen Computern?” hat mit seiner Verwirrung vollkommen recht. Euch möchte ich folgenden Artikel ans Herz legen FTDI – FT232RL.
Zum Aufbau der seriellen Verbindung benötigt man lediglich zwei IOs des PIC-Controllers. Eine Leitung zum Senden TX und eine zum Empfangen im Vollduplex-Betrieb RX. In der Regel dienen hierzu die Pins RC6 und RC7 wobei neuere PIC-Typen über mehrere EUSART-Module verfügen. Die IO-Pins des Controllers werden als Open-Collector betrieben, damit es zu keinen Kurzschlüssen auf dem Bus kommen kann.
Protokoll
Das Protokoll der UART-Schnittstelle ist denkbar einfach. Übertragen werden Rohdaten mit einer Größe von acht Bit. Zusätzlich wird ein vorangehendes Startbit, ein oder wahlweise zwei nachfolgende(s) Stoppbit(s) sowie, wenn gewünscht ein Paritätsbit (gerade oder ungerade). Somit besteht ein vollständiges Datenpaket aus mindestens 10 bis maximal 12 Bits, siehe:
Die Spezifikation sieht vor, dass eine logische 1 (auch als Mark bezeichnet) mit einem Pegel von -12V und eine logische 0 (auch als Space bezeichnet) mit einem Pegel von +12V übertragen wird. Sprich, die Pegel auf dem Medium können als invertiert in Bezug zu den logischen Pegeln betrachtet werden. Natürlich gibt der Mikrocontroller keine Pegel von +/-12V an seinen Pins aus. Hierfür gibt es entsprechende Pegelwandler-ICs wie zum Beispiel den MAX232 . Wenn man aber zum Beispiel eine Verbindung über UART zu modernen PCs herstellen möchte, die in aller Regel keinen RS232-Anschluss mehr besitzen, so greift man auf so genannte USB/Seriell-Konverter zurück. Ein sehr prominenter Vertreter ist der FT232RL von FTDI. Dieser übernimmt alle Umwandlungen jeglicher Pegel und bietet die Möglichkeit den USB-Anschluss am Rechner zu nutzen um eine virtuelle serielle Verbindung mit einem Controller herzustellen. Siehe den verlinkten Artikel für mehr Details.
Initialisierung
Damit das EUSART-Modul (oder USART, UART) eines PIC-Controllers verwendet werden kann, muss dieses natürlich zunächst entsprechend den Anforderungen konfiguriert werden. Die Konfiguration gestaltet sich ausgesprochen einfach, wie wir jetzt sehen werden. Als erstes schauen wir uns das Register TXSTAX (Transmit Status And Control Register) an.
BIT 7 | BIT 0 | ||||||
---|---|---|---|---|---|---|---|
CSRC | TX9 | TXEN | SYNC | SENDB | BRGH | TMRT | TX9D |
TX9D (neuntes Datenbit)
Dieses Bit stellt den Inhalt im Falle einer 9-Bit-Datenübertragung und ist für den normalen RS232-Anwendungsfall uninteressant.
TMRT (Transmit Shift Register Status Bit)
Das Timer Shift Register zeigt an ob das Ausgangs-Schieberegister leer oder voll ist. Wir werden das Bit jedoch nicht gebrauchen. Eine abgeschlossene Übertragung ermitteln wir später durch das zum Modul zugehörige TX-Interrupt-Flag.
BRGH (High Baud Rate Select Bit)
Dieses Bit wird ausschließlich im asynchronen Modus verwendet um zwischen hoher (1) und niedriger (0) Baudrate zu unterscheiden. Je nach gewünschte Baudrate muss dieses Bit gesetzt oder gelöscht werden. Im Datenblatt des verwendeten PIC gibt es entsprechend Tabellen mit diversen Baudraten sowie der dazu passenden Einstellung für das BRGH-Bit. Zusätzlich besteht hier eine Abhängigkeit zur verwendeten Taktquelle des PIC.
SENDB (Send Brake Character Bit)
Dieses Bit wird ebenfalls ausschließlich im asynchronen Modus verwendet um zum Beispiel dem LIN-Bus-Spezifikationen eines bestimmten Break-Codes zu entsprechen. Im normalen RS232-Betrieb wird dieses Bit nicht verwendet.
SYNC (Mode Select Bit)
Mit diesem Bit wird zwischen dem üblicherweise verwendetem asynchronen Modus (0) oder dem synchronen Modus (1) gewählt.
TXEN (Transmit Enable Bit)
Dieses Bit schaltet das Modul ein (1) oder aus (0).
TX9 (9-Bit Transmit Enable Bit)
Wenn dieses Bit auf ‘1’ gesetzt wird, können neun statt der üblichen acht Bits pro Daten (netto) übertragen werden. In der Regel verwenden wir jedoch den normalen Modus mit 8 Datenbits und setzten das Bit folglich auf ‘0’.
CSRC (Clock Source Select Bit)
Im synchronen Modus wählt man mit diesem Bit aus, ob der PIC als Master (1) oder als Slave (0) fungieren soll. Im asynchronen Modus ist das Bit hingegen irrelevant.
Als nächstes betrachten wir das RCSTAX Register (Receive Status And Control Register).
BIT 7 | BIT 0 | ||||||
---|---|---|---|---|---|---|---|
SPEN | RX9 | SREN | CREN | ADDEN | FERR | OERR | RX9D |
RX9D (neuntes Datenbit)
Wie schon im entsprechenden TX-Register dient dieses Bit für die optionale 9-Bit-Daten-Kommunikation.
OERR (Overrun Error Bit)
Das Überlaufbit wird gesetzt, wenn der Empfangs-FIFO übergelaufen ist. Solange das Bit nicht gelöscht wird (durch Löschen des CREN Bits im RCSTAx-Register), können keine neuen Zeichen/Daten über den Bus empfangen werden.
FERR (Framing Error Bit)
Wenn in einem Datenpaket ein Stopp-Bit nicht erkannt wurde, wird das FERR-Bit gesetzt. Das Bit kann ausschließlich gelesen werden. Es ist nicht notwendig das Bit zu löschen. Sobald das nächste Zeichen eingelesen wird, wird das Bit wieder aktualisiert.
ADDEN (Address Detect Enable Bit)
Dieses Bit ist ausschließlich im 9-Bit-Modus relevant.
CREN (Continous Receive Enable Bit)
Im asynchronen-Modus wird mit einer ‘1’ das Empfangs-Modul aktiviert und mit einer ‘0’ deaktiviert. Im synchronen-Modus hingegen wird der dauerhafte Empfang aktiviert (1) oder deaktiviert (0).
SREN (Single Receive Enable Bit)
Im asynchronen-Modus ist dieses Bit nicht von Interesse. Ebenso, wenn der Controller im 9-Bit-Slave-Modus betrieben wird.
RX9 (9-th Receive Enable Bit)
Mit diesem Bit wird zwischen 9-Bit-Kommunikation (1) oder 8-Bit-Kommunikation (0) unterschieden. In der Regel verwenden wir den 8-Bit-Modus.
SPEN (Seriel Port Enable Bit)
Mit einer ‘1’ in diesem Bit wird das Empfangs-Modul aktiviert, mit einer ‘0’ deaktiviert.
Im BAUDCONx-Register werden Einstellungen zur Baudrate getätigt. Außerdem befinden sich ein paar Flags in diesem Register.
BIT 7 | BIT 0 | ||||||
---|---|---|---|---|---|---|---|
ABDOVF | RCIDL | DTRXP | CKTXP | BRG16 | – | WUE | ABDEN |
In diesem Register interessiert uns im Wesentlichen nur da Bit BRG16. Mit diesem Bit lässt sich zwischen 8 und 16-Bit-Baudgenerator unterschieden. Dies ist wichtig um später die richtige Baudrate auf Grundlage des PIC-Taktgebers zu generieren. Hierfür ist zusätzlich das Register SPBRGx wichtig. Der notwendige Wert für die gewünschte Baudrate kann wie folgt berechnet werden:
Das Ergebnis wird entsprechend Integer im Nachkomma-Bereich abgeschnitten und auf die beiden Register (SPBRGHx und SPBRGx) aufgeteilt.
Anwendung
An dieser Stelle möchte ich lediglich in aller Kürze Codeschnipesel zur Verfügung stellen. Als erstes den Code (am Beispiel eines PIC18F45K22) zur Initialisierung des EUSART-Moduls zum Verwenden der asynchronen UART im 8 Bit Modus. Die Baudrate ist dabei auf 9600 bit/s konfiguriert.
void initEUSART(void) { TXSTA = 0b00100000; RCSTA1 = 0b10010000; BAUDCON1bits.BRG16 = 0; SPBRG1 = 12; IPR1bits.RC1IP = 1; PIE1bits.RC1IE = 1; }
Der nächste Codeschnipsel zeigt eine Funktion die zum Aussenden eines Datenpaketes verwendet werden kann.
void transmitEUSART(char value) { /*are you ready to send out new data?*/ if(PIR1bits.TX1IF) { /*load the byte into the buffer*/ TXREG1 = value; } }
Zu guter letzt eine entsprechende Interrupt-Service-Routine, die beim Empfang eines neuen Datenpaketes (über die RX-Leitung) aufgerufen wird. Beachte, dass die Priorität auf hoch gesetzt wurde.
char eusartInput; // ... void interrupt highPrio(void) { /*is there an unreaded byte in the buffer?*/ if(PIR1bits.RC1IF == 1) { /*clear int-flag by readout the buffer*/ eusartInput = RCREG1; } }
Dabei ist die Initialisierung von oben bereits so geschrieben, dass der Interrupt für empfangene Pakete aktiviert ist (zusätzlich muss wie immer die globale Aktivierung für Interrupts erfolgt sein).
Ausblick
In den nächsten Kapiteln dieses PIC-Tutorials beschäftigen wir uns mit Interrupts, der Konfiguration (bzw. dem Konfigurationswort
) des PIC sowie mit der Möglichkeit einen bereits in der Schaltung / auf dem Board befindlichen PIC zu Programmieren (siehe In Circuit Serial Programming ICSP
). Wenn du am Ball bleiben möchtest, dann geht es hier direkt weiter: PIC18 Tutorial – Interrupt, Konfig und ICSP