In Kapitel 0, Abschnitt RS232-Verbindung (Seite 18) wurde schon einmal auf die serielle Schnittstelle eingegangen. In diesem Kapitel soll eine Library erstellt werden, mit der die Kommunikation von und zum PC ermöglicht wird. Die Library soll Funktionen zur Initialisierung des UARTs sowie das Senden und Empfangen mit Hilfe des UART-Interrupts enthalten. Die Funktionen für die serielle Schnittstelle sind in der RS232Lib.c abgelegt. Für die Übergabeparameter (Baudrate, Betriebsart des UARTs, usw.) werden Felder des Aufzählungstyps enum angelegt (siehe Listing 66, ). Somit sind die Übergabeparameter besser zu lesen, da nicht der Reloadwert, sondern die Baudrate angegeben wird. Der Unterstrich vor dem Wert z. B. „_300“ ist nötig, da eine Konstante nicht mit einem numerischen Wert beginnen darf.
F !! Die Berechnung der Reloadwerte können sie den Formeln aus dem Kapitel 10.11 UART, „Abschnitt Baudraten erzeugen“ [3] entnehmen. !!
Die Funktion „uc_Init_Serial()“ übernimmt die Initialisierung des UARTs (siehe Listing 67, ). Im ersten Übergabeparameter wird die Baudrate und im zweiten Parameter die Betriebsart übergeben. Als nächstes wird der Baudratengenerator gestoppt und das SM1 Flag im SFR PCON für die Initialisierung der Betriebsart freigegeben (siehe ‚). Es folgt das Setzen der Betriebsart und die Initialisierung des Baudratengenerators (siehe ƒ). Zum Schluss wird der Baudratengenerator gestartet und das SM1 für die Erkennung des „Framing Error“ freigegeben (siehe „). Die Funktionsweise des „Framing Error“ können sie im Kapitel 10, Abschnitt „Framing Error“ [3] nachlesen.
Für das Senden eines Zeichens bzw. einer Zeichenkette wird die Struktur RS232BUF verwendet. Der Aufbau der Struktur befindet sich im H-File RS232Lib.h (siehe Listing 66, ‚). Er entspricht der LV-Codierung. Im ersten Byte wird die Länge der Daten abgespeichert. Das Array „ucBuf[]” enthält die Daten. Es können bis zu 16 Bytes gespeichert werden. Sollen mehr Bytes gespeichert werden, ist der Index des Arrays zu erhöhen. Für das Senden bzw. Empfangen stehen zwei separate Strukturen (stRecRS232, stSendRS232) zur Verfügung. Somit kann gleichzeitig gesendet und Empfangen werden.
Erstellen sie das Projekt aus Tabelle 68. Der Start eines Sendevorgangs wird mit Hilfe der Funktion „uc_SendRS232()“ eingeleitet. Dieser Funktion wird die Startadresse des zu sendenden Bytes bzw. der Zeichenkette und die Länge übergeben (siehe †). Der Startvorgang des Sendens wird mit einem Schreibzugriff in das SFR SBUF begonnen (siehe ‡). In der ISR wird mit Hilfe des RI-Flags (siehe ˆ) zuerst überprüft, ob ein Zeichen empfangen wurde. Ist dies der Fall, wird der Wert aus SBUF ausgelesen und in das Array der Struktur „stRecRS232“ geschrieben. Der Offsetzeiger für die Verwaltung des Arrays (stRecRS232.ucLen) wird danach um eins erhöht (siehe ‰).
F !! Zum Schluss muss noch das RI-Flag gelöscht werden, da es nicht per Hardware zurückgesetzt wird. !!
Danach wird geprüft, ob das TI-Flag gesetzt ist (siehe Š). Ist dies der Fall, kann ein neues Zeichen gesendet werden. Mit Hilfe der Variablen „ucSendOffset“ wird überprüft, ob noch Zeichen gesendet werden müssen (siehe Œ). Ist dies der Fall, wird das nächste Zeichen in das SFR SBUF geschrieben und „ucSendOffset“ um eins erhöht. Sind schon alle Zeichen gesendet worden, wird „stSendRS232.ucLen“ auf „0“ gesetzt (siehe ). Somit kann vom Hauptprogramm erkannt werden, dass alle Zeichen aus dem Array ausgesendet worden sind.
F !! Am Ende muss noch das TI-Flag gelöscht werden, da es nicht per Hardware zurückgesetzt wird. !!
|
Projektname |
Verzeichnis |
Verwendete Sourcemodule |
|
RS232Library |
Test_RS232Lib |
Test_RS232Lib.c |
|
|
..\Library |
RS232Lib.c, PortConfig.c |
Tabelle 68 Projekt RS232Library
|
‚
|
// Definitionen // Berechnung Reloadwert fuer Baudratengenerator bei 7.3728 MHz enum enUARTBaudrate { _600= 0x2FF0, _1200= 0x17F0, _2400= 0x0BF0, _4800= 0x05F0, _9600= 0x02F0, _19200=0x0170, _38400= 0x00B0,_57600= 0x0070,_115200= 0x0030}; enum enUARTConfig { _8BIT= 0x40, _9BIT= 0xC0};
struct RS232BUF { unsigned char ucLen; unsigned char ucBuf[16]; };
// Funktionsdeklarationen extern unsigned char uc_Init_Serial(unsigned int uiBaudRate, unsigned char ucUARTMode); extern unsigned char uc_SendRS232(unsigned char * ucPtr, unsigned char ucLen);
// Variablendeklarationen extern struct RS232BUF stRecRS232, stSendRS232; |
Listing 66 Inhalt von RS232Lib.h
|
‚ ‚
ƒ ƒ ƒ
„ „ …
†
‡
ˆ
‰ ‰
Š
Œ
|
#include <REG932.H> #include <RS232Lib.H> #include <string.h>
struct RS232BUF stRecRS232, stSendRS232; unsigned char ucSendOffset;
// Funktion fuer Baudratengenerator unsigned char uc_Init_Serial(unsigned int uiBaudRate, unsigned char ucUARTMode) { PCON = PCON & 0xBF; // SMOD0 (SFR PCON) auf "0" setzen BRGCON = 0x02; // Baudratengenerator stoppen
SCON = ucUARTMode; // Betriebsart (8/9-bit), Flags loeschen BRGR1 = uiBaudRate /256; // Baudrate setzen BRGR0 = (unsigned char)uiBaudRate;
BRGCON = 0x03; // Baudratengenerator starten PCON = PCON | 0x40; // SMOD1 (SFR PCON) auf "1" setzen stRecRS232.ucLen = 0; ES = 1; // Freigabe der ser. Schnittstelle return(0); }
unsigned char uc_SendRS232(unsigned char * ucPtr, unsigned char ucLen) { if (ucLen == 0 || ucLen > 16) // Laengenueberpruefung return -1; stSendRS232.ucLen = ucLen; memcpy(stSendRS232.ucBuf, ucPtr, ucLen);
SBUF = stSendRS232.ucBuf[0]; ucSendOffset = 1; return 0; }
void v_IntSer(void) interrupt 4 using 1 { if (RI) // wurde Zeichen empfangen ? { if (stRecRS232.ucLen < 16) // Ueberpruefung auf Ueberlauf { stRecRS232.ucBuf[stRecRS232.ucLen] = SBUF; stRecRS232.ucLen++; } RI = 0; } if (TI) { if (ucSendOffset < stSendRS232.ucLen) { SBUF = stSendRS232.ucBuf[ucSendOffset]; ucSendOffset++; } else stSendRS232.ucLen =0; // alle Daten sind gesendet TI = 0; } } |
Listing 67 Inhalt von RS232Lib.c
Um die RS232-Library verwenden zu können, muss das H-File mit inkludiert werden (siehe Listing 68, ). Der zu sendende Text ist in diesem Beispiel im code-Bereich abgelegt (siehe ‚). Die Konfiguration der Portpins für die serielle Schnittstelle wird mit Hilfe der Funktion „v_PortConfig()“ durchgeführt (siehe ƒ). Die serielle Schnittstelle wird mit 9600 Baud und 8 Bit konfiguriert (siehe „). Die Freigabe der ISR sowie des Empfängers erfolgt im Hauptprogramm (siehe …). Der Sendevorgang wird mit dem Aufruf der Funktion „uc_SendRS232()“ gestartet (siehe †). Der sizeof()-Operator ermittelt die Größe des zu sendenden Bereichs. Er kann auf Arrays, Datentypen und Zeichenketten angewendet werden.
|
‚
ƒ
„
…
…
†
|
#include <REG932.H> #include <PortConfig.H> #include <RS232Lib.H>
unsigned char code ucInfo[]={"Test der RS232"};
void main(void) { // Konfiguration der Portpins (UART) v_PortConfig(Port1, Pin0 | Pin1, BiDir);
// Initialisierung der seriellen Schnittstelle uc_Init_Serial(_9600, _8BIT);
// Interrupt Konfiguration EA = 1; // allg. Interruptfreigabe
// Freigabe des Empfangsteils (UART) REN = 1;
uc_SendRS232(&ucInfo, sizeof(ucInfo)); while(stSendRS232.ucLen != 0); // Warten bis alle Zeichen ausge- // sendet sind. while(1); } |
Listing 68 Inhalt von Test_RS232.c
Um zu überprüfen, ob ein oder mehrere Zeichen empfangen wurden, muss der Wert aus „stRecRS232.ucLen“ überprüft werden (siehe Listing 69, ). Sind Zeichen vorhanden, können die Daten aus der Struktur ausgelesen werden.
|
|
if (stRecRS232.ucLen) // Daten sind empfangen worden { } |
Listing 69 Auswertung des Empfangsbuffers
Die im enum-Feld abgelegten Werte sind für die interne RC-Frequenz von 7,3728 MHz ausgelegt. Wird z. B. ein externer Quarz mit 12 MHz verwendet, müssen die Reloadwerte im enum-Feld neu angepasst werden. Die Anpassung auf die jeweilige Frequenz kann mit Hilfe einer #define-Anweisung und einer Berechnung erfolgen (siehe Listing 70, ).
|
|
#define XTAL 7372800L // Frequenzangabe enum enUARTBaudrate { _600=(XTAL/600) -16, _1200=(XTAL/1200) -16, _2400=(XTAL/2400) -16, _4800=(XTAL/4800) -16, _9600=(XTAL/9600) -16, _19200=(XTAL/19200)-16, _38400=(XTAL/38400) -16, _57600=(XTAL/57600)-16, _115200=(XTAL/11520)-16}; |
Listing 70 Berechnung der Reloadwerte mit Hilfe einer #define-Anweisung
Wird eine andere Frequenz verwendet, muss nur die Definition von XTAL angepasst werden. Die CPU-Frequenz kann aus den Projekt Settings ausgelesen werden. Die unter Options for Target, Reiter Target im Eingabefeld XTAL eingetragene Frequenz (siehe Abbildung 63) kann auch vom C-Compiler genutzt werden. Dieser Wert kann mit Hilfe der Makro Konstanten %%CPU_XTAL ausgelesen werden (siehe Listing 71, ).
F !! Der Aufbau dieser Makro-Konstanten kann im Kapitel 5, Abschnitt Konstanten, [1] nachgelesen werden. !!
|
|
Abbildung 63 Options for Target, Reiter Target
|
|
#define %%CPU_XTAL
// Berechnung Reloadwert fuer Baudratengenerator bei 7.3728 MHz enum enUARTBaudrate { _600=(CPU_XTAL/600)-16, _1200=(CPU_XTAL/1200) -16, _2400=(CPU_XTAL/2400) -16,_4800=(CPU_XTAL/4800) -16, _9600=(CPU_XTAL/9600) -16,_19200=(CPU_XTAL/19200)-16, _38400=(CPU_XTAL/38400) -16,_57600=(CPU_XTAL/57600)-16, _115200=(CPU_XTAL/115200)-16}; |
Listing 71 Berechnung der Reloadwerte mit Hilfe der Makro-Konstanten
Die Makro-Konstanten haben den Vorteil, dass die Library-Funktionen nicht verändert werden müssen und somit die Funktionen in mehreren Projekten verwendet werden können.
Die Taktfrequenz CCLK für die Versorgung des UARTs kann mit Hilfe des SFRs DIVM beeinflusst werden. Dies ist z. B. der Fall, wenn der Chip Strom sparen soll. In diesem Fall muss der Reloadwert in Abhängigkeit von DIVM ermittelt werden. Die Formel für die Berechnung des Reloadwertes lautet wie folgt:
|
|
Abbildung 64 Berechnung der Baudrate mit dem Baudratengenerator
Da jetzt die Baudrate und nicht mehr der Reloadwert an die Funktion zur Initialisierung des UARTs übergeben wird, kann der Übergabeparameter „ucBaudRate“ auf den Datentyp „unsigned char“ reduziert werden (siehe Listing 72, ). Die Werte im Aufzählungstyp „enUARTBaudrate“ sind so angelegt, das sich die Baudraten auf ein Vielfaches von 600 beziehen (siehe ‚). Mit dieser Vorgehensweise lässt sich die Berechnung des Reloadwertes sehr stark vereinfachen (siehe Listing 72, ). Der cast auf unsigned long wird benötigt, da Zwischenergebnisse größer als 16 Bit sein können.
F !! Die Funktionsweise des cast-Operators wird im Kapitel 14, „Typkonvertierung“ [1] ausführlich behandelt. !!
|
Projektname |
Verzeichnis |
Verwendete Sourcemodule |
|
RS232DynLibrary |
Test_RS232DynLib |
Test_RS232DynLib.c |
|
|
..\Library |
RS232DynLib.c, PortConfig.c |
Tabelle 69 Projekt RS232DynLibrary
|
|
unsigned char uc_Init_Serial(unsigned char ucBaudRate, unsigned char ucUARTMode) { unsigned int uiReload; PCON = PCON & 0xBF; // SMOD0 (SFR PCON) auf "0" setzen BRGCON = 0x02; // Baudratengenerator stoppen
SCON = ucUARTMode; // Betriebsart (8/9-bit), Flags loeschen
uiReload = CPU_XTAL/((unsigned long)(ucBaudRate *600) * (DIVM + 1 )) -16; BRGR1 = uiReload /256; // Baudrate setzen BRGR0 = (unsigned char)uiReload;
BRGCON = 0x03; // Baudratengenerator starten PCON = PCON | 0x40; // SMOD1 (SFR PCON) auf "1" setzen stRecRS232.ucLen = 0; ES = 1; // Freigabe der ser. Schnittstelle return(0); } |
Listing 72 Auszug aus RS232DynLib.c
Da die Abweichung der Baudrate bei der RS232-Verbindung zwischen Sender und Empfänger maximal ± 5 % Abweichung haben darf (siehe EIA-232-D), sollte bei der Berechnung des Reloadwertes überprüft werden, ob die erzeugte Baudrate noch innerhalb dieser Toleranz liegt. Die Überprüfung kann dadurch erfolgen, indem die Baudrate anhand des Reloadwertes ermittelt wird. Unter dem folgenden Link finden sie noch weitere Informationen zur RS232-Schnittstelle:
http://www.camiresearch.com/Data_Com_Basics/RS232_standard.html
Die Funktion „printf()“ ist ein Bestandteil der C-Library. Mit dieser Funktion können Variable und Konstanten formatiert und der dadurch entstandene Text ausgegeben werden. Die Ausgabe erfolgt per default über die serielle Schnittstelle. Damit Anpassungen mit der Ausgabe vorgenommen werden können, ruft „printf()“ ihrerseits die Funktion „putchar()“ auf. Auch diese Funktion ist in der C-Library vorhanden. Die Funktion arbeitet mit der XON/XOFF- Flusssteuerung. Die Funktionsweise dieser Flusssteuerung ist in Kapitel 3, Abschnitt „XON/XOFF-Protokoll“ [2] ausführlich beschrieben.
|
|
#define XON 0x11 #define XOFF 0x13
/* * putchar (full version): expands '\n' into CR LF and handles * XON/XOFF (Ctrl+S/Ctrl+Q) protocol */ char putchar (char c) {
if (c == '\n') { if (RI) { if (SBUF == XOFF) { do { RI = 0; while (!RI); } while (SBUF != XON); RI = 0; } } while (!TI); TI = 0; SBUF = 0x0d; /* output CR */ } if (RI) { if (SBUF == XOFF) { do { RI = 0; while (!RI); } while (SBUF != XON); RI = 0; } } while (!TI); TI = 0; return (SBUF = c); } |
Listing 73 Auszug aus Putchar.c
In Abbildung 65 ist der Ablauf beim Compilieren und Linken dargestellt. Der C-Compiler erzeugt ein OBJ-File. Dieses enthält einen Aufruf zur Funktion „_printf()“. Der Unterstrich vor dem Funktionsnamen gibt an, dass der Funktion Parameter in Registern übergeben werden (siehe Kapitel 8.8 „Aufbau von Funktionsnamen“ [1]). Der Linker findet diesen Funktionsaufruf und sucht nach einem Einsprung mit diesem Label im OBJ-File. Wenn er dieses Label nicht finden kann, sucht er in der Library nach diesem. Wenn er das Label gefunden hat, in diesem Fall „_printf“, nimmt der Compiler den entsprechenden Programmteil und fügt ihn zum Programm hinzu. Da „_printf“ seinerseits noch „putchar“ aufruft, wird auch dieser Programmteil aus der Library geholt und zum Projekt hinzugefügt.
|
|
Abbildung 65 Funktionsweise Compiler, Linker, Library
Soll die Funktion „putchar()“ an die eigenen Wünsche angepasst werden (z. B. ohne XON-/OFF-Steuerung), muss die Funktion mit in das C-Modul aufgenommen werden. Der Linker verwendet dann diese Funktion für den Aufruf aus „_printf“.
Erstellen sie ein Projekt, das die Ausgabe über die RS232-Schnittstelle mit Hilfe der Funktion „printf()“ vornimmt. Die Funktion „putchar()“ soll die Zeichen ohne Änderung und ohne XON-/XOFF-Steuerung aussenden (siehe Listing 74, ). In Tabelle 70 ist die Projektstruktur enthalten.
|
Projektname |
Verzeichnis |
Verwendete Sourcemodule |
|
RS232Printf |
Test_RS232Printf |
Test_RS232Printf.c |
|
|
..\Library |
RS232Lib.c, PortConfig.c |
Tabelle 70 Projekt RS232Printf
|
‚ |
#include <REG932.H> #include <rs232lib.h> #include <PortConfig.H> #include <stdio.h>
void main(void) { // Konfiguration der Portpins (UART) v_PortConfig(Port1, Pin0 | Pin1, BiDir);
// Initialisierung der seriellen Schnittstelle uc_Init_Serial(_9600, _8BIT);
TI = 1; // Freigabe des Senderegisters printf("Test der RS232 mit printf()"); while(1); }
char putchar (char c) { while (!TI); // Warten bis letztes Zeichen gesendet TI = 0; SBUF = c; // Zeichen wird ausgesendet return(0); } |
Listing 74 Inhalt von Test_ RS232Printf.c
Die Ausgabe der Zeichen erfolgt ohne die ISR. In der Funktion „putchar()“ wird solange gewartet, bis TI wieder von der Hardware gesetzt wird (siehe ‚). Das Programm befindet sich solange in der Funktion „printf()“, bis das letzte Zeichen ausgesendet wurde. Bei diesem Beispiel werden 27 Zeichen zu 10 Bit (Startbit + Daten + Stoppbit) gesendet. Bei der Übertragungsrate von 9600 Baud (104 µs/Bit) ergibt sich eine Übertragungszeit von 28.08 ms. Wird eine Baudrate von 300 Baud verwendet, so liegt die Übertragungszeit bei 898.56 ms. Um hier eine Unabhängigkeit zwischen der Ausgabe mit Hilfe der Funktion „printf()“ und der Übertragungsgeschwindigkeit der seriellen Schnittstelle zu erreichen, müssen die erzeugten Daten von „printf()“ in einem Ringbuffer zwischengespeichert werden. Die ISR der seriellen Schnittstelle liest dann die Daten aus dem Ringbuffer aus und versendet sie. Die genaue Funktionsweise dieses Verfahrens ist in Kapitel 3, Abschnitt „Zeitunkritische Ausgaben unter Verwendung des seriellen Interrupts“ [2] ausführlich beschrieben.
Auch die Funktion „scanf()“ ist ein Bestandteil der C-Library. Im Gegensatz zur Funktion „printf()“, die Daten formatiert und mit Hilfe der Funktion „putchar()“ ausgibt, liest „scanf()“ Daten mit Hilfe der Funktion „getkey()“ ein und weist sie der Variablen zu. Die Formatierung der Daten bei „scanf()“ ist identisch mit der Formatierung bei „printf()“. In Listing 75 (siehe ) wird ein Byte eingelesen und der Variablen „cA“ zugewiesen. Beim Aufruf der Funktion „scanf()“ wird jetzt solange die Funktion „_getkey()“ (siehe Listing 76, ) aufgerufen, bis alle benötigten Daten empfangen wurden. In „_getkey()“ wird jetzt solange das RI-Flag gepollt (siehe ‚), bis dieses den Wert „1” annimmt. Der Wert wird aus dem Empfangsbuffer SBUF ausgelesen und der Variablen „c“ zugewiesen (siehe ƒ). Danach wird das RI-Flag wieder auf „0“ gesetzt.
|
Projektname |
Verzeichnis |
Verwendete Sourcemodule |
|
RS232Scanf |
Test_RS232Scanf |
Test_RS232Scanf.c |
|
|
..\Library |
RS232Lib.c, PortConfig.c |
Tabelle 71 Projekt RS232Scanf
|
|
#include <REG932.H> #include <rs232lib.h> #include <PortConfig.H> #include <stdio.h>
void main(void) { char cA; // Konfiguration der Portpins (UART) v_PortConfig(Port1, Pin0 | Pin1, BiDir);
// Initialisierung der seriellen Schnittstelle uc_Init_Serial(_9600, _8BIT); REN=1; // Freigabe des Empfangs
scanf("%c",&cA); while(1); } |
Listing 75 Inhalt von Test_ RS232Printf.c
|
‚ ƒ |
char _getkey () { char c;
while (!RI); c = SBUF; RI = 0; return (c); } |
Listing 76 Inhalt von \c51\lib\Getkey.c