Die UART-Klasse

Die UART (Universal Asynchronous Receiver Transmitter) oder besser gesagt USART (Universal Synchronous/Asynchronous Receiver Transmitter) ist für eingebettete Systeme eine der wichtigsten Kommunikationsschnittstellen. Technisch gesehen verfügen AVR-Mikrocontroller über eine USART, aber in den meisten Fällen wird diese als UART, also asynchron betrieben. Zwischen Controllern erfolgt die Verbindung oft direkt und über größere Entfernungen mit entsprechenden Treiberbausteinen (vgl. RS232/RS485). Die grundlegenden Funktionen der UART sind im myAVR C++ Framework in der gleichnamigen Klasse Uart zusammengefasst.

Wichtig ist noch zu erwähnen, dass die Klasse Uart auch ein Ereignis onEvent auslöst, wenn ein Datenbyte empfangen wurde. Dabei ist die Uart-Instanz der Sender des Ereignisses und als Daten wird das empfangene Byte übergeben.

Die UART nutzen

Bei dem hier verwendeten Experimentierboard ist die serielle Schnittstelle über den mySmartUSB MK2 bereits mit dem PC verbunden. Da heute der gute alte COM-Port an PCs und vor allem an Notebooks leider kaum noch anzutreffen ist, wird als Treiberbaustein eine USB-UART-Bridge verwendet. Wir schließen das Bord dementsprechend über USB an. Der Mikrocontroller merkt gar nicht, dass er per USB angeschlossen wurde und auf PC-Seite wird im System ein virtueller COM-Port eingerichtet. Somit ist das recht aufwendige USB-Protokoll vor dem Anwender verborgen und es werden ganz simpel einzelne Datenbytes hin und her geworfen - wie in den guten alten Zeiten.

Auf PC-Seite benötigt man nur noch ein kleines Programm, um Datenbytes vom COM-Port abzuholen. Dafür gibt es zahlreiche Lösungen. In manchen Betriebssystemen wird solch ein Terminal-Programm sogar noch mitgeliefert. In der hier verwendeten Entwicklungsumgebung SiSy steht das myAVR ControlCenter dafür zur Verfügung. Entscheidend für eine erfolgreiche Kommunikation ist, dass beide Kommunikationspartner mit den gleichen Einstellungen arbeiten. Zum Beispiel mit 9600 Baud Geschwindigkeit, 8 Daten-Bits, 1 Stopp-Bit und ohne Paritätsprüfung (9600,n,8,1). Die folgende Darstellung zeigt genau diese Einstellungen im ControlCenter.

Wir wollen diesen Einstellungen hier im Tutorial als unseren Kommunikationsstandard betrachten. Beachten Sie, dass der konkrete COM-Port an jedem System unterschiedlich zugeordnet sein kann. Oft wird COM3 oder COM4 bei der Erstinstallation als virtueller COM-Port zugewiesen. Sie können die Zuordnung des konkreten COM-Ports über den Gerätemanager überprüfen und auch ändern. Des Weiteren wurden im myAVR ControlCenter spezielle Optionen für den mySmartUSB MK2 aktiviert. Dadurch wird dieser beim Testen der Anwendung automatisch vom Programmer-Modus in den Daten-Modus und zurück umgeschaltet.

Beachte: Handhabung des mySmartUSB MK2

Wir wollen uns an die UART langsam herantasten. Zunächst gilt es, eine serielle Verbindung zum PC aufzubauen und ganz simple Daten an diesen zu senden. Die Geschwindigkeit mit der die Daten gesendet werden soll 9600 Baud betragen. Bei AVR Controllern sind die üblichen Einstellungen von 8 Daten-Bits, keine Paritätsprüfung und 1 Stoppbit Werksstandard und für die USART nach dem RESET voreingestellt. Es ist der folgende Plan zu realisieren:

////////////////////////////////////////////////////////
// ENTWURF Beispiel Uart1
////////////////////////////////////////////////////////
class Application : public Controller
{
    // eine 8-Bit Variable für den zu senden Wert anlegen
    // eine Instanz der UART-Klasse anlegen
 
    public: void onStart()
    {
       // Variable und UART initialisieren
    }
 
    public: void onWork()
    {
	// den Wert per Uart senden
	// etwas warten damit die Daten nicht zu fix kommen
    }
 
} app;

Mag sein, dass es etwas übertrieben ist ein Klassenattribut anzulegen, um einen konstanten Wert zu senden. Aber das Beispiel soll noch etwas ausgebaut werden. Da wir jeweils ein Byte (8 Bit) senden, reicht uns eine entsprechende Variable. Die Instanz der Klasse Uart repräsentiert für uns die Gegenstelle, an welche wir Daten senden. Der Instanz-Name sollte also nicht immer einfach nur uart lauten sondern durchaus zum Beispiel controlCenter, konsole, terminal oder auch leitstand. Wichtig bei der Wahl des Instanz-Namen ist der geplante Einsatzfall der Anwendung und der darin vorkommenden konkreten Bausteine. Die reale Zielumgebung des Systems sollen ausschlaggebend für die Namen in der Anwendung sein. Das erleichtert Verständnis und vor allem späteren Wartungsaufwand in der Software. Wir wählen den Begriff terminal für unsere Uart-Instanz und initialisieren diese mit 9600 Baud. Zusätzlich sorgen wir mit der Funktion waitMs dafür, dass die Handbremse etwas angezogen wird. Damit nachher nicht alles viel zu schnell geht.

////////////////////////////////////////////////////////
// Beispiel Uart1
////////////////////////////////////////////////////////
class Application : public Controller
{
    protected: uint8 wert;   // eine 8-Bit Variable anlegen
    protected: Uart terminal; // eine Instanz der UART-Klasse anlegen
 
    public: void onStart()
    {
        // Variable und UART initialisieren
     	wert=65;
     	terminal.config(9600);
    }
 
    public: void onWork()
    {
	terminal.sendByte(wert);  // den Wert per Uart senden
	waitMs(100);             // etwas warten damit die Daten nicht zu fix kommen
    }
} app;

Bilden, übertragen und testen Sie das Programm. Mit der Schaltfläche Ausführen startet SiSy das myAVR ControlCenter. Überprüfen Sie die Einstellungen für den COM Port und aktivieren Sie die serielle Verbindung.

Mit der Schaltfläche Start wird der COM-Port geöffnet und es beginnt die Kommunikation.

Sie erhalten im Terminalprogramm das empfangene Zeichen als Buchstabe „A“ dargestellt. Wenn Sie die Initialisierung der Variable wert mit der Zahl 65 und der Kodierung von druckbaren Zeichen in der ASCII-Tabelle anschauen, sollte klar werden warum im Controlcenter ein A erscheint. Sie können sich die empfangenen Werte auch als Zahl anschauen, indem Sie das Darstellungsformat von Text auf Zahl umschalten.

Das Programm lässt sich leicht erweitern, um etwas weniger langweilig ein A nach dem anderen zu senden. Ergänzen Sie in der Operation onWork das Hochzählen des zu sendenden Wertes.

class Application : public Controller
{
    protected: uint8 wert;   // eine 8-Bit Variable anlegen
    protected: Uart terminal; // eine Instanz der UART-Klasse anlegen
 
    public: void onStart()
    {
        // Variable und UART initialisieren
     	wert=65;
     	terminal.config(9600);
    }
 
    public: void onWork()
    {
	terminal.sendByte(wert); // den Wert per Uart senden
	waitMs(100);             // etwas warten damit die Daten nicht zu fix kommen
    	wert++;                  // den Wert hochzählen
    }
 
} app;

Bilden, übertragen und testen Sie das Programm. Siehe da - der Mikrocontroller kennt das Alphabet!

Jetzt macht es auch Sinn sich das Ganze mal grafisch anzuschauen, indem man das Ausgabeformat Oszi im Controlcenter wählt.

Es ist Zeit sich an etwas Größeres heran zu trauen. Für eingebettete Systeme ist es nicht nur interessant Informationen zum Beispiel an einen Leitstand zur Visualisierung und weiteren Verarbeitung zu senden, sondern von diesem auch Kommandos zu erhalten, um vorprogrammierte Aufgaben zu erfüllen. Wir bauen eine Fernsteuerung! Die Kommandos werden per UART gesendet und können, je nach Treiberbaustein, zum Beispiel mit dem MAX485 bis zu 1,2 Kilometer weit übertragen werden. Denkbar ist auch, dass eine Übertragung der Fernbedienungskommandos per Funk erfolgen kann.

Zunächst wieder erst der Plan:

////////////////////////////////////////////////////////////////
// Entwurf Beispiel Uart-Kommando-Interpreter
////////////////////////////////////////////////////////////////
class Application : public Controller
{
    // ein Attribut für das Kommando anlegen
    // eine Instanz der Uart anlegen
    // Instanzen für die Aktoren anlegen
 
    public: void onStart()
    {
       // Aktoren initialisieren
    }
 
    public: void onWork()
    {
        // WENN ein Kommando da ist
        // DANN hole das Kommando vom Terminal
        // BEI Kommando: 
        //   'R' rote LED an
        //   'r' rote LED aus
        //   'Y' gelbe LED an
        //   'y' gelbe LED aus
        //   'G' grüne LED an
        //   'g' grüne LED aus
        //   'A' alle LEDs an
        //   'a' alle LEDs aus
        // SONST 
        // mache nichts und sende ein Fragezeichen zurück
    }
} app;

Als Aktoren benutzen wir die LEDs und schließen diese an Port B Bit 0 bis 3 an.

Jetzt kann die Realisierung beginnen.

////////////////////////////////////////////////////////////////
// Beispiel Uart-Kommando-Interpreter
////////////////////////////////////////////////////////////////
class Application : public Controller
{
    protected: char kommando;
    protected: Led ledRot,ledGelb,ledGruen;
    protected: Uart terminal;
 
    public: void onStart()
    {
        kommando=0;
        ledRot.config(portB,bit0);
        ledGelb.config(portB,bit1);
        ledGruen.config(portB,bit2);;
     	terminal.config(9600);
    }
 
    public: void onWork()
    {
        if (terminal.hasData())
        {
          kommando=terminal.getByte();
          switch (kommando)
          {
              case 'R': ledRot.on();    break;
              case 'r': ledRot.off();   break;
              case 'Y': ledGelb.on();   break;
              case 'y': ledGelb.off();  break;
              case 'G': ledGruen.on();  break;
              case 'g': ledGruen.off(); break;
              case 'A': ledRot.on();
                        ledGelb.on();
                        ledGruen.on();  break;
              case 'a': ledRot.off();
                        ledGelb.off();
                        ledGruen.off(); break;
              default : kommando = '?';  break;
          }
          terminal.sendByte(kommando);
        }
    }
} app;

Verbinden Sie die LEDs mit den entsprechenden Ports. Bilden, übertragen und testen Sie die Anwendung. Sie können jetzt mit den Kommandos die LEDs vom PC aus kontrollieren.

Soll die Steuerung, während Sie auf die Kommandos wartet, weitere Aufgaben übernehmen wie zum Beispiel Messwerte erfassen und eine entsprechende Regelung realisieren, dann kann es günstig sein, den Kommandointerpreter in der Ereignisbehandlung onEvent zu positionieren. Das kann dann wie folgt aussehen:

//////////////////////////////////////////////////////////
// Beispiel Kommandointerpreter 2
//////////////////////////////////////////////////////////
class Application : public Controller
{
    protected: char zeichen;
    protected: Led ledRot,ledGelb,ledGruen;
    protected: Uart terminal;
 
    public: void onStart()
    {
     	zeichen=0;
        ledRot.config(portB,bit0);
        ledGelb.config(portB,bit1);
        ledGruen.config(portB,bit2);;
     	terminal.config(9600);
    }
 
    public: void onWork()
    {
        // bleibt leer bzw. kann für andere Aufgaben genutzt werden
    }
 
    public: void onEvent(const Object& sender, uint8 data)
    {
        if (sender == terminal)
        {
          zeichen=data;
          switch (zeichen)
          {
              case 'R': ledRot.on();    break;
              case 'r': ledRot.off();   break;
              case 'Y': ledGelb.on();   break;
              case 'y': ledGelb.off();  break;
              case 'G': ledGruen.on();  break;
              case 'g': ledGruen.off(); break;
              case 'A': ledRot.on();
                        ledGelb.on();
                        ledGruen.on();  break;
              case 'a': ledRot.off();
                        ledGelb.off();
                        ledGruen.off(); break;
              default : zeichen = '?';  break;
          }
          terminal.sendByte(zeichen);
          terminal.sendByte(10);
        }
     }
 
} app;

Ändern, bilden, übertragen und testen Sie das Programm.

Videozusammenfassung

Nächstes Thema