1.Einleitung #

Der BroMille ist ein Shot-Mix-Gerät, das abhängig vom individuellen Alkoholspiegel jedes Nutzers den Alkoholgehalt im Getränk anpasst, um einen bestimmten Pegel zu halten  — ihn aber nicht zu überschreiten!

Die Entwicklung des Gerätes ist Teil der Lehrveranstaltung „Embedded Systems“ an der „Hochschule der Medien Stuttgart“ des Sommersemesters 2017.

Der BroMille soll lustige Abende oder gemeinschaftliche Trinkspiele unterstützen. In geselligen Runden, in denen Alkohol fließt, kommt es vor, dass nach einiger Zeit jeder einen anderen Alkoholspiegel aufweist. Der eine trinkt schneller und der andere mehr — einer trinkt zu viel, ein anderer ist noch fast nüchtern und kann die anderen nicht mehr ertragen.

Damit ist jetzt Schluss!

Denn der BroMille misst vor dem Ausschenken den Alkoholpegel jeder Person und mischt den Shot über ein Pumpsystem entsprechend stark.

Der Ablauf:

  1. Über einen Regler gibt man zu Beginn kollektiv den „Modus des Betrunken-werdens“ an (1.0‰, 1.5‰ oder 2.0‰).
  2. Man stellt in das Fach auf der Rückseite des Bromille ein alkoholisches und ein nicht-alkoholisches Getränk. Diese müssen mit den entsprechenden Schläuchen verbunden werden.
  3. Jedem Spieler wird ein Button (1-8) zugewiesen. Es können somit acht Personen gleichzeitig den Bromille bedienen.
  4. Jedes mal wenn ein Spieler einen Shot „zapfen“ möchte, muss er seinen Button drücken. Das System kann ihn somit identifizieren.
  5. Daraufhin pustet er in den blauen Schlauch. Bromille unterstützt ihn dabei akustisch und gibt Anweisungen.
  6. Unmittelbar nach dem Pusten wertet Bromille den aktuellen Atemalkoholgehalt aus und stellt ihn, in Abhängigkeit zum eingestellten Ziel-Gehalt, auf den acht roten LEDs über dem blauen Schlauch dar.
  7. Direkt im Anschluss wird das Getränk im richtigen Mischverhältnis ausgegeben. Es werden immer 2cl ausgegeben.

Als Herzstück und Recheneinheit dient ein Arduino mini pro. Die Programmierung des Prozessors findet in C statt. Arduino IDE Libraries wurden nicht verwendet.

2.Toolchain #

Eine AVR-Toolchain besteht aus avr-gcc, den avr-Binutils (Assembler, Linker, etc) und einer Standard-C-Bibliothek. Üblich ist die AVR-LibC, die in nahezu allen avr-gcc Distributionen enthalten ist. Die Hardware gehört nicht zur Toolchain und wird auch nicht zwingend benötigt, da man auch ohne Hardware C-Programme für AVRs schreiben und kompilieren lassen. Als grafische Oberfläche zum Entwickeln bieten IDEs/Editoren wie Atmel Studio, Eclipse oder Microsofts Visual Studio Code. Die meisten IDEs verfügen über optionale AVR/Arduino Plugins, die in vielerlei Hinsicht den Workflow erleichtern: Einmal alle Einstellungen festgelegt, kann man über einen Button den Compile- und Upload- bzw. Flash-Vorgang starten. Im Folgenden ist eine schrittweise Installation aller nötigen Tool auf für MacOS beschrieben.

homebrew installieren:


avr.lib installieren:

avr-binutils und avr-gcc werden mit installiert.


avrdude installieren:

Empfehlenswert ist die Installation von CrossPack für MacOS, um beispielsweise den avr-gdb zum Debuggen zu erhalten. Das Paket enthält ebenfalls die C Libraries für AVR, den AVRDUDE und weiter nützliche Tools.

C-Code -> Compilierung avr-gcc -> avrdude flash -> Bootloader arduino -> run app

Aus dem Quellcode (C-Code) erzeugt der avr-gcc-Compiler (zusammen mit Hilfsprogrammen wie z.B. Präprozessor, Assembler und Linker) Maschinencode für den AVR-Controller. Üblicherweise liegt dieser Code dann im Intel Hex-Format vor („Hex-Datei„). AVRDUDE liest diese Datei ein und überträgt die enthaltene Information im Maschinencode in den Speicher des Controllers. Im Prinzip muss also „nur“ der avr-gcc-Compiler mit den „richtigen“ Optionen aufgerufen werden, um aus C-Code eine Hex-Datei zu erzeugen.

Die ersten Schritte beim Aufbau des Embedded Projekts sind es, die für den Prozessor bzw. das Board spezifischen Eigenschaften herauszufinden. In unserem Fall sind dies:

  1. Board bzw. Chip (MCU): atmega328p
  2. Tacktung: 16MHz
  3. BauRate: 57600
  4. Port bestimmen: Win (COM Ports), OSX (Terminal: ls /dev/cu*)
  5. Bootloader: Standardmäßig Arduino Bootloader

Empfehlung: Zuerst mit der Ardunio IDE Beispielprojekte laden, um die Einstellungen zu überprüfen. Dort alle Parameter eingeben und mit einem „Blink“ Beispielprojekt flashen. Achtung: Der Reset Button auf dem Arduino Board muss unmittelbar vor dem Upload und nach dem Compile-Prozess gedrückt werden.

Im Kapitel 4.4 wird die Generierung von Maschinencode für einen AVR (Hex-Datei) aus C-Quellcode anhand von make und den Makefiles näher erläutert. Viele der darin beschriebenen Optionen findet man auch im Konfigurationsdialog des avr-gcc-Plugins von AVRStudio (AVRStudio generiert ein Makefile in einem Unterverzeichnis des Projektverzeichnisses) oder in der Konfiguration des AVR-Plugins von Eclipse.

3.Elektronik #

In diesem Kapitel werden die elektrotechnischen Arbeitsschritte des Projekts beschrieben. Rund um den Arduino als Basiseinheit werden einige technische Module verwendet, die eine tragende Rolle im Projekt spielen. Diese werden im Folgenden erläutert.

3.1.Schaltplan #

Der Schalplan ist die Ausgangslage für die Entwicklung des Projekts auf elektronischer Ebene. Alle relevanten Anschlüsse und Bauteile werden hier miteinander verbunden.

Hinweis: Der Schalplan wird als technisch korrekt und funktionsfähig angeführt, ist allerdings aus syntaktischer Sicht nicht unbedingt korrekt. Es werden lediglich Verbindungen zwischen den einzelnen Komponenten visualisiert. Erstellt wurde der Plan mit EAGLE.

Da die Studenten-Version von EAGLE die Größe der Platine beim automatischen Setzten der Komponenten beschränkt, haben wir für dieses Projekt die Anordnung manuell vorgenommen. Auf einer 1:1 Kopie der Europlatine (inkl. 2x D-Sub Anschlüsse) haben wir die Schaltungen eingezeichnet und die Anordnungen konzipiert. Alle Ausgänge für die LEDs, Buttons und Schalter sollten über die D-Sub Ausgänge an diese entsprechend weitergeleitet werden. Die Bilder zeigen diverse Konzeptionsstände und das folgende Video gibt einen perspektivischen Blick auf die fertig bestückte Platine.

3.2.Komponenten #

Elektronisch gesehen hat das Projekt acht Basismodule:

  • Die Steuerung der Pumpen über eine Relais-Schaltung

Die zwei Pumpen werden unabhängig voneinander über eine Relais-Schaltung gesteuert (siehe Schaltplan). Das ermöglicht es in sehr kurzer Geschwindigkeit die Getränkeausgabe durchzuführen und alkoholische und nicht-alkoholische Getränke vorab nicht zu vermischen. Die Aufgabe der Relais hätten auch von Transistoren übernommen werden können. Es war jedoch für den Entwicklungsprozess von entscheidender Bedeutung, ein akustisches Feedback bei einer Schaltung zu bekommen, um nicht bei jedem Test direkt die Pumpen trocken zu belasten.

  • Die Steuerung der Alkohol-Level-Ausgabe über 8 LEDs, gesteuert über ein Shiftregister

Um direkt nach dem Blasen ein unmittelbares visuelles Feedback liefern zu können, sind direkt über den Schlauch acht LEDs angebracht. Diese zeigen in Abhängigkeit des gewählten Modus den individuellen Alkoholpegel direkt nach dem Pust-Vorgang an. Die LEDs werden über ein Schieberegister gesteuert. Dies spart sechs Digital-Pins auf dem Controller-Board. Lediglich ein Clock- und ein Daten-Signal werden benötig.

Shift Register M54HC164 Datenblatt

  • Steuerung der Button-LEDs über ein Shiftregister und eine Transistorschaltung

Wie auch bei der Alkohol-Level-Ausgabe werden die Feedback-Leuchten der Taster über ein Schieberegister gesteuert. Der Vorteil bei dieser Schaltung ist, dass lediglich ein zusätzliches Clock-Signal vom Arduino benötigt wird. Somit können beide Schieberegister und alle LED-Ausgaben über lediglich drei Digital-Pins gesteuert werden. Bei der Steuerung der Buttons-LEDs kommt erschwerend hinzu, dass diese aufgrund der Bauart 12 Volt benötigen. Daher ist direkt an die Shiftregister-Ausgabe eine Transistorschaltung angebunden, die den 12 Volt-Stromkreis entsprechend schaltet.

  • Acht Buttons zur Personenidentifikation

Die acht Buttons dienen dazu, immer die gleiche Person zu identifizieren, die Alkohol ausgeben möchte. Mit Hilfe des PID-Reglers kann dann über den vorherigen Alkohol-Wert der Person die aktuelle Ausgabe-Menge berechnet werden. Um auch hier wieder Digital-Pins zu sparen, werden die Buttons (genauer Taster) die über einen Spannungsteiler und unterschiedliche Vorwiderstände analog eingelesen. Jeder Taster bekommt +1kΩ vorgeschaltet. Somit besitzt der erste Taster einen Vorwiderstand von 1kΩ und der achte Taster 8kΩ (ohne Berücksichtigung des Spannungsteilers).

Durch den Spannungsteiler, der zur Entstörung nötig ist (Pull-up Widerstand 10kΩ), ergeben sich folgende Spannungen, die vom AD Wandler eingelesen werden. Anhand dieser Werte lassen sich die Taster eindeutig identifizieren.

1. switch (1x1kΩ) | (1kΩ/(10kΩ+1kΩ)) * 5 Volt = 0,45 Volt
2. switch (2x1kΩ) | (2kΩ/(10kΩ+2kΩ)) * 5 Volt = 0,83 Volt
3. switch (3x1kΩ) | (3kΩ/(10kΩ+3kΩ)) * 5 Volt = 1,15 Volt
4. switch (4x1kΩ) | (4kΩ/(10kΩ+4kΩ)) * 5 Volt = 1,43 Volt
5. switch (5x1kΩ) | (5kΩ/(10kΩ+5kΩ)) * 5 Volt = 1,67 Volt
6. switch (6×10Ω) | (6kΩ/(10kΩ+6kΩ)) * 5 Volt = 1,88 Volt
7. switch (7×10Ω) | (7kΩ/(10kΩ+7kΩ)) * 5 Volt = 2,06 Volt
8. switch (8×10Ω) | (8kΩ/(10kΩ+8kΩ)) * 5 Volt = 2,23 Volt

  • Soundmodul zur Sprachausgabe

Das Breakout-Modul WTV020SD ist eine kleines Modul mit micro-SD-Karte zur Audio-Wiedergabe. Dieses Modul wird häufig in „sprechendem“ Kinderspielzeug verwendet. Die Platine verfügt über einen 5V bzw. 3V Anschluss und einen Micro-SD-Kartensteckplatz. Die Wiedergabe kann im Tasten- oder Serienmodus ausgelöst werden. Der Key-Modus bietet einen eigenständigen Betrieb, bei dem mit drei Drucktasten und einem Lautsprecher eine Art Component-MP3-Player-System implementiert werden kann.
Der serielle Modus bietet sich für unsere Projekt an, da eine zwei-adrige Schnittstelle zum Arduino, über eine DATA– und CLK-Leitungen, möglich wird. Es stehen dann Operationen wie PLAY-, PAUSE-, STOP– und VOLUME-Steuerfunktionen über serielle Befehle zur Verfügung.

https://www.lelong.com.my/arduino-wtv020-sd-audio-voice-sound-wav-player-module-iot-robotedu-173357125-2018-01-Sale-P.htm
http://www.datasheetbank.com/detail-image1/ETC/139731-DI1.gif

Die Audiokompression, die in diesem Modul verwendet wird, ist unkonventionell (4-Bit ADPCM @ 6-32kHz).
Die genaue Verarbeitung der Daten ist im Datenblatt erwähnt.

WTV020 Datenblatt

  • Spannungswandler für die 5 Volt Spannungsversorgung der einzelnen Komponenten

Da für einige Komponenten 12 Volt  (z.B. Pumpen und Taster LEDs) und für andere 5 Volt (z.B. Arduino und Soundmodul) benötigt werden, benötigt das Projekt einen Spannungswandler. Der Teil dieser Schaltung ist entsprechend dem Schaltplan zu entnehmen.

LM7805 Datenblatt

  • Alkoholsensor MQ-3

Mit dem MQ-3 Alkoholsensor kann man die Alkoholkonzentrationen in der Atemluft messen. Spezifisch für den MQ-3 ist, dass er eine sehr hohe Empfindlichkeit gegenüber Alkohol und eine geringe Empfindlichkeit für Benzin und Benzingemische hat. Dadurch eignet er sich besonders als Atemluftdetektor in diesem Projektaufbau. Der Sensor wird als Modul verbaut. Das heißt, er verfügt über zwei Ausgänge, die benutzt werden können. Ein digitaler Ausgang, der ab einem bestimmten Wert ein „HIGH“ sendet, oder einen analogen Ausgang, über den sich kontinuierlich Werte auslesen lassen. Da sich durch die Auswahl des Modus jeweils die Alkohol-Schwelle ändert, können wir den digitalen Ausgang nicht benutzten. Die Werte werden AD gewandelt und auf die Range von 0 bis 1023 gemapped.

https://banggood.com/

MQ-3 Datenblatt

  • Auswahl der Modi über Drehschalter

Ein einfacher Drehknopf ermöglicht es, zu Beginn des „Spiels“, den Modus zu wählen. Die ersten drei Schaltungsstufen legen das maximale Alkohol-Limit fest. Der vierte Modus ist der Durchspül-Modus, bei dem die Pumpen dauerhaft angeschaltet werden, um die Schläuche und die Pumpen reinigen zu können. Die vier Eingaben, die der Drehschalter ermöglicht, werden direkt über vier Digital-Pins an den Arduino weitergegeben.

Drehschalter Datenblatt

3.3.Kosten #

Die Gesamtkosten des Geräts belaufen sich auf ca. 150€. Eine detaillierte Kostenauflistung ist angehängt. Einige Komponenten für das Gehäuse wurden freundlicherweise von der Firma Schurter GmbH zur Verfügung gestellt.

Kosten für die Elektronik und die Pumpentechnik:

Bauteile/Funktionen Kosten Link
Gas Sensor Modul MQ3 7,80€ www.geras-it.de
2x Shift Register 0,60€ www.segor.de
LEDs inkl. Widerstände 3,00€ www.segor.de
Drehschalter 3×4 3,30€ www.segor.de
Steckplatte Protoboard 6,50€ www.segor.de
Jumper Set 6,50€ www.amazon.de
2x Pumpen 24,00€ www.conrad.de
2x Relais 2,20€ www.conrad.de
Zwei Meter Silikon-Schläuche 5,58€ www.conrad.de
Spannungsregler inkl. Kühlkörper 1,90€ www.conrad.de
Europlatine 3,59€ www.conrad.de
Arduino mini Pro 11,10€ www.amazon.de

Kosten für das Gehäuse:

Aluminium Gehäuse (gekantet) 31,05€ www.feld-eitorf.de
Taster ca. 160€ sponsored by Schurter GmbH
Acryl-Verkleidung ca. 30€ sponsored by Schurter GmbH
Schläuche und Anschlüsse ca. 20€ sponsored by Schurter GmbH
Ausguss und Gewinde ca. 10€ sponsored by Schurter GmbH
Folienplott ca. 8€ sponsored by Daniel Grießhaber

4.Code Dokumentation #

Der komplette Quellcode der Anwendung gliedert sich entsprechend der folgenden Verzeichnisstruktur:

Im Verzeichnis lib/ befinden sich externe Libraries in Form von C- und Header-Dateien. Eigene Libraries sind dagegen im Verzeichnis src/ untergebracht. Die beiden Dot-Files .editorconfig und .gitignore dienen der Konfiguration des Editors und Gits. Das Makefile bildet die Basis des Kompilierungsprozesses aller Libraries und der zentralen Datei main.c.

Alle Verzeichnisse und Dateien werden in den nachfolgenden Unterkapiteln ausführlich dokumentiert. Zunächst soll jedoch der grundsätzliche Aufbau der Libaries beziehungsweise Module geschildert werden.

Die Libraries/Module bestehen entweder aus einer C-Datei und/oder einer Header-Datei. In den Header-Dateien werden lediglich Makros und Funktionsdeklarationen; die eigentliche Logik von Funktionen wird falls vorhanden in den entsprechenden C-Dateien definiert. Die #include Anweisungen teilen sich bei Vorhandensein beider Dateien auf, sodass in der C-Datei nur solche Header-Dateien inkludiert werden, die ausschließlich dort benötigt werden. Darüber hinaus wird der „Once-Only“-Ansatz konsequent durchgezogen, der besagt, dass Header-Files nur einmalig inkludiert werden können. Dafür wird der vollständige Inhalt einer Header-Datei mit Conditionals versehen:

4.1.Externe Libraries #

Der Quellcode der externen Libraries gliedert sich entsprechend der folgenden Verzeichnisstruktur:

ATmega Timers

Die Library atmega-timers ist für die Steuerung der Hardware-Timer (oder Counter) verantwortlich, welche auf der CPU Clock Frequenz basieren. Der Timer kann durch einige spezielle Register programmiert werden. Der Controller des Arduino (ATmega328) hat 3 Timer, genannt timer0, timer1 und timer2. timer0 und timer2 sind 8-Bit-Timer, wobei timer1 ein 16-Bit-Timer ist. Somit unterscheiden sich die Timer in der Auflösung (265 Timerwerte oder 65536 Timerwerte) Die höhere Auflösung von 16-Bit bedeutet an dieser Stelle eine längere Zählung. Alle Timer hängen von der Systemuhr (16MHz) des Arduinos ab. In der Arduino-Firmware wurden alle Timer auf eine 1kHz-Frequenz konfiguriert und Interrupts sind aktiviert. Die Library konfiguriert lediglich diese drei Timer-Register.

Dieser Aufruf des Timers, wie er bei der Initialisierung unmittelbar beim Start des Geräts aufgerufen wird, ruft die Funktion handleUser auf, die bei jedem Aufruf die aktuelle Verfügbarkeit der Nutzer neu auswertet und die LED-Ausgaben entsprechend steuert (ob ein Nutzer wieder an der Riehe ist – 5 Min. Regel).

Der gewählte Prescaler in der Funktion ist 5 (TIMER1_PRESCALER_1024) mit 1024.
Wenn man die 16 MHz des Chips durch den Prescaler dividiert erhält man 15.625 Ticks.

Bei einer Frequenz von einem Hertz wird die Funktion handleUser() somit alle 5 Sekunden aufgerufen. Mit einer Genauigkeit von 5 Sekunden kann das Gerät also bestimmen, ob die Zeit eines jedes Nutzers abgelaufen ist und er erneut ein Getränk bekommt.

Interrupt

Mithilfe der Library avr/interrupt lassen sich Interrupts realisieren. Diese unterbrechen das laufende Programm, um zeitkritische Aufgaben durchzuführen. Eine solche zeitkritische Aufgabe kann beispielsweise durch ein eingehendes Signal angestoßen werden, in unserem Fall aber durch den Timer.

Wir rufen die Funktion sei() in unserem Hauptprogramm auf, um die Verwendung von Interrupts zu ermöglichen. Tatsächlich verwendet werden Interrupts dann in der ebenfalls externen Library atmega-timers. Diese setzt Interrupt-Flags in Form eines speziellen Bits, um einen Interrupt auszulösen.

 

qPIDs

Bei qPIDs handelt es sich um eine digitale PID (proportional–integral–derivative) Controller Library von Alan Kharsansky, die auf GitHub (https://github.com/akharsa/qPID) zu finden ist. Sie besteht aus jeweils einer C- und Header-Datei. Diese ermöglichen das Erstellen/Initiieren von PID-Controller-Instanzen, mit denen später die Menge an Alkohol abhängig vom Atemalkoholgehalt bestimmt werden soll.

Da insgesamt bis zu acht Personen teilnehmen können, müssen acht Instanzen erzeugt und initiiert werden. Um diese einfacher ansprechen zu können, wird ein Array aus Pointern vom Typ qPID erstellt, indem der Speicher von acht Instanzen alloziiert und der passende Pointer zurückgegeben wird:

Um die Instanziierung und Modifikation zu simplifizieren und doppelten Code zu vermeiden, wird die weitere Handhabung über eine eigene Library geregelt. Damit sind an dieser Stelle lediglich zwei Funktionen dieser Library von Bedeutung. Mit der Funktion qPID_Init() lassen sich erzeugte Instanzen initialisieren und mit qPID_Process() ein den Umgebungsvariablen entsprechender Ausgabewert generieren. Beiden Funktionen muss die jeweilige Instanz übergeben werden; der letzteren Funktion zudem noch Soll-/Ist-Werte.

Um die Funktionsweise zu demonstrieren, anbei das bereitgestellte Beispiel in vereinfachter Form:

Das oben gezeigte Beispiel soll die Steuerung eines einfachen Motors demonstrieren. Die Funktionen readSensor(), readSetPoint() und setActuator() dienen dazu, den Soll-/Ist-Wert der Geschwindigkeit abzufragen und den Motor anzusprechen. Im Anschluss daran findet das Erstellen und Initialisieren einer qPID() Instanz statt. Diese wird nun iterativ mit qPID_Process() aufgerufen, um auf Basis der Soll-/Ist-Werte den Motor entsprechend ansteuern zu können.

Block Diagramm

Der PID-Regler beziehungsweise die acht PID-Regler sind im Kontext des BroMille notwendig, um die Auswirkung der abgegebenen Alkoholmenge zeitlich auf den Atemalkohol abzubilden. Ähnlich wie beim Motor oder bei einem Thermostat wirkt sich auch die Einnahme von Alkohol nicht direkt auf den Alkoholpegel aus. So nimmt der Atemalkohol mit der Zeit ab und der Blutalkohol zu. Mit den PID-Reglern und deren feingranularer Konfiguration lässt sich jene Problemstellung sehr gut handhaben, sodass der Nutzer möglichst zu keinem Zeitpunkt den angestrebten Alkoholpegel überschreitet. Die Anpassungsfähigkeit der Regelstrecke ist durch einen gleichbleibenden Sollwert möglich und erlaubt Abweichungen und Verzögerungen zu kompensieren.

Die dargestellten Werte wurden experimentell ermittelt. Der Output wird auf Werte zwischen 0 und 100 begrenzt und soll die Prozentmenge an notwendigem Alkohol darstellen. Weitere Erläuterungen befinden sich im Unterkapitel zur eigenen Library als Ergänzung zu dieser Ausführung.

wtv020

Auf dem Modul wtv020 können Sounddateien auf einer Mikro SD-Karte gespeichert und in das Modul eingesteckt werden. Bis zu 512 Dateien dürfen auf einer maximal 1GB Speicherkarte abgelegt werden. Einzelne Dateien können wahlweise Wörter, Sätze oder ganze Musiktitel sein, die im ad4-Format vorliegen müssen. Die Ansteuerung des Moduls erfolgt über nur 3 beliebige Datenports, die in Kapitel 3.2 in der Grafik vermerkt sind.

In diesem Auszug des wtv020-Headerfiles sieht man die Spezifikation der genannten Ports. PB5 entspricht dem Pin 13 auf dem Arduino-Board und stellt den Datenpin dar. Pin 8 (PORTB PB0) liefert das Clocksignal für das Modul. Pin 9 gibt den Impuls für den Reset. Der Auschnitt zeigt außerdem die definierten grundlegenden Commands der Moduleinheit. Hervorzuheben ist dabei der Command wtv020_sendCommand(ValueX). ValueX entspricht dem Soundfile, dass an gegebener Stelle abgespielt werden soll. Der folgende Abschnitt zeigt das Aufrufen des Soundfiles mit für einen spezifischen Nutzer (zb. Nutzer 5). Er wird damit aufgefordert nun in den blauen Schlauch zu pusten.

Der User muss an dieser Stelle um drei aufaddiert werden, da die speziell vorgefertigten Nutzeransprachen erst mit dem vierten Soundfile beginnt. Die vorgefertigten Sound-Snippets sind wie folgt abgelegt:

Folgender Auszug aus der main.c zeigt die Initialisierung des wtv020-Moduls:

4.2.Libraries #

Alcohol

Die Library alcohol.c/alcohol.h enthält verschiedene Funktionen zur Handhabung des Alkoholsensors MQ-3 und umfasst folgende Definitionen:

Die Funktion getTargetAlcohol() liest die PIN2 und PIN3 aus, um festzustellen, welcher Spielmodus vom Nutzer gewählt wurde.
Entsprechend der PIN-Belegung wird der dem angestrebten Alkoholpegel entsprechenden Analogwert ausgegeben: 512, 768 oder 1023.

Das Herzstück der Library ist jedoch getAlcoholAnalog(). In dieser Funktion wird READ_SAMPLE_TIMES mal im Abstand von READ_SAMPLE_INTERVAL Millisekunden der analoge Input am Alkoholsensor MQ-3 gemessen. Während die erste Tertile ignoriert wird, um zu spätes Pusten zu kompensieren, wird die mittlere doppelt gewichtet. Dadurch wird eine eventuell mit der Dauer einhergehende Reduktion der Stärke des Pustens kompensiert. Auf Basis der Messreihe wird ein gewichteter Mittelwert gebildet.

Die Funktion getAlcoholPromille() stellt lediglich einen Wrapper für die zuvor erläuterte Definition dar, der statt analogen Werten einen ungefähren Promillewert ausgibt.

Analog

Diese Bibliothek handhabt lediglich die analogen PINs und deren Lesevorgänge.
Folgende Definitionen sind enthalten:

Neben der Definition der analogen PINs enthält diese Bibliothek lediglich den analogen Lesevorgang. Diese Funktion vereint sowohl das Initialisieren als auch das analoge Auslesen. Dabei wird der PIN auf den ADMUX (ADC Multiplexer) gelegt und die Versorgungsspannung AVCC als Referenz angelegt. Anschließend wird der Frequenzvorteiler gesetzt, ADC aktiviert und eine Wandlung vollführt; dabei wird das ADC Control and Status Register verwendet. Nach Abschluss der Konvertierung wird der entsprechende Wert zurückgegeben.

Button

Die Library handhabt die Buttons, die auf der Vorderseite des BroMille angebracht und je einem Anwender zugeordnet sind.
Es werden folgende Funktionen und Variablen definiert:

Die Variable currentUserButtons stellt eine nummerische Entsprechung der aktuell leuchtenden/aktiven Buttons dar. Ist kein Button aktiv, enthält die Variable den Wert 0; sind alle aktiv den Wert 255.

Die Funktion waitForButton() verbleibt solange in einer Schleife, solange kein Button länger als 100 Millisekunden gedrückt wurde. Sobald der beschriebene Fall eintritt, wird der von getButton() ermittelte Wert ausgegeben und das Programm fortgeführt.

Die soeben bereits erwähnte Funktion getButton() gibt zurück, welcher Button (1-8) aktuell gedrückt ist. Falls keine gedrückt wurde, wird 0 zurückgegeben. Dafür wird der analoge PIN2 eingelesen und nur ausgewertet, sollte der Wert unter 768 liegen. Anderenfalls wird davon ausgegangen, dass kein Button gedrückt wurde. Um den analogen Eingangswert einem Button zuordnen zu können, wurde die Spannung beim Drücken jedes Buttons gemessen und in eine polynomiale Regression dritten Grades überführt. In diese Gleichung kann der analoge Wert eingesetzt und dadurch diesem Wert eindeutig ein Button zugewiesen werden.

Die letzte Funktion getButtonValue() ermittelt aus der Nummer des Nutzers dessen Wert als Zweierpotenz, sodass dieser Wert einfach zu currentUserButtons addiert beziehungsweise davon subtrahiert werden kann. Die jeweiligen Rechenoperationen haben zur Folge, dass der dem Nutzer entsprechende Button an-/ausgeschalten wird, ohne die anderen Buttons zu beeinträchtigen.

Digital

Die Library, die digitale Schreib-/Lesevorgänge handhabt, orientiert sich sehr stark an digitalWriteFast. Dieses Modul besteht ausschließlich aus einer Header-Datei mit einer Vielzahl an Macros. Wir haben diese Datei übernommen, für uns unbenutzte Macros entfernt und um eigene erweitert. Die Header-Datei besteht im Wesentlichen und neben den Port-Definitionen aus drei Teilen, welche im Folgenden vorgestellt werden.

Zunächst wurden die Bitwise-Operationen übernommen, die es ermöglichen, Bits zu lesen/schreiben/clearen/setzen.
Nachfolgend wurden Port-Range-Checker eingeführt, die anhand der PIN-Nummer prüfen, ob sich die PINs in den Blöcken der B/C/D-Pins befinden. Damit lassen sich dann die digitale PIN-Nummern auf zugehörige Variablen mappen, sodass den eigentlichen Funktionen nur die digitale PIN-Nummer übergeben werden muss und auf Angaben wie PORTD, DDRD oder PIND verzichtet werden kann. Diese Funktionen zum Mappen werden von wiederum von Funktionen verwendet, die ausschließlich in anderen Libraries bzw. in der Logik verwendet werden. Diese erlauben unter anderem das Definieren eines PINs als Input/Output, das Lesen/Beschreiben eines PINs und/oder das Prüfen ob ein PIN gesetzt/clear ist:

Hilfsfunktionen wie toggle() wrappen hingegen weitere Funktionalität, um die User Experience des Entwicklers zu erhöhen.

LED

Bei dieser Library handelt es sich um die kleinste; sie besteht lediglich aus einer einzelnen Funktion mit der Definition:

Wie der Name verrät, erlaubt diese Funktion, alle LEDs am Gerät blinken zu lassen. Das Verhalten kann durch die Anzahl der Blink-Vorgänge und die Dauer der einzelnen Stati (an/aus) reguliert werden.

PID

Die eigentliche Library der PIDs wurde bereits vorgestellt. An dieser Stelle soll unsere Ergänzung dazu erläutert werden, die das Initialisieren der PID-Instanzen vereinfacht und aus folgenden Definitionen/Variablen besteht:

Zunächst wird eine Vektor für acht Pointer aus qPID-Instanzen erzeugt. Die Funktion initPids() iteriert durch diesen Vektor, allokiert entsprechend Speicher und initialisiert einzelne qPID-Instanzen mit initPid(). Dieser Funktion erlaubt es, alle Instanzen mit gleichen Parametern zu definieren.

Pumpen

Um die beiden Pumpen anzusprechen, wurde die Library pump.c entwickelt, die verschiedene Funktionalitäten abdeckt und folgende Definitionen umfasst:

Die erste Funktionen handhabt das Ansaugen der beiden Flüssigkeiten, indem die Pumpen aktiviert werden, sobald Button Eins gedrückt wurde und jene erst wieder deaktiviert, wenn dieser Button losgelassen wird. Die folgende Funktionen koordinieren das Durchspülen zum Säubern der Pumpen und Schläuche; ähnlich zur ersten Funktion, jedoch findet das Aktivieren/Deaktivieren über PIN5, einen PIN des Potentiometers, statt. Interessant und wichtig ist die letzte Funktion dieser Library namens mix(). Ihr kann die prozentuale Menge an Alkohol übergeben werden. Zusammen mit der Zeit, die beide Pumpen brauchen, um ein Glas zu füllen, werden die Pumpen entsprechend gesteuert. Dafür wird der Prozentsatz auf die Gesamtdauer übertragen.

Shift

Die beiden Shift-Register — die LEDs der Buttons sowie der Status-LEDs — werden mit Hilfe einer eigenen Library verwaltet. Dafür existiert eine Funktion zum digitalen Befüllen der Register:

Zudem werden PINs definiert, die für die Clock-Signale der jeweiligen Register sowie dem Data-Signal benötigt werden.
Der Funktion wird der Identifier des anzusprechenden Registers, der Wert und die Geschwindigkeit übergegeben, der Wert übergeben. Intern wird der Wert entsprechend umgewandelt, zudem werden Clock und Delay gehandhabt. Die Umwandlung des Wertes sieht wie folgt aus:

Dabei nimmt i alle gerade Werte zwischen 0 und 15 an. Hierdurch lassen sich die LEDs einzeln in beliebiger Geschwindigkeit ansprechen, wodurch verschiedene visuelle Effekte erzeugt werden können.

UART

UART ist die Abkürzung für Universal Asynchronous Receiver Transmitter und dient zur Realisierung digitaler serieller Schnittstellen. Bei einem AVR-Projekt dient die UART-Schnittstelle zum Senden und Empfangen von Daten über eine Datenleitung von Mikrocontroller zu PC. Diese Library, die auf einem Beispiel von Patrick Servello aufgebaut wurde, gibt den über printf() erzeugten String weiter, welcher dann via UART ausgegeben werden kann. Dafür muss der UART zuerst initialisiert werden. In der folgenden Initialisierung müssen je nach gewünschter Funktionsweise die benötigten Bits im sogenannten UCR (UART Control Register) gesetzt werden.

Zur Initialisierung gehört auch die korrekte Einstellung der Baudratenregister UBRR( L und H). Die Berechnung der Werte für das Baudratenregister setzt sich aus Taktrate (F_CPU) und Baudrate (BAUD) zusammen.

Über die Funktion usart_send_byte() wird der zu sendende String als Stream an den Serial Output gesendet, dabei wird solange in einer Loop-Funktion abgefragt, ob die Entsprechenden Bits im Register gesetzt sind. Eine detaillierte Beschreibung der Register und ihrer Funktionalität ist im Datenblatt des ATmega 328p Chips nachzulesen.

User

Das Spieler-Management wird ebenfalls von einer eigenen Library gehandhabt. In dieser Library wird neben der maximalen Spieler-Zahl eine Vielzahl an Hilfsfunktionen implementiert:

Die erste Variable namens userWait definiert, wie lange ein Spieler in Sekunden aussetzen muss. Der Vektor userTimers enthält für alle aktiven Spieler die laufenden Timer-Stati, beginnend bei 0. Der Vektor activeUsers enthält für alle Spieler die Information, ob sie aktiv sind oder nicht.

Nun folgt eine Reihe an Hilfsfunktionen. Die Funktion getUser() wandelt lediglich die Nummerierung der Buttons (1-8) in ihre Äquivalente der Nutzer (0-7) um. Die beiden Funktionen enableUser()/disableUser() führen zwei Operationen durch: die aktivieren/deaktivieren den Spieler im activeUserTimers Vektor und schalten die entsprechende Button-LED an/aus. Auch die beiden Funktionen isActive()/isRunning() besitzen ähnliche Funktionalitäten: während isActive() lediglich Aufschluss darüber gibt, ob ein Spieler-Timer aktiv ist, gibt isRunning() zurück, ob ein Timer schon die Wartezeit erreicht hat oder noch läuft.

Die übrigen Funktionen handleUsers()/handleUser() gehören ebenfalls zusammen. Erstere wird sekündlich aufgerufen und ruft für alle aktiven Spieler die Funktion handleUser() auf. Diese wiederrum inkrementiert die entsprechenden Timer bzw. resettet ihn und deaktiviert die dazugehörige LED.

Utils

Sämtliche übrigen Hilfsfunktionen und Definitionen, die keiner der einzelnen Library zugeordnet werden konnten, werden als Utils definiert:

Die Macros MIN()/MAX() geben jeweils die kleinere/größere Zahl zurück und MAP() mapped eine Zahl (0-1023) auf acht LEDs bzw. Zweierpotenzen von 2^0 bis 2^7. Die Funktion reduce() bricht lediglich einen Wert um einen bestimmten Faktor zu 1023 herunter: so würde die Funktion für x=4 und range=511 den Wert 2 zurückgeben. Neben freeRam(), das den noch verfügbaren Speicherplatz im RAM ausgibt, sind die übrigen Funktionen simple Wrapper zur Berechnung von Zweierpotenzen bzw. zum dynamischen Einsatz von Delays.

4.3.Logik #

Im Folgenden wird die Ablauf-Logik grafisch dargestellt. Nachdem der Bromille mit Strom versorgt wird, startet dieser lineare Ablauf. Im unteren Teil sieht man die Wiederholung (Verweis auf Nutzereingabe „Button X pressed“), die den laufenden Betrieb widerspiegelt. Alle Punkte davor sind lediglich für das Setup und die Initialisierung zu Beginn.

Der Ablauf kann anhand der main.c Datei gut nachverfolgt werden. Zur besseren Nachvollziehbarkeit ist der Code hier einsehbar:

4.4.Makefile #

Die in Kapitel 2  beschriebenen Parameter müssen im Makefile definiert werden, um beim automatisierten Flashen und Kompilieren berücksichtigt zu werden. Zu Beginn werden alle Parameter wie folgt definiert:

Als zweiter Schritt werden die Libraries eingebunden. Der Übersichtlichkeit wegen werden eigene Libs (SRCS) und externe Libs (LIBS) auch im Makefile separat betrachtet. Außerdem werden die zu verlinkenden Object-Files gelistet:

Der folgende Aufruf zum Flashen und Kompilieren wird wie folgt im Makefile festgehalten. Dort werden Referenzen auf die zuvor gesetzten globalen Eigenschaften gelegt:

Darüber hinaus werden im Makefile drei Actions definiert, die wiederum in der Action all zusammengefasst werden:

Die Action compose besitzt die definierten Object-Files als Dependencies, welche all diese verlinken und in einer Binary zusammenführen. Anschließend wird aus der Binary eine Intel-HEX-Datei erstellt, die hexadezimalen Maschinencode enthält.

Die daraus entstehende HEX-Datei wird mit der Action programm auf den Controller geflasht; hierbei wird avrdude verwendet. Des Weiteren werden hier MCU, PORT, BAUD und PROGRAMMER übergeben.

Um danach das lokale Dateisystem sauber zu halten, werden nach dem Kompilieren und Flashen die erzeugten Datei entfernt. Dafür wird die Action clean eingesetzt, die auf dem Bash-Command find basiert.

Nun fehlen letzten Endes nur noch die für compose benötigten Object Files. Hierfür wird folgende Regel im Makefile definiert:

Mit diesem Command werden – um alle definierten Object Files zu erzeugen – die entsprechenden C-Dateien mit avr-gcc kompiliert. Mit Hilfe der Platzhalter %, $@ und $< lässt sich der Befehl generisch gestalten, ohne dass für jede Datei ein eigenes, spezifisches Target definiert werden muss. Zusätzlich werden dem Compiler noch die CPU-Frequenz und MCU übergeben.

Dieses Makefile ermöglicht ein einfaches Kompilieren, Verlinken, Flashen und Aufräumen. Sollten neue Dateien hinzukommen oder bestehende entfernt werden, muss lediglich darauf geachtet werden, dass LIBS und SRCS gepflegt werden.

5.Allgemein #

Dieses Kapitel gibt Aufschluss über weitere Hintergrunde zum Projekt, die über die technische Dokumentation hinaus gehen. Dazu gehören Bilder aus der Bau- und Konzeptionsphase und aus dem Betrieb. Des Weiteren sind die Präsentation aus der Lehrveranstaltung hinterlegt und eine arbeitstechnische Bewertung wird vorgenommen.

5.1.Bilder #

5.2.Github #

Der Code zum Projekte ist öffentlich bei Github bereitgestellt.

Der selbe Stand des Projekts kann auf der HdM eigenen GitLab hier eingesehen werden.

5.3.Zeitplan #

Das Projekt umfasste den Zeitraum des Sommersemesters 2017. Start der Planungs- und Entwicklungsphase war somit Anfang März 2017. Der folgende Ausschnitt aus der Endpräsentation zeigt den groben Projektablauf im gegebenen Semesterzeitraum.

5.4.Präsentationen #

Einführungs-Präsentation vom 04.04.2017:

Abschluss-Präsentation vom 27.06.2017:

5.5.Lessons Learned #

Workflow

Die ersten Wochen im Projekt zeigten sehr schnell, dass die Arbeiten an einem Embedded-Projekt sich bezogen auf den Workflow sehr von einem klassischen Software-Projekt unterscheiden. Zum einen gab es eine sehr intensive Einarbeitungsphase, die viel direkte und persönliche Abstimmung bedarf und zum anderen war man immer direkt an die Hardware gebunden. Unser Ansatz war daher von Anfang an, alle Schritte in der Gruppe gemeinsam zu bearbeiten. Das ist sicherlich nicht der effizienteste Ansatz eines Software-Projekt, war aber gerade mit Hinblick auf die neue, unbekannte Technologie von großem Vorteil. Diese enge Zusammenarbeit mache ein frühes Zusammenfügen von Komponenten möglich, um landläufige Fehler zu vermeiden, die oft den Zeitplan durcheinander bringen können. Nennenswerte Verschiebungen im geplanten Timing sind daher nicht aufgetreten. Mit ein Grund dafür war sicher auch der schrittweise Aufbau der Anwendung. Zuerst wurden alle Basic-Libs aufgebaut und jedes mal mit einer kleinen Testanwendung zum Laufen gebracht. Jedes Modul stand für sich alleine und konnte einzeln angesprochen werden. So wurden beispielsweise noch ohne konkrete Vorstellung über den Ablauf des Geräts und die genauen Komponenten an der Alkoholmessung mit dem Sensor gearbeitet.

Herausforderungen

  • Wie schon erwähnt, bestand die größte Herausforderung in der neuen Thematik an sich, da bisher keiner von uns Vorkenntnisse im Embedded-Bereich hatte.
  • Die spezielle Coding-Anforderungen, die pure C mit sich bringt, waren eine weiter Herausforderung, die eine gewisse Einarbeitungszeit mit sich brachte.
  • Das hardware-orientierte Programmieren stellt Herausforderungen dar, die bei klassischen Software-Projekten zum Teil vernachlässigt werden können, wie z.B. der Umgang mit enorm begrenztem Speicher.
  • Neben der Herausforderung, die Regelungstechnik hinter den PID-Reglern zu verstehen, mussten wir viel an der Fehlerbehebung bei der Initialisierung des PID Reglers arbeiten.
  • Spannungsschwankungen beim Gerät führten zu Problemen im Betrieb. Da die Button-Eingabe analog eingelesen wird (siehe Kapitel 3.2 „Acht Buttons zur Personenidentifikation“) und daher genau definierte Spannungsbereiche nötig sind, kam es beim Hinzufügen von Komponenten oder einer neuen Spannungversorgung immer wieder zu Schwankungen, die fehlerhafte Eingaben erzeugten.
  • Das AVR-Projekt stellte uns vor die Herausforderung, effiziente Wege des Debuggers zu finden. Zu Beginn war die einzige Möglichkeit ein Feedback zu bekommen, eine LED entweder an oder aus zu schalten. Im zweiten Schritt war es dann möglich über das UART Protokoll printf Ausgaben über die Konsole auszulesen. Dies erleichterte die Arbeit ab einem gewissen Komplexitätsgrad enorm.
  • Die frühzeitige Planung des Gehäuses war für das Projekt sehr wichtig, weil das Design des Gehäuses Auswirkungen auf den Luftstrom des Sensors hat und somit zu Verfälschungen bei der Alkoholmessung beiträgt. Eine Gehäuseplanung in der Anfangsphase des Projekts war daher unerlässlich.
  • Der Umgang mit dem Alkoholsensor (MQ-3) stellte ebenfalls eine Herauforderung dar, da wir lediglich die Spannungsschwankungen am Sensor einlesen und darüber Rückschlüsse auf den Alkoholanteil in der Luft führen konnten. Trotz der Verwendung von „echten“ Alkoholmessgeräten, war es schwierig, eine korrekte Messreferenz zu finden. Auch eine korrekte Umrechnung von Atem- zu Blutalkohol war eine Herausforderung, die aber auch bis heute offiziell umstritten ist.
  • Abschließend lässt sich daher sagen, dass dieses Projekt Themenbereiche weit über die Programmierung von Mikroprozessoren hinaus geht. Vielmehr waren es die Off-Topic Herausforderungen, die das Projekt zu einem interdisziplinären Meisterstück gemacht haben.