Grafische Programmierung mit der UML

Mit objektorientierten Programmiersprachen hat der Entwickler mächtige Sprachmittel, um komplexe Systeme realisieren zu können. C++ ist eine weit verbreitete objektorientierte Standardsprache. Als Visualisierungsmittel objektorientierter Programme gilt die international standardisierte Beschreibungssprache UML (Unified Modeling Language). SiSy AVR bietet dem Entwickler das UML Klassendiagramm mit Codegenerierung für AVR C++. Der folgende Abschnitt beschreibt die Grundelemente des Klassendiagramms in SiSy AVR.

Vielleicht haben Sie ja dieses Tutorial schon mal abgearbeitet ohne sich all zu sehr bei der trockenen Theorie aufzuhalten. Spätestens jetzt sollten Sie sich vielleicht doch den einen oder anderen Sachverhalt aus dem Grundlagenabschnitt vergegenwärtigen.

Die folgende Abbildung zeigt Ihnen eine Kurzübersicht der Modellierungselemente des UML Klassendiagramms.

Darstellung von Attributen:
Attribute beginnen mit einem Kleinbuchstaben.

Sichtbarkeit name : Typ = Initialwert {Merkmal}
# temperatur : uint8_t = 25

Schreibweise von Operationen:
Operationen beginnen mit einem Kleinbuchstaben.

Sichtbarkeit name (Parameter:Typ = Standardwert, ...) : Rückgabetyp {Merkmal}
+ setTemperatur ( temp : integer = 25 ) : bool

Eine Klasse ist in der Programmierung zuerst einmal der Name und die unter diesem Namen subsumierte Beschreibung der Attribute und Operationen eines bestimmten Typus von Systembausteinen. Die UML fordert, eine Klasse als Rechteck darzustellen. Der Name der Klasse soll mit einem Großbuchstaben beginnen.

class Controller
{
   Controller();  // Konstruktor
   ~Controller(); // Destruktor
};

Jeder Controller kann eingeschaltet werden und soll dann arbeiten. Das sind Operationen, die der Controller ausführen soll. Diese werden in der UML der Klasse zugeordnet. Operationen erscheinen als Liste im Klassenrahmen.

class Controller
{
   Controller();    // Konstruktor
   ~Controller();   // Destruktor
   void powerOn();  // Initialisierungssequenz
   void run();      // Opertion mit der MainLoop
};

Objekte sind in der Programmierung Instanzen von Klassen. In der UML werden Objekte ebenfalls als Rechteck dargestellt. Die Kennzeichnung als Instanz erfolgt durch Unterstreichen des Namens. Zusätzlich kann der Typ der Instanz angezeigt werden. Die Instanzbeziehung zwischen einem Objekt und seiner Klasse wird als Abhängigkeit (gestrichelte Linie mit offenem Pfeil) und dem Stereotyp «instanceOf» dargestellt.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Controller verfügt über die Operationen powerOn und run.
  • Es gibt ein globales Objekt mit dem Namen system vom Typ Controller.
  • Das Objekt system ist eine Instanz der Klasse Controller.
class Controller
{
   Controller();    // Konstruktor
   ~Controller();   // Destruktor
   void powerOn();  // Initialisierungssequenz
   void run();      // Opertion mit der MainLoop
};
// Instanz der Klasse Controller
Controller system;

Klassen können Eigenschaften von anderen Klassen erben. Die Verwendung von Klassenbibliotheken und der darin enthaltenen Klassen als Basisklassen der eigenen Anwendung beschleunigen die Entwicklungsarbeit enorm. Bei der Vererbung kann man auch je nach Lesart von einer Generalisierung (von unten nach oben gelesen) oder einer Spezialisierung (von oben nach unten gelesen) sprechen. Eine Generalisierung wird in der UML als Voll-Linie mit einem großen nicht ausgemalten Pfeil zur Basisklasse dargestellt. Die Eselsbrücke für die korrekte Richtung des Pfeils lautet „ist ein“.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Controller verfügt über die Operationen powerOn und run.
  • Der Controller „ist ein“ ATmega328
  • Es wird zugesichert, dass die Basisklasse ATmega328 aus dem Paket AVR::MCU stammt.
  • Es gibt ein globales Objekt mit dem Namen system vom Typ Controller.
  • Das Objekt system ist eine Instanz der Klasse Controller.
#include "avr/mcu.h"
class Controller : public ATmega328
{
   Controller();    // Konstruktor
   ~Controller();   // Destruktor
   void powerOn();  // Initialisierungssequenz
   void run();      // Opertion mit der MainLoop
};
// Instanz der Klasse Controller
Controller system;

Die UML kennt ein zweites Ausdrucksmittel für den Sachverhalt „ist ein“. Es gibt zahlreiche Anwendungsfälle, bei denen Vorlagen, Muster verwendet oder Vorschriften eingehalten werden sollen. So etwas könnten Struktur- oder Verhaltensmuster, aber auch Schnittstellendefinitionen sein. Da es sich hierbei nicht um eine Vererbung im eigentlichen Sinne handelt, wird zwar der selbe Pfeiltyp verwendet, aber die Linie wird gestrichelt dargestellt. In der UML spricht man von einer Realisierung.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Controller verfügt über die Operationen powerOn und run.
  • Der Controller ist ein ATmega328 und realisiert ein AppKernel
  • Es wird zugesichert, dass die Basisklasse ATmega328 aus dem Paket AVR::MCU stammt.
  • AppKernel ist ein Template (Muster) aus dem Paket myAVR_App
  • Es gibt ein globales Objekt mit dem Namen system vom Typ Controller.
  • Das Objekt system ist eine Instanz der Klasse Controller.
#include "avr/mcu.h"
class Controller : public ATmega328
// : implements AppKernel
{
   Controller();     // Konstruktor
   ~Controller();    // Destruktor
   void powerOn();   // Initialisierungssequenz
   void run();       // Opertion mit der MainLoop
 
   // generiert vom Template AppKernel
   virtual void onSysTick();
};
// Instanz der Klasse Controller
Controller system;

Objektorientierte Programmiersprachen kennen verschiedene Konzepte, um die Stabilität von Anwendungen sicherzustellen. Eines der Konzepte ist die Kapselung. Dabei ist es möglich, Elementen, z. B. Attributen und Operationen von Klassen, sogenannte Sichtbarkeiten zuzuordnen. Damit kann verhindert werden, dass die so geschützten Elemente unberechtigt benutzt werden. Die meisten Programmiersprachen unterstützen dies durch entsprechende Schlüsselworte wie public, protected und privat. Die UML bietet Symbole, welche zwischen den Sichtbarkeiten + public, ~ package, # protected und - privat unterscheiden. Die Sichtbarkeit wird bei Operationen und Attributen dem Namen vorangestellt.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Controller verfügt über die Operationen powerOn und run.
  • Die Operation powerOn ist öffentlich und kann von außen aufgerufen werden.
  • Die Operation run ist geschützt und kann nur aus der Klasse heraus aufgerufen werden.
  • Der Controller ist ein ATmega328 und realisiert ein AppKernel
  • AppKernel ist ein Template (Muster) aus dem Paket myAVR_App
  • Es wird zugesichert, dass die Basisklasse ATmega328 aus dem Paket AVR::MCU stammt.
  • Es gibt ein globales Objekt mit dem Namen system vom Typ Controller.
  • Das Objekt system ist eine Instanz der Klasse Controller.

Zusätzlich sind in dieser Darstellung die Rückgabetypen der Operationen auf void festgelegt worden. Dieses UML-Klassendiagramm kann jetzt in Quellcode überführt werden. Dieser kann, hier als vereinfachter Ausschnitt, so aussehen:

// SiSy UML C++ Codegenerator ////////////////////////////////////////////////
#include "avr/mcu.h"
class Controller : public ATmega328
// implements AppKernel
{
   public: Controller();   // automatisch generierter Konstruktor
   public: ~Controller();  // automatisch generierter Destruktor
   public: void powerOn();
   protected: void run();
   // aus dem Template AppKernel 
   protected: virtual void onSysTick();
};
//////////////////////////////////////////////////////////////////////////////
// Instanz der Klasse Controller
Controller system;
//////////////////////////////////////////////////////////////////////////////

Der Stand bis hier entspricht in etwa dem Grundgerüst einer AVR-UML-Application in SiSy. Das Werkzeug kümmert sich dabei um solche Details wie includes, defines, etc. Der Entwickler arbeitet im Klassendiagramm. Den generierten Quellcode muss er sich eigentlich nicht mehr als Ganzes anschauen. Das UML-Projekt ist sein „Source“, die generierten Quellcodedateien nur noch temporäre Zwischenprodukte beim Bilden. Übrigens stellt Ihnen das Werkzeug SiSy derartige Grundgerüste an den entsprechenden Punkten der Projekterarbeitung jeweils zur Auswahl. Die muss der Entwickler sich nicht jedes mal neu aufbauen.

Systeme bestehen aus Komponenten, die Komponenten aus Bausteinen, diese wiederum aus Einzelteilen usw. Diese Ganz-Teil-Struktur lässt sich in der UML als Aggregation bzw. Komposition abbilden. Dabei wird durch den oben angesprochenen Codegenerator solch eine Aggregation als Attribut im Code abgebildet.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Controller verfügt über die Operationen powerOn und run.
  • Die Operation powerOn ist öffentlich und kann von außen aufgerufen werden.
  • Die Operation run ist geschützt und kann nur aus der Klasse heraus aufgerufen werden.
  • Der Controller ist ein ATmega328 und realisiert ein AppKernel
  • AppKernel ist ein Template (Muster) aus dem Paket myAVR_App
  • Es wird zugesichert, dass die Basisklasse ATmega328 aus dem Paket AVR::MCU stammt.
  • Es gibt ein globales Objekt mit dem Namen system vom Typ Controller.
  • Das Objekt system ist eine Instanz der Klasse Controller.
  • Die Klasse Controller besitzt einen digitalen Ausgang.
    • Die Klasse DigitalOut wird unter dem Namen led in der Klasse Controller als öffentliches Attribut aggregiert.
    • es wird zugesichert, dass die Klasse DigitalOut aus dem Paket AVR::DigitalInOut stammt
// SiSy UML C++ Codegenerator ////////////////////////////////////////////////
#include "avr/mcu.h"
#include "avr/digitalInOut.h"
 
class Controller : public ATmega328
// implements AppKernel
{
   // Attribute der Klasse:
   public: DigitalOut led; // Aggregation des Bausteins 
 
   // Operationen der Klasse:
   public: Controller();   // automatisch generierter Konstruktor
   public: ~Controller();  // automatisch generierter Destruktor
   public: void powerOn();
   protected: void run();
 
   // aus dem Template AppKernel 
   protected: virtual void onSysTick();
};
 
//////////////////////////////////////////////////////////////////////////////
// Instanz der Klasse Controller
Controller system;
//////////////////////////////////////////////////////////////////////////////

Die Aggregation entspricht also einem Attribut der Klasse. Somit ist die folgende UML Darstellung letztlich genau dasselbe. Die Attributdarstellung spart Platz im Klassenmodell, ist aber weniger übersichtlich was die Systemarchitektur betrifft.

Wer jetzt schon darauf brennt es mit der UML zu versuchen kann das gerne tun. Sie können diese Übung aber auch überspringen. Die hier beschriebenen Lösungen werden im Tutorial noch ausführlicher behandelt. Für diesen Abschnitt benötigen Sie dann auch schon mal die entsprechende Hardware und Enwicklungsumgebung. Es sind zunächst folgende Arbeitsschritte auszuführen:

  1. Projekt anlegen
  2. ggf. eine Bibliothek aus dem LibStore laden (auch wenn wir diese vorerst nicht benutzen)
  3. ein Klassendiagramm mit einem einfachen Grundgerüst aus dem LibStore anlegen
  4. Die Firmware aus dem Klassendiagramm erstellen und auf den Controller übertragen

Das erste Beispiel ist wie üblich ein Blinky. Wir wollen auf dem myAVR MK 2 Board die rote LED an Port B Bit 0 anschließen und blinken lassen.

Da wir noch nicht mit der Bibliothek arbeiten wollen wir uns die Mühe machen eine eigene Klasse für die LED zu erstellen. Dazu müssen wir folgendes tun:

  1. eine Klasse Led anlegen (Schreibweise beachten!)
  2. in die Klasse die folgenden Operationen einfügen
    • public: void init();
    • public: void on();
    • public: void off();

Die entsprechenden Elemente müssen aus der Objektbibliothek gezogen und im Klassendiagramm zur folgenden Darstellung zusammengesetzt werden:

Diese muss mit der Applikationsklasse dem Controller verbunden werden. Dabei ziehen Sie die Verbindung vom Controller zur Klasse Led. Als Verbindungstyp wählen Sie die Aggregation. Der Name des entsprechenden Attributes (Rolle) wird automatisch vorgeschlagen.

Damit unser Blinky funktioniert müssen die Operationen mit den entsprechendem Befehlen ergänzt werden. Selektieren sie die entsprechenden Operationen und ergänzen die Quelltexte wie folgt:

void init()

ddrB.bit0=1;

void on()

portB.bit0=1;

void off()

portB.bit0=0;

void main()

  // Diese Operation als Start-Operation für main angeben
  // hier Initialisierungen durchführen
  led.init();
  // mainloop starten
  run();

void run()

  do {
     led.on();
     waitMs(200);
     led.off();
     waitMs(300);
  } while (true);

Wenn wir alles richtig eingegeben haben können wir aus dem Klassendiagramm den Quellcode generieren, diesen übersetzen und auf den Controller übertragen. Das erfolgt über das Aktionsmenü in der Objektbibliothek. Wählen sie dort den Menüpunkt Erstellen, Brennen & Ausführen.

Videozusammenfassung

Und hier diesen Abschnitt als kurze Videozusammenfassung.

Seminarhinweise

Nächstes Thema

Falls Sie noch nicht über die nötige Hard- und Software verfügen

dann weiter mit:

  • grafische_programmierung_mit_der_uml.txt
  • Zuletzt geändert: 2021/01/26 16:29
  • von huwi