Eine Stoppuhr mit Lichtschrankensteuerung für einen Sportverein...

Im Mikrocontroller-Forum wurde angefragt, ob Jemand ein Programm für den Mega8 schreiben würde, das eine lichtschrankengesteuerte Stoppuhr für 4 Bahnen (Laufen) realisiert. Da ich durch dieses Projekt sicherlich nicht dümmer werde, habe ich mich bereit erklärt, dieses Programm zu schreiben.

Für das Projekt sollte ein myAVR™-Board mit mySmartUSB™-Aufsatz von myAVR verwendet werden, da es bereits vorhanden war. Weiterhin kam ein Punktmatrix-LCD mit 8 Zeilen je 24 Zeichen (Controller M50530) zum Einsatz, unter dem 4 Taster zur Bedienung vorgesehen wurden. Mit dem finalen Gerät habe ich (wie üblich) wieder mal nichts zu tun, deshalb gibt es bei mir wieder mal nur den Testaufbau. Da mein Laptop den USB-Treiber für mySmartUSB™ nicht mag (liegt vermutlich daran, dass der Lappi nur USB1.1 hat), musste ich auf den USB-Aufsatz verzichten, worauf ich mir eine kleine Adapterplatine (Lochraster) gebaut habe, die mir Anschlüsse für ISP und RX/TX zur Verfügung stellt. Somit konnte ich meinen Eigenbau-ISP-Programmer und ein aktives RS232-Adapter (mit MAX232) zum Programmieren bzw. zur Kommunikation mit dem PC einsetzen. Beim Sportverein funktioniert es aber über USB.


Testaufbau der Schaltung:

Foto vom Versuchsaufbau Nebenstehendes Bild zeigt den Versuchsaufbau. Oben rechts ist ein Blei-Gel-Akku 12V/1,2Ah zur Stromversorgung. Oben Mitte ist das myAVR™-Board mit aufgestecktem Adapter für ISP und RX/TX. Links daneben ist der RS232-Adapter zu sehen. Diese kleine Platine enthält neben der SUB-D-9-Buchse einen IC MAX232 und die erforderlichen Kondensatoren und ist durch Akku-Schrumpfschlauch geschützt.

In der Erweiterungsbuchsenleiste steckt ein (geflextes) Adapter, an welches das LCD und die 4 Bedientaster angeschlossen sind. Am linken Ende sieht man ein Trimmpoti (10k), das zum Einstellen der Kontrastspannung des LCDs dient. Diese soll etwa 8..12V betragen, weshalb mit einer "fliegenden Leitung" die 12V vom Plus-Anschluss des Brückengleichrichters (zusätzlich angelöteter Pfostensteckerstift) angezapft wurde.

Zwischen myAVR™-Board und LCD ist ein Lochraster-Streifen mit 5 Tastern zu sehen. Diese dienen zur Simulation der 4 Lichtschranken (potentialfreie Relaiskontakte) und des Kontaktes in der Startpistole. Sie sind nur mittels Schaltdraht an den Buchsenleisten des Boards angeschlossen. Im finalen Gerät soll da noch eine Schutzbeschaltung die Eingänge vor gefährlichen Störspannungen schützen. Die Holzklötzchen unter dem Platinenstreifen sollen einen stabilen Stand gewährleisten und Kurzschluss mit dem LCD/Tasten-Adapter verhindern.

Wie bereits gesagt, das ist der Testaufbau, mit dem finalen Gerät habe ich nichts zu tun.


Erste Version der Software:

Zu Anfang ist erstmal nur autarker Betrieb auf dem Sportplatz vorgesehen, mit anschließender Datenübertragung zum PC. Für später wäre eine Zusammenarbeit mit dem Laptop denkbar, aber vorerst eben noch nicht.

Der Mega8 wird mit einem baudratentauglichen Quarz mit 3,6864MHz betrieben. Dies sichert eine stabile UART-Kommunikation für die Übertragung der Daten zum PC. UART und LCD nutzen gemeinsame Print-Routinen und auch einen gemeinsamen Ausgabe-Treiber. Der Ausgabetreiber arbeitet im Timer-Interrupt mit 800Hz und sendet Zeichen aus einem Ringbuffer von 256 Bytes Größe an LCD oder UART. Wegen der Platzierung der Zeichenausgabe im Interrupt wurde diese Zeichenausgabe auch ins Hauptprogramm übernommen und nicht, wie eigentlich üblich, in der Include-Datei belassen. Einige vereinbarte Steuerzeichen bestimmen, wohin die darauf folgenden Zeichen gesendet werden. So schaltet jeder Locate-Befehl (Kennung '0' gefolgt von Position im DD-RAM des LCDs) die Ausgabe auf LCD, das Steuerzeichen '1' schaltet die Ausgabe auf UART. Zur Steuerung der Ausgabe dient das Flag 'v24aktiv' im Register 'flags'. Diese kombinierte Zeichenausgabe bietet die Möglichkeit, die Routinen für die formatierte Ausgabe gemeinsam für LCD und UART zu nutzen. Die restlichen LCD-Routinen sind in einer eigenen Include-Datei zusammengefasst und werden teils von den Routinen der formatierten Ausgabe, teils vom Hauptprogramm aufgerufen.

Gliederung des Programms:
Wie fast jedes ASM-Programm beginnt es nach den Vereinbarungen von Konstanten (auch Include-Datei für Portdefinitionen 'm8def.inc'), SRAM-Variablen und Register-Variablen mit der Interrupt-Sprunhtabelle. Es wird nur der Reset-Vektor und der Timer1-CompareA-Interrupt verwendet. Danach werden die Include-Dateien für LCD und formatierte Ausgabe eingebunden. Dann folgt die Reset-Routine, in der alle Variablen und benutzte Hardware-Komponenten initialisiert werden. Da beim Programmstart das Menü aufgerufen werden muss, um das Display mit Dialog-Text zu füllen, wurden die Menü-Routinen noch vor der Hauptschleife angeordnet. Auch die Routine 'tastaus' zum Löschen der Tastenflags und zur Ausgabe der Menü-Dialogtexte wurde noch vor der Mainloop platziert und wird von den Menüpunkt-Routinen statt der Mainloop angesprungen, wodurch sich der Programmcode der Menüpunkte vereinfacht.

In der Mainloop werden dann die Tastenflags und Lichtschrankenflags abgefragt und in deren Abhängigkeit zum Menü bzw. Stoppuhr verzweigt. Dann wird noch bei aktivem Stopuhrbetrieb die laufende Zeit an das LCD ausgegeben. Dann folgt die Menü-Sprungtabelle (indizierter Routinenaufruf) und die Routinen für die einzelnen Menüpunkte.

Die ISR des Timers setzt erstmal den Zeitpunkt ihres nächsten Interrupts (in 1,25ms). Dann wird das nächste Zeichen aus dem Ringbuffer an LCD oder UART ausgegeben, falls der Buffer noch Zeichen enthält. Dabei werden auch Steuerzeichen (LCD-Ausgabe-Position, Umschaltung auf UART) erkannt und ausgeführt. Nun sorgt ein Vorteiler alle 10ms oder 50ms (je nach eingestellter Auflösung gemäß Flag 'fine' in 'flags') dafür, dass bei aktiver Stopuhr die Zeit hochgezählt wird. Dann werden alle 10ms (nur bei 'scharfer' Anlage) die Lichtschrankeneingänge (incl. Startpistolenkontakt) entprellt. Anschließend ist (auch alle 10ms) die Entprellung der Taster an der Reihe. Beide Entprellroutinen entsprechen dem Algorithmus nach Peter Dannegger ('PeDa') und sind etwas modifiziert. Die Entprellvariablen werden im SRAM gehalten und die Lichtschrankenentprellung wurde um eine 'Wiederholsperre' erweitert. Bei laufender Stopuhr wird von der ISR noch in zyklischen Abständen das Flag 'gibaus' in 'flags' gesetzt, welches die LCD-Ausgabe der laufenden Zeit synchronisiert.

Der Programmcode endet mit den Textbausteinen für Menü und UART.

Das Menü des Programms

Es wurden Menüpunkte für die Eingabe der Startnummern (und dafür die Auswahl der Bahn), das Rennen, das Anzeigen von Rangliste und Startliste, den Datentransfer zum PC und dem Löschen des EEPROMs und Einstellens der Auflösung vorgesehen. Hier die Menüpunkte im Einzelnen:
  1. Auswahl Bahn (+1, -1)
    Hier wird die Bahn ausgewählt, für die die Startnummer eingetragen werden soll. Die ausgewählte Bahn wird durch Pfeile links und rechts markiert. Da nach jedem Lauf automatisch die niedrigsten bisher unbenutzten Startnummern ermittelt und eingetragen werden, ist das Eingeben von Startnummern nur in Sonderfällen erforderlich.

  2. Startnummer x 100 (+100, -100)
    Hier wird die Startnummer der markierten Bahn um 100 erhöht bzw. vermindert. Da nur Startnummern von 1 bis 252 erlaubt sind, sollte man diese Auswahl mit Verstand benutzen.

  3. Startnummer x 10 (+10, -10)
    Hier wird die Startnummer der markierten Bahn in Zehnerschritten verändert.

  4. Startnummer x 1 (+1, -1)
    Hier wird die Startnummer der markierten Bahn um 1 verändert.

  5. Rennen (Start, Save)
    Mit 'Start' wird die Stoppuhr scharf gemacht, also auf 0 gestellt und die Abfrage von Startpistole und Lichtschranken aktiviert. Das Abschießen der Pistole startet die Uhr, das Verlassen des Menüpunktes stoppt die Uhr und verwirft die bisher ermittelten Zeiten. Das Auslösen einer Lichtschranke übernimmt die aktuelle Zeit für diese Bahn, das Auslösen aller Lichtschranken stoppt die Uhr. Mit der Taste 'Save' werden die ermittelten Zeiten ins EEPROM des Gerätes übernommen, falls die zugehörige Lichtschranke ausgelöst hatte. Hatte die Lichtschranke nicht ausgelöst, dann wird zu dieser Startnummer keine Zeit gespeichert. Die Startnummer steht dann in weiteren Rennen zur Verfügung. Die Uhr wird dann auch angehalten.

  6. Rangliste (+4, -4)
    Es wird die nach Zeit sortierte Liste der bisherigen Läufer angezeigt. Dabei werden Rang (Platz) Startnummer und Zeit angezeigt. Gleiche Zeiten mehrerer Läufer werden nicht berücksichtigt. Bei gleichen Zeiten bekommt die niedrigere Startnummer den besseren Rang. Mit den Tasten kann in der Liste in Viererschritten geblättert werden.

  7. Startliste (+4, -4)
    Es wird eine Liste der Startnummern und Zeiten angezeigt. Mit den Tasten kann in Viererschritten geblättert werden.

  8. PC (SendA, SendB)
    Hier erfolgt die Datenübertragung über UART zum PC oder Drucker. 'SendA' sendet die Rangliste als ASCII-Text an den PC oder Drucker, dabei wird zwischen Startnummer und Zeit etwas Platz zum nachträglichen handschriftlichen Eintragen des Namens gelassen. Es werden nur die Einträge mit erfassten Zeiten gesendet. 'SendB' überträgt nur Startnummer und Zeit. Es ist zur Übertragung der Daten an ein (noch nicht geschriebenes) PC-Programm vorgesehen, dass die Daten selbst sortiert und aufbereitet. Falls z.B. ein Datenimport in EXCEL angestrebt wird, kann diese Routine an die Erfordernisse angepasst werden. Auch hier werden nur die erfassten Daten gesendet.

  9. EEPROM loeschen (50ms, 10ms)
    Hier wird das Löschen der erfassten Daten im EEPROM mit dem Einstellen der Messauflösung kombiniert. Dies sichert ab, dass bereits erfasste Zeiten nicht mit der falschen Auflösung angezeigt werden. Das Löschen des EEPROMs wird durch das Auswählen einer der beiden Auflösungen eingeleitet. In beiden Auflösungen wird die Zeit als 16-Bit-Ganzzahl gespeichert, aber je nach Auflösung unterschiedlich schnell gemessen (hochgezählt) und unterschiedlich angezeigt. Die eingestellte Auflösung ist im EEPROM unter Adresse 0 gespeichert und bleibt somit auch beim Ausschalten des Gerätes erhalten.

Das LCD zeigt in der oberen Zeile den Menüpunkt als Überschrift und in der unteren Zeile die Bedeutung der 4 Tasten in diesem Menüpunkt. Die mittleren 4 Zeilen zeigen die dem Menüpunkt entsprechenden Listen oder sind leer. Die zweite Zeile von oben und von unten enthält eine Trennlinie. In der oberen Zeile ganz rechts ist die aktuell eingestellte Auflösung zu sehen.

Einige Betrachtungen zum Datenformat und zur Speicherverwaltung

Um die 512 Bytes EEPROM effizient nutzen zu können, wurde das Datenformat für die erfasste Zeit auf 16 Bit begrenzt. Dabei wurde vereinbart, dass die letzten 256 Byte (also 255 im H-Byte) nicht genutzt werden und als 'leer' (ungültig) gelten. Da in einigen Teilen des Programms das EEPROM nach gültigen Zeiten durchsucht werden muss, wurde das H-Byte des Zeitstempels in die untere Page des EEPROMs gelegt, denn diese lässt sich aufgrund der vordefinierten Registervariable 'null' recht schnell adressieren. In der oberen Page des EEPROMs liegen dann die zugehörigen L-Bytes der Zeiten. Diese werden dann nur gelesen, wenn das H-Byte ungleich 255 ist. Das Aufteilen der beiden Bytes eines Datensatzes (Zeitstempels) auf beide Pages hat den Vorteil, dass die untere EEPROM-Adresse (eearl) der Startnummer entspricht und die Berechnung der EEPROM-Adresse damit entfällt.

Im Normalfall wird mit einer Auflösung von 10ms gearbeitet. Das misst zwar auf die Hundertstelsekunde, reicht aber nur bis 10 Minuten und 52 Sekunden. Um auch längere Zeiten messen zu können, wurde eine zweite Auflösung von 50ms eingeführt. Diese ist zwar etwas ungenauer, reicht aber bis 54 Minuten und 24 Sekunden. Dies erforderte auch zwei getrennte Anzeigeroutinen für die Zeit, die vom Flag 'fine' im Register 'flags' ausgewählt werden.

Im SRAM wurde der Bereich $060..$0ff für normale Variablen genutzt. Der Bereich $100..$1ff dient als Ringbuffer für das LCD, der Bereich $200..$2ff dient als Indextabelle für die Rangliste. Der Bereich ab $300 aufwärts ist noch unbenutzt, am oberen Ende liegt aber der nach unten wachsende Stack.