29.05.2016 Aufrufe

Hardwarenahe_Programmierung_in_C_V1_2

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

mit dem µP-Kit 68332<br />

Dieses Skript be<strong>in</strong>haltet e<strong>in</strong>e E<strong>in</strong>führung <strong>in</strong> die hardwarenahe <strong>Programmierung</strong> <strong>in</strong><br />

C, am Beispiel des 68332 von Motorola. Gute Kenntnisse <strong>in</strong> der Programmiersprache<br />

C werden vorausgesetzt<br />

Ivo Oesch<br />

Mai 2004<br />

April 2005<br />

Mai 2006


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Inhaltsverzeichnis<br />

1 E<strong>in</strong>führung....................................................................................................................................3<br />

1.1 Verschieden Computersysteme ............................................................................................3<br />

1.1.1 Universalrechner (PC)..................................................................................................3<br />

1.1.2 Grossrechner.................................................................................................................3<br />

1.1.3 Mikrocomputer.............................................................................................................3<br />

1.1.4 Echtzeitrechner.............................................................................................................3<br />

1.2 Technische Prozesse.............................................................................................................4<br />

1.3 Datenfluss zwischen Echtzeitrechner und technischem Prozess..........................................5<br />

2 Mikroprozessorsysteme................................................................................................................6<br />

2.1 Aufbau e<strong>in</strong>es Mikroprozessorsystems..................................................................................6<br />

2.2 Adressraum...........................................................................................................................6<br />

2.3 Speicherorganisation ............................................................................................................7<br />

2.3.1 Little Endian / Big Endian............................................................................................7<br />

2.4 Speicherverwaltung unter e<strong>in</strong>er Hochsprache ......................................................................8<br />

2.5 Zugriff auf Hardware............................................................................................................8<br />

3 Direkter Speicherzugriff mit C.....................................................................................................9<br />

3.1 Benutzung von Po<strong>in</strong>ter .........................................................................................................9<br />

3.2 Compileroptimierungen unterdrücken .................................................................................9<br />

3.3 Aufgabe 1: ..........................................................................................................................12<br />

3.3.1 Initialisierung der TPU...............................................................................................12<br />

3.3.2 Ansteuerung der LED.................................................................................................12<br />

3.3.3 Lesen der Schalter und Tastenzustände:.....................................................................12<br />

3.4 Die serielle RS232 Schnittstelle.........................................................................................13<br />

3.5 Aufgabe 2 ...........................................................................................................................14<br />

3.6 Umlenken der E<strong>in</strong>- und Ausgabefunktionen <strong>in</strong> C. .............................................................15<br />

3.7 Aufgabe 3 ...........................................................................................................................16<br />

3.8 Weitere Anpassungen der Standardbibliothek ...................................................................16<br />

4 Startupcode.................................................................................................................................17<br />

5 Interrupts ....................................................................................................................................18<br />

5.1 E<strong>in</strong>führung..........................................................................................................................18<br />

5.2 Def<strong>in</strong>ition e<strong>in</strong>er Interruptrout<strong>in</strong>e ........................................................................................19<br />

5.3 Sperren von Interrupts ........................................................................................................19<br />

5.4 Interrupthandl<strong>in</strong>g beim 68xxx............................................................................................21<br />

5.4.1 Interruptvektoren ........................................................................................................22<br />

5.5 Aufgabe 4 ...........................................................................................................................25<br />

5.6 Aufgabe 5 ...........................................................................................................................25<br />

5.7 Interrupts und Standardbibliothek......................................................................................25<br />

Anhang A Bitzugriff...........................................................................................................................26<br />

Anhang B Zahlensysteme...................................................................................................................29<br />

Das Kapitel 1 wurde mit freundlicher Genehmigung dem Skript 'Informatik 3', September 2003 von Roger Weber*<br />

entnommen. (Mit kle<strong>in</strong>en Änderungen)<br />

* Professor für Technische Informatik, HTI Burgdorf<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 2/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

1 E<strong>in</strong>führung<br />

1.1 Verschieden Computersysteme<br />

1.1.1 Universalrechner (PC)<br />

Universalrechner s<strong>in</strong>d Rechnersysteme für den allgeme<strong>in</strong>e E<strong>in</strong>satz. Sie s<strong>in</strong>d standardisiert, werden<br />

<strong>in</strong> grossen Volumen hergestellt, und es gibt für fast alle denkbaren E<strong>in</strong>sätze bereits existierende<br />

Software.<br />

1.1.2 Grossrechner<br />

Grossrechner werden vor allem für sehr rechen<strong>in</strong>tensive Arbeiten wie Simulationen (Wettervorhersage,<br />

Modellierung von physikalischen Prozessen, usw.) oder bei riesigen Datenmengen verwendet.<br />

Sie werden nur <strong>in</strong> kle<strong>in</strong>en Stückzahlen oder als E<strong>in</strong>zelanfertigung produziert, und es gibt auch nur<br />

selten bereits existierende Software dazu. Diese Rechner werden meist auf den jeweiligen E<strong>in</strong>satz<br />

h<strong>in</strong> konstruiert, und auch die entsprechende Software und Compiler werden jeweils speziell dafür<br />

geschrieben.<br />

1.1.3 Mikrocomputer<br />

Mikrocomputer s<strong>in</strong>d kle<strong>in</strong>e Rechner, welche meist auf Steuerungsaufgaben spezialisiert s<strong>in</strong>d, oft<br />

bereits mit vielen E<strong>in</strong>- und Ausgabeleitungen ausgestattet, und mit zusätzlichen E<strong>in</strong>- Ausgabemöglichkeiten<br />

erweitert werden können. Sie werden als E<strong>in</strong>plat<strong>in</strong>encomputer, als Module, ohne Gehäuse<br />

oder <strong>in</strong> Industrietauglichen Gehäusen gefertigt. Auf nicht notwendige Komponenten wie Graphikkarten<br />

(Monitoranschluss), Tastatur, Harddisks und ähnliches wird verzichtet, diese können aber bei<br />

e<strong>in</strong>igen Systemen nachgerüstet werden. Das Pr<strong>in</strong>zip ist: So e<strong>in</strong>fach wie möglich, und sowenig Bauteile<br />

wie nötig.<br />

1.1.4 Echtzeitrechner<br />

Für die Steuerung von technischen Prozessen werden Echtzeitrechner e<strong>in</strong>gesetzt. Diese erfüllen<br />

folgende Kriterien:<br />

• E<strong>in</strong>haltung e<strong>in</strong>er zeitlichen Abfolge<br />

• Def<strong>in</strong>ierte, schnelle Bearbeitungs- und Reaktionszeiten<br />

• Zeitdeterm<strong>in</strong>istisch (Vorhersagbares und reproduzierbares Zeitverhalten)<br />

Je nach Komplexität des Prozesses kommen folgende Rechner zum E<strong>in</strong>satz:<br />

• Grossrechner<br />

• PC / SPS<br />

• Mikrocomputer<br />

Mikrocomputer können weiter unterteilt werden <strong>in</strong>:<br />

• S<strong>in</strong>gle Chip µC (<strong>in</strong> e<strong>in</strong>em Chip <strong>in</strong>tegriert)<br />

• S<strong>in</strong>gle Board µC (auf e<strong>in</strong>er gedruckten Schaltung mit anderen Bauelementen)<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 3/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Im Gegensatz zu Echtzeitrechnern erfüllen allgeme<strong>in</strong> datenverarbeitende Rechner die Echtzeit-<br />

Kriterien normalerweise nicht (z.B. W<strong>in</strong>dows-PC zur Textverarbeitung). Dies ist aber vor allem<br />

auch e<strong>in</strong>e Frage des Betriebsystemes.<br />

In diesem Skript werden ausschliesslich Mikrocomputer behandelt. Mögliche E<strong>in</strong>satzgebiete s<strong>in</strong>d:<br />

• Telekommunikation (Zentralen, Endgeräte ...)<br />

• Mediz<strong>in</strong>altechnik (Blutzuckermessgerät, Hörgeräte, Implantate ...)<br />

• Sicherheitstechnik (Brandmeldeanlagen, Bahnübergänge ...)<br />

• Industrieautomation (Sensoren, Aktoren, Steuerungen von Anlagen...)<br />

• Automobil<strong>in</strong>dustrie, Bahnen, Flugzeuge<br />

• Consumer-Elektronik (Fotoapparat, Drucker, Hifi ...)<br />

Mikrocomputer <strong>in</strong> den oben genannten Anwendungen werden oft auch als 'Embedded Controller'<br />

bezeichnet, da sie 'e<strong>in</strong>gebettet' <strong>in</strong> e<strong>in</strong>em Gerät arbeiten.<br />

1.2 Technische Prozesse<br />

Die folgende Abbildung zeigt e<strong>in</strong>en groben Überblick über das Zusammenspiel zwischen Echtzeitrechner<br />

und technischem Prozess:<br />

Sensoren<br />

techn.<br />

Prozess<br />

Echtzeitrechner<br />

User<br />

Interface<br />

Aktoren<br />

Zusammenspiel zwischen Echtzeitrechner und technischem Prozess<br />

Der Prozess ist Quelle und Senke der Prozessdaten. Der Rechner liest über Sensoren den Zustand<br />

des Prozesses e<strong>in</strong>. Über Aktoren kann er den Zustand des Prozesses bee<strong>in</strong>flussen. Der Anwender hat<br />

die Möglichkeit, über e<strong>in</strong> User-Interface den Prozess zu bee<strong>in</strong>flussen (Konfiguration, Parametrisierung)<br />

oder Zustände des Prozesses abzufragen.<br />

Beispiele für Sensoren:<br />

• mechanische, <strong>in</strong>duktive oder optische Schalter<br />

• A/D Wandler<br />

• Temperatursensoren, Drucksensoren<br />

Beispiele für Aktoren:<br />

• Ventile<br />

• Motoren<br />

• Anzeigen, LED<br />

• Relais<br />

• D/A Wandler<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 4/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

1.3 Datenfluss zwischen Echtzeitrechner und technischem Prozess<br />

Die folgende Abbildung zeigt die detaillierte Darstellung des Datenflusses zwischen Echtzeitrechner<br />

und technischem Prozess:<br />

Echtzeitrechner<br />

Regelungs-Algorithmus<br />

Prozess-Abbild<br />

Ausgabe-<br />

Daten<br />

E<strong>in</strong>gabe-<br />

Daten<br />

Ereignisse<br />

Stell-<br />

Grössen<br />

Prozess-<br />

Kennwerte<br />

Prozess-<br />

Parameter<br />

Mess-<br />

Grössen<br />

diskrete<br />

Ereignisse<br />

Prozess-<br />

Input<br />

technischer<br />

Prozess<br />

Prozess-<br />

Output<br />

Stör-<br />

Grössen<br />

Rückwirkung<br />

Datenfluss zwischen Echtzeitrechner und technischem Prozess<br />

Der Prozess besitzt e<strong>in</strong>en Input und e<strong>in</strong>en Output. Dies können sowohl Informationen als auch Materialien<br />

se<strong>in</strong>. Weiter wird der Prozess durch Störgrössen sowie Rückwirkungen des Outputs bee<strong>in</strong>flusst.<br />

Dem Prozess werden zudem Kennwerte und Parameter mitgegeben.<br />

Der Echtzeitrechner erstellt anhand der Messgrössen sowie diskreter Ereignisse e<strong>in</strong> Prozessabbild.<br />

Mit Hilfe e<strong>in</strong>es Regelungsalgorithmus berechnet der Echtzeitrechner die Stellgrössen des Prozesses<br />

und sorgt so für dessen korrekten Ablauf.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 5/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

2 Mikroprozessorsysteme<br />

2.1 Aufbau e<strong>in</strong>es Mikroprozessorsystems<br />

E<strong>in</strong> allgeme<strong>in</strong>es Mikroprozessorsystem setzt sich aus Prozessor (CPU), RAM (Daten und Programmspeicher),<br />

ROM (Programmspeicher) und Peripherie zusammen. Die verschiedenen Komponenten<br />

s<strong>in</strong>d über e<strong>in</strong> Bussystem mite<strong>in</strong>ander verbunden<br />

CPU<br />

Bus für Instruktionen und Daten<br />

Programm-<br />

Speicher<br />

Daten-<br />

Speicher<br />

Peripherie<br />

2.2 Adressraum<br />

Der Speicher e<strong>in</strong>es Prozessors besteht üblicherweise aus e<strong>in</strong>er Menge von Bytes, die von 0 an<br />

durchnumeriert s<strong>in</strong>d. Die Nummer e<strong>in</strong>er Speicherzelle wird dabei als ihre Adresse bezeichnet, und<br />

der Bereich aller möglichen Nummern e<strong>in</strong>es Prozessors als Adressraum des Prozessors. Die Adresse<br />

wird als b<strong>in</strong>äre Nummer auf den Bus ausgegeben, und die Breite des Busses (Nebst der Prozessorarchitektur)<br />

legt fest, welches die grösstmögliche Adresse ist. Mit 16 Bit kann man von 0 bis 65535<br />

(2^16-1) zählen, also 64 KB (Kilobytes, 1 KB = 1024 Bytes) adressieren. Mit 24 Bit können 16 MB<br />

(Megabytes, 1 MB = 1024 KB), und mit 32 Bit 4 GB (Gigabytes, 1 GB = 1024 MB) adressiert werden.<br />

Normalerweise ist jedoch nicht jede adressierbare Speicherzelle auch wirklich vorhanden, oft<br />

existieren auch 'Löcher' im Adressraum <strong>in</strong> welchen nichts vorhanden ist.<br />

Die effektive Zuordnung von Adressbereichen zu RAM, ROM und Pheripheriebauste<strong>in</strong>en wird<br />

durch Hardwareschaltkreise erledigt, manchmal unter Zuhilfenahme von spezialisierten Pheripheriebauste<strong>in</strong>en<br />

(Was <strong>in</strong> e<strong>in</strong>em gewissen Umfang e<strong>in</strong>e softwaremässige Konfiguration ermöglicht).<br />

Oft wird der Speicher e<strong>in</strong>es Prozessorsystems mit e<strong>in</strong>er Memorymap dargestellt, aus welcher klar<br />

ersichtlich ist, wo was liegt. In e<strong>in</strong>em Mikrokontrollersystem f<strong>in</strong>det man üblicherweise ROM (Hier<br />

ist der Programmcode dauerhaft abgespeichert), RAM (Hier werden Variablen und Daten abgelegt)<br />

und Peripherie (Damit kommuniziert das System mit der Aussenwelt). Und meist auch Bereiche, an<br />

denen gar nichts steht, die also leer s<strong>in</strong>d. (Z. B. für zukünftige Erweiterungen reservierte Bereiche).<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 6/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Beispiel e<strong>in</strong>er Memorymap<br />

0xFFFFFF<br />

0xF80000<br />

0xF7FFFF<br />

0xF00000<br />

0xFFFFFF<br />

0xFFFFE0<br />

0xFFFFDF<br />

0xFFFF80<br />

0xFFFF7F<br />

0x028000<br />

0x027FFF<br />

0x020000<br />

0x01FFFF<br />

0x000400<br />

0x0003FF<br />

0x000000<br />

ROM<br />

Unbenutzt<br />

Timer<br />

UART<br />

Unbenutzt<br />

RAM<br />

(Akkugepuffert)<br />

RAM<br />

RAM<br />

(Vektortabelle)<br />

Üblicherweise beg<strong>in</strong>nt die Darstellung e<strong>in</strong>er Memorymap am unteren Ende bei 0, sie kann aber<br />

durchaus auch <strong>in</strong> der anderen Richtung dargestellt werden.<br />

2.3 Speicherorganisation<br />

Der Speicher fast aller Mikroprozessoren ist Byteweise organisiert, das heisst, dass 16-Bit und 32-<br />

Bit Variablen mehrere Bytes im Speicher belegen. Aus Effizienzgründen sollten 16-Bit Werte immer<br />

auf geraden, und 32-Bit Werte immer auf durch 4 teilbaren Adressen liegen (Dies ist bei e<strong>in</strong>igen<br />

Prozessoren sogar Pflicht). Damit ist gewährleistet, dass bei 16-Bit, resp. 32-Bit breiten Datenbussen<br />

immer alle Bits auf e<strong>in</strong>mal gelesen werden können, ansonsten s<strong>in</strong>d mehrere Zugriffe notwendig.<br />

2.3.1 Little Endian / Big Endian<br />

Es gibt zwei gängige Möglichkeiten, wie 16 und 32-Bit Werte im Speicher abgelegt werden. Beim<br />

ersten Format (Little Endian) werden die Bits 0-7 an der Adresse n, die Bits 8-15 an der Adresse<br />

n+1 und entsprechend die Bits 24-31 an der Adresse n+3 abgelegt. Beim zweiten Format (Big Endian)<br />

ist es gerade umgekehrt, die Bits 23-31 liegen auf der Adresse n und die Bits 0-7 auf der Adresse<br />

n+3.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 7/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Little Endian<br />

Big Endian<br />

32-Bit Wert (long)<br />

Bit 0-7 Bit 8-15 Bit 16-23 Bit 24-31<br />

n n+1 n+2 n+3<br />

32-Bit Wert (long)<br />

Bit 24-31 Bit 16-23 Bit 8-15 Bit 0-7<br />

n n+1 n+2 n+3<br />

n+4<br />

n+3<br />

Bit 24-31<br />

n+2<br />

Bit 16-23<br />

n+1<br />

Bit 8-15<br />

n<br />

Bit 1-7<br />

n-1<br />

n-2<br />

n+4<br />

n+3<br />

Bit 1-7<br />

n+2<br />

Bit 8-15<br />

n+1<br />

Bit 16-23<br />

n<br />

Bit 24-31<br />

n-1<br />

n-2<br />

Der Wert 0x12345678 würde also jeweils wie folgt im Speicher abgelegt werden:<br />

0x78, 0x56, 0x34, 0x12 0x12, 0x34, 0x56, 0x78<br />

n n+1 n+2 n+3 n n+1 n+2 n+3<br />

Der im Unterricht e<strong>in</strong>gesetzte Prozessor 68332 gehört <strong>in</strong> die Klasse der Big Endian Prozessoren.<br />

Das heisst, das an den tieferen Adressen die höherwertigen Bytes stehen.<br />

2.4 Speicherverwaltung unter e<strong>in</strong>er Hochsprache<br />

Normalerweise wird die Verwaltung des Speichers vom Compiler übernommen, kann aber durch<br />

den Benutzer mit diversen Compiler- und L<strong>in</strong>ker- E<strong>in</strong>stellungen gesteuert werden. Wenn man direkt<br />

auf die HW zugreift, muss man sich selbst um die korrekten Zugriffe kümmern (D.h. def<strong>in</strong>ieren ob<br />

man auf 8 oder 16-Bit zugreifen will, und an welcher Adresse nun welches Byte steht).<br />

2.5 Zugriff auf Hardware<br />

Sobald man Programme für Mikrocontroller oder selbst gebaute Hardware schreibt, wird es unumgänglich,<br />

vom Code direkt auf die Hardware zuzugreifen. Meistens s<strong>in</strong>d die Funktionen der Hardware<br />

über Register (=Bestimmte Speicherzellen) erreichbar, die häufig (aber nicht immer) irgendwo<br />

im Speicherbereich stehen. Das heisst, dass an bestimmten Stellen im Prozessorspeicher ke<strong>in</strong> RAM<br />

(Arbeitsspeicher), sondern eben diese Peripherieregister e<strong>in</strong>geblendet werden. Die genaue Funktion<br />

und Bedeutung dieser Register muss jeweils der entsprechenden Dokumentation entnommen werden.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 8/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

3 Direkter Speicherzugriff mit C<br />

Um <strong>in</strong> C auf Hardware zugreifen zu können müssen Po<strong>in</strong>ter, Assemblermodule oder Assemblere<strong>in</strong>schlüsse<br />

verwendet werden. Solange die Hardware im normalen Speicherbereich e<strong>in</strong>geblendet ist,<br />

können Po<strong>in</strong>ter verwendet werden, wenn die Hardware nicht direkt adressierbar ist (Eigener Adressraum,<br />

Prozessorregister, spezielle Prozessorbefehle) muss auf Assembler ausgewichen werden. Hier<br />

wird nur die Variante mit Po<strong>in</strong>ter besprochen.<br />

3.1 Benutzung von Po<strong>in</strong>ter<br />

Via Po<strong>in</strong>ter kann <strong>in</strong> C auf jede beliebige Speicheradresse zugegriffen werden, aber man muss darauf<br />

achten, den passenden Po<strong>in</strong>tertypen zu verwenden.<br />

Unser System (68332) ist e<strong>in</strong> 32/16-Bit System, und besitzt unter der Task<strong>in</strong>g Entwicklungsumgebung<br />

folgende Datentypen: char 8 Bit, <strong>in</strong>t 32 Bit, long 32 Bit, short <strong>in</strong>t 16 Bit.<br />

8-Bit breite HW-Register müssen somit durch e<strong>in</strong>en unsigned char Po<strong>in</strong>ter, 16-Bit Register<br />

durch e<strong>in</strong>en unsigned short <strong>in</strong>t Po<strong>in</strong>ter und 32-Bit Register durch e<strong>in</strong>en unsigned long<br />

<strong>in</strong>t Po<strong>in</strong>ter angesprochen werden.<br />

Um zum Beispiel auf die Adresse 0x00f1278 (Könnte z. B. e<strong>in</strong> Parallelport se<strong>in</strong>) den 16-Bit Wert<br />

0x1234 zu schreiben können folgende Codevarianten benutzt werden:<br />

Direkt:<br />

*((unsigned short <strong>in</strong>t*)0x00f1278 ) = 0x1234;<br />

/* Umwandeln der Zahl 0x00f1278 <strong>in</strong> e<strong>in</strong>e Adresse und darauf zugreifen */<br />

Via Po<strong>in</strong>ter<br />

/* Po<strong>in</strong>tervariable def<strong>in</strong>ieren */<br />

unsigned short <strong>in</strong>t *Parallelport;<br />

/* Po<strong>in</strong>ter auf Adresse zeigen lassen */<br />

Parallelport = (unsigned short <strong>in</strong>t*)0x00f1278;<br />

/* Und Wert dorth<strong>in</strong> schreiben wo der Po<strong>in</strong>ter h<strong>in</strong>zeigt */<br />

*Parallelport = 0x1234;<br />

Mit Hilfe e<strong>in</strong>es Makros<br />

/* Koennte <strong>in</strong> e<strong>in</strong>er Headerdatei stehen */<br />

#def<strong>in</strong>e Parallelport *((unsigned short <strong>in</strong>t*)0x00f1278 )<br />

/* Benutzung des Makros im Code */<br />

Parallelport = 0x1234;<br />

3.2 Compileroptimierungen unterdrücken<br />

Im Zusammenhang mit direktem Zugriff auf Hardware und Zusammenarbeit mit Interrupts gew<strong>in</strong>nt<br />

das Schlüsselwort volatile an Bedeutung. Alle guten Compiler versuchen, möglichst optimalen<br />

Code zu erzeugen, dazu gehört auch, überflüssigen oder redundanten Code zu entfernen. Dazu e<strong>in</strong>ige<br />

Beispiele:<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 9/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Das folgende Codestück<br />

while (Parallelport == 0) {<br />

i++; /* Warten bis Parallelport bereit */<br />

}<br />

könnte vom Compiler zu der Form<br />

if (Parallelport == 0) { /* Testen ob Parallelport 0 ist */<br />

while (1) {<br />

i++; /* Falls ja <strong>in</strong> Endlosschleife e<strong>in</strong>treten */<br />

}<br />

}<br />

optimiert werden, um die Speicherzugriffe auf die Variable Parallelport zu m<strong>in</strong>imieren und schnelleren<br />

Code zu Erzeugen (Aus der Sicht des Compilers kann die Variable Parallelport ihren Inhalt ja<br />

nicht ändern). Solange die Variable Parallelport ihren Wert nicht 'magisch' ändert, s<strong>in</strong>d die beiden<br />

Codevarianten identisch, aber die zweite ist schneller.<br />

E<strong>in</strong> weiteres Beispiel wäre die Sequenz<br />

Parallelport = 0;<br />

while (Parallelport == 0) {<br />

i++; /* Warten bis Parallelport bereit */<br />

}<br />

Welche zu<br />

Parallelport = 0;<br />

while (1) {<br />

i++; /* Warten bis Parallelport bereit */<br />

}<br />

optimiert werden kann, hier 'weiss' der Compiler ja, dass die Variable Parallelport den Wert 0 hat,<br />

und kann sich so auch noch die Abfrage (if) sparen.<br />

E<strong>in</strong> anderes Beispiel wäre e<strong>in</strong>e Sequenz zum ausgeben e<strong>in</strong>es Bitmusters:<br />

Parallelport = 0; /* Alle Leitungen auf 0 setzen */<br />

Parallelport = 1; /* Clock auf 1 setzen */<br />

Parallelport = 0; /* Clock auf 0 setzen */<br />

Parallelport = 2; /* Datenleitung auf 1 setzen */<br />

Parallelport = 3; /* Clock auf 1 setzen */<br />

Parallelport = 2; /* Clock auf 0 setzen */<br />

Parallelport = 0; /* Alle Leitungen auf 0 setzen */<br />

welche vom Compiler zu<br />

Parallelport = 0; /* Alle Leitungen auf 0 setzen */<br />

optimiert werden kann, da schliesslich nur die letzte Anweisung den Wert dauerhaft setzt, und die<br />

Werte alle vorherigen Zuweisungen überschrieben werden.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 10/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Um den Compiler dazu zu zw<strong>in</strong>gen, jeden Schreib oder Lesezugriff wirklich und sofort durchzuführen,<br />

also alle Optimierungen für e<strong>in</strong>e bestimmte Variable zu unterlassen, muss man diese Variable<br />

als volatile ('flüchtig/unbeständig') def<strong>in</strong>ieren.<br />

Die Def<strong>in</strong>itionen der Variablen Parallelport müssten also jeweils wie folgt mit volatile ergänzt<br />

werden:<br />

Direkt:<br />

*((volatile unsigned short <strong>in</strong>t*)0x00f1278 ) = 0x1234;<br />

Via Po<strong>in</strong>ter<br />

/* Po<strong>in</strong>tervariable def<strong>in</strong>ieren */<br />

volatile unsigned short <strong>in</strong>t *Parallelport;<br />

/* Po<strong>in</strong>ter auf Adresse zeigen lassen */<br />

Parallelport = (volatile unsigned short <strong>in</strong>t*)0x00f1278;<br />

/* Und Wert dorth<strong>in</strong> schreiben wo der Po<strong>in</strong>ter h<strong>in</strong>zeigt */<br />

*Parallelport = 0x1234;<br />

Mit Hilfe e<strong>in</strong>es Makros<br />

/* Koennte <strong>in</strong> e<strong>in</strong>er Headerdatei stehen */<br />

#def<strong>in</strong>e Parallelport *((volatile unsigned short <strong>in</strong>t*)0x00f1278)<br />

/* Benutzung des Makros im Code */<br />

Parallelport = 0x1234;<br />

Grundsätzlich sollte man alle Variablen, die unbemerkt vom Compiler ihren Zustand ändern, oder<br />

deren Änderungen direkt Hardware bee<strong>in</strong>flusst, immer als volatile deklarieren. Das heisst: Alle<br />

Variablen, welche mit Hardware verbunden s<strong>in</strong>d, und alle Variablen, welche von Interruptrout<strong>in</strong>en<br />

verändert werden.<br />

Oft funktioniert der Code auch ohne volatile e<strong>in</strong>wandfrei, aber nach e<strong>in</strong>er Ergänzung oder Änderung<br />

des Codes treten plötzlich Probleme auf, welche meist nur sporadisch auftreten und oft<br />

kaum zu lokalisieren s<strong>in</strong>d.<br />

Normalerweise gehen die vom Compiler ausgeführten Optimierungen noch viel weiter als die oben<br />

angegebenen Beispiele. E<strong>in</strong>e Variable kann unter Umständen bereits zu Beg<strong>in</strong>n e<strong>in</strong>er Funktion <strong>in</strong><br />

e<strong>in</strong> Prozessorregister (=Schneller lokaler Speicher vom Prozessor) geladen werden, alle weiteren<br />

Zugriffe erfolgen anschliessend auf dieses Register, und erst am Ende der Funktion wird der Inhalt<br />

des Registers <strong>in</strong> den Speicher zurückgeschrieben. Der Code wird dadurch natürlich viel effizienter<br />

und schneller, besonders wenn die Variable oft verwendet wird, aber wenn dabei Zugriffe auf<br />

Hardware unterdrückt werden, funktioniert der Code nicht mehr korrekt.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 11/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

3.3 Aufgabe 1:<br />

Schreiben Sie e<strong>in</strong> Programm, welches die Schalter S1 bis S4 vom Kit e<strong>in</strong>liest, und den Zustand der<br />

Schalter auf den Led's L1 bis L4 ausgibt. H<strong>in</strong>weis, schreiben Sie zuerst e<strong>in</strong> Programm, das nur die<br />

LED ansteuert, und fügen sie erst nach erfolgreichem Test den Code zum Auslesen der Schalterzustände<br />

h<strong>in</strong>zu.<br />

Das E<strong>in</strong>lesen der Schalter und Ausgeben der LED-Zustände erfolgt über die TPU, dies ist e<strong>in</strong> komplexer<br />

Mikrosequenzer, der eigentlich viel mehr kann als nur die E<strong>in</strong>- und Ausgabe von e<strong>in</strong>fachen<br />

Pegeln. Deshalb ist auch die Initialisierung dieser E<strong>in</strong>heit recht aufwendig.<br />

3.3.1 Initialisierung der TPU<br />

Zur Initialisierung der TPU müssen etwa 36 Register korrekt <strong>in</strong>itialisiert werden, deshalb wird e<strong>in</strong><br />

Modul (tpufuncs) zur Initialisierung der TPU zur Verfügung gestellt.<br />

3.3.2 Ansteuerung der LED<br />

Zur Ansteuerung der Leds L1 bis L4 ist folgender Ablauf nötig (Ablauf unbed<strong>in</strong>gt e<strong>in</strong>halten!):<br />

- In das Register HSRR1 den gewünschten LED-Zustandscode schreiben<br />

- Das Register HSQR1auf 0 setzen<br />

- Das Register CPR1 auf 0xFFFF setzen<br />

Der Ledzustandscode besteht für jede LED aus zwei Bits. Bit 0 und 1 steuern die Led L1, Bit 2 und<br />

3 die Led L2 usw. Dabei bedeuten die Bitkomb<strong>in</strong>ationen 01 E<strong>in</strong>schalten, und 10 Ausschalten der<br />

entsprechenden Led:<br />

0 0 0 0 0 0 0 0 Led L4 Led L3 Led L2 Led L1<br />

Bit 15 Bit 8 Bit 7 Bit 0<br />

Die benötigten Register liegen auf folgenden Adressen:<br />

HSRR1: 16-Bit Register, auf Adresse 0xFFFE1A<br />

HSQR1: 16-Bit Register, auf Adresse 0xFFFE16<br />

CPR1: 16-Bit Register, auf Adresse 0xFFFE1E<br />

Die restlichen Register der TPU bitte nicht verändern.<br />

3.3.3 Lesen der Schalter und Tastenzustände:<br />

Der Zustand der Schalter S1 bis S4 ist jeweils im Bit 7 der Register PL8 bis PL11 abgelegt, der Zustand<br />

der Tasten T1 bis T4 ist im Bit 7 der Register PL12 bis PL15 abgelegt. (Achtung, Tasten s<strong>in</strong>d<br />

Null-aktiv)<br />

Die benötigten Register liegen auf folgenden Adressen:<br />

PL8: 16-Bit Register, auf Adresse 0xFFFF82<br />

PL9: 16-Bit Register, auf Adresse 0xFFFF92<br />

PL10: 16-Bit Register, auf Adresse 0xFFFFA2<br />

PL11: 16-Bit Register, auf Adresse 0xFFFFB2<br />

PL12: 16-Bit Register, auf Adresse 0xFFFFC2<br />

PL13: 16-Bit Register, auf Adresse 0xFFFFD2<br />

PL14: 16-Bit Register, auf Adresse 0xFFFFE2<br />

PL15: 16-Bit Register, auf Adresse 0xFFFFF2<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 12/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

3.4 Die serielle RS232 Schnittstelle<br />

RS-232 ist e<strong>in</strong>e serielle Übertragung, bei der die Datenbytes mit dem LSB (Bit 0) voran übertragen<br />

werden. Der Beg<strong>in</strong>n e<strong>in</strong>er Übertragung wird durch e<strong>in</strong> 'Startbit', e<strong>in</strong>em Bit mit dem Wert 0, angezeigt.<br />

Anschliessend folgen die Datenbytes, und zum Abschluss folgen e<strong>in</strong> oder 2 Stoppbits (Bits<br />

mit dem Wert 1). Zwischen dem letzten Datenbit und dem Stoppbit kann noch e<strong>in</strong> Paritätsbit e<strong>in</strong>gefügt<br />

werden.<br />

Die Datenleitung hat im Ruhezustand den Wert 1, so kann jederzeit das Startbit e<strong>in</strong>er neuen Übertragung<br />

detektiert werden.<br />

Bei e<strong>in</strong>er normgerechten RS232-Verb<strong>in</strong>dung besitzt die logische 1 den Pegel von -12Vund die logische<br />

0 e<strong>in</strong>en Pegel von +12V.<br />

Damit ergibt sich für e<strong>in</strong>e Beispielübertragung folgendes Zeit/Spannungsdiagramm. (Achtung -12V<br />

entspricht e<strong>in</strong>er logischen 1 und +12V e<strong>in</strong>er logischen 0!)<br />

+12V<br />

-12V<br />

Idle<br />

Start<br />

Bit<br />

D0 D1 D2 D3 D4 D5 D6 D7 P<br />

Datenbits<br />

Parity<br />

Bit<br />

Stop<br />

Bit<br />

Idle<br />

Zur Verb<strong>in</strong>dung zwischen Sender und Empfänger reichen 2 Leitungen aus, e<strong>in</strong>e Datenleitung und<br />

e<strong>in</strong>e Masseleitung. Wenn <strong>in</strong> beide Richtungen kommuniziert werden soll, braucht es e<strong>in</strong>e weitere<br />

Datenleitung, also <strong>in</strong>sgesamt 3 Leitungen. Die Anschlüsse werden mit folgenden Kürzeln bezeichnet:<br />

Masse mit Gnd (P<strong>in</strong> 5), Datenausgang mit TxD (P<strong>in</strong> 3) und Datene<strong>in</strong>gang mit RxD (P<strong>in</strong> 2), die<br />

P<strong>in</strong>angaben gelten jeweils für den 9-Poligen D-Sub Stecker. Dabei muss jeweils TxD mit RxD des<br />

Kommunikationspartners verbunden werden. Die serielle Schnittstelle besitzt noch weitere Steuerleitungen,<br />

auf die aber hier nicht weiter e<strong>in</strong>gegangen wird. (Diese dienen zum 'Hardware Handshak<strong>in</strong>g',<br />

und erlauben dem Empfänger, den Sender zu stoppen, wenn die Daten zu schnell e<strong>in</strong>treffen.<br />

Wenn man ke<strong>in</strong> Handshak<strong>in</strong>g benötigt, muss es entweder Softwaremässig ausgeschaltet werden,<br />

oder aber die Steuerleitungen am Stecker müssen entsprechend ausgekreuzt werden.)<br />

Damit sich Sender und Empfänger verstehen, müssen beide dasselbe Format benutzen. Das heisst,<br />

dass die Bitbreite, die Anzahl Datenbits und die Art der Parität bei beiden gleich konfiguriert werden<br />

muss. Die Bitbreite wird über die Baudrate (=Anzahl Bits pro Sekunde) def<strong>in</strong>iert.<br />

Üblicherweise muss aber das Protokoll für die serielle Schnittstelle nicht selbst programmiert werden<br />

(Ausnahme: kle<strong>in</strong>e Mikrokontroller oder Kostenersparnis durch Softwareemulation und Verzicht<br />

auf e<strong>in</strong>en UART-Bauste<strong>in</strong>), sondern es werden Hardwarebauste<strong>in</strong>e (UARTs, 'Universal Asynchronous<br />

Receiver and Transmitter') e<strong>in</strong>gesetzte, welche die Kommunikation und die Umwandlung<br />

von Seriell nach Parallel und zurück übernehmen.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 13/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

3.5 Aufgabe 2<br />

Schreiben Sie e<strong>in</strong> Programm, welches Zeichen über die serielle Schnittstelle empfängt und selbst<br />

wieder Zeichen über die serielle Schnittstelle ausgibt. Starten sie auf dem PC e<strong>in</strong> Term<strong>in</strong>alprogramm<br />

(E<strong>in</strong> Term<strong>in</strong>alprogramm emuliert e<strong>in</strong> Term<strong>in</strong>al, das heisst, es stellt alle über die serielle<br />

Schnittstelle empfangenen Zeichen auf dem Bildschirm dar, und sendet alle Tastendrücke über die<br />

serielle Schnittstelle weiter.), verb<strong>in</strong>den Sie ihr Kit mit der seriellen Schnittstelle vom PC und konfigurieren<br />

Sie das Term<strong>in</strong>alprogramm auf 9600 Baud, 8 Datenbit, 1 Stoppbit, ke<strong>in</strong>e Parität (9600<br />

1N8) und ke<strong>in</strong> Handshak<strong>in</strong>g. Das Term<strong>in</strong>alprogramm zeigt Ihnen anschliessend alle Zeichen an, die<br />

über die serielle Schnittstelle e<strong>in</strong>treffen, und sendet alle Tastendrücke über die serielle Schnittstelle.<br />

Zum Testen des Term<strong>in</strong>alprogramms können sie auch P<strong>in</strong> 2 mit P<strong>in</strong> 3 <strong>in</strong> Richtung PC kurzschliessen<br />

(Büroklammer oder ähnliches), anschliessend sollten Sie im Term<strong>in</strong>alprogramm Ihre eigenen<br />

Tastendrücke sehen (Oder doppelt sehen wenn das lokale Echo aktiviert ist).<br />

Genauere Informationen über die Register der seriellen Schnittstelle des Kits und deren Konfiguration<br />

können sie dem Dokument MC68332UM.pdf ab Kapitel D.4 'Queued Serial Module' (Seite<br />

D18) entnehmen. Sie müssen die Register SCCR0 und SCCR1 korrekt konfigurieren, der Systemtakt<br />

beträgt 16.666666 MHz (Wird zur Berechnung der Baudrate benötigt).<br />

Im Register SCSR können Sie mit den Bits TDRE und RDRF feststellen, ob Sie das nächste Byte<br />

senden dürfen, oder ob neue Daten e<strong>in</strong>getroffen s<strong>in</strong>d. Daten senden und empfangen Sie über das<br />

Register SCDR, wobei nur die unteren 8 Bits verwendet werden. Alles was Sie <strong>in</strong> dieses Register<br />

schreiben wird gesendet (Ausser wenn Sie bereits e<strong>in</strong>en neuen Wert <strong>in</strong> das Register schreiben, bevor<br />

der alte Wert gesendet wurde). Und alles was empfangen wird, können Sie aus diesem Register lesen<br />

(Ausser wenn Sie zulange warten, dann kann e<strong>in</strong> alter Wert durch e<strong>in</strong>en neuen überschrieben<br />

werden).<br />

Die Register der seriellen Schnittstelle bef<strong>in</strong>den sich auf den folgenden Adressen:<br />

Zugriffsmode Breite Adresse Registername<br />

S 16 Bit 0xFFFC00 QSMCR<br />

S 16 Bit 0xFFFC04 QSM INTERRUPT LEVEL (QILR) QSM INTERRUPT VECTOR (QIVR)<br />

S/U 16 Bit 0xFFFC08 SCI CONTROL 0 (SCCR0)<br />

S/U 16 Bit 0xFFFC0A SCI CONTROL 1 (SCCR1)<br />

S/U 16 Bit 0xFFFC0C SCI STATUS (SCSR)<br />

S/U 16 Bit 0xFFFC0E SCI DATA (SCDR)<br />

Der Zugriffsmode ist für diese Übung nicht von Bedeutung.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 14/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

3.6 Umlenken der E<strong>in</strong>- und Ausgabefunktionen <strong>in</strong> C.<br />

In e<strong>in</strong>em Mikroprozessorsystem gibt es im Allgeme<strong>in</strong>en weder e<strong>in</strong> Dateisystem, noch andere standardisierte<br />

E<strong>in</strong>- und Ausgabefunktionen. Die Standartbibliothek von C ist jedoch so aufgebaut, dass<br />

es sehr e<strong>in</strong>fach ist, sämtliche E<strong>in</strong>- und Ausgaben auf die passende Hardware umzulenken. Es müssen<br />

im e<strong>in</strong>fachsten Fall nur die Funktionen putc() und getc() (und ev. ungetc()) selbst geschrieben<br />

werden, alle anderen Funktionen wie z. B. pr<strong>in</strong>tf(), scanf() oder gets() rufen<br />

diese Funktionen auf.<br />

Wenn man e<strong>in</strong> Dateisystem und mehrere E<strong>in</strong>-/Ausgabekanäle unterstützen möchte, muss man zusätzlich<br />

noch fopen(), fclose() und andere spezielle Dateizugriffsfunktionen wie ftell()<br />

oder fseek() selbst schreiben. Natürlich brauchen nur diejenigen Funktionen geschrieben zu<br />

werden, welche man auch wirklich benötigt.<br />

Wenn man ke<strong>in</strong> Dateisystem nachbildet, kann man bei den Funktionen getc() und putc() das<br />

stream (FILE *) Argument ignorieren. Um die E<strong>in</strong> und Ausgaben <strong>in</strong> unserem Kit umzulenken,<br />

müssten die beiden Funktionen also ungefähr wie folgt def<strong>in</strong>iert werden:<br />

<strong>in</strong>t putc (<strong>in</strong>t c, FILE *stream)<br />

{<br />

SendByteToSerialPort(c);<br />

return c; /* Sollte im Fehlerfall noch EOF zurueckgeben */<br />

}<br />

<strong>in</strong>t getc (FILE *stream)<br />

{<br />

/* naechstes Zeichen zurueckliefern (Oder EOF im Fehlerfall)*/<br />

return GetByteFromSerialPort();<br />

}<br />

Wichtig: Die Funktion getc() muss solange warten, bis e<strong>in</strong> Zeichen von der seriellen Schnittstelle<br />

e<strong>in</strong>trifft, oder e<strong>in</strong>en Fehlercode (EOF) zurückliefern. Sie darf nicht 'e<strong>in</strong>fach so' zurückkehren, wenn<br />

ke<strong>in</strong> Zeichen e<strong>in</strong>getroffen ist.<br />

Die Funktionen SendByteToSerialPort(c) und GetByteFromSerialPort() müssen vom Anwender<br />

selbst geschrieben werden, und sollen e<strong>in</strong> Zeichen auf die serielle Schnittstelle schreiben, resp. e<strong>in</strong><br />

Zeichen von dieser Lesen. Anstelle des Funktionsaufrufs kann natürlich auch direkt der Code zum<br />

Schreiben und Lesen der UART-HArdware e<strong>in</strong>gesetzt werden.<br />

Da die höheren E<strong>in</strong>gabefunktionen wie scanf() auch die Funktion ungetc() verwenden, sollte<br />

diese ebenfalls noch implementiert werden. ungetc() legt e<strong>in</strong> zuviel gelesenes Zeichen wieder <strong>in</strong><br />

den E<strong>in</strong>gabepuffer zurück (ungetc() darf natürlich auch mehr als e<strong>in</strong> Zeichen zurücklegen, der<br />

Standard fordert e<strong>in</strong>fach e<strong>in</strong> M<strong>in</strong>imum von e<strong>in</strong>em Zeichen.<br />

Somit müssten die Funktionen etwa wie folgt def<strong>in</strong>iert werden:<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 15/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

<strong>in</strong>t putc (<strong>in</strong>t c, FILE *stream)<br />

{<br />

SendByteToSerialPort(c);<br />

return c; /* Sollte im Fehlerfall noch EOF zurueckgeben */<br />

}<br />

static <strong>in</strong>t UngottenCharacter = -1;<br />

<strong>in</strong>t getc (FILE *stream)<br />

{<br />

<strong>in</strong>t Zeichen = 0;<br />

/* auf e<strong>in</strong> mit ungetc() zurückgelegtes Zeichen testen */<br />

if (UngottenCharacter >= 0) {<br />

}<br />

/* zurueckgelegtes Zeichen benutzen und Buffer wieder loeschen */<br />

Zeichen = UngottenCharacter;<br />

UngottenCharacter = -1;<br />

} else {<br />

/* naechstes Zeichen zurueckliefern (Oder EOF im Fehlerfall)*/<br />

Zeichen = GetByteFromSerialPort();<br />

}<br />

return Zeichen;<br />

<strong>in</strong>t ungetc (<strong>in</strong>t Zeichen, FILE *stream)<br />

{<br />

/* das Zeichen zuruecklegen, es wird nur e<strong>in</strong> Zeichen behalten…*/<br />

UngottenCharacter = Zeichen;<br />

return Zeichen; /* Sollte im Fehlerfall noch EOF zurueckgeben */<br />

}<br />

3.7 Aufgabe 3<br />

Sorgen Sie dafür, dass sämtliche E<strong>in</strong>- und Ausgaben über die serielle Schnittstelle erfolgen, und<br />

br<strong>in</strong>gen sie e<strong>in</strong> paar Ihrer älteren Programme mit Text E<strong>in</strong>- und Ausgabe aus dem C-Unterricht auf<br />

dem Kit zum laufen.<br />

3.8 Weitere Anpassungen der Standardbibliothek<br />

Um die gesamte Funktionalität der Standartbibliothek auf e<strong>in</strong>em Mikrocomputersystem zur Verfügung<br />

zu haben, müssen noch weitere Anpassungen vorgenommen werden:<br />

Neben der E<strong>in</strong>- und Ausgabe müssen noch die Funktionen für die dynamische Speicherverwaltung,<br />

sowie die Funktionen die mit Zeiten arbeiten (time.h) angepasst werden. Natürlich auch nur falls sie<br />

benötigt werden.<br />

Um die dynamische Speicherverwaltung an die jeweilige Hardware anzupassen müssen gelegentlich<br />

die Funktionen malloc() und free() modifiziert oder komplett selbst geschrieben werden,<br />

meist reichen aber E<strong>in</strong>stellungen <strong>in</strong> den L<strong>in</strong>keroptionen, man muss nur def<strong>in</strong>ieren, welcher Speicherbereich<br />

von der Speicherverwaltung benutzt werden darf.<br />

Um die Funktionen für die Zeitverwaltung (time.h) anzupassen empfiehlt sich die Konsultation der<br />

Dokumentation zum Entwicklungssystem.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 16/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

4 Startupcode<br />

Bevor e<strong>in</strong> C-Programm zu laufen beg<strong>in</strong>nt (ma<strong>in</strong>() aufgerufen wird) müssen e<strong>in</strong>ige Initialisierungsaufgaben<br />

erledigt werden. Diese s<strong>in</strong>d hochgradig Prozessorspezifisch und müssen meistens (m<strong>in</strong>d.<br />

teilweise) <strong>in</strong> Assembler programmiert werden. Dazu gehören üblicherweise die Initialisierung der<br />

globalen und statischen Variablen (In C++ auch der statischen Objekte), Initialisierung gewisser<br />

Bibliotheksfunktionen, Initialisierung der Standard E<strong>in</strong>/-Ausgabefunktionen, M<strong>in</strong>imalkonfiguration<br />

der Hardware und Initialisierung spezieller Prozessorregister (E<strong>in</strong>schalten und Konfiguration des<br />

RAM, falls nötig, eventuell e<strong>in</strong> kurzer Systemtest), und schliesslich Initialisieren des Stack- und<br />

Framepo<strong>in</strong>ters (zur Verwaltung von Unterprogrammaufrufen und lokalen Variablen). Erst jetzt kann<br />

und wird die Funktion ma<strong>in</strong>() aufgerufen.<br />

In unserem System bef<strong>in</strong>det sich der Startupcode <strong>in</strong> der Datei pmn332.68k. Nach e<strong>in</strong>em Reset wird<br />

zuerst <strong>in</strong> diesen Initialisierungscode gesprungen, und erst von hier aus ma<strong>in</strong>() aufgerufen.<br />

Auf die genaue Funktion dieses Assemblercodes wird hier nicht weiter e<strong>in</strong>gegangen, <strong>in</strong> dieser Datei<br />

sollten sie ke<strong>in</strong>e Änderungen vornehmen.<br />

Der grundsätzliche Ablauf nach dem E<strong>in</strong>schalten der Speisung sieht bei e<strong>in</strong>em Autonomen System<br />

etwa wie folgt aus (Vorausgesetzt der Programmcode bef<strong>in</strong>det sich <strong>in</strong> e<strong>in</strong>em ROM).<br />

Ablauf:<br />

Reset<br />

Startupcode<br />

Bibliothek und Runtimesystem<br />

Initialisieren<br />

ma<strong>in</strong>()<br />

Ende<br />

Bei Programmen für Mikrocontroller kehrt die Funktion ma<strong>in</strong>() normalerweise nie zurück, sondern<br />

arbeitet <strong>in</strong> e<strong>in</strong>er Endlosschlaufe die Prozesssteuerungsaufgaben ab.<br />

Wenn ma<strong>in</strong>() doch e<strong>in</strong>mal zurückkehren sollte, ist nicht genau def<strong>in</strong>iert was geschieht. Meist wird<br />

e<strong>in</strong>fach <strong>in</strong> e<strong>in</strong>e Endlosschlaufe gesprungen, aber es wäre auch s<strong>in</strong>nvoll, das System zurückzusetzen<br />

und wieder von vorne zu beg<strong>in</strong>nen.<br />

Bei e<strong>in</strong>em PC ist klar was nach dem Ende von ma<strong>in</strong>() geschieht: Das Programm ist zu Ende, es werden<br />

alle Fenster des Programms geschlossen und das System wartet auf weitere Benutzere<strong>in</strong>gaben,<br />

oder fährt herunter, wenn auch das Betriebssystem selbst beendet wird.<br />

Bei Mikrokontrollern gibt es üblicherweise ke<strong>in</strong> solches übergeordnetes System, und so muss sich<br />

der Entwickler selbst überlegen, was er <strong>in</strong> diesem Falle tun will. (Möglichst etwas S<strong>in</strong>nvolles, das<br />

die Sicherheit und die Stabilität des zu steuernden Prozesses nicht bee<strong>in</strong>trächtigt. E<strong>in</strong> Navigationssystem<br />

sollte sich vielleicht wieder neu <strong>in</strong>itialisieren oder das Fahrzeug anhalten, aber nicht e<strong>in</strong>fach<br />

anhalten und das Fahrzeug <strong>in</strong> die letztgewählte Richtung weiterfahren lassen).<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 17/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

5 Interrupts<br />

5.1 E<strong>in</strong>führung<br />

Interrupts s<strong>in</strong>d e<strong>in</strong> wichtiges Hilfsmittel, um Echtzeitanforderungen zu erfüllen. E<strong>in</strong> Interrupt wird<br />

immer durch e<strong>in</strong> (meist externes) Ereignis ausgelöst und unterbricht das laufende Programm zu e<strong>in</strong>em<br />

beliebigen Zeitpunkt.<br />

Tritt e<strong>in</strong> Interrupt auf, so wird augenblicklich e<strong>in</strong> spezielles Unterprogramm aufgerufen, welches<br />

das entsprechende Ereignis behandelt. In diesem Zusammenhang spricht man von e<strong>in</strong>er 'Interrupt<br />

Service Rout<strong>in</strong>e', kurz ISR. Die Unterbrechungsanforderung wird 'Interrupt Request' genannt. Wenn<br />

die Interruptrout<strong>in</strong>e zu Ende ist (Das Ende der Funktion oder e<strong>in</strong> return erreicht), wird im unterbrochenen<br />

Programm weitergefahren, als ob nichts geschehen wäre.<br />

Beispiel für e<strong>in</strong>en Interrupt aus dem täglichen Leben:<br />

Sie sitzen vor dem Computer und schreiben an e<strong>in</strong>em Stück Programmcode. Plötzlich<br />

kommt e<strong>in</strong> Kollege, der Ihnen e<strong>in</strong>e Frage stellt. Sie unterbrechen das Programmieren<br />

und beantworten dem Kollegen se<strong>in</strong>e Frage, anschliessend arbeiten Sie an Ihrem Programm<br />

weiter. Es wäre auch möglich, dass, während Sie mit dem Kollegen sprechen Sie<br />

e<strong>in</strong> Lehrer unterbricht und fragt was los ist. (Die Unterbrechung wird durch e<strong>in</strong>e weitere<br />

Unterbrechung unterbrochen.)<br />

Wenn Sie gerade mitten im Schreiben von e<strong>in</strong>em schwierigen Stück Code s<strong>in</strong>d, kann es<br />

auch se<strong>in</strong> dass Sie nicht gestört werden wollen, dann ignorieren Sie Ihren Kollegen oder<br />

sagen Ihm, dass er kurz warten soll. (Aber wenn der Lehrer ersche<strong>in</strong>t, werden Sie Ihre<br />

Arbeit vielleicht dennoch unterbrechen).<br />

Schematische Darstellung e<strong>in</strong>es Interrupt Requests:<br />

Hauptprogramm<br />

Interruptrout<strong>in</strong>e<br />

Kontext<br />

sichern<br />

Ereignis<br />

Interrupt Request<br />

laufende<br />

Instruktion<br />

erste<br />

Instruktion<br />

nächste<br />

Instruktion<br />

letzte<br />

Instruktion<br />

Kontext wiederherstellen<br />

Der 'Kontext' ist der aktuelle Zustand vom Hauptprogramm (Prozessorregister, Stack) und die Interruptrout<strong>in</strong>e<br />

muss sicherstellen, dass das Hauptprogramm am Ende der Interruptrout<strong>in</strong>e so weiterfährt,<br />

als ob nichts geschehen wäre.<br />

Wie wenn Sie auf e<strong>in</strong>em Notizzettel festhalten, woran Sie gerade gearbeitet haben und<br />

was Sie als nächstes tun wollten, bevor Sie sich Ihrem Kollegen zuwenden.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 18/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

5.2 Def<strong>in</strong>ition e<strong>in</strong>er Interruptrout<strong>in</strong>e<br />

Da e<strong>in</strong>e Interruptrout<strong>in</strong>e e<strong>in</strong> laufendes Programm zu e<strong>in</strong>em beliebigen Zeitpunkt unterbrechen kann,<br />

muss der Compiler für e<strong>in</strong>e Interruptrout<strong>in</strong>e speziellen Code erzeugen. Bei normalen Unterprogrammen<br />

weiss der Compiler immer genau, wann sie auftreten, und welche <strong>in</strong>ternen Daten (Prozessorregister)<br />

er sichern muss. Bei Interruptrout<strong>in</strong>en ist das nicht bekannt, deshalb müssen zu Beg<strong>in</strong>n<br />

der Rout<strong>in</strong>e vorsorglich alle wichtigen Daten gerettet, und am Ende der Rout<strong>in</strong>e wieder hergestellt<br />

werden. Dazu muss man aber dem Compiler sagen, dass es sich um e<strong>in</strong>e Interruptrout<strong>in</strong>e handelt.<br />

Dies ist <strong>in</strong> der Sprache C nicht vorgesehen, deshalb hat jeder Compilerhersteller se<strong>in</strong>e eigene<br />

Spracherweiterung dazu. E<strong>in</strong>ige erwarten e<strong>in</strong> Schlüsselwort wie <strong>in</strong>terrupt, __<strong>in</strong>t,<br />

<strong>in</strong>terupt_nn oder #pragma <strong>in</strong>terrupt. Die genaue Syntax muss man dem jeweiligen<br />

Compilerhandbuch entnehmen. Bei Task<strong>in</strong>g muss man das Schlüsselwort _IH der Funktion voranstellen,<br />

die Funktion darf zudem ke<strong>in</strong>en Rückgabewert und ke<strong>in</strong>e Argumente besitzen. Mit Task<strong>in</strong>g<br />

müsste also e<strong>in</strong> Interrupthandler wie folgt def<strong>in</strong>iert werden:<br />

_IH void TimerInterruptHandler(void) /* Wird regelmaessig alle */<br />

{ /* 100ms vom Timer */<br />

Timeout--; /* Interrupt aufgerufen */<br />

if (Timeout 0) {<br />

Magic = 1.0 / (float)Timeout;<br />

}<br />

}<br />

In obigem Beispiel kann e<strong>in</strong> Division durch Null Fehler auftreten! Weshalb?<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 19/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Weil der Interrupt gerade nach der Abfrage auftreten könnte, und Timeout von 1 auf 0 dekrementieren<br />

könnte, bevor die Division durchgeführt wird.<br />

Um das zu umgehen muss dieses Stück Code von Interrupts geschützt werden:<br />

_IH void TimerInterruptHandler(void)<br />

{<br />

Timeout--;<br />

}<br />

void Trouble (void)<br />

{<br />

DisableInterrupts();<br />

if (Timeout > 0) {<br />

Magic = 1.0 / (float)Timeout;<br />

}<br />

EnableInterrupts();<br />

}<br />

Das ist schon viel Besser, hat aber immer noch e<strong>in</strong> kle<strong>in</strong>es Problem. Wenn die Interrupts beim Aufruf<br />

der Funktion Trouble() bereits ausgeschaltet waren, s<strong>in</strong>d sie nach dem Aufruf der Funktion wieder<br />

e<strong>in</strong>geschaltet, was normalerweise nicht erwünscht ist. Deshalb gibt es meist noch e<strong>in</strong>e Funktion,<br />

mit der man herausf<strong>in</strong>den kann, ob die Interrupts im Moment gesperrt oder freigegeben s<strong>in</strong>d. Optimal<br />

würde der Code also wie folgt aussehen:<br />

_IH void TimerInterruptHandler(void)<br />

{<br />

Timeout--;<br />

}<br />

void Trouble (void)<br />

{<br />

<strong>in</strong>t OldInterruptState;<br />

}<br />

OldInterruptState = GetInterruptState();<br />

DisableInterrupts();<br />

if (Timeout > 0) {<br />

Magic = 1.0 / (float)Timeout;<br />

}<br />

/* Enable Interrupts only if they were enabled before */<br />

if (OldInterruptState == 1) {<br />

EnableInterrupts();<br />

}<br />

ACHTUNG<br />

Auch sche<strong>in</strong>bar sicherer Code wie a++ oder a = a + 1 muss geschützt werden, weil dieser nicht<br />

<strong>in</strong> e<strong>in</strong>e e<strong>in</strong>zige, sondern üblicherweise <strong>in</strong> e<strong>in</strong>e Folge von mehreren Prozessoranweisungen (Lesen,<br />

Inkrementieren, Schreiben) übersetzt wird, die auch unterbrochen werden kann. Bei e<strong>in</strong>igen Prozessoren<br />

gibt es für diese Fälle spezielle 'atomare' Instruktionen, welche nicht unterbrochen werden<br />

können. Aber um diese e<strong>in</strong>setzen zu können s<strong>in</strong>d wieder Compilererweiterungen erforderlich, welche<br />

dem entsprechenden Compilerhandbuch entnommen werden müssen.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 20/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

5.4 Interrupthandl<strong>in</strong>g beim 68xxx<br />

Bei unserem System läuft es im Pr<strong>in</strong>zip genau so wie im vorigen Kapitel beschrieben. Die Prozessoren<br />

der 68000er Reihe haben zusätzlich noch e<strong>in</strong>e Interruptpriorisierung, das heisst dass jeder Interrupt<br />

e<strong>in</strong>e Priorität von 1 bis 7 besitzt, und beim Sperren von Interrupts die Priorität angeben wird,<br />

bis zu welcher Interrupts unterdrückt werden. Interrupts mit der Priorität 7 können gar nicht unterdrückt<br />

werden, und sollten nur für wichtige Ereignisse wie Stromausfall oder ähnliches benutzt werden.<br />

Die aktuell zugelassene Priorität wird <strong>in</strong> der Interruptmaske des Statusregisters vom Prozessor abgelegt.<br />

In der Programmiersprache C gibt es aber ke<strong>in</strong>e direkte Möglichkeit, auf Prozessorregister zuzugreifen,<br />

also müsste man Assemblere<strong>in</strong>schlüsse dazu verwenden. Der Task<strong>in</strong>gcompiler stellt aber<br />

Funktionen zum Lesen und Schreiben der Interruptmaske zur Verfügung, so muss nicht auf Assembler<br />

zurückgegriffen werden.<br />

Zum Lesen der aktuellen Interruptmaske dient die Funktion _GPL(), sie liefert e<strong>in</strong>e Zahl zwischen 0<br />

bis 7 (0 = alle Interrupts erlaubt, 7 = alle bis auf Priorität 7 gesperrt (Priorität 7 ist flankengesteuert),<br />

6 = alle bis auf Priorität 7 gesperrt (Priorität 7 ist Pegelgesteuert), 5 = alle bis auf Priorität 6 und 7<br />

gesperrt, usw.).<br />

Zum Setzen der Interruptmaske (Sperren der Interrupts) dient die Funktion _SPL(). Um alle Interrupts<br />

(bis auf Level 7) zu sperren müsste also _SPL(7) benutzt werden. Um alle Interrupts zu erlauben<br />

müsste _SPL(0) benutzt werden.<br />

Für unser System würde das Beispiel aus dem vorherigen Kapitel wie folgt aussehen:<br />

_IH void TimerInterruptHandler(void)<br />

{<br />

Timeout--;<br />

}<br />

void Trouble (void)<br />

{<br />

<strong>in</strong>t OldInterruptLevel;<br />

OldInterruptLevel= _GPL(); /* Alten Zustand merken */<br />

_SPL(7); /* Interupts sperren */<br />

if (Timeout > 0) {<br />

Magic = 1.0 / (float)Timeout;<br />

}<br />

}<br />

/* Alten Zustand wieder herstellen */<br />

_SPL(OldInterruptLevel);<br />

Pegelgesteuerte Interrupts versus Flankengetriggerte Interrupts:<br />

Pegelgesteuerte Interrupts werden vom Prozessor solange erkannt, wie der entsprechende Pegel anliegt.<br />

Das heisst der Prozessor spr<strong>in</strong>gt gleich wieder <strong>in</strong> die Interruptrout<strong>in</strong>e, wenn der Interrupt nach<br />

dem Ende (Oder der Freigabe der entsprechenden Priorität) immer noch anliegt.<br />

Bei flankengetriggerten Interrupts reagiert der Prozessor auf e<strong>in</strong>en Wechsel von <strong>in</strong>aktiv auf aktiv, es<br />

wird nur der Wechsel beachtet, der Pegel spielt ke<strong>in</strong>e Rolle. Flankengetriggerte Interrupts können<br />

oft nicht gesperrt werden. Falls doch merkt sich der Prozessor den Pegelwechsel, und spr<strong>in</strong>gt nach<br />

der Freigabe des Interrupts gleich <strong>in</strong> die entsprechende Rout<strong>in</strong>e (Aber nur e<strong>in</strong>mal pro Flanke, wenn<br />

im gesperrten Zustand mehrere Flanken auftreten, wird nur e<strong>in</strong>e gespeichert).<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 21/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

5.4.1 Interruptvektoren<br />

Das Def<strong>in</strong>ieren e<strong>in</strong>er Interruptrout<strong>in</strong>e alle<strong>in</strong>e reicht noch nicht aus, der Prozessor muss auch wissen,<br />

welche Rout<strong>in</strong>e er im Falle e<strong>in</strong>es Interrupts aufrufen soll. Das Pr<strong>in</strong>zip ist bei allen Prozessoren dasselbe.<br />

Entweder gibt es irgendwo im Speicher e<strong>in</strong>e Tabelle, <strong>in</strong> welcher die Adressen der Funktionen<br />

e<strong>in</strong>getragen werden müssen, oder der Prozessor spr<strong>in</strong>gt an e<strong>in</strong>e vom Prozessorhersteller vordef<strong>in</strong>ierte<br />

Adresse, und es ist die Aufgabe des Programmierers dafür zu sorgen, dass an dieser Stelle s<strong>in</strong>nvoller<br />

Programmcode steht.<br />

Die 68000er Familie arbeitet mit e<strong>in</strong>er Vektortabelle, welche im folgenden vorgestellt wird.<br />

In der Tabelle ist Platz für 256 Interruptrout<strong>in</strong>en vorgesehen. An jedem Platz steht die Anfangsadresse<br />

(Zeiger auf die Funktion) der jeweiligen Interruptrout<strong>in</strong>e. Die ersten 64 E<strong>in</strong>träge (0 bis 63)<br />

s<strong>in</strong>d für Systemzwecke vordef<strong>in</strong>iert und behandeln spezielle Ereignisse wie unerlaubte Speicherzugriffe,<br />

Illegale Instruktionen, Reset, Trap<strong>in</strong>sstruktionen, Division durch Null, un<strong>in</strong>itialisierte Interrupts<br />

und weitere.<br />

Der Anwender kann (Und sollte) auch Behandlungsrout<strong>in</strong>en für diese Ereignisse schreiben, welche<br />

s<strong>in</strong>nvoll darauf reagieren. Das kann im e<strong>in</strong>fachsten Fall das E<strong>in</strong>schalten e<strong>in</strong>er Fehler-Led und Anhalten<br />

des Prozessors se<strong>in</strong>.<br />

Die restlichen 192 E<strong>in</strong>träge (64-255) s<strong>in</strong>d zur freien Verfügung für den Benutzer. Jeder Peripheriebausten,<br />

der e<strong>in</strong>en Interrupt auslösen kann, muss der CPU mitteilen, welcher Vektor benutzt werden<br />

soll. Diese Vektornummer muss ebenfalls vom Benutzer <strong>in</strong> e<strong>in</strong> Register des Peripheriebauste<strong>in</strong>s<br />

programmiert werden. Für Peripheriebauste<strong>in</strong>e, die ke<strong>in</strong>en Vektor liefern können, gibt es für die 7<br />

Interruptprioritätsstufen je e<strong>in</strong>en 'Autovektor' <strong>in</strong> den reservierten 64 Tabellene<strong>in</strong>trägen.<br />

Die Vektortabelle beg<strong>in</strong>nt üblicherweise bei der Adresse 0x000000, kann aber vom Programmierer<br />

an e<strong>in</strong>en beliebigen Ort im Speicher plaziert werden. Dies muss aber <strong>in</strong> Assembler geschrieben werden,<br />

und erfolgt am besten im Startupcode.<br />

Pr<strong>in</strong>zipieller Aufbau des Interrupthandl<strong>in</strong>gs:<br />

Memory des Prozessors<br />

Timer<br />

Vektornummer<br />

Zeitzähler<br />

78<br />

123994<br />

Code für Timer<strong>in</strong>terrupt<br />

Tastenüberwacher<br />

Vektornummer 75<br />

Tastencode 1001<br />

Code für Tasten<strong>in</strong>terrupt<br />

CPU<br />

Vektortabelle 78<br />

76<br />

75<br />

74<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 22/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Jedem Peripheriebauste<strong>in</strong> wird e<strong>in</strong>e eigene Vektornummer für se<strong>in</strong>e Interrupts zugeteilt. Anschliessend<br />

wird <strong>in</strong> der Interruptvektortabelle am entsprechenden Platz die Adresse der dazugehörigen<br />

Interruptrout<strong>in</strong>e e<strong>in</strong>getragen. Die Interruptvektortabelle kann man sich unter C als e<strong>in</strong> Array von<br />

Zeigern auf Funktionen vorstellen, und so muss auch auf sie zugegriffen werden. (Da <strong>in</strong> der Tabelle<br />

Adressen e<strong>in</strong>getragen werden, und jede Adresse 4 Bytes (32 Bit) Platz belegt, ist die Adresse des<br />

E<strong>in</strong>trags gleich der vierfachen Vektornummer).<br />

Um e<strong>in</strong>e Handlerfunktion <strong>in</strong> die Vektortabelle e<strong>in</strong>tragen zu können, muss die E<strong>in</strong>tragsadresse <strong>in</strong><br />

e<strong>in</strong>en Zeiger auf e<strong>in</strong>en Zeiger auf e<strong>in</strong>e Funktion die void zurückliefert und ke<strong>in</strong>e Argumente hat<br />

umgewandelt werden (uff!).<br />

Nehmen wir an, wir wollen den TimerInterupthandler auf den Vektor 129 setzen, dann ergäbe sich<br />

folgender Code:<br />

*((void (**)(void))(129*4)) = TimerInterruptHandler;<br />

Zur Er<strong>in</strong>nerung:<br />

<strong>in</strong>t *p; ist e<strong>in</strong> Zeiger auf e<strong>in</strong>en <strong>in</strong>t.<br />

<strong>in</strong>t **p; ist e<strong>in</strong> Zeiger auf e<strong>in</strong>en Zeiger auf e<strong>in</strong>en <strong>in</strong>t.<br />

void (*p)(void);<br />

void (**p)(void);<br />

ist e<strong>in</strong> Zeiger auf e<strong>in</strong>e Funktion der Form void f(void).<br />

ist e<strong>in</strong> Zeiger auf e<strong>in</strong>en Zeiger auf e<strong>in</strong>e Funktion der Form void<br />

f(void).<br />

Der Umwandlungsoperator (Cast-Operator) sieht immer gleich aus wie die entsprechende Variablendeklaration, nur dass<br />

der Variablenname weggelassen wird, und der ganze Ausdruck <strong>in</strong> Klammern gesetzt wird.<br />

(<strong>in</strong>t *)123;<br />

Wandelt die Zahl 123 <strong>in</strong> e<strong>in</strong>en Zeiger auf <strong>in</strong>t (Adresse) um.<br />

(void (*)(void))123; Wandelt die Zahl 123 <strong>in</strong> e<strong>in</strong>en Zeiger auf e<strong>in</strong>e Funktion der Form<br />

void f(void).um.<br />

(void (**)(void))123; Wandelt die Zahl 123 <strong>in</strong> e<strong>in</strong>en Zeiger auf e<strong>in</strong>en Zeiger auf e<strong>in</strong>e<br />

Funktion der Form void f(void) um.<br />

*((<strong>in</strong>t *)123) = 17; Wandelt die Zahl 123 <strong>in</strong> e<strong>in</strong>en Zeiger auf <strong>in</strong>t (Adresse) um, und<br />

schreibt 17 an diese Adresse. An der Adresse 123 im Speicher steht<br />

also jetzt der Wert 17.<br />

*((void (**)(void))123) = f; Wandelt die Zahl 123 <strong>in</strong> e<strong>in</strong>en Zeiger auf e<strong>in</strong>en Zeiger<br />

auf e<strong>in</strong>e Funktion der Form void f(void) um und schreibt die Adresse<br />

der Funktion f dorth<strong>in</strong>. An der Adresse 123 im Speicher steht also<br />

nun die Adresse der Funktion f (Oder anders gesagt, e<strong>in</strong> Zeiger<br />

auf die Funktion f).<br />

Mit e<strong>in</strong>em Makro kann man das ganze etwas 'entschärfen':<br />

#def<strong>in</strong>e SET_INTERRUPT_HANDLER(VectorNumber, Handler) \<br />

(*((void (**)(void)) (4*VectorNumber)) = Handler)<br />

SET_INTERRUPT_HANDLER(129, TimerInterruptHandler);<br />

(Mit e<strong>in</strong>em '\' kann e<strong>in</strong>e Makrodef<strong>in</strong>ition auf die nächste Zeile erweitert werden, die beiden Zeilen werden von Compiler<br />

[resp. Präprozessor] als e<strong>in</strong>e e<strong>in</strong>zige Zeile betrachtet.)<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 23/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Und mit Typedefs wird das ganze noch e<strong>in</strong>mal etwas besser lesbar:<br />

typedef void (*Handler)(void); /* Zeiger auf Funktion */<br />

typedef Handler *HandlePtr; /* Zeiger auf Zeiger auf fkt */<br />

*((HandlePtr) (4*129)) = TimerInterruptHandler;<br />

/* Oder mit Makros */<br />

#def<strong>in</strong>e SET_INTERRUPT_HANDLER(VectorNumber, Handler) \<br />

(*((HandlePtr)(4*VectorNumber)) = Handler)<br />

SET_INTERRUPT_HANDLER(129, TimerInterruptHandler);<br />

Oder wenn man das ganze Arraymässig betrachtet (Was durchaus s<strong>in</strong>nvoll ist, da die Interruptvektortabelle<br />

schliesslich nichts anderes als e<strong>in</strong> Array von 256 Zeiger auf Funktionen ist), ergibt sich<br />

folgender Code:<br />

typedef void (*Handler)(void); /* Zeiger auf Funktion */<br />

Handler *VektorTabelle = (Handler *)0; /* Tabelle beg<strong>in</strong>nt bei */<br />

/* Adresse 0 */<br />

VektorTabelle[129] = TimerInterruptHandler;<br />

/* Weitere E<strong>in</strong>traege s<strong>in</strong>d jetzt e<strong>in</strong>fach moeglich */<br />

VektorTabelle[134] = TastenInterruptHandler;<br />

VektorTabelle[156] = PowerLossInterruptHandler;<br />

Unbenutzte E<strong>in</strong>träge lässt man am Besten auf e<strong>in</strong>en Defaulthandler verweisen, welcher e<strong>in</strong>e Fehlermeldung<br />

ausgibt, das System stoppt oder e<strong>in</strong>e andere, s<strong>in</strong>nvolle Massnahme ergreift.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 24/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

5.5 Aufgabe 4<br />

Schreiben Sie e<strong>in</strong> Programm, welches mit Hilfe des Timer<strong>in</strong>terrupts e<strong>in</strong>e oder mehrere Led bl<strong>in</strong>ken<br />

lässt. Nebenbei soll das Programm noch E<strong>in</strong> und Ausgaben via serielle Schnittstelle tätigen, z. B.<br />

mit scanf() zwei Zahlen e<strong>in</strong>lesen, und deren Summe mit pr<strong>in</strong>tf() ausgeben.<br />

Genauere Informationen über die Register des Timers des Kits und dessen Konfiguration können sie<br />

dem Dokument MC68332UM.pdf ab Kapitel 4.2.11, 'Periodic Interrupt Timer' (Seite 4-7) sowie<br />

Kapitel D.2.13 und D.2.14 (Seite D-11) entnehmen. Sie müssen die Register PICR und PITR korrekt<br />

konfigurieren, der Basistakt des Timers beträgt 32768Hz.<br />

Die Register des Timers bef<strong>in</strong>den sich auf den folgenden Adressen:<br />

Zugriffsmode Breite Adresse Registername<br />

S/U 16 Bit 0xFFFA22 PICR<br />

S/U 16 Bit 0xFFFA24 PITR<br />

Der Zugriffsmode ist für diese Übung nicht von Bedeutung.<br />

5.6 Aufgabe 5<br />

Erweitern Sie Ihr Programm so, dass die E<strong>in</strong>- und Ausgaben über die Serielle Schnittstelle ebenfalls<br />

mit Interrupts erledigt werden. E<strong>in</strong>gehende Zeichen sollen <strong>in</strong> e<strong>in</strong>em Buffer abgelegt werden, und zu<br />

sendende Zeichen aus e<strong>in</strong>em Buffer gelesen werden. Das Hauptprogramm (putc() und getc()) soll<br />

aus diesen Buffer lesen und schreiben. Damit wird das Hauptprogramm von der seriellen Schnittstelle<br />

entkoppelt, und es gehen ke<strong>in</strong>e Zeichen verloren, wenn das Hauptprogramm nicht augenblicklich<br />

reagiert.<br />

H<strong>in</strong>weis: Die Empfangs- und Sendebuffer können sie als Arrays implementieren.<br />

Wenn ganze Telegramme gepuffert werden sollen, empfiehlt sich der E<strong>in</strong>satz von Listen, es muss<br />

aber sichergestellt se<strong>in</strong>, dass das Erzeugen von Listenelementen sicher und schnell ist, was bei Malloc<br />

nicht unbed<strong>in</strong>gt garantiert ist. (Zudem muss auch sichergestellt werden, dass malloc() und free()<br />

aus Interruptrout<strong>in</strong>en heraus verwendet werden dürfen).<br />

5.7 Interrupts und Standardbibliothek<br />

Auf die Verwendung von Funktionen aus der Standardbibliothek sollte grundsätzlich verzichtet<br />

werden. Erstens sollte e<strong>in</strong>e Interruptrout<strong>in</strong>e möglichst kurz se<strong>in</strong> und deshalb möglichst wenig andere<br />

Funktionen aufrufen, und zum anderen dürfen nur Funktionen verwendet werden, welche reentrant<br />

s<strong>in</strong>d (Also aufgerufen werden dürfen, während sie bereits am laufen s<strong>in</strong>d). Wenn z. B. gerade mit<br />

pr<strong>in</strong>tf() etwas auf dem Bildschirm ausgegeben wird, und jetzt e<strong>in</strong> Interrupt auftritt welcher se<strong>in</strong>erseits<br />

etwas mit pr<strong>in</strong>tf() ausgibt, ist das Chaos garantiert. Ganz abgesehen davon, dass die pr<strong>in</strong>tf()-<br />

Funktion viel zu lang ist, um aus Interrupts aufgerufen zu werden.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 25/33


Anhang A Bitzugriff<br />

Bei der hardwarenahen <strong>Programmierung</strong> muss oft auf e<strong>in</strong>zelne Bits zugegriffen werden. Dazu bieten<br />

sich zwei Möglichkeiten an. E<strong>in</strong>erseits die Bitmaskierung mit den bitweisen logischen Operatoren<br />

| und &, andererseits der E<strong>in</strong>satz von Bitfeldern.<br />

Bitmaskierung<br />

Um <strong>in</strong> der Variablen 'Value' das Bit mit der Nummer n zu setzen, kann folgende Anweisung verwendet<br />

werden:<br />

Value = Value | (1


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Bitfelder<br />

Peripherieregister können zur e<strong>in</strong>facheren Handhabung auch auf Bitfelder abgebildet werden, da<br />

Bitfelder aber sehr Compilerabhängig s<strong>in</strong>d, muss jeweils die Compilerdokumentation zu Rate gezogen<br />

werden, bevor man sich für deren E<strong>in</strong>satz entscheidet.<br />

Nach Standard belegen Bitfelder immer e<strong>in</strong> vielfaches e<strong>in</strong>es <strong>in</strong>ts, also ergeben sich schon Probleme,<br />

wenn e<strong>in</strong> <strong>in</strong>t 32 Bit breit ist, aber die Peripherieregister nur 16 Bit breit s<strong>in</strong>d. Bei vielen Compiler<br />

kann deshalb die Grösse der Bitfelder separat festgelegt werden.<br />

Ebenfalls nicht festgelegt ist die Zuordnung der Bits, also ob der Compiler die Bits von l<strong>in</strong>ks nach<br />

rechts (31-0) oder rechts nach l<strong>in</strong>ks (0-31) vergibt.<br />

Als Beispiel dient folgendes Register (An Adresse 0xFF1230):<br />

x x Err EI x OE Q Pp0 Richtung F S Bitzähler<br />

Bit 15 Bit 8 Bit 7 Bit 0<br />

x = unbenutzt<br />

Umgesetzt <strong>in</strong> C ergibt sich folgendes Bitfeld, wenn der Compiler die Zuteilung bei Bit 0 beg<strong>in</strong>nt:<br />

struct DemoRegister {<br />

unsigned <strong>in</strong>t BitZaehler : 4; /* 4 Bits */<br />

unsigned <strong>in</strong>t S : 1; /* 1 Bit */<br />

unsigned <strong>in</strong>t F : 1; /* 1 Bit */<br />

unsigned <strong>in</strong>t Richtung : 2; /* 2 Bits */<br />

unsigned <strong>in</strong>t Pp : 1; /* 1 Bit */<br />

unsigned <strong>in</strong>t Q : 1; /* 1 Bit */<br />

unsigned <strong>in</strong>t OE : 1; /* 1 Bit */<br />

unsigned <strong>in</strong>t x : 1; /* 1 Bit, unbenutzt */<br />

unsigned <strong>in</strong>t EI : 1; /* 1 Bit */<br />

unsigned <strong>in</strong>t Err : 1; /* 1 Bit */<br />

};<br />

/* Zeiger auf Register def<strong>in</strong>ieren und setzen */<br />

volatile struct DemoRegister *Register;<br />

Register = (volatile struct DemoRegister *) 0xFF1230;<br />

/* Auf die Bits des Registers zugreifen */<br />

Register->F = 0;<br />

if (Register->Richtung == 2) {<br />

Register->BitZaehler = 10;<br />

}<br />

while (Register->F == 0) {}; /* Warten bis Bit gesetzt */<br />

Achtung: Auch wenn der Code e<strong>in</strong>fach aussieht, der Compiler erzeugt daraus ähnlichen Code wie<br />

im vorigen Kapitel 'Bitmaskierung' vorgestellt wurde. E<strong>in</strong>e e<strong>in</strong>fache Zuweisung wird also zu e<strong>in</strong>er<br />

Folge von Schiebeoperationen und bitweisen Verknüpfungen.<br />

Bei unbenutzten Bits <strong>in</strong> e<strong>in</strong>em Bitfeld kann der Name auch weggelassen werden, also<br />

unsigned <strong>in</strong>t : 1; /* 1 Bit, unbenutzt */<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 27/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Schattenregister<br />

Da Hardwareregister nicht immer gelesen werden können, man aber bei Bit Setz- und Löschoperationen<br />

den aktuellen Zustand des Registers kennen muss, wird für dieses Problem e<strong>in</strong>e Lösung benötigt.<br />

Die e<strong>in</strong>fachste Lösung ist die E<strong>in</strong>führung e<strong>in</strong>es 'Schattenregisters', das ist e<strong>in</strong>fach e<strong>in</strong>e (statische)<br />

Variable, auf welche parallel zum Register immer alle Schreiboperationen durchgeführt werden,<br />

und anstelle des Registers wird aus dieser Variablen gelesen.<br />

Das führt zu folgendem Code:<br />

static unsigned <strong>in</strong>t ShadowRegister;<br />

/* Initialisierung */<br />

ShadowRegister = 0; /* Schattenregister und */<br />

Register = 0; /* Echtes Register <strong>in</strong>itialisieren */<br />

/* E<strong>in</strong> Bit setzen */<br />

ShadowRegister = ShadowRegister | (1 4 wird zu 0xF8000000<br />

0x80000000u >> 4 wird zu 0x08000000<br />

Deshalb beim Rechtsschieben immer auf das Vorzeichen achten, und am Besten unsigned Datentypen<br />

verwenden.<br />

Probleme beim Casten (Umwandeln)<br />

Beim Umwandeln e<strong>in</strong>es kle<strong>in</strong>en Datentypen <strong>in</strong> e<strong>in</strong>en grösseren muss man beachten, dass bei vorzeichenbehafteten<br />

Zahlen das Vorzeichen dupliziert wird (Vorzeichenrichtige Erweiterung), was oft zu<br />

Problemen führt.<br />

Beispiel:<br />

char Byte = 0x80; /* Annahme: char ist signed */<br />

Value = 0x00430000;<br />

Value = Value | (Byte


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Anhang B Zahlensysteme<br />

Die meisten Mikroprozessoren rechnen im B<strong>in</strong>ärsystem. Im Normalfall bemerkt man als Programmierer<br />

<strong>in</strong> e<strong>in</strong>er Hochsprache nicht viel davon (Ausser bei den Datentypen).<br />

Sobald man aber auf Hardware zugreift, s<strong>in</strong>d Kenntnisse der Zahlensysteme und der <strong>in</strong>ternen Repräsentation<br />

der Zahlen von Vorteil.<br />

Gegenüberstellung der Zahlensysteme<br />

Unser Dezimalsystem hat als Basis die Zahl 10, und besteht aus den Ziffern 0 bis 9. Das B<strong>in</strong>ärsystem<br />

besitzt die Basis 2 und besteht aus den Ziffern 0 und 1. Und das Hexadezimalsystem benutzt<br />

die Basis 16, und die Ziffern 0 bis 9 sowie A bis F.<br />

Dezimal Hexadezimal B<strong>in</strong>är<br />

00<br />

01<br />

02<br />

03<br />

04<br />

05<br />

06<br />

07<br />

08<br />

09<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20<br />

21<br />

22<br />

23<br />

24<br />

25<br />

26<br />

00<br />

01<br />

02<br />

03<br />

04<br />

05<br />

06<br />

07<br />

08<br />

09<br />

0A<br />

0B<br />

0C<br />

0D<br />

0E<br />

0F<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20<br />

00000<br />

00001<br />

00010<br />

00011<br />

00100<br />

00101<br />

00110<br />

00111<br />

01000<br />

01001<br />

01010<br />

01011<br />

01100<br />

01101<br />

01110<br />

01111<br />

10000<br />

10001<br />

10010<br />

10011<br />

10100<br />

10101<br />

10110<br />

10111<br />

11000<br />

11001<br />

11010<br />

Nebenstehend die Zahlen von 0 bis 26 <strong>in</strong> den<br />

verschiedenen Systemen e<strong>in</strong>ander gegenübergestellt.<br />

Zahlen können sehr e<strong>in</strong>fach vom Hexadezimal<br />

<strong>in</strong>s B<strong>in</strong>ärsystem und zurück konvertiert werden,<br />

da e<strong>in</strong>e Ziffer des Hexadezimalsystems<br />

immer 4 Bit im B<strong>in</strong>ärsystem entspricht. Man<br />

muss also nur die B<strong>in</strong>ärzahl <strong>in</strong> Vierergruppen<br />

unterteilen, und den Gruppen jeweils e<strong>in</strong>e<br />

Hexziffer zuweisen, oder für jede Hexziffer<br />

vier Bits schreiben. (Der grau h<strong>in</strong>terlegte Bereich<br />

der Tabelle kann zur Umwandlung e<strong>in</strong>er<br />

Hexziffer benutzt werden)<br />

Die Umwandlung von Dezimal <strong>in</strong> B<strong>in</strong>är und<br />

umgekehrt ist viel schwieriger.<br />

Deshalb wird bei der hardwarenahen <strong>Programmierung</strong><br />

meist das Hexadezimalsystem<br />

angewandt. (Auch weil die Zahlen so kürzer<br />

und leichter lesbar s<strong>in</strong>d als <strong>in</strong> der B<strong>in</strong>ärdarstellung)<br />

Negative Zahlen werden <strong>in</strong>tern meist <strong>in</strong> der sogenannten Zweierkomplementdarstellung gespeichert.<br />

Dabei zeigt das höchstwertige Bit an, ob die Zahl positiv oder negativ ist. Nachfolgend e<strong>in</strong>e kle<strong>in</strong>e<br />

Tabelle positiver und negativer Zahlen, mit den jeweiligen B<strong>in</strong>är, resp. Hezadezimale repräsentationen.<br />

Dezimal Hex, 16-Bit, vorzeichenbehaftet (Hex) B<strong>in</strong>är<br />

+32767 0x7FFF 0111 1111 1111 1111<br />

+255 0x00FF 0000 0000 1111 1111<br />

+16 0x0010 0000 0000 0001 0000<br />

+1 0x0001 0000 0000 0000 0001<br />

0 0x0000 0000 0000 0000 0000<br />

-1 0xFFFF (0x10000 - 0x0001) 1111 1111 1111 1111<br />

-16 0xFFF0 (0x10000 - 0x0010) 1111 1111 1111 0000<br />

-255 0xFF01 (0x10000 - 0x00FF) 1111 1111 0000 0001<br />

-32768 0x8000 (0x10000 - 0x8000) 1000 0000 0000 0000<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 29/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Fliesskommazahlen<br />

Um e<strong>in</strong>en grösseren Dynamikbereich zu erhalten, werden Fliesskommazahlen e<strong>in</strong>gesetzt. Diese bestehen<br />

aus e<strong>in</strong>er Mantisse und e<strong>in</strong>em Exponenten. Solche Zahlen s<strong>in</strong>d aus dem wissenschaftlichen<br />

Umfeld nicht wegzudenken. E<strong>in</strong> Beispiel wäre 123.45*10 9 welches auch als 123.45E9 geschrieben<br />

werden kann.<br />

Im Computer kommt auch für Fliesskommazahlen wieder das B<strong>in</strong>ärsystem zum Zug. Fliesskommazahlen<br />

werden <strong>in</strong>tern im Format nnnnnn*2 eeee . gespeichert, wobei nnnnn und eeee jeweils B<strong>in</strong>ärzahlen<br />

s<strong>in</strong>d. Die meisten Computer und Compiler benutzen das im IEEE 754 Standard festgelegte Format<br />

zum Speichern von Fliesskommazahlen.<br />

Der Standard legt grundsätzlich e<strong>in</strong> 32-Bit Format (S<strong>in</strong>gle Precision / float) und e<strong>in</strong> 64-Bit Format<br />

(Double Precision / double) fest. (Zusätzlich werden noch erweiterte Formate def<strong>in</strong>iert).<br />

Name Vorzeichen (s) Exponent (e) Mantisse (m) Offset (b)<br />

S<strong>in</strong>gle Precision 1 [Bit 31] 8 [Bits 30-23] 23 [Bits 22-00] 127<br />

Double Precision 1 [Bit 63] 11 [Bits 62-52] 52 [Bits 51-00] 1023<br />

Beispiel für S<strong>in</strong>gle Precision:<br />

s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm<br />

Bit 31 Bit 0<br />

Das Dezimaläquivalent e<strong>in</strong>er solchen Fliesskommazahl berechnet sich zu (-1) s * 1.m * 2 (e-b) .<br />

Vor dem Abspeichern wird die Zahl 'normalisiert', das heisst sie wird <strong>in</strong> die Form 1.xxx * 2 y (im<br />

B<strong>in</strong>ärsystem) gebracht. Der Exponent wird dabei so gewählt, dass die Mantisse genau e<strong>in</strong>e Vorkommastelle<br />

mit dem Wert 1 hat (Ausser bei dem Wert 0). Und weil diese Vorkommastelle immer<br />

1 ist, wird sie nicht mit abgespeichert. So können mit 23 Mantissenbits eigentlich 24 Bits gespeichert<br />

werden. Von der Mantisse wird immer der Betrag abgespeichert. Der Exponent wird nicht<br />

direkt abgespeichert, sondern es wird noch e<strong>in</strong> Offset dazu addiert, so dass der abgespeicherte Wert<br />

immer positiv ist. Bei S<strong>in</strong>gle Precision wird also für den Exponenten 0 der Wert 127 abgespeichert.<br />

Die Werte -127 und 128 s<strong>in</strong>d reserviert für Spezialfälle und dürfen für normale Exponenten nicht<br />

verwendet werden. Zahlen, welche mit dem zulässigen Exponentenbereich (-126 bis +127) nicht<br />

dargestellt werden können, werden <strong>in</strong> Unendlich, Null oder 'Denormalisierten Zahlen' umgewandelt.<br />

Spezielle Werte<br />

Null:<br />

Der Wert Null wird durch e<strong>in</strong> Exponentenfeld von 0 und e<strong>in</strong>em Mantissenfeld von 0 dargestellt.<br />

Das Vorzeichenbit wird berücksichtigt, es gibt also +0 und -0, welche bei Vergleichen aber als identisch<br />

angesehen werden.<br />

Denormalisiert:<br />

Wenn das Exponentenfeld 0 ist, aber die Mantisse von Null verschieden, liegt e<strong>in</strong>e Denormalisierte<br />

Zahl vor. Dies ist e<strong>in</strong>e Zahl, welche ke<strong>in</strong>e führende 1 vor dem Dezimalpunkt hat (Weil sie zu kle<strong>in</strong><br />

zum normalisieren war). Null ist also e<strong>in</strong> Spezielafall e<strong>in</strong>er denormalisierten Zahl.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 30/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Unendlich:<br />

Wenn das Exponentenfeld aus lauter 1, und Mantisse 0 ist, liegt e<strong>in</strong> unendlicher Wert vor. Mit unendlichen<br />

Werten kann normal gerechnet werden, das verhalten ist genau def<strong>in</strong>iert. Das Vorzeichenbit<br />

unterscheidet zwischen positiv und negativ Unendlich.<br />

Not A Number (NaN)<br />

Der Wert NaN repräsentiert e<strong>in</strong>e ungültige Zahl. (Z. B. als Ergebnis e<strong>in</strong>er undef<strong>in</strong>ierten mathematischen<br />

Operation). Wenn an e<strong>in</strong>er Berechnung e<strong>in</strong>e NaN beteiligt ist, wird das Ergebnis wieder zu<br />

e<strong>in</strong>er NaN. E<strong>in</strong>e NaN erkennt man an e<strong>in</strong>em Exponentenfeld aus lauter 1 und e<strong>in</strong>er von Null verschiedenen<br />

Mantisse. Man uneterscheidet noch zwischen SNaN (Signal<strong>in</strong>g NaN, können Fehlermeldungen<br />

auslösen) und QNaN (Quiet NaN, ohne Meldung). QNaN s<strong>in</strong>d für undef<strong>in</strong>ierte mathematische<br />

Operationen gedacht, und SNaN für unerlaubte Operationen, oder zum Füllen von un<strong>in</strong>itialisiertem<br />

Speicher.<br />

Hier noche<strong>in</strong>mal kurz zusammengefasst die verschieden Bedeutungen nach IEEE 754:<br />

Vorz. Exponent (e) Fraction (f) Wert<br />

0 00..00 00..00 +0<br />

0 00..00<br />

0<br />

00..01<br />

:<br />

11..10<br />

00..01<br />

:<br />

11..11<br />

XX..XX<br />

0 11..11 00..00 +Unendlich<br />

0 11..11<br />

00..01<br />

:<br />

01..11<br />

0 11..11<br />

10..00<br />

:<br />

11..11<br />

1 00..00 00..00 -0<br />

1 00..00<br />

00..01<br />

:<br />

11..11<br />

1<br />

00..01<br />

:<br />

11..10<br />

XX..XX<br />

1 11..11 00..00 -Unendlich<br />

1 11..11<br />

00..01<br />

:<br />

01..11<br />

1 11..11<br />

10..00<br />

:<br />

11.11<br />

Positive denormalisierte Zahl 0.f × 2 (-b+1)<br />

Positive normalisierte Zahl 1.f × 2 (e-b)<br />

SNaN (Signal<strong>in</strong>g not a Number)<br />

'Meldende' Ungültige Zahl.<br />

QNaN (Quiet not a Number)<br />

'Stille' Ungültige Zahl<br />

Negative denormalisierte Zahl -0.f × 2 (-b+1)<br />

Negative normalisierte Zahl -1.f × 2 (e-b)<br />

SNaN (Signal<strong>in</strong>g not a Number)<br />

'Meldende' Ungültige Zahl.<br />

QNaN (Quiet not a Number)<br />

'Stille' Ungültige Zahl<br />

Achtung, da Fliesskommazahlen nur e<strong>in</strong>e beschränkte Auflösung haben, muss auf Probleme wie<br />

Auslöschung signifikannter Stellen geachtet werden. Auch sollte man nie auf Gleichheit testen,<br />

sondern immer auf e<strong>in</strong>en Bereich (statt a == b besser (fabs((a - b)/a) < epsilon, wobei epsilon der<br />

gewünschten, mit der Auflösung [24/53 Bit] noch möglichen Genauigkeit entspricht)<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 31/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

Anhang C Interrupts<br />

Um sich den grundsätzlichen Ablauf des Interupthandl<strong>in</strong>g vorzustellen, kann man sich folgenden<br />

Ablauf denken: Man nimmt e<strong>in</strong>fach an, dass der Prozessor nach jeder ausgeführten Instruktion kurz<br />

überprüft, ob e<strong>in</strong> Interruptereignis e<strong>in</strong>getroffen ist. Falls sich e<strong>in</strong> Interrupt ereignet hat, wird umgehend<br />

die entsprechende Handlerfunktion aufgerufen (Noch bevor die nächste Instruktion ausgeführt<br />

wird). Mit der nächsten Instruktion wird erst nach Beendigung der Handlerfunktion weitergefahren.<br />

Nur um sich das Pr<strong>in</strong>zip vorzustellen, kann man sich folgenden Code<br />

.......<br />

a = b * 3;<br />

while (a < 77) {<br />

a = a * a;<br />

b --;<br />

}<br />

.......<br />

<strong>in</strong> diesen Code umgesetzt vorstellen:<br />

.......<br />

if (InteruptPend<strong>in</strong>g) { /* Interruptrout<strong>in</strong>e ausführen, falls nötig */<br />

HandleInterrupt();<br />

}<br />

a = b * 3;<br />

if (InteruptPend<strong>in</strong>g) { /* Interruptrout<strong>in</strong>e ausführen, falls nötig */<br />

HandleInterrupt();<br />

}<br />

while (a < 77) {<br />

if (InteruptPend<strong>in</strong>g) { /* Interruptrout<strong>in</strong>e ausführen, falls nötig */<br />

HandleInterrupt();<br />

}<br />

a = a * a;<br />

if (InteruptPend<strong>in</strong>g) { /* Interruptrout<strong>in</strong>e ausführen, falls nötig */<br />

HandleInterrupt();<br />

}<br />

b --;<br />

if (InteruptPend<strong>in</strong>g) { /* Interruptrout<strong>in</strong>e ausführen, falls nötig */<br />

HandleInterrupt();<br />

}<br />

}<br />

if (InteruptPend<strong>in</strong>g) { /* Interruptrout<strong>in</strong>e ausführen, falls nötig */<br />

HandleInterrupt();<br />

}<br />

.......<br />

Selbstverständlich wird <strong>in</strong> der Realität nicht Extracode e<strong>in</strong>gefügt, sondern die Abfrage und der Aufruf<br />

der Interruptfunktion erfolgt automatisch durch die Hardware im Prozessor. Zudem erfolgt die<br />

Abfrage natürlich nicht nach jedem C-Befehl, sondern nach jedem Masch<strong>in</strong>ensprachebefehl. Und<br />

weil die meisten C-Befehle <strong>in</strong> mehrere Masch<strong>in</strong>ensprachebefehle umgesetzt werden, kann die Unterbrechung<br />

auch mitten <strong>in</strong> e<strong>in</strong>em C-Befehl erfolgen. (Bei a = b * c; zum Beispiel nach der Multiplikation,<br />

aber vor der Zuweisung). Und gerade dieses Unterbrechen e<strong>in</strong>er Anweisung führt zu den<br />

gängigen Problemen, welche e<strong>in</strong> zeitweises Ausschalten (Disable/Enable) von Unterbrechungen<br />

erfordern.<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 32/33


<strong>Hardwarenahe</strong> <strong>Programmierung</strong> <strong>in</strong> C<br />

typedef void (*Handler)(void); /* Zeiger auf Funktion */<br />

Handler *HandlerTable= (Handler *)0; /* Tabelle beg<strong>in</strong>nt bei */<br />

/* Adresse 0 */<br />

void HandleInterrupt(void)<br />

{<br />

if (InterruptEnabled) { /* Nur weiter wenn Interupts */<br />

<strong>in</strong>t Index = 0; /* erlaubt s<strong>in</strong>d */<br />

Index = GetVectorNumberFromHardware(); /* Vektornummer von */<br />

/* Pheripherie holen */<br />

HandlerTable[Index](); /* Und passenden Handler via */<br />

} /* Tabelle aufrufen */<br />

}<br />

Gedruckt am 23.05.2006 11:27:00 Letzte Änderung am: 23. Mai 2006 Version 1.2, I. Oesch 33/33

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!