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