Diesmal habe ich mir eine ganz besondere Aufgabe gestellt.
Schaffe ich, alle Phasen der Software-Entwicklung von der Spezifikation über Design, Kodierung einschließlich Test mit einer IDE zu realisieren?
Zusätzlich erlaubt sind Plugins, sowie kleine Extra-Tools die sich mit ein paar Handgriffen bedienen lassen. Strikt verboten sind Textprogramme wie MS-Word und UML-Mal-Tools. Okay, Schmierzettel und Kugelschreiber zählen nicht.
Also, los gehts.
Vor mir liegt eine kleine Ampelanlage. Sie besteht aus zwei Ampeln realisiert mittels 6 LEDs. Ein Taster dient als Hauptschalter. Die Ampelanlage beinhaltet einen Arduino UNO – einen Einplatinenrechner. Der Micro ist ein ATmega328/P. In der unteren Darstellung ist die Mini-Anlage zu sehen. NR steht für Nebenrichtung und HR für Hauptrichtung. Die Schaltung benötigt eine Steuerung – die Ampelsteuerung. Sie wird in C++11 realisiert.
Die funktionalen Anforderungen werden ich in Gherkin-Syntax beschrieben. Dafür habe ich vorab das Plugin specflowC installiert. Mit dieser Beschreibungssprache beschreibt man ein oder mehre Features mittelst textueller und spezifischer UseCases. Spezifisch, da konkrete Eingabewerte, Handlungen und Ergebnisse / Ausgabewerte anzugeben sind. Später werden daraus Tests wie UnitTests teilautomatisiert durch einen Parser generiert.
Anforderungen in Gherkin-Syntax
Given: Ein Scenario beginnt mit einem Anfangszustand. When: Eine bestimmte Handlung oder ein bestimmtes Ereignis tritt ein. And : Zusätzliche Handlungen oder Ereignisse. Then: Draus folgt ein Ergebnis. And : Zusätzliche Ergebnisse.
Die Daten sind in einer Tabelle aufgelistet.
Software-Design im Quellcode mittels Doxygen und Grapgviz
Und nun zum Software-Design, das recht simpel ist. Der Startpunkt ist die Main-Datei. Die Klasse Ampelanlage abstrahiert die Hardware für die Fachklasse Ampelsteuerung.
Die Klassen werden in der Header-Dateien entworfen. Schnittstellenfunktionen deklariert. Der Quellcode wird in der Doxygen– /Grapgviz-Notation dokumentiert. Daraus generiert das Tool Doxywizard die Design-Dokumentation.
Die Ampelanlagen-zustände werden mittels einer Enum in der Klasse Ampelsteuerung realisiert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/*! \class Ampelsteuerung \brief Die Ampelsteuerung steuert die Betriebszustaende und Ampelphasen ueber ein Zustandsautomat. */ class Ampelsteuerung final { private: /*! \enum Ampelstatus \brief Enum Ampelstatus beinhaltet die Betriebszustaende und Ampelphasen \see run() */ enum class Ampelstatus : uint8_t{ AUS, /*!< Betriebszustand AUS: Die Hautrichtung ist aus und die Nebenrichtung ist gelb-blinkend */ NOT_Betrieb, /*!< Betriebszustand Notaus: Die Hauptrichtung (HS) ist aus und die Nebenrichtung (NS) ist gelb-blinkend */ AN_Phase_1, /*!< Betriebszustand AN Phase 1: HS und NS sind rot */ AN_Phase_2, /*!< Betrienszustand AN Phase 2: HS rot-gelb, NR ist rot */ AN_Phase_3, /*!< Betrienszustand AN Phase 3: HS ist gruen, NR ist rot */ AN_Phase_4, /*!< Betrienszustand AN Phase 4: HS ist gelb, NR ist rot */ AN_Phase_5, /*!< Betrienszustand AN Phase 5: HS und NS sind rot */ AN_Phase_6, /*!< Betrienszustand AN Phase 6: HS ist rot, NS ist rot-gelb */ AN_Phase_7, /*!< Betriebszustand AN Phase 7: HS ist rot, NS ist gruen */ AN_Phase_8 /*!< Betriebszustand AN Phase 8: HS ist rot, NR ist gelb */ }; |
Doxywizard generiert daraus folgende Darstellung.
In der Design-Dokumentation soll der Zustandsautomat grafisch dargestellt werden, deshalb füge ich eine zusätzliche Beschreibung ein.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/*! \fn void run() \brief Zustandsmaschine der Ampelsteuerung \see enum class Ampelstatus \dot digraph g { subgraph cluster0 { style=filled; color=lightgrey; node [style=filled,color=white]; AUS } subgraph cluster1 { style=filled; color=lightgrey; node [style=filled,color=white]; AN_Phase_1 -> AN_Phase_2; AN_Phase_2 -> AN_Phase_3; AN_Phase_3 -> AN_Phase_4; AN_Phase_4 -> AN_Phase_5; AN_Phase_5 -> AN_Phase_6; AN_Phase_6 -> AN_Phase_7; AN_Phase_7 -> AN_Phase_8; AN_Phase_8 -> AN_Phase_1; label="ZEIT==FERTIG" } subgraph cluster2 { style=filled; color=lightgrey; node [style=filled,color=white]; NOT_Betrieb } AN_Phase_8 -> NOT_Betrieb [ltail=cluster2, lhead=cluster3] [label=" Notaus==BETAETIGT"] [style=filled,color=red]; NOT_Betrieb -> AUS [label=" Hauptschalter==AUS"] [style=filled,color=orange]; AUS -> AN_Phase_1 [label=" Hauptschalter==AN"] [style=filled,color=green]; AN_Phase_1 -> AUS [label=" Hauptschalter==AUS"] [style=filled,color=orange]; } \enddot */ void run(); |
Doxywizard generiert daraus folgendes.
Klassendiagramme – welche die Beziehungen zischen den Klassen darstellen – generiert Doxywizard automatisch. Eine zusätzlich Beschreibung ist hier nicht von Nöten.
So, das Design ist geschafft. Nun steht die Realisierung der UseCases in C++11-Code an. Ich fange mit der Main an. Zuerst wird die Ampelsteuerung initialisiert. Die Klasse Ampelanlage konfiguriert die entsprechenden Register; die 6 LEDs auf I/O-Ausgabe-Ports legen, den Hauptschalter (Taster) auf einen I/O-Eingabe-Port legen und den Interrupt konfigurieren.
Datei main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include "Betriebsmodus.h" #include <avr/io.h> #include "Ampelsteuerung.h" #include <avr/interrupt.h> //! Variable der Klasse Ampelsteuerung Ampelsteuerung ampel_steuerung{}; /*! \fn int main(void) \brief Legt eine Variable der Ampelsteuerung an. Ruft einmalig setup() und zyklisch run() auf. */ int main(void) { ampel_steuerung.setup(); while (1) { ampel_steuerung.run(); } return 0; } /*! \fn ISR(INT0_vect) \brief Service-Interrupt-Routine wird aufgerufen wenn Hauptschalter tastet. */ ISR(INT0_vect) { ampel_steuerung.getAmpelanlage().set_Hauptschalter_tastet(); } |
Klasse Ampelanlage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include "Ampelanlage.h" void Ampelanlage::setup() { /*! Initialisierung der Ausgaenge: Ampel_HS: gruen, Ampel_HS: gelb, Ampel_HS: rot, Ampel_NS: rot, Ampel_NS: gruen, Ampel_NS: gelb */ DDRB |= (1<<DDB0); DDRD |= ( (1<<DDD3) | (1<<DDD4) | (1<<DDD5) | (1<<DDD6) | (1<<DDD7) ); /*! Initialisierung der Eingaenge: Hauptschalter Eingang */ DDRD &= ~(1 << DDD2); /*! Interrupts: Hauptschalter Eingang */ EICRA |= ((1 << ISC00) | (1<<ISC01)); /*!< Setze INT0 auf Triggerung mittels steigender Flanke */ EIMSK |= (1 << INT0); /*!< Einschalten mittels INT0 */ sei(); /*!< Interrupts einschalten */ } |
Im run-Betrieb werden die entsprechen Ausgänge gesetzt, das Blinken der gelben Nebeneinrichtungs-LED generiert und der Interrupt verarbeitet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
bool Ampelanlage::ist_Haupstschalter_tastet() { bool result{false}; //uint8_t sreg_tmp; //sreg_tmp = SREG; /* Sichern */ cli(); result = hauptschalter; hauptschalter = false; //SREG = sreg_tmp; /* Wiederherstellen */ sei(); return result; } void Ampelanlage::Ampel_HS_Rot_An() { PORTD |= (1<<PD4); } void Ampelanlage::Ampel_HS_Rot_Aus() { PORTD &= ~(1<<PD4); } void Ampelanlage::Ampel_HS_Gelb_An() { PORTD |= (1<<PD3); } |
Der Ampelsteuerung-Ablauf ist mittels switch-case realisiert. Die Logik ist gut zu lesen und zu verstehen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
void Ampelsteuerung::run() { switch( ampel_status ) { case Ampelstatus::AUS: { ampel_anlage.Ampel_HS_Rot_Aus(); ampel_anlage.Ampel_NS_Rot_Aus(); ampel_anlage.Ampel_NS_Gelb_blinken(); if( ampel_anlage.ist_Haupstschalter_tastet() ) ampel_status = Ampelstatus::AN_Phase_1; break; } case Ampelstatus::AN_Phase_1: { ampel_anlage.Ampel_NS_Gelb_Aus(); ampel_anlage.Ampel_HS_Rot_An(); ampel_anlage.Ampel_NS_Rot_An(); _delay_ms(2000); if( ampel_anlage.ist_Haupstschalter_tastet() ) ampel_status = Ampelstatus::AUS; else ampel_status = Ampelstatus::AN_Phase_2; break; } case Ampelstatus::AN_Phase_2: { ampel_anlage.Ampel_HS_Gelb_An(); _delay_ms(1000); ampel_status = Ampelstatus::AN_Phase_3; break; |
Fazit
Die Software-Spezifikation konnte mittels specflowC in der IDE erstellt werden. Das Design erfolgte direkt in den Header-Datein in der Doxygen- und Graphviz-Notation. Ein aktuelles Design-Dokument mittels Doxywizard war schnell zu generieren. Die Funktionalität ist in C++11 realisiert, natürlich in abgespeckter Form, bspw. können keine Objekte zur Laufzeit generiert werden.
Offen
Als nächstes steht der UnitTest an … oh, je testen.