Burkhard Kainka, André Helbig

Messen, Steuern, Regeln mit C-Control II

Franzis-Verlag, ca. Dezember 2002


Auszüge aus Kapitel 10: Assembler-Systemfunktionen

Mit eigenen Unterprogrammen in Assembler oder C lassen sich zeitkritische Aufgaben lösen, die mit der Programmiersprache C2 allein nicht lösbar sind. C-Control II liegt das Entwicklungspaket Tasking C/C++ Tools in einer eingeschränkten Demo-Version bei, die sich hervorragend für die Arbeit mit dem System eignet. Der Einstieg ist allerdings nicht ganz einfach und es sind einige Hürden zu überwinden. Wertvolle Hilfe wurde auf www.CC2Net.de gefunden. Besonders die Beiträge von Martin Förster, Erik Hospel und Cris Ullmann haben uns etliche Stunden mühevoller Vorversuche erspart, wofür wir uns herzlich bedanken.

Grundsätzlich ist es möglich, völlig eigenständige Programme in Assembler zu schreiben, die ohne das Betriebssystem der CC2-Unit auskommen und autonom starten. Immerhin hat man hier eine sehr leistungsfähige Hardware, die mit Assembler zu wahren Höchstleistungen auflaufen kann. Allerdings müssen dann alle Initialisierungen des Controllers selbst vorgenommen werden, was mit erheblichem Aufwand verbunden ist. Deshalb sollen hier nur kleine Unterprogramme vorgestellt werden. Auch komplexe Lösungen können wesentlich vereinfacht werden, wenn man die Leistungen des Betriebssystems so weit wie möglich in Anspruch nimmt und nur die wirklich zeitkritischen Teile eines Programms in Assembler schreibt.

10.1 Aufruf von Systemfunktionen

Für die ersten Versuche wird von einem kleinen Assembler-Unterprogramm ausgegangen, das bereits in assemblierter Form als test1.hex auf der CD vorliegt. Das kleine Programm erzeugt 10 schnelle Impulse am Port P1L.0, wobei die Initialisierung als Ausgangsport vorausgesetzt wird. Der Quelltext test1.asm wird erst im nächsten Abschnitt genauer vorgestellt. Hier soll es zunächst um die Verwendung und den Aufruf aus C2 gehen.

Eine fertig übersetzte Systemroutine muss zunächst in die C-Control II Unit geladen werden. Dazu wird nicht die C2-Entwicklungsumgebung verwendet, sondern das Download-Tool. Der vorgesehene Adressbereich für eigene Routinen ist das Segment 3 im Flash-EEPROM, also der Bereich ab Adresse 30000h. Der Anwender muss diese Adresse jedoch nicht selbst eingeben, weil sie bereits im Hex-File steht und automatisch vom Downloadprogramm beachtet wird. Man braucht nur das Download-Programm zu starten und eine Datei vom Typ INTEL-HEX zu laden, wobei die Unit sich im Hostmodus befinden muss.

Abb. 10.1 Laden einer Hex-Datei

Abb. 10.2 Übertragen des Programms

Dieses Programm steht nun im Segment 3 dauerhaft zur Verfügung und übersteht beliebig viele Versuche mit neu geladenen C2-Programmen, die im Segment 4 geführt werden. Das Programm test1.hex kann von jedem C2-Programm mit system.call (3,0) aufgerufen werden, wobei als Parameter das Segment (3) und die Einsprungadresse (0) innerhalb des Segments angegeben werden müssen. Die genaue Adresse richtet sich nach dem verwendeten Assembler- oder C-Projekt. Ein Projekt kann z.B. mehrere aufrufbare Funktionen haben, die dann unterschiedliche Einsprungadressen besitzen.

Das folgende C2-Programm ruft das Assembler-Unterprogramm in schneller Folge auf, nachdem zuvor der Port 0 (P1L.0) durch ports.set in Ausgaberichtung geschaltet wurde.

/********************************
    ASM1.c2
    Schnelle Portausgaben
    Aufruf von ASM1.hex
********************************/

//------------
  thread main
//------------
{
  int n;
  ports.set(0,0);
  for n=1...200
  {
    system.call(3,0);
    sleep 1;
  }
  sleep 800;
}

Listing 10.1 Verwendung eines Systemaufrufs

Das Ergebnis kann sich sehen lassen: Mit einem Oszilloskop erkennt man schnelle Impulspakete an P1L.0. Es werden jeweils 10 Impulse in insgesamt 8 Mikrosekunden ausgegeben. Im Vergleich zu reinen C2-Programmen ist also mit Assembler eine rund 1000 mal höhere Geschwindigkeit möglich! Das Ausgangssignal reicht damit schon in den Hochfrequenzbereich. Falls kein Oszilloskop zur Hand ist, genügt auch ein Radio, um den Erfolg zu überprüfen. Ein kurzes Stück Draht am Port P1L.0 dient als Antenne. Das Radio empfängt dann im Mittelwellenbereich bei etwa 1200 kHz die Signale der CC2-Unit. Man hört eine Art Zeitzeichen, also kurze 1-kHz-Töne im Abstand einer Sekunde.

Außer dem Unterprogrammaufruf mit system.call(3,0) gibt es auch den Programmsprung mit system.jump(3,0), der hier allerdings nicht verwendet werden kann, weil das Assembler-Unterprogramm mit einem Rücksprungbefehl endet. Ein Programmsprung kann aber verwendet werden, wenn ein Programm ganz auf die virtuelle Maschine verzichten möchte und endgültig zu einem Maschinenprogramm verzweigen soll.

Statt eines direkten Aufrufs kann ein (verändertes!) Maschinen-Unterprogramm auch an ein Interruptereignis angehängt werden. Die virtuelle Maschine kennt dafür fünf mögliche Interruptquellen. Neben dem immer aktiven 1-ms-Timer der Unit (EVENT_TIMER) können auch die Interrupt-sensiblen Ports P1H0 (EVENT_P1H0) bis P1H3 (EVENT_P1H0) eingesetzt werden. Das System ruft bereits selbst Interruptfunktionen auf. Der Timer sorgt auf diese Weise für die Verwaltung der Echtzeituhr und den Empfang der DCF-Signale. Über Port-Interrupts werden auch interne Zähler und Frequenzmesser aufgerufen. Eine eigene Interruptfunktion kann nun die vorhandenen Systemfunktionen entweder ersetzen (HOOK_REPLACE) oder an sie angehängt werden (HOOK_AFTER) oder ihnen vorangestellt werden (HOOK_BEFORE). Damit ändert sich jeweils der genaue Zeitpunkt des Aufrufs. Für den Aufruf über einen Timer-Interrupt bietet es sich an, die eigne Routine an die erste Stelle zu setzen, sodass die Timer-gesteuerten Systemroutinen mit ihrem unterschiedlich langen Zeitbedarf nicht zu einer Verschiebung des genauen Zeitrasters führen können. Das Maschinen-Unterprogramm benötigt eine etwas andere Art des Rücksprungs, was weiter unten noch genauer erläutert wird. Hier wird das bereits kompilierte Programm ASM2.hex eingebunden, das sich im Ordner des C2-Projekts ASM2.c2p befindet.

/********************************
    ASM2.c2
    Schnelle Portausgaben
    im Timer-Interrupt (ASM2.hex)
********************************/

//------------
  thread main
//------------
 
{ 
  int n;
  ports.set(0,0);
  system.hook (system.EVENT_TIMER,3,0,system.HOOK_BEFORE);
  loop
  {
    ports.set (1,0);
    ports.set (1,1);
  }
}

Listing 10.2 Verwendung eines Timer-Events

Mit diesem Programm passieren zwei Dinge praktisch gleichzeitig. In der Hauptschleife werden laufend relativ langsame Impulse an Port P1L.1 erzeugt. Gleichzeitig werden im Timer-Interrupt kurze Impulspakete an P1L.0 ausgegeben. Beide Vorgänge stehen in keinem festen zeitlichen Verhältnis zueinander. Die Timer-Interrupts werden nun an P1L.0 sichtbar. Dabei erfährt man übrigens, dass die 1-ms-Intervalle des Systemtimers tatsächlich etwas kürzer als eine Millisekunde sind. Die Echtzeituhr ist aber trotzdem exakt, d.h. eine Minute besteht aus etwas mehr als 60000 Hardware-Millisekunden.

10.2 Der Controller C164CI

Um den Controller der C-Control-II Unit selbst hardwarenah programmieren zu können, benötigt man relativ umfassende Informationen über die Hardware des Controllers und seine Programmierung. Auf der CD zu C-Control II findet man die Demoversion des Programmierwerkzeugs Tasking C166-ST10 und das Datenblatt des Controllers. Der C164 gehört in die C166-Familie und verwendet den selben Assembler oder C-Compiler. Die direkt verfügbaren Informationen z.B. über Assembler-Befehle und ihre Anwendung sind aber recht spärlich. Die Herstellerfirmen des Controllers und der Software gehen davon aus, dass jeder Anwender das Vorgängermodell C166 schon gut kennt, deshalb werden im Datenblatt speziell die Besonderheiten des C164 behandelt. Wer allerdings als neuer Anwender der CC2-Unit zum ersten Mal einen 16-Bit-Prozesor dieser Familie programmieren möchte, hat einige Startprobleme. So erging es den Autoren auch. Die mühsame Suche nach genaueren Informationen brachte jedoch einige Ergebnisse, die vielleicht auch dem Leser helfen werden. Hier sollen die wichtigsten Informationsquellen und einige grundlegende Besonderheiten des C164 genannt werden.

Bei der Arbeit mit dem System hat das 80C166er-Lehrbuch von Elektor geholfen, einen ersten Eindruck zu gewinnen (vgl. [6]). Der frei ladbare Compiler READS 166 der Firma Rigel (www.rigelcorp.com) ist speziell für Einsteiger entwickelt worden. Zwar wurde nicht versucht, diesen Compiler für C-Control II einzusetzen, aber das Programm enthält eine wertvolle Hilfedatei mit Informationen zum Befehlssatz des C166.

Eine zunächst ungewohnte Besonderheit des C166/C164 ist sie Verwendung von Daten- und Code-Segmenten. Der gesamte Adressraum ist in Segmente eingeteilt, weil die verwendeten 16-Bit-Register nur jeweils bis zu 64 KB adressieren können. Man muss also jeweils angeben, in welchem Segment man gerade arbeitet. Es gibt vier Datenpointer dpp0 bis dpp3, von denen einer für das Projekt ausgewählt werden soll.

assume dpp3:userseg

Segment 0 enthält das Betriebssystem im Flash-ROM sowie den internen RAM-Bereich des Controllers von 2 K im Bereich F000h bis F7FF. Außerdem befinden sich die Spezialfunktionsregister (SFR und ESFR) des Controllers im Bereich F800h bis FFFFh. Eigene Systemroutinen sollen in das Segment 3 ab Adresse 30000h geschrieben werden. Das 64-K-RAM liegt im Segment 8 und beherbergt C2-Variablen. Es lässt sich jedoch ebenfalls für kleine Assemblerroutinen nutzen (vgl. Kap 10.6).

Auf eine Auflistung der Assemblerbefehle des C164 soll hier aus Platzgründen verzichtet werden. Der Leser kann aber mit den einfachen und leicht überschaubaren Beispiel-Listings die wichtigsten Befehle erlernen. Wer sich tiefer mit der Systemprogrammierung auseinandersetzen will, kommt ohne zusätzliche Literatur nicht aus.

10.3 Der Kommandozeilen-Assembler

Die Tasking C166_ST10 Demo auf der CD zu C-Control II enthält einen Assembler, einen C-Compiler und eine Entwicklungsoberfläche (EDE), mit der sich auch gemischte Projekte erzeugen lassen. Die ersten Schritte mit der Software sind für den ungeübten Anwender nicht ganz einfach, weil es sehr viele mögliche Einstellungen gibt. Die Arbeit mit Tasking soll deshalb hier zunächst etwas genauer vorgestellt werden.

Der folgende Quelltext test1.asm soll in ein Hex-File übersetzt werden. Die Compiler-Anweisungen am Beginn des Quelltexts legen fest, dass hier mit Segmenten gearbeitet wird und dass der Zielprozessor ein C164CI ist. Entscheidend ist die Angabe der Startadresse 30000h, also der Anfang des Segments 3. Das Programm erzeugt 10 Impulse an P1L.0. Vor dem eigentlichen Rücksprung mit RETS müssen zwei Werte vom Stack mit POP abgeholt werden, sofern das Unterprogramm von einem C2-Programm aus mit system.call aufgerufen werden soll. Weitere Einzelheiten zu Assembler-Quelltexten sollen weiter unten erläutert werden.

$case
$segmented
$model(medium)
$extend
$nomod166
$stdnames(reg164ci.def)

                regdef R2
;***************************************************
userseg SECTION CODE word at 30000h
assume  dpp3:userseg
;***************************************************
;  10 Impulse an P1L.0

                public  testport

testport        proc far
                mov     R2,#1
                Loop1:
                bset    P1L.0                   ;einschalten
                nop
                bclr    P1L.0                   ;ausschalten
                cmpi1   R2,#10
                jmpr    cc_SLT,Loop1    ;10 mal
                pop     R2                      ;2xPOP 
                pop     R2
                rets                            ;Rücksprung
testport        endp
userseg ends
                end

Listing 10.3 Schnelle Impulsausgabe in Assembler

Als erstes soll ein vorgegebener Quelltext "zu Fuß" übersetzt werden. Der eigentliche Assembler hinter der Entwicklungsoberfläche kann auch direkt aufgerufen werden. Dazu legt man am besten ein neues Verzeichnis C:\dc166\test an, in das die Programme A166.EXE und IHEX166.EXE sowie der Quelltext test1.asm kopiert werden. Der Aufruf des Assemblers über das Windows-Menü Start/Ausführen lautet dann:

C:\dc166\Test\A166.EXE Test1.asm

Der Assembler liefert die Datei test1.obj, die erst noch mit IHEX166.exe in das Intel-Hex-Format übertragen werden muss, damit das Programm in die Unit geladen werden kann.

C:\dc166\Test\IHEX166.EXE Test1.obj Test1.hex

Das Ergebnis der Konvertierung ist die Datei Test1.hex, die mit einem Texteditor angesehen werden kann:

:020000040003F7
:12000000E0120F82CC000E8280A2CDFBFCF2FCF2DB006E
:00000001FF

Listing 10.4 Das Intel-Hex-File

Es kann nützlich sein, den Aufbau Intel-Hex-Formats zu kennen, weil damit mögliche Fehler erkannt werden. Listing 10.4 verrät z.B. dass das Programm tatsächlich im Segment 3 des Adressraums übersetzt wurde. Außerdem erkennt man die Gesamtlänge von nur 18 Bytes. Wenn wesentlich mehr Code übersetzt wird oder wenn andere Segmente benutzt werden, muss ein Fehler passiert sein.

Ein Intel-Hex-File besteht aus einzelnen Zeilen-Records, die jeweils einem bestimmten Record-Typ angehören, eine Startadresse und die Anzahl der enthaltenen Bytes angeben und mit einer Prüfsumme abgesichert sind.

:NNAAAARRHHHHHHHHHHHH....HHHHCCTT

: Zeilenanfang
NN Anzahl der Programmbytes in der Zeile
AAAA Adresse des ersten Bytes
RR Record-Typ: 00=Maschinencode, 01=letzte Zeile, 04=Codesegment
HH Programmbytes
CC Prüfsumme
TT Zeilenende: CR und LF

Der Inhalt von Test1.hex zeigt also folgendes:

:020000040003F7

Zwei Bytes ab Adresse 0000, Record-Typ 04, Codesegment 0003. Die Adresse spielt hier keine Rolle. Aber das Code-Segment sagt dem Download-Programm, wohin der nun folgende Code geladen werden soll.

:12000000E0120F82CC000E8280A2CDFBFCF2FCF2DB006E

Es folgen 18 Bytes (12h) Maschinencode ab Adresse 0000h innerhalb des aktuellen Segments. Der Code beginnt mit E0 und endet mit 00. Die Prüfsumme ist 6E.

:00000001FF

Die letzte Zeile bildet das Ende des Codes mit dem Ende-Record 01.

Das Ergebnis des Assemblers kann also nun mit dem Download-Tool geladen werden und in einem C2-Programm verwendet werden.

10.4 Assembler in der Tasking EDE

Die Verwendung der EDE bringt einige Vorteile, auf die man nicht verzichten sollte. Insbesondere unterstützen Fehlermeldungen und umfangreiche Hilfedateien die Arbeit. Andererseits hat die Entwicklungsumgebung ein starkes Eigenleben. Wenn der erzeugte Code plötzlich wesentlich länger ist als erwartet, dann ist das auf die unerwünschte Hilfestellung der EDE zurückzuführen. Das Werkzeug scheint am Anfang schwer zu beherrschen, zumindest solange man die relevanten Einstellungen noch nicht kennt. Hier wird der komplette Weg beim Aufbau eines Assembler-Projekts beschreiben.

Auf der CD zu diesem Buch findet sich das Projekt ASM1.pjt im Unterverzeichnis TASKING\ASM1. Um dieses Projekt nutzen zu können, müssen alle Pfade mit der ursprünglichen Installation übereinstimmen. Der Compiler muss dazu in C:\dc166 installiert sein. Das Projekt ASM1 muss im Verzeichnis C:\dc166\ASM1 stehen. Dies ist nötig, weil einige Dateien im Projektverzeichnis absolute Pfadangaben enthalten. Zwar können auch andere Pfade verwendet werden, dann ist es allerdings aufwendiger, fertige Projekte zu verwenden.

Ein Projekt ist immer eine Ansammlung zusammengehöriger Dateien. Außer dem eigentlichen Quelltext test1.asm gibt es noch Dateien, die die aktuellen Einstellungen der EDE enthalten, also z.B. die Adresslage, das gewünschte Dateiformat für die Ausgabe, eventuelle zusätzliche Programmteile, die automatisch eingefügt werden sollen usw. Man kann dieses Projekt laden und dann mit Projekt/Build übersetzen und erhält dann die Datei asm1.hex, die bereits im vorigen Abschnitt eingesetzt wurde. Hier soll jedoch der mühsamere Weg eines Starts ohne Vorgaben beschreiben werden, um alle relevanten Einstellungen zu beleuchten.

Als erstes sollte man einen Ordner mit dem Projektnamen anlegen. Die Tasking Tools selbst installieren sich voreingestellt unter C:\dc166. Das eigene Projekt soll im Verzeichnis c:\dc166\Asm1 stehen. Unter Project/New wird dann das neue Projekt Asm1.pjt angelegt. Mit File/New wird ein neue Datei Asm1.asm erzeugt, in die dann der Quelltext kopiert oder getippt wird. Die neue Datei muss nun noch in das Projekt eingefügt werden. Unter Project/Properties/Files markiert man dazu die Datei Asm1.asm und fügt sie mit Add ein.

Abb. 10.3 Einfügen des Quelltexts in das neue Projekt

Als nächsten müssen einige wichtige Eigenschaften des Projekts festgelegt werden. Unter EDE/Project Options/CPU soll der Mikrocontroller C164CI gewählt werden.

Abb. 10.4 Wahl der CPU

Dann wählt man unter EDE/Project Options/Language die Einstellung Assembler.

Abb. 10.5 Wahl der Sprache

Das Ausgabeformat Intel-Hex stellt man unter EDE/Project Options/Linker/Format ein.

Abb. 10.6 Wahl des Ausgabeformats

Wenn man mit diesen Einstellungen das Projekt übersetzen möchte, wählt man Project/Build. Tatsächlich entsteht eine Hex-Datei, die man sich mit einem Texteditor ansehen kann. Das Ergebnis ist aber wesentlich umfangreicher als das Ausgangsfile des Kommandozeilen-Assemblers. Ein genauer Blick auf die einzelnen Records verrät, dass die EDE noch umfangreichen Code im Segment 0 angefügt hat. Tasking geht davon aus, dass man ein Stand-Alone-Programm schreiben wollte, das entsprechende Programmteile im Segment 0 benötigt. Um den zusätzlichen Code zu unterbinden, muss eine Einstellung im Menü EDE/Project Options/Linker/Linker/Locator verändert werden. Hier stören die voreingestellten Optionen "Generate Vector Table" und "Initialize unused vectors to endless loop". Dabei geht es um die Interrupt-Einsprungadressen des Controllers, an denen sinnvolle Sprungbefehle stehen sollen. Diese sind jedoch im Betriebssystem bereits enthalten. Da nur ein Unterprogramm erzeugt werden soll, darf kein zusätzlicher Code eingefügt werden.

Abb. 10.7 Unterbinden des zusätzlichen Start-Codes

Zusätzlich ist noch eine Option in EDE/Project Options/Macro Preprocessor/Options wichtig. Hier stört die Einstellung "Generate system startup macros for ASM files".

Abb. 10.8 Verhindern eines Startup-Makros

Mit diesen Einstellungen kann nun ein neuer Übersetzungsversuch gestartet werden. Nach Project/Build erhält man eine Warnmeldung und keinen Fehler.

Abb. 10.9 Meldungen der EDE nach Project/Build

Die Warnung "missing system stack definition" kann übergangen werden, weil dies ebenfalls Aufgaben des Betriebssystems betrifft. Ein Blick in die neu erzeugte Ausgangsdatei Asm1.hex zeigt den Erfolg: Die Hexdatei ist nun genauso kurz wie die mit der Kommandozeilen-Methode erzeugte. Zur Probe kann man Test1.hex in die Unit laden und mit dem Programm ASM1.C2 aufrufen. Es entstehen wieder die schon beobachteten kurzen Impulsserien an P1L.0.

Alle Einstellungen des Projekts werden in der Projektdatei gespeichert und stehen beim nächsten Aufruf wieder zur Verfügung. Man kann also nun ohne Probleme Änderungen am Quelltext ausprobieren. Dabei unterstützt die EDE die Arbeit mit Fehlermeldungen.

Will man ein neues Projekt mit den gleichen Einstellungen beginnen, lässt sich viel Arbeit sparen, indem man die alte Projektdatei kopiert und umbenennt. Hier soll das Projekt Asm2.pjt im Verzeichnis C:\dc166\ASM2 erzeugt werden. Außer der Datei Asm1.pjt wird auch noch die Datei Asm1.asm in das neue Verzeichnis kopiert. Die Dateien werden umbenannt in Asm2.pjt und Asm2.asm. Unter Project/Open kann nun das neue Projekt geöffnet werden. Es enthält allerdings noch die alte Quelltextdatei Asm1.asm. Unter Project/Properties/Files muss der alte Quelltext entfernt und der neue eingefügt werden. Danach kann man die Datei Asm2.asm in den Editor laden und verändern.

Der Quelltext Asm2.asm soll nun so abgeändert werden, dass das Unterprogramm im Timer-Interrupt von C-Control II verwendet und mit system.hook aufgerufen werden kann. In diesem Fall dürfen keine Daten mit pop vom Stack abgehoben werden, was ja für einen Aufruf mit system.call erforderlich war. Zusätzlich sollen einige NOP-Befehle eingefügt werden, um die entstehenden Impulse etwas zu verlängern. Listing 10.5 zeigt den veränderten Quelltext.

$case
$segmented
$model(medium)
$extend
$nomod166
$stdnames(reg164ci.def)

                regdef R2
;***************************************************
userseg SECTION CODE word at 30000h
assume  dpp3:userseg
;***************************************************
;  10 Impulse an P1L.0, Aufruf mit system.hook

                public  testport

testport        proc far
                mov     R2,#1
                Loop1:
                bset    P1L.0                   ;einschalten
                nop
                nop
                nop
                nop
                bclr    P1L.0                   ;ausschalten
                nop
                nop
                nop
                cmpi1   R2,#10
                jmpr    cc_SLT,Loop1            ;10 mal
                ;pop    R2                      ;2xPOP 
                ;pop    R2                      
                rets                            ;Rücksprung
testport        endp
userseg         ends
                end

Listing 10.5 Der veränderte Quelltext für den Aufruf mit system.hook

Bei der Übersetzung wird außer dem Ausgabefile Asm2.hex auch ein Listfile Asm2.lst erzeugt. Es zeigt u.a. die Adressen und den erzeugten Code. Der Code wird ab Adresse 0000h im aktuellen Segment übersetzt.

              17   testport proc far
0000 E012     18            mov     R2,#1
0002          19            Loop1:
0002 0F82     20            bset    P1L.0   ;einschalten
0004 CC00     21            nop
0006 CC00     22            nop
0008 CC00     23            nop
000A CC00     24            nop
000C 0E82     25            bclr    P1L.0   ;ausschalten
000E CC00     26            nop
0010 CC00     27            nop
0012 CC00     28            nop
0014 80A2     29            cmpi1   R2,#10
0016 CDF5     30            jmpr    cc_SLT,Loop1 ;10 mal
              31            ;pop    R2           
              32            ;pop    R2                      
0018 DB00     33            rets             ;Rücksprung

Listing 10.6 Auszüge aus dem Listfile Asm2.lst

Das Ausgabefile Asm2.hex wurde bereits weiter oben im Kapitel 10.1 für Timer-Aufrufe im Programm ASM2.c2 verwendet.