Projekt Buzzer
Aufgabe
Ein Imbiss-Geschäft wollte sein bisheriges Bonuskartensystem durch etwas "Peppigeres" ersetzen. Die Idee war mit Hilfe eines Buzzers und einer kleinen Schaltung irgendwie zufällig (mit einstellbarer Wahrscheinlichkeit) entscheiden zu lassen ob die Bestellung kostenlos sein wird oder eben nicht. Das sollte natürlich auch irgendwie spannend sein und fetzig verpackt werden. Also musste es blinken und Lärm machen.
Die Lösung
Erstes Problem war die Zufälligkeit, die in der Aufgabe zu realisieren. Eine Lösung mithilfe einer diskreten Schaltung aus Registern, Schwingkreisen, Gattern oder ähnlichem schied aus, da die Komplexität wohl viel zu groß geworden wäre. Ebenso war es nicht möglich, vorgefertigte ICs (wie beim Zähler) zu verwenden, da derartig spezielle ICs nicht angeboten werden. Also fiel die Wahl auf einen programmierbaren Chip.
ICs
Alles, was am Anfang da war, war der Buzzer, der die ganze Anlage steuern sollte.
PIC-Controller
Bei der Schaltung sollte es sich möglichst nicht um einen kompletten PC handeln, aber gewisse freie Programmierbarkeit war trotzdem notwendig. Die Familie der PIC-Controller von Microchip bietet eine reiche Auswahl an Typen. Nachdem abschätzbar war, welche Anforderungen an Geschwindigkeit, Speicherbedarf und IO-Pin-Anzahl gestellt waren, fiel die Wahl auf einen 16F84A.
Der 16F84A bietet:
- Taktraten zwischen 32 kHz und 10 MHz, wobei bis zu 4 MHz bereits mit einfachen RC-Oszillatoren (also ein Widerstand und ein Kondensator) erreicht werden können
- 68 Byte Daten-RAM (für Variable im Programm)
- 1k Zeilen Programmspeicher (als Flash-ROM und damit bis zu 1000 mal neu beschreibbar)
- 13 individuell auf Eingang oder Ausgang programmierbare IO-Pins
- des weiteren Hardware-Interrupts, Unterprogramme und einen eingebauten Timer
Zudem existieren für die Entwicklung von PIC-Programmen eine ganze Reihe Tools unter GPL (General Public Licence) und die Dokumentation der PICs seitens Microchip ist ausgezeichnet. Letztendlich sind die PICs auch ausgesprochen preiswert.
Tonerzeugung
Mit einem akkustischen Signal soll dem Kunden mitgeteilt werden, ob er gewonnen oder verloren hat. Das Problem war, wie der Ton generiert werden soll. Der PIC kann zwar schnell genug digitale Signale erzeugen, die man dann jedoch irgendwie in eine Sinus-Schwingung für den Lautsprecher umwandeln muss. Außerdem war nicht klar, ob überhaupt genug Speicher für ca. 10 Sekunden Ton vorhanden ist und wie der Ton im PIC sinnvoll gespeichert werden kann. Die Suche nach speziellen Soundgeneratoren war nicht sehr erfolgreich. Die gefundenen Sirenen- oder Alien-Geräusche waren eher ungeeignet.
Ein Hinweis in den Net-News führte zur ISD-Chipfamilie von Winbond. Diese Chips finden eigentlich in digitalen Anrufbeantwortern Verwendung. Der letztendlich verwendete ISD-1416 hat folgende Eigenschaften:
- Integrierter analoger Eingang zur Aufnahme von Ton
- Adressierbarer Flash-Speicher (mehrere getrennte Samples gleichzeitig im Chip speicherbar)
- 16 Sekunden Speicherdauer bei 125ms Chunk-Größe und 8 kHz Sampling-Rate
- Einfache Ansteuerung durch Startimpulse (nach Startsignal spielt Chip bis zum Ende des Sample)
- Eingebauter Verstärker zur Ansteuerung des Lautsprechers
Spannungsstabilisierung
Obwohl ein stabilisiertes Netzteil zum Einsatz kam, zeigte es sich, dass dessen Ausgangsspannung mit einem 50 Hz Brummen überlagert war. Obwohl der PIC als auch der ISD einen internen Taktgeber besitzen, führte dieses Brummen zu einer gestörten Tonaufnahme. Eine Stabilisierung mit Kondensatoren erwies sich als wirkunglos. Zudem produzierte das Netzteil 6V, die Schaltung sollte jedoch als TTL (max. 5V) ausgelegt sein. Der nötige Spannungsabfall sollte eigentlich von einer Diode erbracht werden. Spannungsabfall und -stabilisierung zugleich bot ein LM7805.
Baugruppen
Die komplette Elektronik sollte in einem kleinen Kunststoffgehäuse untergebracht werden. An dieses Gehäuse sollten der Buzzer, der Lautsprecher und die LED-Anzeige angeschlossen werden.
Controller
In diesem soll eine kleine Platine untergebracht werden, auf der die gesamte Elektronik (bis auf die LEDs) untergebracht werden soll. Die Anschlüsse sind in unterschiedlichen Bauformen zu realisieren, um im späteren Betrieb Beschädigungen durch falsches Zusammenstecken zu vermeiden. Außerdem sollte ein Drehschalter zur Auswahl der Wahrscheinlichkeit eingebaut werden.
LED-Anzeige
Ein vom Controller abgesetztes zweites Gehäuse sollte eine Reihe roter und grüner LEDs beherbergen, die durch unterschiedliches Blinken optisches Feedback liefern. Es ergab sich, dass 12 grüne und 13 rote LED einen Kreis bzw. ein Kreuz bilden. Jeweils die Hälfte der LEDs einer Farbe wurden zu einer Gruppe zusammengeschaltet. Dadurch wurden interessantere Blinkmuster möglich. Weil der PIC mit seinen IO-Pins keinesfalls genug Leistung ausgeben konnte, um 25 LEDs (je 20 mA) gleichzeitig anzusteuern, wurden noch Transistoren als Verstärkerstufen eingebunden. Für die LEDs sind die 5V-TTL-Level wiederum zu hoch. Also braucht jede LED einen zusätzlichen Widerstand, der entsprechend Spannung abfallen lässt. Die Anzahl der Bauteile konnte reduziert werden, indem die Widerstände in SIL-Bauweise mit gemeinsamer Masse nach den LEDs eingebaut wurden. LEDs und Widerstände wurden in ein weiteres Gehäuse ausgelagert, welches mit einem 6-poligen Kabel per D-Sub-Stecker angeschlossen wurde.
PIC-Programm
Das Grundprinzip des Programmes lässt sich folgendermaßen beschreiben: Im Normalbetrieb blinken die LEDs in einem gleichförmigen Muster, welches eine gewisse Unbestimmtheit zeigt. Sobald der Kunde auf den Buzzer drückt, wird das Blinken unterbrochen und es wird ermittelt, ob der Kunde gewonnen hat oder nicht. Das Blinken wechselt das Muster und je nach Ausgang der Gewinn-Entscheidung wird letztendlich Rot oder Grün angezeigt und ein entsprechender Ton ausgegeben. Danach setzt das Programm das normale Blinken fort.
Grundsätzliches zum Programm
Der PIC wird beim Übertragen des Programmes gleichzeitig konfiguriert. So wurde der integrierte Watchdogtimer deaktiviert (_WDT_OFF) und die Oszillatorkonfiguration auf den einfachen RC-Typ (_RC_OSC) eingestellt.
PICs sind streng nach Harvard-Architektur aufgebaut. Neben einem Speicher für Programmcode existiert ein getrennter Speicher für Daten. Mit Hilfe des cblock Konstruktes oder einer Reihe von equ-Statements können die Adressen für verschiedene Variablen definiert werden. Die so definierten Namen können anstelle numerischer Werte als Ziel oder Quelle von Registeroperationen verwendet werden. Eine Reihe von Registern übernehmen spezielle Funktionen innerhalb des PIC und sind nicht als normaler Variablenspeicher verwendbar (z.B. STATUS, INTCON, PORTA/B, TMR0 ...). Für diese Register sind entsprechene Bezeichner im Include-File definiert, so dass auch dafür symbolische Namen verwendet werden.
Nach dem Reset bzw. Systemstart beginnt der PIC stets an Adresse 0 mit der Programmausführung. An Adresse 4 befindet sich jedoch sofort der Interruptvektor. Das ist die Adresse, an der fortgesetzt wird, sobald ein Interrupt auftritt. Da das hier notwendige Hauptprogramm nicht in nur 4 Befehle passt, beginnt das Programm mit einem Sprung zum eigentlichen Hauptprogramm weiter hinten im Speicher. Nachdem dort einige Initialisierungen durchgeführt wurden, beginnt das Programm eine Endlosschleife abzuarbeiten, die - außer im Interruptfall - nie verlassen wird.
Normales Blinken
Die Ansteuerung der vier LED-Gruppen (je zwei grün bzw. rot) erfolgt über PortA.0 bis PortA.3 des PIC. Die Belegung dieser vier Bit soll ähnlich der Wertefolge bei der Ansteuerung eines Schrittmotors im Halbschrittbetrieb sein (siehe Tabelle). Da der PIC nicht direkt aus dem Programmspeicher lesen kann und das Ablegen dieser Werte im RAM zu viel Verschwendung kostbaren Speichers darstellt, muss ein Trick angewendet werden:
Der PIC kennt die Anweisung retlw xyz, die nicht nur ein Unterprogramm verlässt, sondern dabei gleichzeitig einen Wert in das W-Register schreibt. Mit mehreren dieser Anweisungen in Folge lässt sich nun eine Wertetabelle im Programmspeicher ablegen. Um bei jedem Aufruf des Unterprogrammes den passenden Wert zurück zu liefern, wird dem Unterprogramm im W-Register die Zeilennummer als Offset übergeben. Dieser Offset wird im Unterprogramm auf das Register PCL addiert, welches einen direkten Zugriff auf die unteren 8 Bit des Programmcounters ermöglicht. Dadurch werden vor dem nächsten Befehlszyklus entsprechend viele Befehle übersprungen und die passende retlw-Anweisung ausgewählt. Im Hauptprogramm zählt ein ensprechender Zähler zwischen 0 und 7 den Offset ab.
gn2 | rt2 | gn1 | rt1 | |
0 | - | - | - | X |
1 | - | - | X | X |
2 | - | - | X | - |
3 | - | X | X | - |
4 | - | X | - | - |
5 | X | X | - | - |
6 | X | - | - | - |
7 | X | - | - | X |
Der PIC ist mit ca. 1 MHz getaktet. Da bei dieser Geschwindigkeit der Mensch vom Blinken praktisch nichts merken würde, muss nach jedem Belegungswechsel am PortA eine entsprechende Pause eingelegt werden. Dazu dient das Unterprogramm namens pause, welches in zwei ineinander geschachtelten Schleifen Rechenzeit "verbrennt". Die Länge der äußeren Schleife kann dabei mit Hilfe eines Wertes in W eingestellt werden. Durch die Belegung der Ausgänge nach obigem Muster wird der "gefühlte" Takt noch einmal grob halbiert, da in 3 von 8 Takten eine LED-Gruppe durchgehend leuchtet.
Zufall und Interrupt
Mit deterministischer Hardware ist es schwierig, echten Zufall zu erzeugen. Hier kam wieder eine Spezialität des PIC zum Einsatz: Der eingebaute Timer TMR0. Dieser Timer läuft parallel zur Programmausführung und wird mit jedem Instruktionszyklus (4 Takte) um 1 erhöht. Hat er seine maximalen Wert (255) erreicht, beginnt er wieder bei Null. Der Zufallswert, der zum Ermitteln des Gewinns verwendet wird, entspricht demnach dem Zählerstand zum Zeitpunkt des Drückens auf den Buzzer. Der Zufall wird also allein durch den Kunden erzeugt. Eine Manipulation durch Abzählen ist nicht möglich, wenn man bedenkt, dass der PIC bei 1 MHz ca. 1000 mal in der Sekunde den Wertebereich des Zählers durchläuft.
Wenn der Buzzer betätigt wird, muss das normale Blinken unterbrochen werden und die Ermittlung des Gewinnes erfolgen. Dazu löst der Buzzer einen Interrupt an IO-PIN PortB.0 aus. Das führt dazu, dass der PIC seine aktuelle Programmausführung sofort unterbricht und an Adresse 4 fortsetzt. Dort werden als erstes die Inhalte des W- und des STATUS-Registers gesichert. Automatisch werden sowohl alle Interrupts global als auch der externe Interrupt deaktiviert.
Um zu entscheiden, ob der Kunde gewonnen hat, wird der Zählerstand durch eine Gewinnzahl dividiert. Der PIC kennt keine Divisionsoperation, daher muss die Division durch fortschreitende Subtraktion realisiert werden. Es wird so lange der Divisor vom Dividenden subtrahiert, bis Null oder ein Übertrag angezeigt wird. Diese beiden Status-Bits werden im STATUS-Register abgelegt. Dabei ist zu beachten, dass bei Subtraktion das Carry-Flag als "not borrow" zu interpretieren ist, d.h. wenn es zu einem Übertrag kommt, ist das C-Flag auf Null gesetzt. Gelang die Division ohne Rest, also wurde die Null getroffen, so hat der Kunde gewonnen.
Die jeweilige Gewinnzahl soll per Drehschalter von außen ausgewählt werden. Es sind sechs unterschiedliche Gewinnzahlen einprogrammiert. Dazu werden PortB.1 bis PortB.6 mit den Ausgängen des Drehschalters verbunden, der je nach Einstellung einen dieser Ausgänge auf Masse verbindet. Auch hier kommt wieder eine Spezialität des PIC zum tragen: Interne Pull-Ups sorgen dafür, dass die nicht auf Masse verbundenen IO-Pins einen definierten Wert, nämlich 1, tragen. Somit ist das IO-Pin, welches den Wert 0 führt, eingestellt.
Anzeige des Ergebnisses
Nachdem über Gewinn oder nicht entschieden wurde, muss diese Information noch nach außen gegeben werden. Damit das etwas spannender wirkt, wird das Ergebnis erst nach ein paar weiteren Blinkschritten angezeigt. Das ursprüngliche Blinken weicht deshalb einem wechselseitigen Blinken aller roter bzw. grüner LEDs. Anfangs ist die Blinkfrequenz etwa so hoch wie das Blinken im Wartezustand. Damit der Eindruck erweckt wird, das Ergebnis würde während des Blinkens ermittelt, wird eine zufällige Zeit mit der hohen Frequenz geblinkt. Die Anzahl der Farbwechsel wird durch die unteren 3 Bit des bereits bekannten TMR0-Registers bestimmt und ist damit wieder zufällig.
Im nächsten Schritt wird das Blinken immer langsamer. Das wird dadurch realisiert, dass die bereits bekannte pause-Funktion mit immer größeren Werten aufgerufen wird. Dazu wird ein Zähler schrittweise verdoppelt, bis es zu einem Übertrag kommt. Dieser Übertrag ist im Carry-Flag zu finden.
Am Ende wird das Abspielen des Tons ausgelöst, indem beim ISD-Chip das Play-Edge-Signal getriggert wird. Daraufhin aktiviert sich der ISD-Chip und spielt das gespeicherte Sample bis zum Ende ab.
Hier galt es erneut ein kleines Problem zu lösen. Der ISD-Chip hat 8 Adress-Pins. Am PIC waren aber nur noch zwei Pins für das Abspielen des Sounds verwendbar. Selbst ein Umbau der gesamten Schaltung hätte niemals genug Pins frei bekommen, um alle 8 Adress-Pins gleichzeitig anzusteuern. Die Lösung bestand darin, nur ein einziges Adress-Pin zu verwenden, Pin A6, und die restlichen Pins auf Masse zu legen. Damit konnte zwar nur noch zwischen Adresse 0000:0000 und Adresse 0010:0000 umgeschalten werden, mehr als zwei Samples waren aber sowieso nicht zu speichern. Da eine Speicherzelle ca. 100ms Ton speichert, ist der erste Bereich ca. 6.4 Sekunden lang und der zweite Teil ca. 9.6 Sekunden.
Nach einer weiteren Pause, in der die jeweils passenden LEDs leuchten, wird an das Ende der Interrupt-Routine gesprungen. Dort wird zuerst der externe Interrupt wieder reaktiviert. Danach werden die gesicherten Register wieder hergestellt. Erst das retfie reaktiviert die Interrupts wieder global und springt an die Stelle, an der das Programm unterbrochen wurde.
Das vollständige Programm ist hier abrufbar.
Schaltung
Der Schaltplan wurde mit Eagle entworfen und mit Eagle3D visualisiert. Zu beachten ist, dass die LEDs und die Widerstände auf der linken Seite in einem abgesetzten Gehäuse sind. Der Drehschalter wurde ebenfalls mit freier Verdrahtung angeschlossen.
Bauteile
Name | Wert | Verwendung |
---|---|---|
C1 | 100p | RC-Oszillator |
C2 | 100n | Schwingungsunterdrückung |
C3 | 100n | Hochpass |
C4 | 330n | Schwingungsunterdrückung |
IC1 | PIC16F84A | Microcontroller |
IC2 | ISD1416 | Voice Recorder |
IC3 | LM7805 | Spannungsregler |
LED_A6 | 5V-LED | Adressauswahlanzeige |
LED_REC | 5V-LED | Aufnahmeanzeige |
R1 | 5.1k | RC-Oszillator |
R2 | 100 | Schutzwiderstand |
R3 | 10k | Pull-Up |
R4 | 27 | LED-Vorwiderstand |
R5 | 15k | Hochpass |
R6 | 220 | Pull-Up |
RN0-RN3 | 8x120 | LED-Vorwiderstand |
S1-S3 | Umschalter | Soundprogrammierung |
S4 | Drehschalter | Zufallsauswahl |
T0-T3 | NPN-Transistor 2N 914 | Verstärker |
LED_BL | Blaue LED | cool aussehen |
LED_GNx | Grüne LED | Blinkblinkblink |
LED_RTx | Rote LED | Blinkblinkblink |
Dazu noch diverse Buchsen, IC-Sockel und Kabel.
Programmierung Sound
Damit man nach ein paar Monaten Betrieb auch mal einen anderen Ton einspielen kann, sollte der Ton auch in der fertigen Schaltung änderbar bleiben. Der ISD-Chip bringt dazu eine eigene Infrastruktur aus Mikrofonverstärker, Pegelanpassung und Rauschfiltern mit. Hat man aber ein Line-Out-Signal, so kann man die Soundquelle auch direkt an ANA_IN anschliessen. Um eine Aufnahme durchzuführen, muss zum einen an A0-A7 die Startadresse des Samples angelegt sein und zum anderen REC auf Masse gezogen werden. So lange REC auf Masse liegt, zeichnet der ISD-Chip den Ton auf. Lässt man REC auf high gehen, wird eine "End of Message" Markierung im Speicher abgelegt, die dazu führt, dass ein Abspielen an dieser Stelle stoppt.
Mit Hilfe der Umschalter S1-S3 kann die Schaltung vom Normalbetrieb auf Aufnahmebetrieb umgeschaltet werden. Dazu wird zum einen durch S1 der Buzzer vom PIC auf den ISD-Chip umgeleitet. Zum anderen wird mit S2 der Adresseingang A6 auf den Umschalter S3 umgeleitet. Mit Umschalter S3 kann nun das Aufnahmeziel (0000:0000 oder 0010:0000) eingestellt werden.