Schnellstart mit dem HC12
Welcome-Kit für den 16-Bit-Mikrocontroller von Motorola
von Andreas Dannenberg und Oliver Thamm
Erschienen in ELEKTRONIKPRAXIS
Sonderheft Embedded Systems - Juni 2000
Der Motorola Mikrocontroller 68HC12 ist der neue "große Bruder" des besonders in Deutschland weit verbreiteten 68HC11. Im Gegensatz zum 8-Bit Vorfahren besitzt der HC12 einen 16-Bit CPU-Kern mit erweitertem Befehlssatz, höherer Taktgeschwindigkeit (8 MHz Bustakt) und leistungsfähigeren Ein/Ausgabeschnittstellen. Für Anwenderprogramme stehen nunmehr bis zu 1 KByte RAM und 4 KByte EEPROM Speicherkapazität zur Verfügung. Vorhandenen Quellcode kann man leicht vom HC11 auf den HC12 übernehmen.
Das HC12 Welcome Kit ermöglicht es, Controller und Software ohne langwierige Vorarbeiten im Hardwarebereich sofort auszuprobieren. Dieses Tutorial beschreibt die Schritte von der Inbetriebnahme bis zum ersten Programm.
Teil 1 - Leuchtendes Beispiel
Der erste Teil des Tutorials ist im Sonderheft Embedded Systems (Juni 2000) der Fachzeitschrift ELEKTRONIKPRAXIS erschienen. Eine Online-Version dieses Artikels befindet sich in Vorbereitung.
Teil 2 - Kleine Nachtmusik
Mit dem Handwerkszeug des ersten Teiles ausgestattet, möchten wir uns nun an ein etwas komplexeres Programmierprojekt wagen, obwohl dieses natürlich auch nicht das gesamte Leistungspotential unseres Controllers repräsentieren kann. Der Controller soll mit der Ausgabe einer Melodie beschäftigt werden.
Dazu sind erst einige kleine hardwareseitige Vorbereitungen nötig. Für unser Projekt benötigen wir einen kleinen Lautsprecher und einen Widerstand von etwa 500 Ohm. Einen Anschluß des Minilautsprechers löten wir über einen Vorwiderstand an Pin 50 (VCC) des Steckverbinders ST6 an. Da wir aber nun an bestimmte Portpins, genauer gesagt die Timer-Portpins gebunden sind, kommen wir nicht daran vorbei, auch den zweiten 50-poligen Steckverbinder zu verwenden. Mit dessen Pin 44 (Timer Port 0 = PT0) ist wieder mittels eines Pfostensteckverbinders die andere Klemme des Lautsprechers zu verbinden.
Um das etwas umfangreichere Programmlisting nicht abtippen zu müssen, liegt es auch auf dem Webserver des Elektronikladen zum Download bereit.
Zählen und zählen lassen
Der Controller besitzt ein kontinuierlich arbeitendes Zählerregister. Es ist 16-Bit breit, der Zählumfang ergibt sich somit zu 0 bis 65535. In gleichbleibenden Abständen (einstellbar von 125 ns bis 4 µs) wird der Zähler weiter getaktet. Nach Erreichen des höchsten Wertes geht es mit Null von vorn los. Der Zählerstand ist jederzeit über das zwei Byte umfassende "Timer/Counter Register" (TCNT) abrufbar. Die Zählfrequenz wird mit drei Bits im "Timer Interrupt Mask 2 Register" (TMSK2) festgelegt - Einzelheiten siehe HC12-Datenblatt.
Für die gezielte Ausgabe bestimmter Frequenzen möchten wir das Interruptsystem unseres Controllers im Zusammenspiel mit der "Output-Compare"-Funktion benutzen. Unmittelbares Ziel ist es dabei, eine Portleitung in bestimmten Abständen (Periodendauer) ein- bzw. auszuschalten.
Ein eleganter Weg besteht in der Nutzung einer von acht mit dem Timersystem verbundenen Portleitungen (PORTT). Diese können automatisch zu speziellen Ereignissen (Output-Compare Interrupts) im Controllerinneren ein-, aus- oder umgeschaltet werden.
Und so läuft ein Output-Compare Ereignis ab: Für jede der acht Leitungen des Port T ist ein 16 Bit breites, genanntes Steuerregister vorhanden (TC0 .. TC7). Eine interne Logik vergleicht ständig den Inhalt dieser Register mit dem Inhalt des freilaufenden Zählers TCNT. Wenn nun der Wert eines dieser Register mit dem Timer übereinstimmt, spricht man von einem Output-Compare Ereignis. Als Reaktion können zwei Aktionen stattfinden. Zunächst wird eine Programmunterbrechung, also ein Interrupt angemeldet. Zudem kann man die zum Ereignis gehörige Portleitung (ein-, aus-, um-) schalten lassen. Das geschieht "hardware-automatisch" und zudem genau im Augenblick der Übereinstimmung von Output-Compare Register und Zähler TCNT.
Und wie ist das mit den Interrupts? Ist die Interruptlogik freigegeben (dazu später mehr), rettet die CPU automatisch den Inhalt sämtlicher CPU-Register und verzweigt zur Interruptserviceroutine. An welcher Adresse sich diese vom Benutzer verfaßte Interruptbehandlung befindet, trägt man in eine Interrupt-Vektortabelle ein. Diese befindet sich ganz am Ende des HC12 Programmspeichers und enthält für jeden HC12 Interrupt einen zwei Byte umfassenden Eintrag - die Startadresse der Interrupt.
Die aufgerufene Interruptserviceroutine sollte den Code enthalten, der die planmäßige, erneute Auslösung eines Interrupts veranlaßt. In diesem Zusammenhang ist auch das Löschen des entsprechenden Interruptflags obligatorisch. Man erreicht das Löschen eines Interruptflags interessanterweise durch Beschreiben der Bitposition mit einer Eins (merken!). Man sollte dabei auf eine einfache STAA- oder MOVB-Anweisung zurückgreifen, eine BSET-Anweisung hingegen kann hier große Probleme verursachen! Nach der Rückkehr aus der Interruptserviceroutine per RTI-Anweisung restauriert die CPU die eingangs gemerkten CPU-Register und führt das Hauptprogramm an der Stelle fort, an der die Unterbrechung stattfand. Voila!
Quelltext-Kompositionen
Dieses System der Timerinterrupts wollen wir jetzt zur Ausgabe bestimmter Tonfrequenzen benutzen. Die Periodendauer T einer Frequenz f läßt sich über die Formel T=1/f bestimmen und daraus die Anzahl der Timerschritte ableiten, nach denen unsere Portleitung umzuschalten ist.
Im ersten Teil des Quelltextes werden per include-Anweisung des Assemblers die Registerdefinitionen des HC12 eingefügt. Das erspart uns das ständige Hantieren mit den hexadezimalen Adressen der Steuerregister. Diese Definitionen können in der Includedatei "reghc12.inc" eingesehen werden und stimmen mit den im Datenblatt des Controllers aufgeführten Bezeichnungen überein.
Das Programm wird im EEPROM (ab Adresse $F000) des Controllers abgelegt. Zusätzlich wird aber noch eine Merkzelle für die Zwischenspeicherung einer Variable benötigt. Deshalb sind im Listing zwei ORG Anweisungen enthalten. Die erste ("org $0800") sorgt dafür, daß im RAM-Bereich Speicher reserviert wird, die zweite ist für die Startadresse des Codes im EEPROM zuständig. Die Reservierung des Speichers geschieht mittels "ds.w"-Pseudoanweisung, was soviel heißt wie "define space word". Es wird also ein Wort (zwei Byte) im RAM reserviert und mit dem Label "BuzzPeriod" darauf verwiesen. Dieser Speicherplatz wird zur Ablage der Periodendauer der auszugebenden Tonfrequenz benutzt.
Pseudovektoren
Die Interruptvektoren liegen normalerweise am Ende des Speichers. Diese Stelle belegt auf dem HC12 Welcome Kit jedoch das Monitorprogramm. Um das Interruptsystem dennoch nutzen zu können, leitet TwinPEEKs alle Vektoren in den RAM um. Dort werden nun 3 Byte in eine "Pseudo"-Interruptvektortabelle eingetragen. Um einen Zeiger für die Interruptroutine des Output-Compare 0 Registers einzurichten, wird der Opcode des JMP-Befehls ($06) an die Adresse $0BE8 und der Zeiger auf die Routine an die Adressen $0BE9 (High-Byte) und $0BEA (Low-Byte) geschrieben. Die Adressen der anderen Interruptvektoren sind im TwinPEEKs-Handbuch dokumentiert.
Im "Timer Interrupt Mask 2 Register" (TMSK2) wird nun der Vorteiler des Hauptzählers auf 4 eingestellt. Bei einem Systemtakt von 8 MHz bedeutet das eine Zeitdauer von 0,5 µs zwischen den Zählschritten. Nach dem Einschalten des Timersystems durch Setzen des Bit 7 im "Timer System Control Register" (TSCR) und der Konfiguration des Portpins PT0 als Output-Compare im "Timer Input Capture/Output Compare Select Register" (TIOS), wird durch einen Eintrag im "Timer Control 2 Register" (TCTL2) der Ausgabepin zunächst vom Timersystem abgekoppelt. Dadurch wird die (unerwünschte) Ausgabe von Tonfrequenzen unterbunden, sozusagen eine Mute-Funktion.
Damit vom Controller Interrupts ausgeführt werden können und unsere Serviceroutine angesprungen wird, ist sowohl die lokale Interruptfreigabe des OC0 Registers durch Setzen des Bits 1 im "Timer Interrupt Mask 1 Register" (TMSK1), als auch die globale Interruptfreigabe mittels CLI Anweisung zu aktivieren.
Von diesem Moment an wird bei jeder Übereinstimmung des TC0 Registers mit dem Timer-Register TCNT die Interruptserviceroutine isrOC0 angesprungen. Als erstes müssen wir dafür sorgen, daß ein neuer Output-Compare Wert eingetragen wird. Diesen neuen Wert errechnen wir, indem wir zum augenblicklichen Stand des Output-Compare Registers eine Konstante addieren. Diese Konstante entspricht der gewünschten Periodendauer. Sie stammt aus der im Hauptprogramm reservierten Speicherzelle BuzzPeriod. Das Ergebnis wird in das Output-Compare Register zurückgeschrieben. Weiterhin wird durch das Schreiben einer Eins auf das "Timer Interrupt Flag 1 Register" (TFLG1) das OC0 Interruptflag gelöscht.
Das Hauptprogramm besteht aus einer Schleife, welche nacheinander die Notenwerte unseres Songs einliest und auswertet. Jeder Notenwert wird dekodiert, um Frequenz und Tondauer (mit Hilfe der beiden Tabellen freqTBL und durTBL) zu ermitteln. Die Frequenz wird danach in eine Periodendauer umgerechnet und diese über die Merkzelle BuzzPeriod an die isrOC0-Routine übergeben. Dabei ist zu beachten, das nur die halbe Periodendauer der tatsächlich auszugebenden Frequenz ermittelt wird, da zur Erzeugung einer Frequenz ja innerhalb deren Periodendauer zweimal der Pegel gewechselt werden muß.
Anschließend wird die Tonausgabe, soweit es sich nicht gerade um eine Pause handelt, durch das Setzen des Bit 1 im "Timer Control 2 Register" (TCTL2) eingeschaltet. Das bewirkt, daß die im Controller integrierte Logik bei jedem Auslösen des TC0 Interrupts den Pegel unserer Portleitung invertiert (Toggle-Modus). Die dadurch ausgegebene Rechteckimpulsfolge wird als Ton hörbar.
Nach Ablauf der aus der durTBL-Tabelle ermittelten Notendauer wird der Ton wieder abgeschaltet (Löschen des Bit 1 im TCTL2 Register). Eine zusätzliche kurze Pause nach jedem Ton gibt der Melodie etwas mehr Rhythmus. Anschließend erfolgt eine Verzweigung zurück zum Beginn des Hauptprogrammes. Es werden solange weitere Notenwerte bearbeitet, bis das Programm auf die Note mit der Ende-Markierung trifft ($80 = Bit 7 gesetzt).
Noten aus der Tabelle
Die Noten des zu spielenden Liedes sind am Programmende unter der Marke song abgelegt. Um beim Komponieren nicht ständig mit den Zahlenwerten der Tabellenoffsets hantieren zu müssen, wurden diese durch symbolische Konstanten mittels EQU-Pseudoanweisung ersetzt. Beim Übersetzen ersetzt der Assembler nun alle Zeichenfolgen, welche links einer EQU-Anweisung stehen, durch die der entsprechenden rechten Seite. So wird beispielsweise ein mit "_AIS" bezeichneter Notenwert durch die Zahl 11 ersetzt, welche dann auf die Frequenz 932 Hz in der freqTBL-Tabelle verweist.
Die Definition der Notenwerte wird durch die dc.b Anweisung realisiert welche Speicherplatz für ein Byte bereitstellt. Wird eine Note ohne weitere Angaben eingetragen, erfolgt die Tonausgabe mit der Länge einer 1/4-Note in der oberen der beiden vorgesehenen Oktaven. Zur Realisierung abweichender Tonlängen bzw. Oktaven sind die im Listing definierten Symbolischen Konstanten _1_8, _3_8 und _1_2 bzw. _DEEP durch einfache Addition zum Notenwert hinzuzufügen. Beispielsweise ergibt die Zeile
dc.b _A + _1_2 + _DEEP
die Ausgabe eines langen Kammertones A. Das Songende wird durch die Marke _STOP gekennzeichnet.
Nach dem Übersetzen des Quelltextes, Senden an unser HC12 Welcome Kit und Start mittels Autostart-Funktion (oder der Eingabe von "G F000") werden wir - jetzt fast schon HC12 Experten - mit einer netten Melodie verwöhnt.
Viel Spaß beim Komponieren!
Quelltext
Halten Sie die Shift Taste beim Anklicken gedrückt, um de Quelltext MELODIE.A auf Ihrer Harddisk abzuspeichern.
Literatur
- Oliver Thamm: Weitere Veröffentlichungen
Links
URL dieser Veröffentlichung: http://hc12web.de/epsh0006/
Motorola's MCU Website: http://www.motorola.com/mcu/
Oliver Thamm's HC12 Web: http://hc12web.de