Library für die serielle Schnittstelle

In Kapitel 0, Abschnitt RS232-Verbindung (Seite 18) wurde schon einmal auf die se­ri­el­le Schnittstelle eingegangen. In diesem Kapitel soll eine Library erstellt werden, mit der die Kommunikation von und zum PC ermöglicht wird. Die Library soll Funk­tio­nen 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 Baud­rate angegeben wird. Der Unterstrich vor dem Wert z. B. „_300“ ist nötig, da eine Kon­stan­te 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 Be­­triebs­art ü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 Er­ken­nung 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 ver­wendet. 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 Start­vor­gang 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 Ver­wal­tung 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 zu­rück­gesetzt 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 Zei­chen in das SFR SBUF geschrieben und  „ucSendOffset“ um eins er­höht. Sind schon alle Zeichen gesendet worden, wird „stSendRS232.ucLen“ auf „0“ ge­setzt (siehe ). So­mit kann vom Hauptprogramm erkannt werden, dass alle Zei­chen aus dem Array aus­ge­sen­det worden sind.

F !! Am Ende muss noch das TI-Flag gelöscht werden, da es nicht per Hardware zu­rück­gesetzt 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 ab­ge­legt (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 Em­pfä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 an­ge­wendet wer­den.

 

 

 

 

 

 

 

ƒ

 

 

 

 

 

 

 

 

 

#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

Automatische Reloadberechnung

Die im enum-Feld abgelegten Werte sind für die interne RC-Frequenz von 7,3728 MHz aus­gelegt. 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-An­wei­sung 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 ange­passt werden. Die CPU-Frequenz kann aus den Projekt Settings ausgelesen werden. Die unter Options for Target, Reiter Target im Eingabefeld XTAL ein­getragene Frequenz (siehe Abbildung 63) kann auch vom C-Compiler genutzt wer­den. Dieser Wert kann mit Hilfe der Makro Konstanten %%CPU_XTAL ausgelesen werden (siehe Listing 71, ).

F !! Der Auf­bau 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ön­nen.

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 die­sem Fall muss der Reloadwert in Ab­hängigkeit von DIVM ermittelt werden. Die For­mel für die Berechnung des Reload­wer­tes 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 Daten­typ „unsigned char“ reduziert werden (siehe Listing 72, ). Die Werte im Aufzählungs­typ „enUARTBaudrate“ sind so angelegt, das sich die Baudraten auf ein Vielfaches von 600 beziehen (siehe ). Mit dieser Vorgehensweise lässt sich die Be­rech­nung des Reload­wer­tes 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] aus­fü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 Emp­fän­ger maximal ± 5 % Abweichung haben darf (siehe EIA-232-D), sollte bei der Be­rech­­nung des Reload­wer­tes überprüft werden, ob die erzeugte Baudrate noch innerhalb die­ser Toleranz liegt. Die Überprüfung kann dadurch erfolgen, indem die Baudrate an­hand des Reloadwertes ermittelt wird. Unter dem folgenden Link finden sie noch weitere In­for­mationen zur RS232-Schnittstelle:

   http://www.camiresearch.com/Data_Com_Basics/RS232_standard.html

Arbeiten mit der Funktion printf() 

Die Funktion „printf()“ ist ein Bestandteil der C-Library. Mit dieser Funktion können Va­ria­ble und Konstanten formatiert und der dadurch entstandene Text ausgegeben werden. Die Aus­gabe erfolgt per default über die serielle Schnittstelle. Damit An­pas­sun­gen mit der Ausgabe vorgenommen werden können, ruft „printf()“ ihrerseits die Funk­tion „putchar()“ auf. Auch diese Funktion ist in der C-Library vorhanden. Die Funk­tion arbeitet mit der XON/XOFF- Flusssteuerung. Die Funktionsweise dieser Fluss­steue­rung ist in Kapitel 3, Abschnitt „XON/XOFF-Protokoll“ [2] ausführlich beschrie­ben.

 

 

 

 

 

 

 

 

 

 

#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 Unter­strich vor dem Funktionsnamen gibt an, dass der Funktion Parameter in Registern über­ge­ben 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 ent­spre­chen­den Programmteil und fügt ihn zum Programm hinzu. Da „_printf“ seinerseits noch „putchar“ aufruft, wird auch dieser Programmteil aus der Library geholt und zum Pro­jekt 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 Funk­­tion „printf()“ vornimmt. Die Funktion „putchar()“ soll die Zeichen ohne Än­de­rung und ohne XON-/XOFF-Steuerung aussenden (siehe Listing 74, ). In Tabelle 70 ist die Projekt­struk­tur ent­halten.

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 solan­ge gewartet, bis TI wieder von der Hardware gesetzt wird (siehe ). Das Programm be­findet sich solange in der Funktion „printf()“, bis das letzte Zeichen ausgesendet wur­de. 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 Übertragungs­zeit bei 898.56 ms. Um hier eine Unabhängigkeit zwischen der Ausgabe mit Hilfe der Funk­tion „printf()“ und der Übertragungsgeschwindigkeit der seriellen Schnittstelle zu errei­chen, müssen die erzeugten Daten von „printf()“ in einem Ringbuffer zwischenge­spei­chert 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, Ab­schnitt „Zeitunkritische Ausgaben unter Verwendung des seriellen Inter­rupts“ [2] ausführlich beschrieben.

Arbeiten mit der Funktion scanf() 

Auch die Funktion „scanf()“ ist ein Bestandteil der C-Library. Im Gegensatz zur Funk­tion „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 For­matierung bei „printf()“. In Listing 75  (siehe ) wird ein Byte eingelesen und der Va­ria­blen „cA“ zu­ge­­wiesen. Beim Aufruf der Funktion „scanf()“ wird jetzt solange die Funktion „_getkey()“ (siehe Listing 76, ) aufgerufen, bis alle benötigten Daten empfangen wur­den. 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 Varia­blen „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