https://www.mikrocontroller.net/api.php?action=feedcontributions&user=Pappnase&feedformat=atomMikrocontroller.net - Benutzerbeiträge [de]2024-03-28T12:43:19ZBenutzerbeiträgeMediaWiki 1.38.5https://www.mikrocontroller.net/index.php?title=AVR-Transistortester&diff=104925AVR-Transistortester2022-01-13T21:05:25Z<p>Pappnase: /* Automatische Abschaltung */</p>
<hr />
<div>''von Markus Frejek''<br />
<br />
==Grundsätzliches==<br />
Jeder Bastler kennt wohl dieses Problem: Man baut einen [[Transistor]] aus einem Gerät aus oder kramt einen aus der Bastelkiste.<br />
Wenn die Typenbezeichnung zu erkennen ist (und sie einem bekannt ist), ist ja alles in Ordnung. Oft ist dem aber nicht so, <br />
und dann geht das Übliche mal wieder los: Typ und Pinbelegung des Transistors aus dem Internet oder einem Buch heraussuchen.<br />
Das nervt auf Dauer natürlich ziemlich. Auch Transistoren im gleichen Gehäuse haben nicht immer die gleiche Pin-Belegung.<br />
Zum Beispiel hat ein 2N5551 eine andere Belegung als ein BC547, obwohl beide ein TO92 Gehäuse haben. Wenn bei einem Transistor die <br />
Bezeichnung nicht mehr zu erkennen ist (oder nicht mal Google was dazu weiß), wird es mit konventionellen Methoden richtig umständlich,<br />
den Transistortypen herauszufinden: Es könnte sich um NPN, PNP, N-oder P-Kanal-Mosfet, etc. handeln. Eventuell hat das Bauteil auch noch<br />
eine Schutzdiode intern. Das macht die Identifizierung noch schwieriger. Durchprobieren per Hand ist also ein recht zeitraubendes Unterfangen.<br />
<br />
Warum soll man das nicht per Mikrocontroller erledigen lassen?<br />
So ist dieser automatische Transistortester entstanden.<br />
<br />
-------------------------------------------------------------------------------<br />
Achtung, Attention, Uwaga !!!<br />
<br />
This project is intended only for non-commercial use.<br />
Any commercial productions require the express permission of the author.<br />
<br />
Dieses Projekt wird nur für nicht-kommerzielle Nutzung bestimmt.<br />
Jegliche kommerzielle Produktionen bedürfen der ausdrücklichen Genehmigung des Autors.<br />
<br />
Ten projekt przeznaczony jest jedynie do niekomercyjnego wykorzystania.<br />
Wszelkie produkcje komercyjne wymagają uzyskania zgody od autora.<br />
<br />
==Features==<br />
* Automatische Erkennung von NPN und PNP-Transistoren, N-und P-Kanal-MOSFETs, Dioden (auch Doppeldioden), Thyristoren, Triacs und auch Widerständen und Kondensatoren.<br />
* Automatische Ermittlung und Anzeige der Pins des zu testenden Bauteils<br />
* Erkennung und Anzeige von Schutzdioden bei Transistoren und MOSFETs<br />
* Ermittlung des Verstärkungsfaktors und der Basis-Emitter-Durchlassspannung bei Transistoren<br />
* Messung der Gate-Schwellspannung und Gatekapazität von Mosfets<br />
* Anzeige der Werte auf einem Text-LCD (2*16 Zeichen)<br />
* Dauer eines Bauteil-Tests: Unter 2 Sekunden (Ausnahme: größere Kondensatoren)<br />
* Ein-Knopf-Bedienung; automatische Abschaltung<br />
* Stromverbrauch im ausgeschalteten Zustand: < 20 nA<br />
<br />
Selbstleitende FETs (z.&nbsp;B. JFETs) werden mittlerweile auch unterstützt. Bei Leistungs-Thyristoren und -Triacs kann es<br />
auch zu Problemen kommen, wenn der Teststrom (ca. 7 mA) unter dem Haltestrom liegt. MOSFETs und Transistoren wurden aber in meinen Tests immer korrekt erkannt.<br><br />
Der Messbereich für Widerstände liegt bei etwa 2Ω bis 20M Ohm, deckt also die meisten Widerstandswerte ab. Die Genauigkeit ist aber nicht sehr hoch.<br><br />
Kondensatoren können von ca. 0,2nF bis gut 7000µF gemessen werden. Oberhalb von rund 4000µF wird die Genauigkeit aber zunehmend schlechter. Prinzipbedingt dauert die Messung großer Kondensatoren auch recht lange, eine Messdauer bis zu einer Minute ist normal.<br />
<br />
==Hardware==<br />
[[Bild: Schaltplan_transistortester.png|thumb|200px|Schaltplan des Transistortesters]]<br />
[[Bild: transistortester.jpg|thumb|200px|Transistortester im Gehäuse]]<br />
[[Bild: transistortester_pruefkabel.jpg|thumb|200px|Adapterkabel, um auch Transistoren testen zu können, die nicht in den Testsockel passen]]<br />
[[Bild: Transistortester_19inch_1.jpg|thumb|200px|Ausführung als Einschub für einen 19"-Baugruppenträger, versorgt über einen Netztrafo, ohne Abschaltautomatik]] <br />
Es empfiehlt sich, den Schaltplan in einem weiteren Browserfenster zu öffnen, um die<br />
folgenden Beschreibungen nachvollziehen zu können.<br />
<br />
Als Mikrocontroller wurde der ATMega8 gewählt. Er verfügt über mehr als genug Flash und RAM.<br />
Außerdem hat er genug Portpins und ist sehr preisgünstig.<br />
Der Transistortester wird mit einer 9V-Block-Batterie betrieben. Die 5V-Betriebsspannung<br />
für den AVR wird ganz konventionell mit einem 78L05 erzeugt.<br />
Am Port B des ATMega8 sind verschiedene Widerstände angeschlossen: Je Transistor-Pin ein großer (470 k) und ein kleiner (680Ω).<br />
Hiermit können zwei verschiedene Stromstärken auf den zu testenden Pin gegeben werden.<br />
Die Widerstände sind mit ADC0 , ADC1 und ADC2 verbunden. An diese Pins wird auch der zu testende Transistor angeschlossen.<br />
Der linke Teil der Schaltung (mit den 3 Transistoren) ist für die automatische Abschaltung zuständig. Dazu später mehr.<br />
An den ersten Pins von Port D ist das LCD angeschlossen. Es handelt sich dabei um ein 2x16 Zeichen Text-LCD mit HD44780-kompatiblem Controller.<br />
<br />
Es ist zu beachten, dass die Test-Eingänge keine Schutzbeschaltung haben. Eine Schutzbeschaltung würde vermutlich auch die Messergebnisse verfälschen. Es sollten also keinesfalls Bauteile, die noch in eine Schaltung eingebaut sind, getestet werden. Sonst könnte der ATMega8 beschädigt werden.<br />
<br />
* Bezugsquelle für Platinen: [http://www.it-wns.de/themes/kategorie/detail.php?artikelid=857&source=2 IT-WNS] (Doppelseitige Platine)<br />
* Dateien zum Selbstätzen: [http://www.mikrocontroller.net/topic/131804#1244222 (Artikel-Link, einseitige Platine)]<br />
<br />
==Testablauf==<br />
Der Test des Transistors funktioniert nach einem einfachen aber effektiven Prinzip:<br />
Es werden zunächst mal alle sechs Kombinationsmöglichkeiten für die Pins durchprobiert. <br><br />
Hierfür werden die Pins entweder über den 680Ω Widerstand auf + oder fest auf Masse gelegt oder ganz freigelassen.<br><br />
Es ergeben sich folgende 6 Möglichkeiten:<br />
<br />
{| border="1" class="wikitable"<br />
! || Zustand Pin1 || Zustand Pin2 || Zustand Pin3 <br />
|-<br />
| 1. Möglichkeit || + || - || frei <br />
|-<br />
| 2. Möglichkeit || + || frei || -<br />
|-<br />
| 3. Möglichkeit || frei || - || +<br />
|-<br />
| 4. Möglichkeit || frei || + || -<br />
|-<br />
| 5. Möglichkeit || - || frei|| +<br />
|-<br />
| 6. Möglichkeit || - || + || frei<br />
|}<br />
<br />
Für jede Kombinationsmöglichkeit wird überprüft, ob zwischen dem + Pin und dem -Pin Durchgang herrscht. Hierfür wird mit den ADCs die <br />
Spannung am + Pin ermittelt. Falls Durchgang besteht und die abfallende Spannung im Bereich zwischen 0,2 V und 4 V liegt,<br />
wird von einer Diode zwischen den betreffenden Pins ausgegangen. Dieses Ergebnis wird gespeichert. Der Test wird dadurch natürlich nicht <br />
abgebrochen, da dieses Ergebnis auch bei einem Transistor eintreten kann und wird (ein Transistor hat zwei pn-Übergänge, also 2 Dioden).<br />
<br />
Falls kein Durchgang herrscht, wird der bisher frei gelassene Pin über 680Ω auf Masse gelegt. Falls jetzt Durchgang besteht, muss es sich<br />
um einen pnp-Transistor oder p-Kanal-MOSFET handeln. In diesem Fall wird der Pin (es handelt sich dann um die Basis) über den 470 k Widerstand<br />
auf Masse gelegt. Jetzt wird die Spannung zwischen dem +Pin und -Pin (Kollektor und Emitter) gemessen und zwischengespeichert.<br><br />
Daraus kann nachher der Verstärkungsfaktor und die "richtige" Anschlussbelegung errechnet werden. Ein Transistor funktioniert nämlich auch "falsch herum"<br />
(also bei einem pnp-Transistor Kollektor auf Plus), allerdings ist der Verstärkungsfaktor deutlich geringer.<br />
<br />
Wenn hingegen immer noch kein Durchgang zwischen dem positiven und negativen Pin herrscht, wird der anfangs frei gelassene Pin über 680Ω auf Plus gelegt.<br />
Besteht nun Durchgang, handelt es sich um einen npn-Transistor, einen n-Kanal-MOSFET oder einen Thyristor/Triac. Der weitere Ablauf entspricht dem bei pnp.<br />
Der anfangs frei gelassene Pin wird dann allerdings nochmal hochohmig geschaltet. Wenn das Bauteil dann immer noch leitet, ist es ein Thyristor oder Triac.<br><br />
Durch die 680Ω-Widerstände ist der mögliche Strom allerdings recht gering (max. ca. 7,4 mA). Wenn der Haltestrom des Thyristors oder Triacs über diesem Wert liegt,<br />
wird das Bauteil als npn-Transistor erkannt. Das dürfte bei vielen Leistungsthyristoren oder -triacs der Fall sein. Eine Verkleinerung der Testwiderstände (für mehr Strom)<br />
wäre zwar möglich, erhöht aber auch das Risiko, sehr empfindliche Transistoren zu zerstören.<br><br />
Die Unterscheidung zwischen Transistor und MOSFET ist nicht schwer: Bei einem Transistor fließt Basisstrom. Somit wird der Basis-Anschluss "zum Emitter hin" gezogen. <br><br />
Bei einem MOSFET passiert das natürlich nicht. Das lässt sich dann leicht über den angeschlossenen ADC ermitteln.<br />
<br />
Zur Messung der Gate-Schwellspannung von N-Kanal-Anreicherungs-Mosfets wird Source fest auf Masse gelegt. Drain wird über einen 680-Ohm-Widerstand gegen Plus gelegt und Gate wird über einen 470kOhm Widerstand gegen Plus gelegt.<br />
Dann wartet der Tester, bis der Mosfet schaltet, also der Drain-Anschluss auf logisch 0 geht. Nun wird die am Gate anliegende Spannung gemessen. Das ist etwa die Gate-Schwellspannung.<br><br />
Zur dann folgenden Messung der Gatekapazität werden Gate, Drain und Source fest gegen Masse gelegt. Das entlädt das Gate komplett. Dann wird das Gate über 470kOhm gegen Plus gelegt, Drain und Source bleiben auf Masse.<br />
Nun wird in einer Schleife die Zeit gemessen, die vergeht bis der Gate-Anschluss auf logisch 1 geht. Das passiert bei recht genau 3,6V, wenn der AVR mit stabilen 5V versorgt wird.<br><br />
Das sind 72% der Betriebsspannung. Somit ergibt sich rechnerisch eine Zeitkonstante von 1,28 Tau (1,28 R*C). Da R mit 470kOhm bekannt ist, lässt sich daraus C, also die Gatekapazität, berechnen.<br><br />
Der reale Faktor für die Berechnung weicht etwas davon ab und kann per Define im Sourcecode angepasst werden.<br><br />
Die Messung der Gate-Kapazität ist, wie auch die Messung der Gate-Schwellspannung, ohnehin nicht besonders genau. Für eine Abschätzung und den groben Vergleich verschiedener Mosfets sind diese Funktionen aber brauchbar.<br />
Der Ablauf der Gate-Schwellspannungs und -Kapazitätsmessung für P-Kanal-Anreicherungs-Mosfets ist identisch mit dem oben beschriebenen Ablauf für N-Kanal-Mosfets. Nur kehren sich alle Polatitäten um und die Zeitkonstante für die Gatekapazität ändert sich.<br><br />
<br />
Für die Messung von Widerständen wird aus einem der internen Widerstände (680Ω oder 470kOhm) und dem Prüfling ein Spannungsteiler aufgebaut.<br />
Die Spannung am Mittelpunkt des Teilers wird gemessen, einmal mit dem 680Ω und einmal mit dem 470kOhm-Widerstand. Daraus lässt sich der Widerstandswert des Prüflings berechnen.<br><br />
Dieses Messverfahren hat das Problem, dass die Genauigkeit stark vom Widerstandswert des Prüflings abhängt.<br><br />
Am genauesten ist die Messung, wenn der Prüfling etwa den gleichen Widerstandswert wie einer der internen Widerstände hat, also 680Ω oder 470kOhm. Dann hat der Spannungsteiler ein Verhältnis von etwa 1:1. Eine kleine Änderung des Widerstandswerts des Prüflings ändert hier die Spannung in der Mitte des Teilers recht stark.<br><br />
Mit dem internen 680Ω-Widerstand ergibt sich mit einem 600-Ohm-Prüfling eine Teilerspannung von 2,34V; mit einem 800-Ohm-Prüfling sind es 2,7V.<br />
Eine Erhöhung des Prüflings um 33% erhöht also die Spannung hier um immerhin 0,36V, erhöht also den ADC-Wert um 74.<br><br />
<br />
Bei einem 6-Ohm-Prüfling dagegen sind es 0,044V in der Mitte des Teilers, bei 8Ω sind es 0,058V.<br />
Hier steigt der Widerstandswert ebenfalls um 33%. Die Spannung in der Teilermitte erhöht sich aber nur um 0,014V, was den ADC-Wert um gerade mal 2 bis 3 erhöht.<br />
<br />
Übrigens wird jeder Prüflings-Widerstand sowohl mit dem 470kOhm-Widerstand als auch mit dem 680Ω-Widerstand vermessen. In der Auswertung wird dann das genauere Ergebnis verwendet, also das Ergebnis, bei dem die Teilerspannung näher am Optimalwert von 2,5V liegt.<br />
<br />
Der gesamte Messbereich liegt bei etwa 5Ω bis 910kOhm.<br />
Im Bereich unter 20Ω ist die Messung sehr ungenau, Abweichungen von 10-20% können schon vorkommen.<br><br />
Nicht besonders genau ist auch der Bereich von ca. 20 bis 100Ω.<br />
Zudem ist auch der Bereich von ca. 10-50kOhm nicht richtig genau, weil dafür keiner der zur Verfügung stehenden Messwiderstände (680Ω oder 470kOhm) gut geeignet ist.<br><br />
Wenn in dem Tester präzise Widerstände (Metallschicht mit <=1% Toleranz) verbaut werden, dürften +/-3% Abweichung über den gesamten Messbereich aber machbar sein. Damit ist die Widerstands-Messfunktion natürlich keine Konkurrenz zu einem guten Multimeter, aber durchaus passabel.<br />
<br />
Die Messung von Kondensatoren ist von der Messung der restlichen Bauteile getrennt. Das bedeutet, dass sie eine eigenständige Messfunktion besitzt, die in allen 6 Pin-Kombinationen auf das Vorhandensein eines Kondensators prüft.<br />
Das ist nötig, weil sich die Messungen sonst gegenseitig stören würden.<br />
<br />
Das Vorhandensein eines Kondensators wird folgendermaßen geprüft:<br />
Einer der beiden Pins wird fest auf Masse gelegt, der andere über 470kOhm auf Masse.<br><br />
Nach einer kurzen Zeit wird die Spannung an dem Pin, der über 470kOhm auf Masse liegt gemessen und der Wert wird gespeichert.<br><br />
Nun wird dieser Pin für 10ms über 680Ω auf Plus gelegt. Nachher wird wieder die Spannung gemessen.<br><br />
Wenn sie um mindestens ca. 10mV gestiegen ist, wird davon ausgegangen, dass ein Kondensator vorhanden ist. Das legt auch die maximal messbare Kapazität von ca. 7350µF fest (680Ω lassen an 5V 7,35mA fließen, was einen Kondensator von 7350µF in 10ms um 10mV lädt).<br><br />
Falls die Spannung nicht hinreichend angestiegen ist, wird der Test mit dem Ergebnis "Kein Kondensator" abgebrochen.<br><br />
Ist die Spannung hingegen angestiegen, wird der Kondensator wieder vollständig entladen. Dann wird die Kapazität gemessen, genau nach dem gleichen Prinzip wie bei der Messung der Gatekapazität beschrieben.<br><br />
Zunächst erfolgt die Messung mit dem 680Ω Widerstand. Falls die Zeit, bis der Pin umschaltet sehr gering ist (geringe Kapazität), wird mit dem 470kOhm Widerstand erneut gemessen.<br><br />
Falls auch da die Zeit, bis der Pin umschaltet sehr gering war, wird die Messung abgebrochen. Damit ist die untere Grenze des Messbereiches von ca. 0,2nF festgelegt. Das dürfte ein sinnvoller Wert sein, sonst können Kapazitäten des Prüfkabels o.ä. schon fälschlicherweise zur Erkennung eines (nicht vorhandenen) Kondensators führen.<br><br />
War die Messzeit im vorgegebenen Bereich, wird der Kondensator als gefundenes Bauteil gespeichert. Die gemesene Zeit wird mit den (per Define festlegbaren) Faktoren in eine Kapazität umgerechnet, die dann in nF bzw. µF angezeigt wird.<br><br />
Auch hier ist die Genauigkeit nicht überragend hoch, bis zu 10% Abweichung sind möglich. Für die ungefähre Bestimmung der Kapazität ist es aber ausreichend.<br />
<br />
<br />
Nachdem die genannten Messungen für alle sechs Kombinationsmöglichkeiten der Pins gemacht wurden, geht es an die Auswertung:<br />
* Bei Bipolartransistoren muss - wie gesagt, die richtige Belegung von Kollektor und Emitter anhand des Verstärkungsfaktors bestimmt werden<br />
* Wurden bei einem Bipolartransistor drei Dioden oder bei einem MOSFET eine Diode gefunden, besitzt das Bauteil eine Schutzdiode. Das wird durch ein kleines Dioden-Symbol rechts unten im LCD dargestellt<br />
* Wenn mehrere Dioden, aber kein Transistor erkannt wurden, wird die Stellung der Dioden zueinander überprüft, um den Bauteiltyp und die Pin-Belegung zu ermitteln (Doppeldiode mit Common Anode/Cathode, zwei Dioden in Serie, zwei Dioden antiparallel)<br />
<br />
Das Ergebnis wird dann auf dem LCD dargestellt. Dann schaltet sich der Tester nach ca. 10 Sekunden automatisch ab.<br />
<br />
==Darstellung auf dem LCD==<br />
<br />
Das ermittelte Testergebnis wird auf dem LCD dargestellt.<br />
In der ersten Zeile links wird der erkannte Bauteiltyp angezeigt. Folgende Bauteile werden bis jetzt erkannt:<br />
{| class="wikitable"<br />
! Bauteil || Display-Anzeige<br />
|-<br />
| NPN-Transistor<br />
| NPN<br />
|-<br />
| PNP-Transistor<br />
| PNP<br />
|-<br />
| N-Kanal-MOSFET('''e'''nhancement type)<br />
| N-E-MOS<br />
|-<br />
| P-Kanal-MOSFET('''e'''nhancement type)<br />
| P-E-MOS<br />
|-<br />
| N-Kanal-MOSFET('''d'''epletion type)<br />
| N-D-MOS<br />
|-<br />
| P-Kanal-MOSFET('''d'''epletion type)<br />
| P-D-MOS<br />
|-<br />
| N-Kanal-JFET<br />
| N-JFET<br />
|-<br />
| P-Kanal-JFET<br />
| P-JFET<br />
|-<br />
| Thyristor<br />
| Thyristor<br />
|-<br />
| Triac<br />
| Triac<br />
|-<br />
| Doppeldiode, gemeinsame Anode<br />
| Doppeldiode CA<br />
|-<br />
| Doppeldiode, gemeinsame Kathode<br />
| Doppeldiode CC<br />
|-<br />
| 2 antiparallele Dioden<br />
| 2 antiparallel Diode<br />
|-<br />
| 2 Dioden in Serie<br />
| 2 Dioden in Serie<br />
|-<br />
| einfache Diode<br />
| Diode<br />
|-<br />
| Widerstand<br />
| Widerstand<br />
|-<br />
| Kondensator<br />
| Kondensator<br />
|}<br />
Je nach Bauteil werden noch weitere Daten angezeigt:<br />
* Pin-Namen und Pin-Belegung<br />
* Verstärkungsfaktor hFE und Basis-Emitter-Durchlassspannung (bei Bipolartransistoren)<br />
* Gate-Schwellspannung und Gate-Kapazität(bei Anreicherungs-MOSFETs)<br />
* Durchlassspannung (bisher nur bei einfachen Dioden, nicht bei Doppeldioden u.ä.)<br />
* Bei Transistoren: Anzeige, ob eine Schutzdiode vorhanden ist (durch ein kleines Dioden-Symbol)<br />
* Widerstandswert bei Widerständen und Kapazität bei Kondensatoren<br />
<br />
Da bei den meisten JFETs Drain und Source gleichwertig sind, können diese Anschlüsse nicht erkannt werden. Es kann also vorkommen, dass Drain und Source bei JFETs vertauscht angezeigt werden.<br />
<br />
Eine Unterscheidung zwischen bipolaren Kondensatoren und gepolten Elkos war ursprünglich geplant.<br><br />
Elkos entladen sich nämlich schneller, wenn sie in die falsche Richtung aufgeladen werden.<br><br />
Allerdings ist dieser Effekt bei Spannungen von 5V und Messzeiten von ein paar 100ms fast unmessbar gering, daher ist diese Erkennung nicht möglich.<br />
<br />
==Automatische Abschaltung==<br />
<br />
Am einfachsten wäre es natürlich, den AVR nach Abschluss des Tests einfach in den Power-Down-Mode<br />
zu versetzen und einfach per Taster beim nächsten Test wieder aufzuwecken. Der AVR braucht in diesem Modus unter 0,3 µA. Da würde die Batterie fast ewig halten.<br />
Da der Tester aber stabile 5 V braucht, kommt man um einen Spannungsregler nicht herum. Hier haben wir auch schon das Problem: Der Spannungsregler läuft munter weiter,<br />
auch wenn der AVR schläft, und verbraucht dabei gar nicht so wenig Strom:<br />
Ein 78L05 braucht ca. 3 mA. Das würde die Batterie in gerade mal einer Woche leeren.<br />
Selbst der sparsame LP2950 braucht noch 75 µA. Da würde die Batterie auch nur neun Monate halten. Schon besser, aber alles andere als ideal.<br />
<br />
Es gibt aber noch eine bessere Möglichkeit: Den Strom mit einem pnp-Transistor in der Plus-Leitung (T3) schalten.<br />
Hiermit liegt der Standby-Strom bei gerade mal ~10 nA (0,01 µA oder 0,00001 mA). Eine 9 V Batterie mit 500 mAh wäre damit theoretisch erst nach über 5000 Jahren leer. Das sollte wohl reichen...<br />
<br />
Beschreibung dieser Schaltung:<br><br />
Im Ruhezustand wird die Basis von T3 über den Widerstand R10 auf Plus gelegt. Der Transistor ist damit gesperrt und die Schaltung nicht in Betrieb.<br />
Wird jetzt aber der Taster S1 gedrückt, wird die Basis von T3 über R7 und die BE-Strecke von T2 auf Masse gelegt.<br />
Damit leitet T3 und die Schaltung bekommt Betriebsspannung. Somit wird auch der AVR aktiv. Seine erste Aktion ist es, den Portpin PD6 (Pin 12) auf Plus zu legen.<br />
Damit bekommt der Transistor T1 Basisstrom und beginnt zu leiten. Dieser lässt nun über R7 und LED1 den Basisstrom von T3 fließen. Der Taster kann jetzt wieder losgelassen werden.<br />
10 Sekunden nach Abschluss des Tests legt der AVR die Basis von T1 wieder auf Masse. Damit sperrt T1, T3 erhält keinen Basisstrom mehr, sperrt auch und schaltet die Betriebsspannung wieder ab.<br />
Der Transistortester ist damit wieder ausgeschaltet.<br />
Transistor T2 leitet, wenn der Taster gedrückt ist. Er ist an PD7 (Pin 13) an dem AVR angeschlossen. Damit merkt der Controller, wenn die Taste im Betrieb erneut gedrückt wird, und startet den Testzyklus neu.<br />
Das ist sinnvoll wenn man mehrere Transistoren hintereinander testen möchte. Sonst müsste man jedesmal warten, bis sich der Tester wieder automatisch abgeschaltet hat (ca. 10s).<br />
R9 und C2 sollen eventuelle Störungen unterdrücken. Sonst kann sich der Transistortester auch unerwünschterweise durch elektromagnetische Störungen einschalten.<br />
LED1 dient dazu, die Spannung an S1 auch im Betrieb hoch genug zu halten um T2 aufzusteuern: Wenn T1 geschaltet ist, liegt dessen Kollektor quasi auf Masse. Die Spannung am Kollektor reicht dann nicht mehr aus,<br />
um beim erneuten Drücken der Taste den Transistor T2 zu schalten. Anstelle der LED können auch zwei normale Dioden (1N4148 o.ä.) in Reihe benutzt werden.<br />
<br />
Die Widerstände R11 und R12 dienen zum Messen der Batteriespannung. Bei zu geringer Batteriespannung wird dann eine entsprechende Meldung ("Batterie leer") auf dem LCD angezeigt.<br />
<br />
[[Bild: transistortester_ohne_Abschaltung.png|thumb|200px|Schaltplan ohne die automatische Abschaltung]]<br />
In dem Thread zum Artikel wurde mehrfach der Wunsch geäußert, den Transistortester ohne die automatische Abschaltung aufzubauen. Stattdessen wird dann ein einfacher Schalter verwendet. Das spart eine ganze Menge Bauteile ein.<br />
Die Software muss dafür nicht geändert werden.<br />
Taster S1 ist optional, wenn die automatische Abschaltung nicht eingebaut wird.. Damit kann man einen neuen Testzyklus starten, ohne den Tester dafür aus- und wieder einschalten zu müssen. Wer das nicht benötigt, kann ihn einfach weglassen.<br />
Achtung: Der Taster ist natürlich nur optional, wenn der Tester ohne die automatische Abschaltung aufgebaut wird!<br />
<br />
== Infos zur Software ==<br />
In dem zum Download bereitstehenden Archiv ist eine fertig kompilierte Firmware-Version für den ATMega8 und eine kleinere für den ATMega48, sowie der komplette Quellcode enthalten.<br />
<br />
Die Version für den ATMega48 hat folgende Features nicht:<br />
* Erkennung von Widerständen und Kondensatoren<br />
* Messung der Gatekapazität von Anreicherungs-Mosfets<br />
* Messung der Basis-Emitter-Spannung von Bipolartransistoren Für diese Features bietet der ATMega48 einfach nicht genug Platz.<br />
<br />
Für "Selbstkompilierer": Um zwischen der Version für den ATMega8 und ATMega48 zu wechseln, einfach in den Projektoptionen oder im Makefile den AVR-Typen entsprechend ändern.<br />
<br />
Über #ifdef-Blocks werden dann automatisch die (vom Platz her) nur auf dem ATMega8 möglichen Features aktiviert bzw. deaktiviert. Es wird aber dringend davon abgeraten, den Tester mit dem ATmega48 aufzubauen: Dieser Controller ist kaum billiger als der ATMega8, und die Firmware für diesen wird kaum noch weiter gepflegt, weil sie (verständlicherweise) ohnehin kaum verwendet wird; außerdem bietet der Controller auch für Programm-Verbesserungen gar keinen Platz mehr. Ich würde es schön finden, wenn ihr gefundene Fehler in der Software oder Verbesserungsvorschläge in den Forums-Thread zum Artikel schreibt.<br />
<br />
==Fehlersuche==<br />
Falls überhaupt nichts auf dem Display anzeigt wird, sollten folgende Dinge überprüft werden:<br />
* Sind alle Verbindungen zum LCD richtig angeschlossen?<br />
* Sind die Fusebits des ATMega8/ATMega48 richtig gesetzt (interner Oszillator mit 1MHz)?<br />
* Wurde die .eep-Datei ins EEPROM des Controllers geflasht?<br />
* Hat das LCD einen HD44780-kompatiblem Controller?<br />
* Handelt es sich möglicherweise um ein Hochtemperatur-LCD? Diese brauchen nämlich eine negative Kontrastspannung.<br />
* Testweise auch mal R14 überbrücken. Dieser Widerstand muss ohnehin an das verwendete LCD angepasst werden, um einen guten Kontrast zu erhalten (ggf. ein Poti verwenden).<br />
<br />
Sollte auf einem 2x16 Display nur eine Zeile als Klötzchen dargestellt werden und in der zweiten Zeile gar nichts, dann ist bei Anschluss mittels Adapterkabel die Verbindung komplett verdreht (Pin 1 der Platine also auf Pin 16 des LC-Displays). Es kann aber auch ein Fehler bei der Display-Initialisierung vorliegen (abweichend von HD44780). Das sind aber nur 2 von mehreren weiteren Möglichkeiten.<br />
<br />
Falls Bauteile nur mit einer bestimmten Reihenfolge der Anschlüsse an den Test-Pins richtig erkannt werden, ein Bauteil erkannt wird obwohl gar keins angeschlossen ist oder Daten wie der Verstärkungsfaktor bei verschiedenen Anschlussreihenfolgen stark voneinander abweichen, sollte die Platine auf Lötbrücken, schlechte Lötstellen o.ä. überprüft werden. Zwischen den Test-Pins sollten auch keine Flussmittelreste verbleiben. Flussmittel ist meist auch geringfügig leitfähig. Da die verwendeten Testströme sehr gering sind, kann auch der über das Flussmittel fließende Strom zu einer Verfälschung des Ergebnisses führen.<br />
<br />
== Links ==<br />
* http://www.elektor.de/jahrgang/2005/april/sc-analyser-2005.63363.lynkx Originalschaltung von 2005 (mit PIC16F876)<br />
* http://www.reichelt.de/?ARTICLE=81766 Dieses Gerät hat mich auf die Idee gebracht, sowas selbst zu bauen<br />
* http://www.mikrocontroller.net/topic/131804 Thread zum Artikel<br />
* http://www.keinschnickschnack.de/?page_id=148 Kompaktes einseitiges Layout mit drei Drahtbrücken, zwei Sockeln (für Bauteile mit dünnen und dicken Pins), 9V-Block Halterung, FW-Kompatibel, alle Files abrufbar (Eagle-Layout, Ätzmasken, PDFs, Beschriftung und Gerber/Excellon)<br />
* http://murlowsky.homedns.org/ovx37/?Bastelbuch:AVR-ATMEGA-ATTINY:AVR_-_Transistortester Sehr kleines Layout mit Stückliste und Ätzmaske<br />
<br />
== Downloads (deutsch) ==<br />
Hinweis: Die Entwicklung findet jetzt in einem SVN-Archiv statt. Dort finden sich auch Extras (z.&nbsp;B. Platinenlayouts). Wer sicher gehen will, eine aktuelle Version zu erhalten, sollte also entweder das Archiv auschecken oder den Snapshot herunterladen, der maximal einen Tag alt ist.<br />
<br />
* [[Media:AVR-Transistortester_neu.zip|Firmware inkl. Schaltplan und Sourcecode]] Es sind fertig kompilierte Programme für den ATMega8 und ATMega48 enthalten. Die Mega48-Version hat nicht alle Features, wie unter [[#Infos_zur_Software|"Infos zur Software"]] beschrieben (''Achtung: Möglicherweise veraltet.'')<br />
<!-- Link is dead<br />
* [http://frickelpower.bplaced.net/ctest/index.php?pglang=de Download-Site, wo die gewünschte Display-Sprache und weitere Optionen ausgewählt werden können] (''empfohlene Download-Quelle'')<br />
--><br />
<br />
* Auf der Seite '''[[AVR Transistortester]] ist eine Weiterführung''' des Projektes beschrieben mit der Bezugsquelle für neuere Software, die bisher hier angegebenen Links existieren nicht mehr.<br />
<br />
== Downloads (english) ==<br />
Note: The development now takes place in a Subversion repository. There you will also find extras (e.g. PCB layouts). Who wants to be sure to get the latest version, should either checkout the repository or download the snapshot, which is maximum one day old.<br />
<br />
* [[Media:AVR-Transistortester_neu.zip|firmware including schematics and source code]] Included are pre-built programs for ATMega8 and ATmega48. The ATMega48 version has not all the features, as described in [[#Infos_zur_Software|"Infos zur Software"]] (''Warning: Possibly outdated.'')<br />
<!-- Link is dead<br />
* [http://frickelpower.bplaced.net/ctest/index.php?pglang=de download site where the desired display language and other options can be selected] (''recommended download source'')<br />
--><br />
<br />
* '''[[AVR Transistortester]] - This is the continuation''' of the project. And there you can get the newer software.<br />
<br />
[[Kategorie:AVR-Projekte]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=KS0108_Library&diff=104683KS0108 Library2021-09-05T21:50:25Z<p>Pappnase: /* Stolperfallen */</p>
<hr />
<div>Dieser Artikel beschreibt die Verwendung einer Library zur Ansteuerung von Grafik-[[LCD]]s mit KS0108 Controllern (meist 128×64 Pixel).<br />
<br />
Eine gut funktionierende Library wurde von Fabian Maximilian Thiele erstellt, [http://www.apetech.de/ dessen Webseite] leider seit längerer Zeit offline ist.<br />
Deshalb werden hier alle nützlichen Informationen, die im Forum verstreut sind, hier zusammengefasst.<br />
<br />
== Verwendung ==<br />
Um die Library zu verwenden muss diese an die eigenen Hardware angepasst werden was in der Datei <code>ks0108.h</code> geschieht. Dazu müssen die folgenden Zeilen angepasst werden:<br />
<syntaxhighlight lang="c">// Ports<br />
#define LCD_CMD_PORT PORTA // Command Output Register<br />
#define LCD_CMD_DIR DDRA // Data Direction Register for Command Port<br />
<br />
#define LCD_DATA_IN PINC // Data Input Register<br />
#define LCD_DATA_OUT PORTC // Data Output Register<br />
#define LCD_DATA_DIR DDRC // Data Direction Register for Data Port<br />
<br />
// Command Port Bits<br />
#define D_I 0 // D/I Bit Number<br />
#define R_W 1 // R/W Bit Number<br />
#define EN 2 // EN Bit Number<br />
#define CSEL1 3 // CS1 Bit Number<br />
#define CSEL2 4 // CS2 Bit Number<br />
</syntaxhighlight><br />
<br />
== Stolperfallen ==<br />
* Diese Library verwendet ''nicht'' den RSTB-Pin des Displays. Mann muss also selber entweder per Hardware oder Software dafür sorgen, dass dieser auf HIGH gesetzt wird<br />
* Bevor man aufs LCD schreibt, muss <code>ks0108SelectFont</code> aufgerufen werden. Ansonst kommt es zu Fehlfunktionen<br />
* Nach den Einschalten brauchen die LCD-Controller je nach Hersteller unterschiedlich lange, bis sie bereit sind. Hier also lieber ein <code>delay</code> zuviel als zu wenig.<br />
<br />
'''''Fetter Text''''''''Fetter Text'''''''''== Eigene Fonts erstellen ==<br />
Wer noch mehr Fonts haben möchte, kann mit dem FontCreator eigene Schriften erstellen. Dazu muss erst die Datei <code>GLCDFontCreator2.zip</code> heruntergeladen und entpackt werden. Falls noch nicht installiert, muss ein [http://www.java.com/de/download/ Java Runtime Environment] heruntergeladen und installiert werden.<br />
<br />
Weitere Stolperfalle: "ks0108ClearScreen()"<br />
Tut nicht wirklich das, was es soll. Da bleibt immer irgendwelcher Pixelbrei übrig.<br />
Dieser Codefetzen behebt das Problem nachhaltig.<br />
<syntaxhighlight lang="c"><br />
void ks0108ClearScreen(void) {<br />
for (uint8_t cnt1 = 0; cnt1 < 8; cnt1++)<br />
{<br />
ks0108GotoXY(0, cnt1*8);<br />
if (ks0108Inverted)<br />
LCD_DATA_OUT = 0xFF; // all black<br />
else<br />
LCD_DATA_OUT = 0x00; // all white<br />
for (uint8_t cnt2 = 0; cnt2 < 64; cnt2++)<br />
{<br />
LCD_CMD_PORT |= 1 << D_I; // D_I = 1<br />
LCD_CMD_PORT &= ~(1 << R_W); // R/W = 0<br />
LCD_CMD_PORT |= (1 << EN1) | (1 << EN2); // EN high level width: min. 450ns<br />
asm volatile("nop\n\t"<br />
"nop\n\t"<br />
"nop\n\t"<br />
::);<br />
LCD_CMD_PORT &= ~((1 << EN1) | (1 << EN2));<br />
for (volatile uint8_t i=0; i<8; i++); // a little delay loop (faster than reading the busy flag)<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
Entsprechend ist die Datei "ks0108.h" anzupassen:<br />
<syntaxhighlight lang="c"><br />
//#define ks0108ClearScreen() {ks0108FillRect(0, 0, 127, 63, WHITE);}<br />
void ks0108ClearScreen(void);<br />
</syntaxhighlight><br />
<br />
== Font erstellen ==<br />
Im Menü File → New Font anwählen. Danach ist das Programm selbsterklärend.<br />
<br />
=== Windows ===<br />
Unter Windows genügt ein Doppelklick auf die Datei <code>start.bat</code>.<br />
<br />
=== Linux (evtl. auch MacOS) ===<br />
Unter Linux kann das Programm aus der Konsole mit dem Befehl<br />
java -classpath . FontCreator<br />
gestartet werden. Natürlich muss man sich im Verzeichnis <code>FontCreator.java</code> befinden.<br />
<br />
== Forenbeiträge ==<br />
* [http://www.mikrocontroller.net/topic/ks0108-glcd-routinen www.mikrocontroller.net: KS0108 GLCD-Routinen]<br />
<br />
== Downloads ==<br />
* Library: [http://www.mikrocontroller.net/attachment/21921/glcd_ks0108_v11.zip www.mikrocontroller.net: glcd_ks0108_v11.zip]<br />
* Font Generator: [http://www.mikrocontroller.net/attachment/22095/GLCDFontCreator2.zip GLCDFontCreator2.zip]<br />
* Fonts<br />
** Arial 14 Bold: in glcd_ks0108_v11.zip enthalten<br />
** Corsiva 12 :in glcd_ks0108_v11.zip enthalten<br />
** Arial 8: [http://www.mikrocontroller.net/attachment/31457/arial8.h www.mikrocontroller.net: arial8.h]<br />
** Nokia 3310: [https://www.mikrocontroller.net/attachment/256242/Nokia3310.h www.mikrocontroller.net: Nokia3310.h]<br />
<br />
== Projekte, die diese Library verwenden ==<br />
* [[Akku Tester]]<br />
* [http://www.scienceprog.com/diy-avr-graphical-lcd-test-board/ http://www.scienceprog.com/diy-avr-graphical-lcd-test-board]<br />
<br />
[[Kategorie:LCD]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=KS0108_Library&diff=104682KS0108 Library2021-09-05T21:50:08Z<p>Pappnase: /* Stolperfallen */</p>
<hr />
<div>Dieser Artikel beschreibt die Verwendung einer Library zur Ansteuerung von Grafik-[[LCD]]s mit KS0108 Controllern (meist 128×64 Pixel).<br />
<br />
Eine gut funktionierende Library wurde von Fabian Maximilian Thiele erstellt, [http://www.apetech.de/ dessen Webseite] leider seit längerer Zeit offline ist.<br />
Deshalb werden hier alle nützlichen Informationen, die im Forum verstreut sind, hier zusammengefasst.<br />
<br />
== Verwendung ==<br />
Um die Library zu verwenden muss diese an die eigenen Hardware angepasst werden was in der Datei <code>ks0108.h</code> geschieht. Dazu müssen die folgenden Zeilen angepasst werden:<br />
<syntaxhighlight lang="c">// Ports<br />
#define LCD_CMD_PORT PORTA // Command Output Register<br />
#define LCD_CMD_DIR DDRA // Data Direction Register for Command Port<br />
<br />
#define LCD_DATA_IN PINC // Data Input Register<br />
#define LCD_DATA_OUT PORTC // Data Output Register<br />
#define LCD_DATA_DIR DDRC // Data Direction Register for Data Port<br />
<br />
// Command Port Bits<br />
#define D_I 0 // D/I Bit Number<br />
#define R_W 1 // R/W Bit Number<br />
#define EN 2 // EN Bit Number<br />
#define CSEL1 3 // CS1 Bit Number<br />
#define CSEL2 4 // CS2 Bit Number<br />
</syntaxhighlight><br />
<br />
== Stolperfallen ==<br />
* Diese Library verwendet ''nicht'' den RSTB-Pin des Displays. Mann muss also selber entweder per Hardware oder Software dafür sorgen, dass dieser auf HIGH gesetzt wird<br />
* Bevor man aufs LCD schreibt, muss <code>ks0108SelectFont</code> aufgerufen werden. Ansonst kommt es zu Fehlfunktionen<br />
* Nach den Einschalten brauchen die LCD-Controller je nach Hersteller unterschiedlich lange, bis sie bereit sind. Hier also lieber ein <code>delay</code> zuviel als zu wenig.<br />
<br />
'''''Fetter Text''''''''Fetter Text'''''''''== Eigene Fonts erstellen ==<br />
Wer noch mehr Fonts haben möchte, kann mit dem FontCreator eigene Schriften erstellen. Dazu muss erst die Datei <code>GLCDFontCreator2.zip</code> heruntergeladen und entpackt werden. Falls noch nicht installiert, muss ein [http://www.java.com/de/download/ Java Runtime Environment] heruntergeladen und installiert werden.<br />
<br />
Weitere Stolperfalle: "ks0108ClearScreen()"<br />
Tut nicht wirklich das, was es soll. Da bleibt immer irgendwelcher Pixelbrei übrig.<br />
Dieser Codefetzen behebt das Problem nachhaltig.<br />
<syntaxhighlight lang="c"><br />
void ks0108ClearScreen(void) {<br />
for (uint8_t cnt1 = 0; cnt1 < 8; cnt1++)<br />
{<br />
ks0108GotoXY(0, cnt1*8);<br />
if (ks0108Inverted)<br />
LCD_DATA_OUT = 0xFF; // all black<br />
else<br />
LCD_DATA_OUT = 0x00; // all white<br />
for (uint8_t cnt2 = 0; cnt2 < 64; cnt2++)<br />
{<br />
LCD_CMD_PORT |= 1 << D_I; // D_I = 1<br />
LCD_CMD_PORT &= ~(1 << R_W); // R/W = 0<br />
LCD_CMD_PORT |= (1 << EN1) | (1 << EN2); // EN high level width: min. 450ns<br />
asm volatile("nop\n\t"<br />
"nop\n\t"<br />
"nop\n\t"<br />
::);<br />
LCD_CMD_PORT &= ~((1 << EN1) | (1 << EN2));<br />
for (volatile uint8_t i=0; i<8; i++); // a little delay loop (faster than reading the busy flag)<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
Entsprechend ist die Datei "ks0108.h" anzupassen:<br />
<syntaxhighlight lang="c"><br />
//#define ks0108ClearScreen() {ks0108FillRect(0, 0, 127, 63, WHITE);}<br />
void ks0108ClearScreen(void);<br />
</syntaxhighlight><br />
<br />
== Font erstellen ==<br />
Im Menü File → New Font anwählen. Danach ist das Programm selbsterklärend.<br />
<br />
=== Windows ===<br />
Unter Windows genügt ein Doppelklick auf die Datei <code>start.bat</code>.<br />
<br />
=== Linux (evtl. auch MacOS) ===<br />
Unter Linux kann das Programm aus der Konsole mit dem Befehl<br />
java -classpath . FontCreator<br />
gestartet werden. Natürlich muss man sich im Verzeichnis <code>FontCreator.java</code> befinden.<br />
<br />
== Forenbeiträge ==<br />
* [http://www.mikrocontroller.net/topic/ks0108-glcd-routinen www.mikrocontroller.net: KS0108 GLCD-Routinen]<br />
<br />
== Downloads ==<br />
* Library: [http://www.mikrocontroller.net/attachment/21921/glcd_ks0108_v11.zip www.mikrocontroller.net: glcd_ks0108_v11.zip]<br />
* Font Generator: [http://www.mikrocontroller.net/attachment/22095/GLCDFontCreator2.zip GLCDFontCreator2.zip]<br />
* Fonts<br />
** Arial 14 Bold: in glcd_ks0108_v11.zip enthalten<br />
** Corsiva 12 :in glcd_ks0108_v11.zip enthalten<br />
** Arial 8: [http://www.mikrocontroller.net/attachment/31457/arial8.h www.mikrocontroller.net: arial8.h]<br />
** Nokia 3310: [https://www.mikrocontroller.net/attachment/256242/Nokia3310.h www.mikrocontroller.net: Nokia3310.h]<br />
<br />
== Projekte, die diese Library verwenden ==<br />
* [[Akku Tester]]<br />
* [http://www.scienceprog.com/diy-avr-graphical-lcd-test-board/ http://www.scienceprog.com/diy-avr-graphical-lcd-test-board]<br />
<br />
[[Kategorie:LCD]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=KS0108_Library&diff=104681KS0108 Library2021-09-05T21:45:15Z<p>Pappnase: /* Stolperfallen */</p>
<hr />
<div>Dieser Artikel beschreibt die Verwendung einer Library zur Ansteuerung von Grafik-[[LCD]]s mit KS0108 Controllern (meist 128×64 Pixel).<br />
<br />
Eine gut funktionierende Library wurde von Fabian Maximilian Thiele erstellt, [http://www.apetech.de/ dessen Webseite] leider seit längerer Zeit offline ist.<br />
Deshalb werden hier alle nützlichen Informationen, die im Forum verstreut sind, hier zusammengefasst.<br />
<br />
== Verwendung ==<br />
Um die Library zu verwenden muss diese an die eigenen Hardware angepasst werden was in der Datei <code>ks0108.h</code> geschieht. Dazu müssen die folgenden Zeilen angepasst werden:<br />
<syntaxhighlight lang="c">// Ports<br />
#define LCD_CMD_PORT PORTA // Command Output Register<br />
#define LCD_CMD_DIR DDRA // Data Direction Register for Command Port<br />
<br />
#define LCD_DATA_IN PINC // Data Input Register<br />
#define LCD_DATA_OUT PORTC // Data Output Register<br />
#define LCD_DATA_DIR DDRC // Data Direction Register for Data Port<br />
<br />
// Command Port Bits<br />
#define D_I 0 // D/I Bit Number<br />
#define R_W 1 // R/W Bit Number<br />
#define EN 2 // EN Bit Number<br />
#define CSEL1 3 // CS1 Bit Number<br />
#define CSEL2 4 // CS2 Bit Number<br />
</syntaxhighlight><br />
<br />
== Stolperfallen ==<br />
* Diese Library verwendet ''nicht'' den RSTB-Pin des Displays. Mann muss also selber entweder per Hardware oder Software dafür sorgen, dass dieser auf HIGH gesetzt wird<br />
* Bevor man aufs LCD schreibt, muss <code>ks0108SelectFont</code> aufgerufen werden. Ansonst kommt es zu Fehlfunktionen<br />
* Nach den Einschalten brauchen die LCD-Controller je nach Hersteller unterschiedlich lange, bis sie bereit sind. Hier also lieber ein <code>delay</code> zuviel als zu wenig.<br />
<br />
'''''Fetter Text''''''''Fetter Text'''''''''== Eigene Fonts erstellen ==<br />
Wer noch mehr Fonts haben möchte, kann mit dem FontCreator eigene Schriften erstellen. Dazu muss erst die Datei <code>GLCDFontCreator2.zip</code> heruntergeladen und entpackt werden. Falls noch nicht installiert, muss ein [http://www.java.com/de/download/ Java Runtime Environment] heruntergeladen und installiert werden.<br />
<br />
Weitere Stolperfalle: "ks0108ClearScreen()"<br />
Tut nicht wirklich das, was es soll. Da bleibt immer irgendwelcher Pixelbrei übrig.<br />
Dieser Codefetzen behebt das Problem nachhaltig.<br />
<syntaxhighlight lang="c"><br />
void ks0108ClearScreen(void) {<br />
for (uint8_t cnt1 = 0; cnt1 < 8; cnt1++)<br />
{<br />
ks0108GotoXY(0, cnt1*8);<br />
for (uint8_t cnt2 = 0; cnt2 < 64; cnt2++)<br />
{<br />
LCD_CMD_PORT |= 1 << D_I; // D_I = 1<br />
LCD_CMD_PORT &= ~(1 << R_W); // R/W = 0<br />
if (ks0108Inverted)<br />
LCD_DATA_OUT = 0xFF; // all black<br />
else<br />
LCD_DATA_OUT = 0x00; // all white<br />
LCD_CMD_PORT |= (1 << EN1) | (1 << EN2); // EN high level width: min. 450ns<br />
asm volatile("nop\n\t"<br />
"nop\n\t"<br />
"nop\n\t"<br />
::);<br />
LCD_CMD_PORT &= ~((1 << EN1) | (1 << EN2));<br />
for (volatile uint8_t i=0; i<8; i++); // a little delay loop (faster than reading the busy flag)<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
Entsprechend ist die Datei "ks0108.h" anzupassen:<br />
<syntaxhighlight lang="c"><br />
//#define ks0108ClearScreen() {ks0108FillRect(0, 0, 127, 63, WHITE);}<br />
void ks0108ClearScreen(void);<br />
</syntaxhighlight><br />
<br />
== Font erstellen ==<br />
Im Menü File → New Font anwählen. Danach ist das Programm selbsterklärend.<br />
<br />
=== Windows ===<br />
Unter Windows genügt ein Doppelklick auf die Datei <code>start.bat</code>.<br />
<br />
=== Linux (evtl. auch MacOS) ===<br />
Unter Linux kann das Programm aus der Konsole mit dem Befehl<br />
java -classpath . FontCreator<br />
gestartet werden. Natürlich muss man sich im Verzeichnis <code>FontCreator.java</code> befinden.<br />
<br />
== Forenbeiträge ==<br />
* [http://www.mikrocontroller.net/topic/ks0108-glcd-routinen www.mikrocontroller.net: KS0108 GLCD-Routinen]<br />
<br />
== Downloads ==<br />
* Library: [http://www.mikrocontroller.net/attachment/21921/glcd_ks0108_v11.zip www.mikrocontroller.net: glcd_ks0108_v11.zip]<br />
* Font Generator: [http://www.mikrocontroller.net/attachment/22095/GLCDFontCreator2.zip GLCDFontCreator2.zip]<br />
* Fonts<br />
** Arial 14 Bold: in glcd_ks0108_v11.zip enthalten<br />
** Corsiva 12 :in glcd_ks0108_v11.zip enthalten<br />
** Arial 8: [http://www.mikrocontroller.net/attachment/31457/arial8.h www.mikrocontroller.net: arial8.h]<br />
** Nokia 3310: [https://www.mikrocontroller.net/attachment/256242/Nokia3310.h www.mikrocontroller.net: Nokia3310.h]<br />
<br />
== Projekte, die diese Library verwenden ==<br />
* [[Akku Tester]]<br />
* [http://www.scienceprog.com/diy-avr-graphical-lcd-test-board/ http://www.scienceprog.com/diy-avr-graphical-lcd-test-board]<br />
<br />
[[Kategorie:LCD]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=KS0108_Library&diff=104680KS0108 Library2021-09-05T21:43:57Z<p>Pappnase: /* Stolperfallen */</p>
<hr />
<div>Dieser Artikel beschreibt die Verwendung einer Library zur Ansteuerung von Grafik-[[LCD]]s mit KS0108 Controllern (meist 128×64 Pixel).<br />
<br />
Eine gut funktionierende Library wurde von Fabian Maximilian Thiele erstellt, [http://www.apetech.de/ dessen Webseite] leider seit längerer Zeit offline ist.<br />
Deshalb werden hier alle nützlichen Informationen, die im Forum verstreut sind, hier zusammengefasst.<br />
<br />
== Verwendung ==<br />
Um die Library zu verwenden muss diese an die eigenen Hardware angepasst werden was in der Datei <code>ks0108.h</code> geschieht. Dazu müssen die folgenden Zeilen angepasst werden:<br />
<syntaxhighlight lang="c">// Ports<br />
#define LCD_CMD_PORT PORTA // Command Output Register<br />
#define LCD_CMD_DIR DDRA // Data Direction Register for Command Port<br />
<br />
#define LCD_DATA_IN PINC // Data Input Register<br />
#define LCD_DATA_OUT PORTC // Data Output Register<br />
#define LCD_DATA_DIR DDRC // Data Direction Register for Data Port<br />
<br />
// Command Port Bits<br />
#define D_I 0 // D/I Bit Number<br />
#define R_W 1 // R/W Bit Number<br />
#define EN 2 // EN Bit Number<br />
#define CSEL1 3 // CS1 Bit Number<br />
#define CSEL2 4 // CS2 Bit Number<br />
</syntaxhighlight><br />
<br />
== Stolperfallen ==<br />
* Diese Library verwendet ''nicht'' den RSTB-Pin des Displays. Mann muss also selber entweder per Hardware oder Software dafür sorgen, dass dieser auf HIGH gesetzt wird<br />
* Bevor man aufs LCD schreibt, muss <code>ks0108SelectFont</code> aufgerufen werden. Ansonst kommt es zu Fehlfunktionen<br />
* Nach den Einschalten brauchen die LCD-Controller je nach Hersteller unterschiedlich lange, bis sie bereit sind. Hier also lieber ein <code>delay</code> zuviel als zu wenig.<br />
<br />
'''''Fetter Text''''''''Fetter Text'''''''''== Eigene Fonts erstellen ==<br />
Wer noch mehr Fonts haben möchte, kann mit dem FontCreator eigene Schriften erstellen. Dazu muss erst die Datei <code>GLCDFontCreator2.zip</code> heruntergeladen und entpackt werden. Falls noch nicht installiert, muss ein [http://www.java.com/de/download/ Java Runtime Environment] heruntergeladen und installiert werden.<br />
<br />
Weitere Stolperfalle: "ks0108ClearScreen()"<br />
Tut nicht wirklich das, was es soll. Da bleibt immer irgendwelcher Pixelbrei übrig.<br />
Dieser Codefetzen behebt das Problem nachhaltig.<br />
<syntaxhighlight lang="c"><br />
void ks0108ClearScreen(void) {<br />
for (uint8_t cnt1 = 0; cnt1 < 8; cnt1++)<br />
{<br />
ks0108GotoXY(0, cnt1*8);<br />
for (uint8_t cnt2 = 0; cnt2 < 64; cnt2++)<br />
{<br />
LCD_DATA_OUT = 0; // write zero<br />
LCD_CMD_PORT |= 1 << D_I; // D_I = 1<br />
LCD_CMD_PORT &= ~(1 << R_W); // R/W = 0<br />
if (ks0108Inverted)<br />
LCD_DATA_OUT = 0xFF; // all black<br />
else<br />
LCD_DATA_OUT = 0x00; // all white<br />
LCD_CMD_PORT |= (1 << EN1) | (1 << EN2); // EN high level width: min. 450ns<br />
asm volatile("nop\n\t"<br />
"nop\n\t"<br />
"nop\n\t"<br />
::);<br />
LCD_CMD_PORT &= ~((1 << EN1) | (1 << EN2));<br />
for (volatile uint8_t i=0; i<8; i++); // a little delay loop (faster than reading the busy flag)<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
Entsprechend ist die Datei "ks0108.h" anzupassen:<br />
<syntaxhighlight lang="c"><br />
//#define ks0108ClearScreen() {ks0108FillRect(0, 0, 127, 63, WHITE);}<br />
void ks0108ClearScreen(void);<br />
</syntaxhighlight><br />
<br />
== Font erstellen ==<br />
Im Menü File → New Font anwählen. Danach ist das Programm selbsterklärend.<br />
<br />
=== Windows ===<br />
Unter Windows genügt ein Doppelklick auf die Datei <code>start.bat</code>.<br />
<br />
=== Linux (evtl. auch MacOS) ===<br />
Unter Linux kann das Programm aus der Konsole mit dem Befehl<br />
java -classpath . FontCreator<br />
gestartet werden. Natürlich muss man sich im Verzeichnis <code>FontCreator.java</code> befinden.<br />
<br />
== Forenbeiträge ==<br />
* [http://www.mikrocontroller.net/topic/ks0108-glcd-routinen www.mikrocontroller.net: KS0108 GLCD-Routinen]<br />
<br />
== Downloads ==<br />
* Library: [http://www.mikrocontroller.net/attachment/21921/glcd_ks0108_v11.zip www.mikrocontroller.net: glcd_ks0108_v11.zip]<br />
* Font Generator: [http://www.mikrocontroller.net/attachment/22095/GLCDFontCreator2.zip GLCDFontCreator2.zip]<br />
* Fonts<br />
** Arial 14 Bold: in glcd_ks0108_v11.zip enthalten<br />
** Corsiva 12 :in glcd_ks0108_v11.zip enthalten<br />
** Arial 8: [http://www.mikrocontroller.net/attachment/31457/arial8.h www.mikrocontroller.net: arial8.h]<br />
** Nokia 3310: [https://www.mikrocontroller.net/attachment/256242/Nokia3310.h www.mikrocontroller.net: Nokia3310.h]<br />
<br />
== Projekte, die diese Library verwenden ==<br />
* [[Akku Tester]]<br />
* [http://www.scienceprog.com/diy-avr-graphical-lcd-test-board/ http://www.scienceprog.com/diy-avr-graphical-lcd-test-board]<br />
<br />
[[Kategorie:LCD]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=KS0108_Library&diff=104679KS0108 Library2021-09-05T21:37:12Z<p>Pappnase: /* Stolperfallen */</p>
<hr />
<div>Dieser Artikel beschreibt die Verwendung einer Library zur Ansteuerung von Grafik-[[LCD]]s mit KS0108 Controllern (meist 128×64 Pixel).<br />
<br />
Eine gut funktionierende Library wurde von Fabian Maximilian Thiele erstellt, [http://www.apetech.de/ dessen Webseite] leider seit längerer Zeit offline ist.<br />
Deshalb werden hier alle nützlichen Informationen, die im Forum verstreut sind, hier zusammengefasst.<br />
<br />
== Verwendung ==<br />
Um die Library zu verwenden muss diese an die eigenen Hardware angepasst werden was in der Datei <code>ks0108.h</code> geschieht. Dazu müssen die folgenden Zeilen angepasst werden:<br />
<syntaxhighlight lang="c">// Ports<br />
#define LCD_CMD_PORT PORTA // Command Output Register<br />
#define LCD_CMD_DIR DDRA // Data Direction Register for Command Port<br />
<br />
#define LCD_DATA_IN PINC // Data Input Register<br />
#define LCD_DATA_OUT PORTC // Data Output Register<br />
#define LCD_DATA_DIR DDRC // Data Direction Register for Data Port<br />
<br />
// Command Port Bits<br />
#define D_I 0 // D/I Bit Number<br />
#define R_W 1 // R/W Bit Number<br />
#define EN 2 // EN Bit Number<br />
#define CSEL1 3 // CS1 Bit Number<br />
#define CSEL2 4 // CS2 Bit Number<br />
</syntaxhighlight><br />
<br />
== Stolperfallen ==<br />
* Diese Library verwendet ''nicht'' den RSTB-Pin des Displays. Mann muss also selber entweder per Hardware oder Software dafür sorgen, dass dieser auf HIGH gesetzt wird<br />
* Bevor man aufs LCD schreibt, muss <code>ks0108SelectFont</code> aufgerufen werden. Ansonst kommt es zu Fehlfunktionen<br />
* Nach den Einschalten brauchen die LCD-Controller je nach Hersteller unterschiedlich lange, bis sie bereit sind. Hier also lieber ein <code>delay</code> zuviel als zu wenig.<br />
<br />
'''''Fetter Text''''''''Fetter Text'''''''''== Eigene Fonts erstellen ==<br />
Wer noch mehr Fonts haben möchte, kann mit dem FontCreator eigene Schriften erstellen. Dazu muss erst die Datei <code>GLCDFontCreator2.zip</code> heruntergeladen und entpackt werden. Falls noch nicht installiert, muss ein [http://www.java.com/de/download/ Java Runtime Environment] heruntergeladen und installiert werden.<br />
<br />
Weitere Stolperfalle: "ks0108ClearScreen()"<br />
Tut nicht wirklich das, was es soll. Da bleibt immer irgendwelcher Pixelbrei übrig.<br />
Dieser Codefetzen behebt das Problem nachhaltig.<br />
<syntaxhighlight lang="c"><br />
void ks0108ClearScreen(void) {<br />
for (uint8_t cnt1 = 0; cnt1 < 8; cnt1++)<br />
{<br />
ks0108GotoXY(0, cnt1*8);<br />
for (uint8_t cnt2 = 0; cnt2 < 64; cnt2++)<br />
{<br />
LCD_DATA_OUT = 0; // write zero<br />
LCD_CMD_PORT |= 1 << D_I; // D_I = 1<br />
LCD_CMD_PORT &= ~(1 << R_W); // R/W = 0<br />
if (ks0108Inverted)<br />
LCD_DATA_OUT = 0xFF; // all black<br />
else<br />
LCD_DATA_OUT = 0x00; // all white<br />
LCD_CMD_PORT |= (1 << EN1) | (1 << EN2); // EN high level width: min. 450ns<br />
asm volatile("nop\n\t"<br />
"nop\n\t"<br />
"nop\n\t"<br />
::);<br />
LCD_CMD_PORT &= ~((1 << EN1) | (1 << EN2));<br />
for (volatile uint8_t i=0; i<8; i++); // a little delay loop (faster than reading the busy flag)<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
== Font erstellen ==<br />
Im Menü File → New Font anwählen. Danach ist das Programm selbsterklärend.<br />
<br />
=== Windows ===<br />
Unter Windows genügt ein Doppelklick auf die Datei <code>start.bat</code>.<br />
<br />
=== Linux (evtl. auch MacOS) ===<br />
Unter Linux kann das Programm aus der Konsole mit dem Befehl<br />
java -classpath . FontCreator<br />
gestartet werden. Natürlich muss man sich im Verzeichnis <code>FontCreator.java</code> befinden.<br />
<br />
== Forenbeiträge ==<br />
* [http://www.mikrocontroller.net/topic/ks0108-glcd-routinen www.mikrocontroller.net: KS0108 GLCD-Routinen]<br />
<br />
== Downloads ==<br />
* Library: [http://www.mikrocontroller.net/attachment/21921/glcd_ks0108_v11.zip www.mikrocontroller.net: glcd_ks0108_v11.zip]<br />
* Font Generator: [http://www.mikrocontroller.net/attachment/22095/GLCDFontCreator2.zip GLCDFontCreator2.zip]<br />
* Fonts<br />
** Arial 14 Bold: in glcd_ks0108_v11.zip enthalten<br />
** Corsiva 12 :in glcd_ks0108_v11.zip enthalten<br />
** Arial 8: [http://www.mikrocontroller.net/attachment/31457/arial8.h www.mikrocontroller.net: arial8.h]<br />
** Nokia 3310: [https://www.mikrocontroller.net/attachment/256242/Nokia3310.h www.mikrocontroller.net: Nokia3310.h]<br />
<br />
== Projekte, die diese Library verwenden ==<br />
* [[Akku Tester]]<br />
* [http://www.scienceprog.com/diy-avr-graphical-lcd-test-board/ http://www.scienceprog.com/diy-avr-graphical-lcd-test-board]<br />
<br />
[[Kategorie:LCD]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=KS0108_Library&diff=104640KS0108 Library2021-08-08T18:18:17Z<p>Pappnase: /* Stolperfallen */</p>
<hr />
<div>Dieser Artikel beschreibt die Verwendung einer Library zur Ansteuerung von Grafik-[[LCD]]s mit KS0108 Controllern (meist 128×64 Pixel).<br />
<br />
Eine gut funktionierende Library wurde von Fabian Maximilian Thiele erstellt, [http://www.apetech.de/ dessen Webseite] leider seit längerer Zeit offline ist.<br />
Deshalb werden hier alle nützlichen Informationen, die im Forum verstreut sind, hier zusammengefasst.<br />
<br />
== Verwendung ==<br />
Um die Library zu verwenden muss diese an die eigenen Hardware angepasst werden was in der Datei <code>ks0108.h</code> geschieht. Dazu müssen die folgenden Zeilen angepasst werden:<br />
<syntaxhighlight lang="c">// Ports<br />
#define LCD_CMD_PORT PORTA // Command Output Register<br />
#define LCD_CMD_DIR DDRA // Data Direction Register for Command Port<br />
<br />
#define LCD_DATA_IN PINC // Data Input Register<br />
#define LCD_DATA_OUT PORTC // Data Output Register<br />
#define LCD_DATA_DIR DDRC // Data Direction Register for Data Port<br />
<br />
// Command Port Bits<br />
#define D_I 0 // D/I Bit Number<br />
#define R_W 1 // R/W Bit Number<br />
#define EN 2 // EN Bit Number<br />
#define CSEL1 3 // CS1 Bit Number<br />
#define CSEL2 4 // CS2 Bit Number<br />
</syntaxhighlight><br />
<br />
== Stolperfallen ==<br />
* Diese Library verwendet ''nicht'' den RSTB-Pin des Displays. Mann muss also selber entweder per Hardware oder Software dafür sorgen, dass dieser auf HIGH gesetzt wird<br />
* Bevor man aufs LCD schreibt, muss <code>ks0108SelectFont</code> aufgerufen werden. Ansonst kommt es zu Fehlfunktionen<br />
* Nach den Einschalten brauchen die LCD-Controller je nach Hersteller unterschiedlich lange, bis sie bereit sind. Hier also lieber ein <code>delay</code> zuviel als zu wenig.<br />
<br />
'''''Fetter Text''''''''Fetter Text'''''''''== Eigene Fonts erstellen ==<br />
Wer noch mehr Fonts haben möchte, kann mit dem FontCreator eigene Schriften erstellen. Dazu muss erst die Datei <code>GLCDFontCreator2.zip</code> heruntergeladen und entpackt werden. Falls noch nicht installiert, muss ein [http://www.java.com/de/download/ Java Runtime Environment] heruntergeladen und installiert werden.<br />
<br />
== Font erstellen ==<br />
Im Menü File → New Font anwählen. Danach ist das Programm selbsterklärend.<br />
<br />
=== Windows ===<br />
Unter Windows genügt ein Doppelklick auf die Datei <code>start.bat</code>.<br />
<br />
=== Linux (evtl. auch MacOS) ===<br />
Unter Linux kann das Programm aus der Konsole mit dem Befehl<br />
java -classpath . FontCreator<br />
gestartet werden. Natürlich muss man sich im Verzeichnis <code>FontCreator.java</code> befinden.<br />
<br />
== Forenbeiträge ==<br />
* [http://www.mikrocontroller.net/topic/ks0108-glcd-routinen www.mikrocontroller.net: KS0108 GLCD-Routinen]<br />
<br />
== Downloads ==<br />
* Library: [http://www.mikrocontroller.net/attachment/21921/glcd_ks0108_v11.zip www.mikrocontroller.net: glcd_ks0108_v11.zip]<br />
* Font Generator: [http://www.mikrocontroller.net/attachment/22095/GLCDFontCreator2.zip GLCDFontCreator2.zip]<br />
* Fonts<br />
** Arial 14 Bold: in glcd_ks0108_v11.zip enthalten<br />
** Corsiva 12 :in glcd_ks0108_v11.zip enthalten<br />
** Arial 8: [http://www.mikrocontroller.net/attachment/31457/arial8.h www.mikrocontroller.net: arial8.h]<br />
** Nokia 3310: [https://www.mikrocontroller.net/attachment/256242/Nokia3310.h www.mikrocontroller.net: Nokia3310.h]<br />
<br />
== Projekte, die diese Library verwenden ==<br />
* [[Akku Tester]]<br />
* [http://www.scienceprog.com/diy-avr-graphical-lcd-test-board/ http://www.scienceprog.com/diy-avr-graphical-lcd-test-board]<br />
<br />
[[Kategorie:LCD]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=FAQ&diff=79834FAQ2013-11-30T20:42:08Z<p>Pappnase: Rechtschreibfehler korrigiert</p>
<hr />
<div>Ein Verzeichnis von im Forum oft gestellten und immer wieder beantworteten Fragen und den zugehörigen Antworten:<br />
<br />
=Wie kann ich Zahlen auf [[LCD]]/[[UART]] ausgeben?=<br />
<br />
Aber die Bibliothek, die du benutzt, stellt nur eine Funktion zur Verfügung, mit der man einen String ausgeben kann... Was tun? <br />
<br />
In den folgenden Beispielen wird eine selbstgeschriebene Funktion zur Stringausgabe auf LCD - die Funktion lcd_string() - aus dem [[AVR-GCC-Tutorial/LCD-Ansteuerung|LCD-Teil des AVR-GCC-Tutorials]] verwendet:<br />
<br />
<syntaxhighlight lang="c"><br />
lcd_string( "Hallo Welt" ); // ggf. auch lcd_out() o.ä. in anderen Libraries<br />
</syntaxhighlight><br />
<br />
Um also eine Zahl (numerische Konstante oder Variableninhalt) auszugeben, muss von dieser Zahl zunächst ihre String-Repräsentation ermittelt werden. Hier geht es aber nur darum, zu zeigen wie man diese String Repräsenation erzeugen kann. Was man dann mit diesem String weiter macht, ob das dann eine LCD-Ausgabe oder eine UART-Übertragung oder das Abspeichern auf SD-Karte oder ... ist, spielt eine untergeordnete Rolle.<br />
<br />
Es gibt mehrere Möglichkeiten, sich die Stringrepräsentation zu erzeugen:<br />
<br />
===itoa() (utoa(), ltoa(), ultoa(), ftoa() )===<br />
<br />
<b>itoa()</b> ist keine C-Standardfunktion (wohl aber ihre Umkehrung <b>atoi()</b> ). Auf manchen Compilern heisst diese Funktion dann folgerichtig <b>_itoa()</b>, wobei der führende _ eben anzeigt, dass es sich um eine Erweiterung des C-Standards handelt. Bei [[WinAVR]] ist itoa() Bestandteil der mitgelieferten Library avr-libc, in der Libary [http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html''stdlib.h''].<br />
<br />
<syntaxhighlight lang="c"><br />
#include <stdlib.h><br />
char Buffer[20];<br />
int i = 25;<br />
<br />
itoa( i, Buffer, 10 );<br />
lcd_string( Buffer ); // ggf. auch lcd_out() o.ä. in anderen Libraries<br />
</syntaxhighlight><br />
<br />
<b>itoa( i, Buffer, 10 );</b> - Die Zahl i wird nach ASCII gewandelt und die String Repräsentierung davon wird in Buffer abgelegt. Die Basis, in der diese Wandlung erfolgt, ist das 10-er System. Wird das dritte Argument von 10 in zb. 2 oder auch 16 abgewandelt, erhält man die binäre oder eben eine hexadezimale Repräsentierung des Wertes. Auch wenn 10, 2 und 16 die häufigsten Angaben an dieser Stelle sind, kann itoa aber grundsätzlich in jedes beliebige Zahlensystem wandlen.<br />
<br />
Wichtig ist, darauf zu achten, dass das Array <i>Buffer</i> groß genug dimensioniert wird, um alle Zeichen der Textrepräsentation der Zahl aufzunehmen - inklusive der 0, die den String abschließt, sowie ein mögliches Vorzeichen.<br />
<br />
Anzumerken bleibt weiter, dass es normalerweise für alle Datentypen entsprechende Umwandlungsfunktionen gibt, wenn es sie für einen Datentyp gibt. Die Namensgebung lehnt sich an das Schema an: ''Kürzel_für_den_Datentyp to a''. Eine Funktion die einen unsigned int wandelt, heißt dann utoa (oder _utoa), Floating Point heißt dann ftoa (oder _ftoa), etc.<br />
<br />
===sprintf()===<br />
<br />
<syntaxhighlight lang="c"><br />
char Buffer[20];<br />
int i = 25;<br />
<br />
sprintf( Buffer, "%d", i );<br />
lcd_string( Buffer ); // ggf. auch lcd_out() o.ä. in anderen Libraries<br />
</syntaxhighlight><br />
<br />
Diese Methode funktioniert auch bei long oder float Werten. Unbedingt beachtet werden muss allerdings, dass die Typkennzeichnungen im sog. Format-String (hier "%d") mit den tatsächlichen Typen der auszugebenden Werten übereinstimmt. Und dass der Buffer, der den Text aufnimmt, auch groß genug dimensioniert wird. Dabei sollte die 0, die den String terminiert, nicht vergessen werden.<br />
<br />
Mit sprintf() hat man dieselben Möglichkeiten zur Formatierung wie bei <b>printf()</b> (siehe unten). Insbesondere gibt es natürlich die Möglichkeit die Zahl gleich in einen umgebenden Text einzubetten bzw. Formatierungen anzugeben:<br />
<br />
<syntaxhighlight lang="c"><br />
char Buffer[20];<br />
int i = 25;<br />
<br />
sprintf( Buffer, "Anzahl: %d Stueck", i );<br />
lcd_out( Buffer );<br />
</syntaxhighlight><br />
<br />
Der "Haken" an der mächtigen Funktion sprintf() ist, daß sie auch bei minimalisierter Konfiguration verhältnismäßig viel Programmspeicher (Flash-ROM) belegt und relativ viel Prozesszeit benötigt. Daher sollte man sprintf() nur verwenden, wenn kein Speicher- und Prozesszeitmangel besteht. Sonst sollte itoa() oder eine eigene, auf die Bedürfnisse optimierte Implementierung auf jeden Fall vorgezogen werden. Der große Vorteil von sprintf liegt darin, dass man über sehr mächtige Formatiermöglichkeiten verfügt, mit der man die Ausgabe einfach steuern und an seine Bedürfnisse anpassen kann. Dinge die man mit einer Funktion wie itoa alle selbst 'händisch' erledigen muss.<br />
<br />
====Formatierungen mit printf====<br />
<br />
Für jedes auszugebende Argument muss es im Formatstring einen entsprechenden Formatbezeichner geben. Der Aufbau eines Formatbezeichners ist immer<br />
<br />
%[Modifizierer][Feldbreite][.Präzision]Typ<br />
<br />
(Die in eckigen Klammern [ ] angegebenen Elemente können auch weggelassen werden, wenn man sie nicht benötigt. Im einfachsten Fall benötigt man also nur das % und den Buchstaben zur Typ-Kennung.)<br />
<br />
<b>Typ</b> ist dabei eine Kennung, der mit dem Datentyp des jeweiligen auszugebenden Argumentes übereinstimmen muss. Einige oft benutzte Kennungen, ohne Anspruch auf Vollständigkeit, sind:<br />
<b>c</b> char<br />
<b>d</b> int<br />
<b>f</b> float, double<br />
<b>ld</b> long<br />
<b>u</b> unsigned int<br />
<b>lu</b> unsigned long<br />
<b>p</b> pointer<br />
<b>s</b> string<br />
<b>x</b> ein int wird ausgegeben, die Ausgabe erfolgt<br />
aber als Hexadezimalzahl<br />
<b>X</b> ein int wird ausgegeben, die Ausgabe erfolgt<br />
aber als Hexadezimalzahl, wobei Grossbuchstaben verwendet werden<br />
<br />
Der''Modifizierer'' bestimmt, wie und womit nicht benutzte Felder des Ausgabefeldes gefüllt werden sollen, wie die Ausrichtung innerhalb des Feldes erfolgen soll und ob ein Vorzeichen auch dann ausgegeben werden soll wenn die auszugebende Zahl positiv ist. Wird kein Modifizierer angegeben, so werden nicht benutzte Felder mit einem Leerzeichen gefüllt, positive Vorzeichen unterdrückt und die Ausgabe im Feld rechts ausgerichtet.<br />
<br />
<b>+</b> Vorzeichen wird immer ausgegeben<br />
<b>-</b> Die Ausgabe wird im Ausgabefeld linksbündig ausgerichtet<br />
<b>0</b> anstelle von Leerzeichen werden führende 0-en ausgegeben<br />
<br />
Die ''Feldbreite'' gibt die Breite des Ausgabefeldes an, in die die Ausgabe durchgeführt werden soll. Reicht die angegebene Feldbreite nicht aus, so vergrößert printf diese Breite eigenmächtig. Die Feldbreite muß nicht angegeben werden. In diesem Fall bestimmt printf selbst die Feldbreite, so dass die Ausgabe darin Platz findet. Mit der Feldbreite hat man eine simple Möglichkeit dafür zu sorgen, dass der erzeugte String immer eine konstante Länge hat, selbst wenn die auszugebende Zahl diese Länge gar nicht benötigen würde (wichtig zb. bei Tabellen, damit die Einerstellen der Tabelleneinträge auch sauber untereinander stehen, auch dann wenn die Zahlen sich in unterschiedlichen Wertebereichen bewegen).<br />
<br />
Die ''Präzision'' kommt nur bei float oder double Zahlen zum Einsatz. Sie legt fest, wieviele Positionen der kompletten Feldbreite für die Ausgabe von Nachkommastellen reserviert werden sollen. Auch sie muss wiederrum nicht angegeben werden und printf benutzt in so einem Fall Standardvorgaben.<br />
<br />
===== Beispiele =====<br />
;<tt>"%d"</tt>: Ausgabe eines Integer<br />
;<tt>"%5d"</tt>: Ausgabe eines Integer in einem Feld mit 5 Zeichen Breite<br />
;<tt>"%05d"</tt>: Ausgabe eines Integer in einem Feld mit 5 Zeichen Breite, wobei das Feld links mit führenden Nullen auf 5 Zeichen aufgefüllt wird<br />
;<tt>"%-5d"</tt>: Ausgabe eines Integer in einem Feld mit 5 Zeichen Breite. Die Zahl wird linksbündig in das Feld gestellt.<br />
;<tt>"%6.3f"</tt>: Ausgabe eines float (oder double). Die Ausgabe erfolgt in einem Feld mit 6 Zeichen Breite, wobei 3 Nachkommastellen ausgegeben werden. Achtung: In der Feldbreite ist auch ein eventuelles Vorzeichen sowie der Dezimalpunkt enthalten. Bei einer Feldbreite von 6 Zeichen und 3 Nachkommastellen, bleiben bei einer positiven Zahl daher nur 2 Positionen für den Vorkommaanteil, bei negativen sogar nur 1 Stelle (6 - 3 Nachkommastellen - 1 Dezimalpunkt - 1 Vorzeichen = 1)<br />
<br />
===Eigene Umwandlungsfunktionen===<br />
<br />
Möchte man <b>itoa()</b> nicht benutzen oder hat es gar auf seinem System nicht zur Verfügung, dann ist es auch nicht schwer, sich selbst eine Funktion dafür zu schreiben:<br />
<br />
<syntaxhighlight lang="c"><br />
void ItoA( int z, char* Buffer )<br />
{<br />
int i = 0;<br />
int j;<br />
char tmp;<br />
unsigned u; // In u bearbeiten wir den Absolutbetrag von z.<br />
<br />
// ist die Zahl negativ?<br />
// gleich mal ein - hinterlassen und die Zahl positiv machen<br />
if( z < 0 ) {<br />
Buffer[0] = '-';<br />
Buffer++;<br />
// -INT_MIN ist idR. größer als INT_MAX und nicht mehr <br />
// als int darstellbar! Man muss daher bei der Bildung <br />
// des Absolutbetrages aufpassen.<br />
u = ( (unsigned)-(z+1) ) + 1; <br />
}<br />
else { <br />
u = (unsigned)z;<br />
}<br />
// die einzelnen Stellen der Zahl berechnen<br />
do {<br />
Buffer[i++] = '0' + u % 10;<br />
u /= 10;<br />
} while( u > 0 );<br />
<br />
// den String in sich spiegeln<br />
for( j = 0; j < i / 2; ++j ) {<br />
tmp = Buffer[j];<br />
Buffer[j] = Buffer[i-j-1];<br />
Buffer[i-j-1] = tmp;<br />
}<br />
Buffer[i] = '\0';<br />
}<br />
</syntaxhighlight><br />
<br />
Das Grundprinzip ist einfach:<br><br />
Die Ermittlung der einzelnen Stellen erfolgt in der zentralen Schleife<br />
<br />
<syntaxhighlight lang="c"><br />
do {<br />
Buffer[i++] = '0' + u % 10;<br />
u /= 10;<br />
} while( u > 0 );<br />
</syntaxhighlight><br />
<br />
durch fortgesetzte Division durch 10 und Restbildung.<br />
<br />
8392<br />
<br />
8392 % 10 -> <b>2</b><br />
8392 / 10 -> 839<br />
<br />
839 % 10 -> <b>9</b><br />
839 / 10 -> 83<br />
<br />
83 % 10 -> <b>3</b><br />
83 / 10 -> 8<br />
<br />
8 % 10 -> <b>8</b><br />
8 / 10 -> 0<br />
<br />
<br />
Nur leider erhält man dadurch die einzelnen Ziffern der Zahl in umgekehrter Reihenfolge im String ('2' '9' '3' '8' anstelle von '8' '3' '9' '2'). Dies ist aber kein Problem, die nachfolgende Schleife<br />
<br />
<syntaxhighlight lang="c"><br />
for( j = 0; j < i / 2; ++j ) {<br />
tmp = Buffer[j];<br />
Buffer[j] = Buffer[i-j-1];<br />
Buffer[i-j-1] = tmp;<br />
}<br />
</syntaxhighlight><br />
<br />
spiegelt den String in sich, sodass danach der String eine korrekte Repräsentation der ursprünglichen Zahl darstellt. Der Funktionsteil vor der 'Zerlegeschleife' behandelt den Sonderfall daß die Zahl negativ ist. Negative Zahlen werden behandelt indem im Endergebnis ein '-' vermerkt wird und danach die Zahl positiv gemacht wird.<br />
<br />
<br />
Siehe auch:<br />
* Forenbeitrag [http://www.mikrocontroller.net/topic/67405#541885 Integer-Zahl in String mit bestimmter Zeichenlänge]<br />
* Forenbeitrag [http://www.mikrocontroller.net/topic/84005#704736 (Resourcenschonend) Wert einer Variable am LCD ausgeben] von Niels Hüsken <br />
* Forenbeitrag [http://www.mikrocontroller.net/topic/121526?goto=new#new Formatierte Zahlenausgabe in C] von Peter Dannegger<br />
* [[Festkommaarithmetik]]<br />
<br />
=Datentypen in Operationen=<br />
Ein häufiges Problem betrifft die Auswertung von Ausdrücken. Konkret die Frage nach den beteiligten Datentypen.<br />
zb<br />
<syntaxhighlight lang="c"><br />
double i;<br />
int j, k;<br />
<br />
i = j / k;<br />
</syntaxhighlight><br />
Die Frage lautet dann: Warum erhalte ich keine Kommastellen, ich weise doch das Ergebnis einem double zu?<br />
<br />
Dazu ist zu sagen, dass C nicht so funktioniert. Die Tatsache dass i eine double Variable ist, ist für die Auswahl der Operation, welche die Division durchführt, völlig irrelevant. C orientiert sich ausschliesslich an den <br />
Datentypen der beteiligten Operanden, um zu entscheiden ob die Division als Integer- oder als Gleitkommadivision durchzuführen ist. Und da sowohl j als auch k ein Integer sind, wird die Division als Integerdivision durchgeführt<br />
unabhängig davon, was mit dem Ergebnis weiter passiert. Erst nach der Division wird das Ergebnis in einen double überführt, um es an i zuweisen zu können. Zu diesem Zeitpunkt gibt es aber keine Kommastellen mehr, eine Integerdivision erzeugt keine. Und damit tauchen klarerweise auch im Ergebnis keine auf.<br />
<br />
Aus genau diesem Grund ist zb das Ergebnis von<br />
<syntaxhighlight lang="c"><br />
double i;<br />
i = 5 / 8;<br />
</syntaxhighlight><br />
eine glatte 0 und nicht 0.625. Die Division 5 / 8 wird als Integer Division gemacht und liefert als solche keine Nachkommastellen. Wird das Ergebnis der Division in einen double umgewandelt, um es an i zuweisen zu können, ist das Kind schon in den Brunnen gefallen: Die Nachkommastellen sind schon längst weg bzw. waren nie vorhanden.<br />
<br />
Will man den Compiler dazu zwingen, die Division als Gleitkommadivision durchzuführen, so muss man daher dafür sorgen, dass mindestens einer der beteiligten Operanden ein double Wert ist. Dann bleibt dem Compiler nichts anderes übrig, als auch den zweiten Operanden ebenfalls zu einem double zu machen und die Operation als Gleitkommaoperation durchzuführen.<br />
<br />
<syntaxhighlight lang="c"><br />
double i;<br />
i = 5.0 / 8.0;<br />
<br />
i = (double)j / k;<br />
</syntaxhighlight><br />
<br />
Generell implementiert der Compiler eine Operation immer im 'höchsten' Datentyp, der in dieser Operation vorkommenden Operanden. Operanden in einem 'niedrigeren' Datentyp werden automatisch immer in diesen 'höchsten' Datentyp umgewandelt, zumindest aber int. Die Reihung orientiert sich dabei an der Regel: Ein 'höherer' Datentyp kann alle Werte eines 'niedrigeren' Datentyps aufnehmen.<br />
<br />
int<br />
unsigned int<br />
long<br />
unsigned long<br />
long long<br />
unsigned long long<br />
double<br />
<br />
float kommt in dieser Tabelle gar nicht vor, da Gleitkommaoperationen grundsätzlich immer als double-Operationen durchgeführt werden. Wohl kann es aber sein, dass double und float dieselbe Anzahl an Bits benutzen. Damit reduziert sich eine double Operation effektiv auf eine float Operation. (Anmerkung: mit dem C99-Standard hat sich dieses geändert. Dort gibt es dann auch echte float Operationen)<br />
<br />
Hat man also in einer Operation 2 Operanden der Datentypen int und long, so wird der int implizit zu einem long gemacht und die Operation als long Operation durchgeführt. Das Ergebnis hat dann den Datentyp long.<br />
<br />
==Welche Datentypen haben Konstanten==<br />
<br />
Auch Zahlenkonstante besitzen einen Datentyp, der selbstverständlich vom Compiler bei der Auswahl der Operation berücksichtigt wird. Hier gilt die Regel: Benutzt wird der Datentyp, der die Zahl gerade noch aufnehmen kann. Eine Zahlenkonstante 5 hat daher den Datentyp int. Die Zahl 32767 passt gerade noch in einen int, und hat daher den Datentyp int. 32768 ist für einen int bereits zu groß und hat daher den Datentyp long. 5.0 ist hingegem immer eine double-Konstante. Der Dezimalpunkt erzwingt dieses.<br />
<br />
Eine kleine Feinheit gibt es noch zu beachten. Konstanten in dezimaler Schreibweise haben immer einen signed Datentyp, während Konstanten in hexadezimaler bzw. oktaler Schreibweise je nach Wert einen signed oder einen unsigned Datentyp haben können.<br />
<br />
Möchte man einer Konstanten einen bestimmten Datentyp aufzwingen, so gibt es dazu 2 (ein halb) Möglichkeiten:<br />
* Entweder man castet die Konstante in den gewünschten Datentyp<br />
<syntaxhighlight lang="c"><br />
(long)5<br />
</syntaxhighlight><br />
* oder man benutzt die in C dafür vorgesehene Schreibweise, indem man der Konstanten einen Suffix anhängt, der den gewünschten Datentyp beschreibt<br />
<syntaxhighlight lang="c"><br />
5L<br />
</syntaxhighlight><br />
Beides ergibt eine dezimale 5, die vom Datentyp long ist.<br />
<br />
* eine Zahl in mit einem Dezimalkomma<br />
<syntaxhighlight lang="c"><br />
5.0<br />
</syntaxhighlight><br />
ist immer eine double Zahl. Es sei denn sie hat explizit eien Suffix<br />
<syntaxhighlight lang="c"><br />
5.0F<br />
</syntaxhighlight><br />
dann hat man es mit einer float Zahl zu tun.<br />
<br />
Die gültigen Suffixe für Zahlen sind U, L, UL und F:<br />
<br />
* U wie unsigned; dabei wird zuerst int angenommen. Es erfolgt eine automatische Ausweitung auf long, wenn die Zahl den Wertebereich eines unsigned int überschreitet. <br />
* L wie long; die Zahl selbst kann int oder double sein.<br />
* UL wie unsigned long. Eigentlich eine Zusammensetzung aus U und L<br />
* F wie float.<br />
<br />
=Aktivieren der Floating Point Version von sprintf bei WinAVR bzw AVR-Studio=<br />
[[Bild:AVR_Studio_float1.gif|thumb|right|300px|Project → Configuration Options → Libraries → Available Link Objects]]<br />
[[Bild:AVR_Studio_float2.gif|thumb|right|300px|Custom Options → Custom Compilation Options → Linker Options]]<br />
Beim WinAVR/AVR-Studio wird standardmässig eine Version der printf-Bibliothek verwendet, die keine Floatingpoint-Verarbeitung unterstützt. Die meisten Programme benötigen keine Floatingpoint-Unterstützung, so dass dadurch wertvoller Programmspeicherplatz gespart werden kann.<br />
<br />
Benutzt man allerdings eine printf-Variante für die Ausgabe von Floatingpoint-Zahlen, so erscheint an Stelle der korrekt formatierten Zahl lediglich ein '?'. Dies ist ein Indiz dafür, dass die Floatingpoint-Verarbeitung im Projekt aktiviert werden muss.<br />
<br />
Um die Floatingpoint-Verarbeitung zu aktivieren, geht man im '''AVR Studio 4''' wie folgt vor: <br />
* Menüpunkt: ''Project → Configuration Options''<br />
* Im sich öffnenden Dialog wird in der linken Navigationsleiste der Eintrag ''Libraries'' ausgewählt.<br />
* Unter ''Available Link Objects'' werden Bibliotheken angeboten. Für die Aktivierung der Floatingpoint-Unterstützung sind 2 interessant<ref><tt>libc.a</tt> sollte nicht zu den Bibliotheken hinzugefügt werden, da sie automatisch eingebunden wird. Etwas Hintergrundinformationen gibt es in [http://www.mikrocontroller.net/topic/173630 diesem Thread.]<br />
</ref>:<br />
** <tt>libprintf_flt.a</tt><br />
** <tt>libm.a</tt><br />
* Beide Bibliotheken werden durch Aktivieren und einen Druck auf ''Add Library → '' in die rechte Spalte übernommen.<br />
* Danach wählt man in der Navigationsleiste den Eintrag ''Custom Options''.<br />
* Unter ''Custom Compilation Options'' wird ''Linker Options'' ausgewählt und in das Textfeld rechts/unten folgender Text eingetragen:<br />
-Wl,-u,vfprintf<br />
* Ein Druck auf ''Add'' befördert die Zeile in das Listenfeld darüber, welches Optionen für den Linker enthält.<br />
* Mit ''OK'' wird die Konfiguration abgeschlossen.<br />
<br />
Bei '''AVR Studio 5''' trägt man die Optionen an anderen Stellen ein ([http://www.mikrocontroller.net/topic/221583#2219612 Beitrag von Hal Smith]): <br />
*''Project Properties → Toolchain → AVR/GNU C-Linker → Libraries''<br/>In ''Libraries'' <tt>(-WI, -I), libprintf_flt.a libm.a</tt> eintragen.<br />
* ''Project Properties → Toolchain → AVR/GNU C-Linker → Miscellaneous''<br/>In ''Other Linker Flags'' <tt>-Wl,-u,vfprintf</tt> eintragen.<br />
{{Absatz}}<br />
<br />
= Wie funktioniert String-Verarbeitung in C? =<br />
: → Siehe: ''[[String-Verarbeitung in C]]''<br />
<br />
= struct Strukturen =<br />
<br />
: → Siehe: ''[[Strukturen in C]]'' <br />
<br />
= Funktionszeiger =<br />
<br />
: → Siehe: ''[[Funktionszeiger in C]]''<br />
<br />
= Header File - wie geht das =<br />
<br />
Ein Header File ist im Grunde nichts anderes als eine Sammlung aller Informationen, die ein 'Aussenstehender' benötigt, um die in einem C-File gesammlten Funktionen benutzen zu können.<br />
<br />
Trotzdem gibt es immer wieder Schwierigkeiten, wie sich die Sache mit Header Files und/oder #include verhält und wie man eine derartige Lösung aufbauen kann.<br />
<br />
Gegeben sei ein System bestehend aus 3 Funktionen<br />
* main()<br />
* functionA()<br />
* functionB()<br />
und einigen globalen Variablen. Für dieses System soll eine Aufteilung erfolgen, so dass funtionA samt seinen zugehörigen globalen Variablen in einer eigenen C-Datei residiert (FileA.c), functionB in einem eigenen File residiert (FileB.c) und main() als Hautpfunktion seine eigens C-File (Main.c) darstellt und jeweils die entsprechenden Header Files existieren.<br />
<br />
Kochrezeptartig kann man in vielen Fällen einfach so vorgehen:<br />
Man schreibt erst mal den C-Code der Funktion, die man implementieren möchte. Zb fängt man an mit FileA.c<br />
<syntaxhighlight lang="c"><br />
// FileA.c<br />
<br />
int VarExtA;<br />
int VarIntA;<br />
<br />
#define PortpinX PortA.1<br />
<br />
int functionIntA( void )<br />
{<br />
VarIntA = VarExtB;<br />
functionB();<br />
PortpinX = 1;<br />
};<br />
</syntaxhighlight><br />
<br />
Jetzt geht man das File, so wie es auch der Compiler macht, von oben nach unten durch schaut den Code durch. Damit functionB aufgerufen werden kann, ist ein Prototyp dafür notwendig. Der kommt aus einem Header File, welches noch nicht existiert, aber im Laufe des Prozesses entstehen und FileB.h heissen wird. FileB.h deshalb, weil die Funktion in FileB.c enthalten ist und man zur Vermeidung von Konfusion die Header Files immer gleich benennt wie die C-Files, nur eben mit einer anderen Dateiendung. FileB.h wird noch geschrieben werden, das hindert uns jetzt aber nicht daran, so zu tun als ob es dieses schon geben würde und ein entsprechender #include ergänzt. (Solange nicht compiliert wird ist das ja auch kein Problem. Irgendwo muss man ja schliesslich mal anfangen die Ergänzungen zu machen.)<br />
<br />
<syntaxhighlight lang="c"><br />
// FileA.c<br />
<br />
#include "FileB.h"<br />
<br />
int VarExtA;<br />
int VarIntA;<br />
<br />
#define PortpinX PortA.1<br />
<br />
int functionA( void )<br />
{<br />
VarIntA = VarExtB;<br />
functionB();<br />
PortpinX = 1;<br />
};<br />
</syntaxhighlight><br />
<br />
Gut. Die Variable VarExtB wird ebenfalls über den include hereingezogen<br />
werden. Damit ist FileA.c erst mal vollständig. Da fehlt jetzt erst mal nichts mehr. Alles was in FileA.c vorkommt sind entweder C-Schlüsselwörter, durch FileA.c selbst definiert oder kommt über den #include herein.<br />
<br />
Der nächste Schritt ist die Überlegung: Was von dem Zeugs in FileA.C soll von anderer Stelle (von anderen C-Files aus) benutzt und verwendet werden können.<br />
Welche Dinge muss FileA von sich preis geben.<br />
<br />
Da sind zu erst mal die beiden Variablen. Was soll mit denen geschehen?<br />
VarExtA soll von aussen zugreifbar sein, VarIntA nicht. Und dann<br />
natürlich die Funktion, die aufrufbar sein soll.<br />
<br />
Also beginnt man ein Header File für FileA.c zu schreiben, in das all das<br />
reinkommt, was FileA.c nach aussen sichtbar machen will.<br />
<br />
<syntaxhighlight lang="c"><br />
// FileA.h<br />
<br />
extern int VarExtA;<br />
<br />
int functionA( void );<br />
</syntaxhighlight><br />
<br />
Die Variable kriegt ein extern davor (und wird damit zu einer Deklaration), bei der Funktion wird einfach die Implementierung weggenommen und die somit zu einer Deklaration veränderte Zeile mit einem ; abgeschlossen. Wenn man möchte kann man den so geschaffenen Prototypen auch mit einem extern einleiten. Notwendig ist es aber nicht.<br />
<br />
Erneuter Blick aufs Header File. Kommt da irgendwas vor, was nicht<br />
Standard C Schlüsselwort ist? Nein. Nichts.<br />
Also ist dieses Header File somit ebenfalls in sich vollständig.<br />
<br />
Zur Sicherheit wird in FileA.c noch einen Include auf das<br />
eigene Header File hinzugefügt, denn dann kann der Compiler überprüfen ob die<br />
Angaben im Header File mit der tatsächlichen Implementierung<br />
übereinstimmen. Und da VarIntA von aussen nicht sichtbar sein soll (auch<br />
nicht durch Tricks), wird sie static gemacht.<br />
<br />
<syntaxhighlight lang="c"><br />
// FileA.c<br />
<br />
#include "FileA.h"<br />
#include "FileB.h"<br />
<br />
int VarExtA;<br />
static int VarIntA;<br />
<br />
#define PortpinX PortA.1<br />
<br />
int functionA( void )<br />
{<br />
VarIntA = VarExtB;<br />
functionB();<br />
PortpinX = 1;<br />
}<br />
</syntaxhighlight><br />
<br />
Dasselbe für FileB.c. Erst mal einfach runterschreiben<br />
<syntaxhighlight lang="c"><br />
// FileB.c<br />
<br />
int VarExtB;<br />
int VarIntB;<br />
<br />
#define SettingB 0x01<br />
<br />
int functionB( void )<br />
{<br />
VarExtA = 1;<br />
functionA();<br />
VarIntB = SettingB;<br />
<br />
MeinePrivateFunktion();<br />
}<br />
<br />
void MeinePrivateFunktion()<br />
{<br />
VarIntB = 8;<br />
}<br />
</syntaxhighlight><br />
<br />
damit functionA aufgerufen werden kann, braucht es wieder einen Prototypen. Den<br />
kriegt man über von FileA.h, welches daher includiert wird.<br />
<br />
<syntaxhighlight lang="c"><br />
// FileB.c<br />
<br />
#include "FileA.h"<br />
<br />
int VarExtB;<br />
int VarIntB;<br />
<br />
#define SettingB 0x01<br />
<br />
int functionB( void )<br />
{<br />
VarExtA = 1;<br />
functionA();<br />
VarIntB = SettingB;<br />
MeinePrivateFunktion();<br />
}<br />
<br />
void MeinePrivateFunktion()<br />
{<br />
VarIntB = 8;<br />
}<br />
</syntaxhighlight><br />
<br />
Was noch? VarExtA. Diese Variable wird aber ebenfalls durch den #include als extern Deklaration ins FileB.c hereingezogen und ist somit bei der Übersetzung von FileB.c bekannt. Kommt sonst noch etwas vor? MeinePrivateFunktion. Diese Funktion soll nur in FileB.c benutzt werden und ist für jemanden ausserhalb FileB.c völlig uninteressant. Nichts destotrotz gilt die Regel: compiliert wird von oben nach unten und verwendet werden kann nur etwas, was auch bekannt ist. D.h. bevor der Aufruf der Funktion in functionB gemacht werden kann, muss es einen Protoypen der Funktion geben. Da diese Funktion aber nicht nach 'aussen' exportiert wird, macht man den Protoypen gleich in das C-File mit hinein.<br />
<br />
<syntaxhighlight lang="c"><br />
// FileB.c<br />
<br />
#include "FileA.h"<br />
<br />
int VarExtB;<br />
int VarIntB;<br />
<br />
#define SettingB 0x01<br />
<br />
void MeinePrivateFunktion();<br />
<br />
int functionB( void )<br />
{<br />
VarExtA = 1;<br />
functionA();<br />
VarIntB = SettingB;<br />
MeinePrivateFunktion();<br />
}<br />
<br />
void MeinePrivateFunktion()<br />
{<br />
VarIntB = 8;<br />
}<br />
</syntaxhighlight><br />
<br />
Würde man die Funktion vorziehen, so dass der Funktionskörper vor der ersten Verwendung steht, dann würde man auch keinen Protoypen benötigen. Eine Funktionsdefinition fungiert als ihr eigener Protoyp.<br />
<br />
Kommt sonst noch etwas vor? Nix mehr. In FileB.c wird sonst nix mehr verwendet was nicht entweder C Schlüsselwort oder im File selber oder durch einen #include reinkommt.<br />
<br />
Dann wieder: das Header File für B schreiben. Dabei einfach nur überlegen: was soll von FileB.c nach aussen getragen werden?<br />
<br />
<syntaxhighlight lang="c"><br />
// FileB.h<br />
<br />
extern int VarExtB;<br />
<br />
int functionB( void );<br />
</syntaxhighlight><br />
<br />
und zur Sicherheit wieder ins eigene C-File includen und alle Variablen<br />
(oder auch Funktionen), die von aussen nicht sichtbar sein sollen, als<br />
static markieren.<br />
<br />
<syntaxhighlight lang="c"><br />
// FileB.c<br />
<br />
#include "FileB.h"<br />
#include "FileA.h"<br />
<br />
int VarExtB;<br />
static int VarIntB;<br />
<br />
#define SettingB 0x01<br />
<br />
static void MeinePrivateFunktion();<br />
<br />
int functionB( void )<br />
{<br />
VarExtA = 1;<br />
functionA();<br />
VarIntB = SettingB;<br />
MeinePrivateFunktion();<br />
}<br />
<br />
void MeinePrivateFunktion()<br />
{<br />
VarIntB = 8;<br />
}<br />
</syntaxhighlight><br />
<br />
damit bleibt nur noch main.c. Auch dieses wird erst mal einfach runtergeschrieben.<br />
<br />
<syntaxhighlight lang="c"><br />
int c;<br />
<br />
int main()<br />
{<br />
functionA();<br />
functionB();<br />
VarExtA = 1;<br />
VarExtB = c;<br />
}<br />
</syntaxhighlight><br />
<br />
Damit functionA aufgerufen werden kann, braucht es einen Prototypen. Woher kommt er? Aus FileA.h. Also gleich mal includen<br />
<syntaxhighlight lang="c"><br />
#include "FileA.h"<br />
<br />
int c;<br />
<br />
int main()<br />
{<br />
functionA();<br />
functionB();<br />
VarExtA = 1;<br />
VarExtB = c;<br />
}<br />
</syntaxhighlight><br />
<br />
Damit functionB aufgerufen werden kann, braucht es einen Prototypen. Wo<br />
kommt der her? Aus FileB.h. Also noch ein #include<br />
<syntaxhighlight lang="c"><br />
#include "FileA.h"<br />
#include "FileB.h"<br />
<br />
int c;<br />
<br />
int main()<br />
{<br />
functionA();<br />
functionB();<br />
VarExtA = 1;<br />
VarExtB = c;<br />
}<br />
</syntaxhighlight><br />
<br />
Fehlt noch was? VarExtA ist durch den #include von FileA.h bereits<br />
abgedeckt und VarExtB ist durch den #include von FileB.h bereits<br />
abgedeckt. Also fehlt nix mehr. Auch in main.c sind damit alle Sachen<br />
abgedeckt und es ist in sich vollständig.<br />
<br />
Das ist der Standardmechanismus:<br />
* Implementierung der Funktionen im C File schreiben<br />
* Überlegen, was von dieser Implementierung von aussen sichtbar sein soll.<br />
* Dasjenige kommt als Deklaration ins Header File, alles andere am besten static machen.<br />
* Für alles im C-File, das seinerseits woanders herkommt, gibt es einen #include, der das jeweils notwendige Header File einbindet.<br />
* Werden im Header File Dinge von woanders benutzt, dann enthält das Header File einen entsprechenden #include<br />
* Jedes File, sowohl Header-File als auch C-File ist in sich vollständig. Werden dort Dinge benutzt, dann müssen diese vor der Verwendung deklariert worden sein. Wie und wo diese Deklaration herkommt, ist dabei zweitrangig. Es kann sein, dass die Deklaration vor der Verwendung steht, es kann aber auch sein, dass die Deklaration über einen weiteren Include mit aufgenommen wird.<br />
<br />
= Ich hab da mehrere *.c und *.h Dateien. Was mache ich damit? =<br />
<br />
[[Bild:c-flow.svg|right|thumb|260px|C-Programmierung: Workflow]]<br />
Zunächst ist es wichtig, sich zu vergegenwärtigen, wie C-Compiler und Linker zusammenarbeiten. Ein komplettes Programmier-Projekt kann und wird im Normalfall aus mehreren Quelldateien bestehen, die alle zusammengenommen das komplette Programm bilden.<br />
<br />
Der Prozess des Erstellens des Programmes geschieht in mehrerern Schritten:<br />
;Compilieren: zunächst werden alle Einzelteile (jede *.c Datei) für sich ''compiliert''. Dabei ensteht aus jeder c-Datei eine sogenannte Object-Datei, in der bereits der Maschinencode für die im c-File programmierten Funktionen enthalten ist<br />
;Linken: die einzelnen Object-Dateien werden mit zusätzlichen Bibliotheken und dem Startup-Code zum fertigen Programm ''gelinkt''.<br />
{{Absatz}}<br />
Angenommen, das komplette Projekt besteht aus 2 Dateien:<br />
<br />
;main.c:<br />
<syntaxhighlight lang="c"><br />
int twice (int);<br />
<br />
int main()<br />
{<br />
twice (5);<br />
}<br />
</syntaxhighlight><br />
<br />
;func.c:<br />
<syntaxhighlight lang="c"><br />
int twice (int number)<br />
{<br />
return 2 * number;<br />
}<br />
</syntaxhighlight><br />
<br />
Dann werden <tt>main.c</tt> und <tt>func.c</tt> ''unabhängig'' voneinander compiliert. Als Ergebnis erhält man die Dateien <tt>main.o</tt> und <tt>func.o</tt>, die den besagten Object-Code enthalten.<br />
Diese beiden Zwischenergebnisse werden dann zusammen mit Bibliotheken zum fertigen Programm gebunden (gelinkt), das dann ausgeführt werden kann.<br />
<br />
Bekommt man also von irgendwo bereits fertige *.c (und zugehörige *.h) Dateien, so genügt es, die *.c Dateien in das Projekt mit aufzunehmen. Dadurch wird das entsprechende C-File compiliert und das Ergebnis davon, das Object-file, wird dann in das fertige Programm mit eingelinkt.<br />
<br />
;Achtung: Da jede der C-Dateien unabhängig von allen anderen compiliert wird, bedeutet das auch, dass jede der C-Dateien in sich vollständig sein muss!<br />
<br />
Wie eine C-Datei in das Projekt mit aufgenommen wird, hängt im wesentlichen von der benutzten Entwicklungsumgebung ab.<br />
<br />
== Makefile ==<br />
<br />
Die zusätzliche *.c Datei wird in die SRC Zeile im makefile eingetragen.<br />
<br />
== AVR-Studio ==<br />
<br />
Hier ist es besonders einfach, eine Datei in das Projekt mit aufzunehmen. Dazu wird im Projektbaum der Knoten "Source Files" aktiviert und mit der rechten Maustaste das Kontextmenü geöffnet. Im Menü wird der Punkt "Add existing Source File(s)" ausgewählt, und anschliessend zeigt man AVR-Studio das zusätzliche C-File. AVR-Studio berücksicht dann diese Datei bei der Projekterzeugung, compiliert es und sorgt dafür, dass es zum fertigen Programm dazugelinkt wird.<br />
<br />
=Globale Variablen über mehrere Dateien=<br />
Ein häufiger Problemkreis in der C Programmierung sind auch globale Variablen, die von mehreren *.c Dateien aus benutzt werden sollen. Was hat es damit auf sich?<br />
<br />
Zunächst mal muß man bei der Vereinbarung von Variablen zwischen ''Definition'' und ''Deklaration'' unterscheiden:<br />
;Definition: Mit einer Definition wird der Compiler angewiesen, eine Variable tatsächlich zu erzeugen. Damit er das kann, muß ihm selbstverständlich der exakte Datentyp und auch der Name der Variablen zur Verfügung stehen. Eine Definition sorgt also dafür, dass im späteren Programm Speicherplatz für diese Variable reserviert wird<br />
;Deklaration: Mit einer Deklaration teilt man dem Compiler lediglich mit, dass eine Variable existiert. An dieser Stelle soll der Compiler also keinen Speicherplatz reservieren, sondern der Compiler soll einfach nur zur Kenntniss nehmen, daß es eine Variable mit einem bestimmten Namen gibt und von welchem Datentyp sie ist.<br />
<br />
Aus obigem folgt sofort, dass eine Definition auch immer eine Deklaration ist. Denn dadurch, daß der Compiler angewiesen wird eine Variable auch tatsächlich zu erzeugen, folgt, dass er dazu auch dieselben Informationen benötigt, die auch in einer Deklaration angegeben werden müssen. Der einzige Unterschied: Bei einer Deklaration trägt der Compiler nur in seinen internen Tabellen ein, dass es diese Variable tatsächlich gibt, während er bei einer Definition zusätzlich auch noch dafür sorgt, dass im fertigen Programm auch Speicher für diese Variable bereitgestellt wird.<br />
<br />
Warum ist diese Unterscheidung wichtig?<br />
<br />
Weil es in C die sog. ''One Definition Rule'' (ODR). Sie besagt, dass in einem vollständigen Programm, also über alle *.c Dateien gesehen, es für eine Variable nur <b>eine</b> Definition geben darf. Es darf allerdings beliebig viele Deklarationen geben, solange diese Deklarationen alle im Datentyp übereinstimmen. Kurz gesagt: Man darf den Compiler nur einmal auffordern, eine Variable zu erzeugen (Definition), kann sich aber beliebig oft auf diese eine Variable beziehen (Deklarationen). Aber Vorsicht! Da der Compiler jede einzelne *.c Datei für sich alleine übersetzt und dabei kein Wissen von ausserhalb benutzt, obliegt es der Verantwortung des Programmierers dafür zu sorgen, dass alle Deklarationen im Datentyp übereinstimmen. Der Compiler kann diese Einhaltung prinzipbedingt nicht überwachen!<br />
<br />
Woran erkennt man eine Definition bzw. Deklaration?<br />
<br />
Eine Definition einer globalen Variable steht immer ausserhalb eines Funktionsblocks. Zb.<br />
<syntaxhighlight lang="c"><br />
int MyData; // Globale Variable namens MyData. Sie ist vom Typ int<br />
char Name[30]; // Globales Array<br />
long NrElements = 5; // Globale Variable, die auch noch initialisiert wird<br />
</syntaxhighlight><br />
<br />
Eine Deklaration unterscheidet sich von einer Definition in 2 Punkten<br />
* Es wird das Schlüsselwort <tt>extern</tt> vorangestellt.<br />
* Es kann keine Initialisierung geben. Sobald eine Initialisierung vorhanden ist, wird das Schlüsselwort <tt>extern</tt> ignoriert und aus der Deklaration wird eine Definition.<br />
<br />
Beispiele für Deklarationen<br />
<syntaxhighlight lang="c"><br />
extern int MyData;<br />
extern char Name[30];<br />
extern long NrElements;<br />
extern long NrElements = 5; // Achtung: Dies ist eine Definition!<br />
</syntaxhighlight><br />
<br />
Besitzt man also 2 *.c Dateien, main.c und helpers.c, und sollen sich diese beiden Dateien eine globale Variable teilen, so muss in eine Datei eine Definition hinein, während in die andere Datei eine Deklaration derselben Variablen erfolgen muß. Traditionell werden die Definitionen in der *.c-Datei gemacht, die als Hauptdatei des Moduls fungiert, zu der diese Variable konzeptionell gehört. Im Zweifel ist das die *.c Datei, in der main() enthalten ist. Das muss nicht so sein, ist aber eine Konvention, die oft Sinn macht. Alternativ wird auch gerne oft eine eigene *.c Datei (zb. globals.c) gemacht, die einzig und alleine die Defintionen der globalen Variablen enthält.<br />
<br />
main.c<br />
<syntaxhighlight lang="c"><br />
int AnzahlElemente; // Dies ist die Definition. Hier wird die globale<br />
// Variable AnzahlElemente tatsächlich erzeugt.<br />
<br />
int main()<br />
{<br />
AnzahlElemente = 8;<br />
...<br />
}<br />
</syntaxhighlight><br />
<br />
helpers.c<br />
<syntaxhighlight lang="c"><br />
extern int AnzahlElemente; // Dies ist die Deklaration die auf die globale<br />
// Variable AnzahlElemente in main.c verweist.<br />
// Wichtig: Der Datentyp muss mit dem in main.c<br />
// angegebenen übereinstimmen<br />
<br />
void foo()<br />
{<br />
...<br />
j = AnzahlElemente;<br />
AnzahlElemente = 9;<br />
}<br />
</syntaxhighlight><br />
<br />
==Praktische Durchführung==<br />
Besteht ein vollständiges Programm aus mehreren *.c Dateien, dann kann man sich vorstellen, daß es mühsam ist, alle Deklarationen immer auf gleich zu halten. Hier bietet sich der Einsatz eines Header Files an, in der die Deklarationen stehen und welches in die jeweiligen *.c Dateien inkludiert wird<br />
<br />
Bsp:<br />
<br />
Global.h<br />
<syntaxhighlight lang="c"><br />
extern int Anzahl;<br />
</syntaxhighlight><br />
<br />
main.c<br />
<syntaxhighlight lang="c"><br />
#include "Global.h"<br />
<br />
int Anzahl; // auch wenn Global.h inkludiert wurde, so muss es eine<br />
// Definition der Variablen geben. In Global.h sind ja nur<br />
// Deklarationen.<br />
<br />
int main()<br />
{<br />
...<br />
Anzahl = 5;<br />
}<br />
</syntaxhighlight><br />
<br />
foo.c<br />
<syntaxhighlight lang="c"><br />
#include "Global.h"<br />
<br />
void foo()<br />
{<br />
...<br />
Anzahl = 8;<br />
...<br />
}<br />
</syntaxhighlight><br />
<br />
bar.c<br />
<syntaxhighlight lang="c"><br />
#include "Global.h"<br />
<br />
void bar()<br />
{<br />
...<br />
j = Anzahl;<br />
}<br />
</syntaxhighlight><br />
<br />
Auf diese Art kann man erreichen, dass zumindest alle Deklarationen ein und derselben Variablen in einem Programm übereinstimmen. Die Datei Global.h wird auch in main.c inkludiert, obwohl man das eigentlich nicht müsste, denn dort wird die Variable ja definiert. Durch die Inclusion ermöglicht man aber dem Compiler die Überprüfung ob die Deklaration auch tatsächlich mit der Definition übereinstimmt.<br />
<br />
Solange kein Initialisierungen der globalen Variablen notwendig sind, gibt es noch einen weiteren Trick, um sich selbst das Leben und die Verwaltung der globalen Variablen zu erleichtern.<br />
Worin besteht das Problem?<br />
Das Problem besteht darin, dass man bei Einführung einer neuen globalen Variablen an 2 Stellen erweitern muss: Zum einen in der Header-Datei, die die 'extern'-Deklaration der Variablen enthält, zum anderen muss in einer C-Datei die Definition der Variablen erfolgen. Das kann man sich mit etwas Präprozessorarbeit auch einfacher machen:<br />
<br />
Global.h<br />
<syntaxhighlight lang="c"><br />
#ifndef EXTERN<br />
#define EXTERN extern<br />
#endif<br />
<br />
EXTERN int Anzahl;<br />
</syntaxhighlight><br />
<br />
main.c<br />
<syntaxhighlight lang="c"><br />
#define EXTERN<br />
#include "Global.h"<br />
<br />
int main()<br />
{<br />
...<br />
Anzahl = 5;<br />
}<br />
</syntaxhighlight><br />
<br />
foo.c<br />
<syntaxhighlight lang="c"><br />
#include "Global.h"<br />
<br />
void foo()<br />
{<br />
...<br />
Anzahl = 8;<br />
...<br />
}<br />
</syntaxhighlight><br />
<br />
Wie funktioniert das Ganze? Im Grunde muss man nur dafür sorgen, dass der Compiler an ''einer'' Stelle das Schlüsselwort '''extern''' ignoriert (hier in main.c) und bei allen anderen Inclusionen beibehält. Dadurch das ein Präprozessor-ifndef benutzt wird, kann dieses erreicht werden. Wird das Header File includiert und ist zu diesem Zeitpunkt das Makro '''EXTERN''' noch nicht definiert, so wird innerhalb des Header Files '''EXTERN''' zu '''extern''' definiert und damit in weiterer Folge im Quelltext '''EXTERN''' durch '''extern''' ersetzt. Wenn daher foo.c das Header File inkludiert, wird die Zeile<br />
<syntaxhighlight lang="c"><br />
EXTERN int Anzahl;<br />
</syntaxhighlight><br />
vom Präprozessor zu<br />
<syntaxhighlight lang="c"><br />
extern int Anzahl;<br />
</syntaxhighlight><br />
umgewandelt.<br />
<br />
In main.c hingegen sieht die Include-Sequenz so aus<br />
<syntaxhighlight lang="c"><br />
#define EXTERN<br />
#include "Global.h"<br />
</syntaxhighlight><br />
Wenn Global.h bearbeitet wird, existiert bereits ein Makro '''EXTERN''', das auf einen leeren Text expandiert. Dadurch wird verhindert, dass innerhalb von Global.h das Makro '''EXTERN''' mit dem Text '''extern''' belegt wird und<br />
<syntaxhighlight lang="c"><br />
EXTERN int Anzahl;<br />
</syntaxhighlight><br />
wird daher vom Präprozessor zu<br />
<syntaxhighlight lang="c"><br />
int Anzahl;<br />
</syntaxhighlight><br />
erweitert, genau wie es benötigt wird.<br />
<br />
= Was hat es mit volatile auf sich =<br />
Immer wieder hört man im Forum die pauschale Aussage "Variablen die in einer ISR verwendet werden, müssen volatile sein". Nun, das ist so nicht ganz richtig.<br />
Welches Problem löst denn eigentlich volatile? Was ist denn das eigentliche Problem, das einer Lösung bedarf?<br />
<br />
Das Problem findet sich diesmal im Optimierer eines C Compilers. C Compiler übersetzen, wenn sie optimieren dürfen, den C Code nicht direkt so, wie ihn der Programmierer geschrieben hat, sondern sie versuchen Ressourcen einzusparen. Das kann sowohl Programmspeicher als auch Laufzeit, häufig auch beides gemeinsam sein. Zu diesem Zweck untersuchen sie das Programm und versuchen in der funktional gleichwertigen, in Maschinensprache übersetzen Version, Anweisungen einzusparen. Das dürfen sie auch. Der C-Standard erlaubt Optimierungen, solange die 'As-If'-Regel eingehalten wird. Das bedeutet: Der Compiler darf das Programm umstellen und verändern, solange die Programmergebnisse dieselben bleiben. Eben "As-if" die Optimierung nie stattgefunden hätte.<br />
<br />
Nehmen wir ein Beispiel<br />
<syntaxhighlight lang="c"><br />
i = 2;<br />
<br />
if( i == 5 )<br />
j = 8;<br />
else<br />
j = 6;<br />
</syntaxhighlight><br />
In diesem Programmausschnitt darf der Compiler seine Kenntnisse ausnutzen. Er weiß an dieser Stelle, dass i den Wert 2 hat. Damit ist aber auch klar, dass die Bedingung niemals wahr sein kann, denn 2 kann niemals gleich 5 sein. Wenn die Bedingung aber niemals wahr sein kann, dann kann auch die Zuweisung von 8 an j niemals ausgeführt werden. Der Compiler kann also diesen Programmtext zu diesem hier kürzen<br />
<syntaxhighlight lang="c"><br />
i = 2;<br />
j = 6;<br />
</syntaxhighlight><br />
ohne das sich an den Programmergebnissen etwas ändert. Die zweite Version ist aber kürzer und wird, wegen des Wegfalles des Vergleiches, auch schneller ausgeführt.<br />
<br />
Eine andere Form der Optimierung betrifft die Verwaltung von µC-Ressourcen und da wieder ganz speziell die Register. Variablen werden ja erst mal im SRAM-Speicher des µC angelegt. Um mit den Werten von Variablen arbeiten zu können, müssen diese Wert aber vom SRAM-Speicher in µC-Register überführt werden (Register kann man sich wie Speicherstellen in der eigentlichen CPU vorstellen). Nur dort können diese Werte mittels Maschinenbefehlen manipuliert werden.<br />
Jetzt haben aber µC nicht beliebig viele Register. Das bedeutet aber auch, der Compiler muss darüber Buch führen, welche Werte (welche Variablen) gerade in welchen Registern liegen und wenn alle Register belegt sind, muss ein anderes Register freigeräumt werden, in dem der Wert aus dem Register wieder ins SRAM zurück übertragen wird.<br />
Allerdings kostet das auch Zeit. Der Compiler wird daher versuchen, Variablen, die in einem Programmstück oft benötigt werden, für längere Zeit in den Registern zu halten, um das Registerladen bzw. -zurückschreiben einzusparen. Die Grundannahme lautet dabei immer: In Anweisungen, in denen eine Variable nicht vorkommt, kann diese Variable auch nicht verändert werden. Im Programmstück<br />
<br />
<syntaxhighlight lang="c"><br />
while( 1 ) {<br />
if( i == 5 )<br />
j = 8;<br />
}<br />
</syntaxhighlight><br />
gibt es für i keine Möglichkeit, verändert zu werden. Der Compiler kann daher entscheiden, dass er diese Variable, *an dieser Stelle*, gar nicht aus dem SRAM laden muss, sondern sich den entsprechenden Wert in einem Register vorhält und dieses Register ausschließlich dafür reserviert. (Er könnte auch entscheiden, dass der Code nie ausgeführt werden kann, aber das ist eine andere Geschichte)<br />
<br />
Der springende Punkt ist nun, dass der Compiler hier eine zu kleine Sicht der Dinge hat. Betrachtet man nur dieses Code Stück, dann gibt es tatsächlich für i keine Möglichkeit, seinen Wert zu verändern. Aber die Dinge ändern sich, wenn Interrupts ins Spiel kommen.<br />
<br />
<syntaxhighlight lang="c"><br />
uint8_t i;<br />
<br />
ISR( irgendein_Interrupt )<br />
{<br />
i = 5;<br />
}<br />
<br />
int main()<br />
{<br />
...<br />
<br />
while( 1 ) {<br />
if( i == 5 )<br />
j = 8;<br />
}<br />
}<br />
</syntaxhighlight><br />
Jetzt gibt es plötzlich eine Möglichkeit, wie i seinen Wert ändern kann: Wenn der entsprechende Interrupt ausgelöst wird, dann wird i auf den Wert 5 gesetzt. i, das ist aber nichts anderes als ein bestimmter Speicherbereich im SRAM. D.h. im SRAM wird die Variable tatsächlich korrekt auf den Wert 5 gesetzt. Nur: Als der Compiler die while-Schleife übersetzt hat, wusste er nichts davon, dass diese Möglichkeit existiert. Er hat entschieden, dass er an dieser Stelle den Wert der Variablen in einem CPU-Register halten wird, um Zugriffe einzusparen. Nur wird diese Kopie des Wertes im Register natürlich nicht verändert, wenn in der ISR das Original von i im SRAM verändert wird.<br />
Fazit: Obwohl die ISR die Variable tatsächlich verändert, kriegt das der Code im while nicht mit, weil der Compiler es mit der Optimierung an dieser Stelle übertrieben hat. In der while-Schleife wird mit einer Kopie des Wertes von i in einem Register gearbeitet und nicht mit dem originalen Wert von i im SRAM.<br />
<br />
Und an dieser Stelle kommt jetzt volatile ins Spiel.<br />
<br />
volatile teilt dem Compiler mit, dass ausnahmslos alle Zugriffe auf eine Variable auch tatsächlich auszuführen sind und keine Optimierungen gemacht werden dürfen, weil eine Variable auf Wegen benutzt werden kann, die für den Compiler prinzipiell nicht einsichtig sind. Im obigen Beispiel könnte man argumentieren, dass der Compiler ja wohl die ISR bemerken könne und daher feststellen könnte, dass i tatsächlich verändert wird. Aber das stimmt so in der allgemeinen Form nicht. Niemand sagt, dass der Compiler die ISR überhaupt zu Gesicht bekommen muss, die könnte ja auch in einem ganz anderen C File stecken.<br />
<br />
<syntaxhighlight lang="c"><br />
volatile uint8_t i;<br />
<br />
ISR( irgendein_Interrupt )<br />
{<br />
i = 5;<br />
}<br />
<br />
int main()<br />
{<br />
...<br />
<br />
while( 1 ) {<br />
if( i == 5 )<br />
j = 8;<br />
}<br />
}<br />
</syntaxhighlight><br />
wird i volatile gemacht, so verbietet man damit dem Compiler explizit, Annahmen über den Datenfluss von i zu treffen. Innerhalb der Schleife muss also tatsächlich jedes Mal wieder erneut i aus dem SRAM geholt werden und mit 5 verglichen werden. Abkürzungen durch Mehrfachverwendung von Registern oder sonstigen Optimierungstricks sind nicht erlaubt. Und damit ist das Problem gelöst. Wird i in der ISR verändert, so bekommt das auch die Abfrage mit, weil ja jetzt auf jeden Fall auf das Original im SRAM zurückgegriffen wird.<br />
<br />
Das Gleiche gilt auch ebenso "in die andere Richtung", wenn also i in der Schleife geändert und in der ISR nur gelesen wird. Auch hier könnte die Optimierung negativ zuschlagen und den Schreibzugriff nur auf eine lokale Kopie der Variable in einem Register durchführen (oder gar ganz wegfallen) lassen, weil der Lesezugriff außerhalb des direkten Programmflusses (in der ISR) für den Compiler nicht ersichtlich ist.<br />
<br />
Ein anderes Problem (das ebenfalls mittels volatile gelöst wird) sind Variablen, die tatsächlich im Code überhaupt nie aktiv verändert werden, sondern es sich um Zusatzhardware handelt, die so verschaltet ist, dass sie im Programm in Form einer Variablen auftaucht, z.B. ein Uhren-IC (oder auch ganz banal: Portpins). In diesem Fall wird z.B. die Variable für Sekunden vom Programm gar nicht vom Programm selber verändert, ändert aber trotzdem ihren Inhalt. Die Zusatzhardware selbst macht das. Aus Programmsicht handelt es sich um Speicherzellen, die magisch selbsttätig ihren Wert ändern. Und damit dürfen selbstverständlich auch hier keinerlei Annahmen über den Inhalt der Variablen getroffen werden. Eine derart angebundene externe Hardware nennt man übrigens "memory-mapped", weil sie ihre Werte ins Memory (=Hauptspeicher) mapped (=einblendet).<br />
<br />
Allerdings kann volatile nur bei den Variablen sinnvoll genutzt werden, die "von außen" auch änderbar sind. Bei lokalen Variablen, auch statischen, einer Funktion kann das nur passieren, wenn ihre Adresse einer ISR z.B. durch einen globalen Pointer bekannt gemacht wird.<br />
<syntaxhighlight lang="c"><br />
uint8_t *v;<br />
ISR( irgendein_Interrupt )<br />
{<br />
i = 5;<br />
*v = 42;<br />
}<br />
<br />
int main()<br />
{<br />
uint8_t i; // kann sich nie unerwartet ändern -> volatile nutzlos, behindert nur Optimizer<br />
<br />
volatile uint8_t j; // kann sich unerwartet ändern (über globalen *v)<br />
v = &j;<br />
...<br />
while( 1 ) {<br />
if( i == 5 )<br />
j = 8;<br />
if( j == 5 )<br />
i = 3;<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
= Konstanten an fester Flash-Adresse =<br />
<br />
Wie kann man eine Konstante an entsprechender Adresse im Flash ablegen?<br />
<br />
Mehmet Kendi hat eine Lösung für [[AVR Studio]] & [[WinAVR]] in <br />
[http://www.mikrocontroller.net/topic/142704#1453079] angegeben.<br />
<br />
= Timer =<br />
== Was macht ein Timer? ==<br />
Oft hört man im Forum die Aussage: Timer sind so kompliziert!<br />
<br />
Aber eigentlich stimmt das nicht. Ganz im Gegenteil, Timer sind eigentlich eine sehr einfache Sache. Was genau macht eigentlich ein Timer? Die Antwort lautet: er zählt unabhängig vom restlichen Programmfluss vor sich hin. Und? Was macht er noch? Nichts. Das wars schon. Im Kern ist genau das auch schon alles was ein Timer macht.<br />
[[Bild:Timer_Basis.gif|framed|center|ein 8-Bit Timer bei der Arbeit]]<br />
<br />
== Wie schnell macht er es? ==<br />
Aber so einfach ist die Sache dann doch wieder nicht. Da erhebt sich zunächst mal die Frage: wie schnell zählt denn eigentlich so ein Timer? Normalerweise ist der Timer mit der Taktfrequenz des Prozessors gekoppelt, so dass zb bei einer Taktfrequnz von 1Mhz der Timer auch genau so schnell zählt. In 1 Sekunde zählt ein Timer also von 0 bis 1000000, also 1 Mio Zählschritte. Nun kann aber ein beispielsweise 8-Bit Timer nicht bis 1000000 zählen, dazu ist er nicht groß genug. Mit 8 Bit kann man bis 255 zählen. Zählt man da dann noch 1 dazu, dann läuft der Timer über und beginnt wieder bei 0. Man kann daher ruhigen Gewissens sagen: In 1 Sekunde zählt dieser Timer 3906 mal den Bereich von 0 bis 255 (und weiter auf 0) durch (das sind 256 Zählschritte) und zuätzlich schafft er es danach noch bis 64 zu zählen. Denn 3906 * 256 + 64 = 1000000 und wir haben wieder die 1 Mio Zählschritte, die der Timer in 1 Sekunde erledigt.<br />
<br />
Das ist ganz schön schnell. Und weil das oft zu schnell ist, hat jeder Timer noch die Möglichkeit sogenannte Vorteiler (Prescaler) vor den Zähltakt zu schalten. Zb einen Vorteiler von 8. Anstelle von 1Mhz bekommt der Timer dann eine Frequenz von 1Mhz / 8 (= 125kHz) präsentiert. Und dementsprechend würde er in 1 Sekunde dann nur noch von 0 bis 125000 (= 1.000.000 / 8) zählen. Als 8 Bit Timer bedeutet das, dass er in 1 Sekunde jetzt nur noch 488 komplette Zyklen 0 bis 255 schafft und dann noch bis 72 zählen kann. Denn 488 * 256 + 72 = 125000<br />
<br />
== Das kann aber nicht alles gewesen sein? ==<br />
Bis jetzt ist das alles noch unspektakulär und man fragt sich: Was hab ich jetzt davon, wenn der Timer vor sich hinzählt? Nun, die Situation ändert sich, wenn man weiß, dass man bei bestimmten Ereignissen und/oder Zählerständen etwas auslösen lassen kann. So ist zb. dieser Überlauf von 255 auf 0 so ein Ereignis. Mittels eines Interrupts kann man auf dieses Ereignis reagieren lassen und als Folge davon wird eine Funktion vollautomatisch aufgerufen. Und zwar unabhängig davon, was der µC gerade sonst so tut. Und das ist schon recht cool, denn es bedeutet, dass man regelmäßig zu erfolgende Dinge in so eine ISR (Interrupt Service Routine, die Funktion die aufgerufen wird) stecken kann und der Timer sorgt ganz von alleine dafür, dass diese Funktionalität auch tatsächlich regelmäßig ausgeführt wird. Regelmäßig bedeutet in diesem Fall dann auch wirklich regelmäßig. Denn der Timer zählt ja losgelöst von den restlichen Arbeiten, die der µC sonst so erledigt, vor sich hin. Es spielt keine Rolle, ob der µC gerade mitten in einer komplizierten Berechnung steckt oder nicht. Der Timer zählt vor sich hin, und wenn das entsprechende Ereignis eintritt, wird der Interrupt ausgelöst. Und wenn der entsprechende Interrupt mit einer ISR-Funktion gekoppelt ist, dann wird der normale Programmfluss unterbrochen und der µC arbeitet genau diese eine Funktion ab. Und zwar in regelmässigen Zeitabständen, weil ja auch der Interrupt regelmässig auftritt.<br />
[[Bild:Timer_ISR.gif|framed|center|ein 8-Bit Timer löst durch seinen Overflow regelmäßige ISR Aufrufe aus]]<br />
Gut, beispielsweise 488 mal in der Sekunde mag für so manchen Zweck zu oft sein, aber es gibt ja auch noch andere Vorteiler (welche steht im Datenblatt) und dann kann man ja auch innerhalb der ISR in einer lokalen Variablen mitzählen und zb nur bei jedem 2.ten Aufruf eine Aktion machen, die dann nur noch 244 mal in der Sekunde ausgeführt wird. Hier gibt es also mehrere Möglichkeiten, wie man die Aufrufhäufigkeit weiter herunterteilen kann, so dass man sich der Zahl annähert, die man benötigt.<br />
<br />
<syntaxhighlight lang="c"><br />
// Für einen Mega16.<br />
// Andere Prozessoren: siehe Datenblatt wie die Timerkonfiguration einzustellen ist<br />
<br />
#define F_CPU 1000000UL<br />
#include <avr/io.h><br />
#include <avr/interrupt.h><br />
<br />
//<br />
// der Timer wird mit 1Mhz getaktet. Vorteiler ist 8<br />
// d.h. der Timer läuft mit 125kHz und würde daher in 1 Sekunde<br />
// von 0 bis 124999 zaehlen.<br />
// Aber nach jeweils 256 Zaehlungen erfolgt ein Overflow.<br />
// Daher werden in 1 Sekunde 125000 / 256 = 488.28125 Overflows erzeugt<br />
// Oder anders ausgedrückt: 1 / 488.2815 = 0.002048<br />
// alle 0.002048 Sekunden erfolgt ein Overflow<br />
//<br />
ISR( TIMER0_OVF_vect ) // Overflow Interrupt Vector<br />
{<br />
static uint8_t swTeiler = 0;<br />
<br />
swTeiler++;<br />
if( swTeiler == 200 ) { // nur bei jedem 200.ten Aufruf. Effektiv teilt dieses die<br />
// die Aufruffrequenz des nachfolgenden Codes nochmal um<br />
swTeiler = 0; // einen Faktor 200. Der nachfolgende Code wird daher nicht<br />
// alle 0.002 Sekunden sondern alle 0.4096 Sekunden ausgeführt.<br />
// Das reicht, dass man eine LED am Port schon blinken sieht.<br />
PORTD = PORTD ^ 0xFF; // alle Bits am Port umdrehen, einfach damit sich was tut<br />
}<br />
}<br />
<br />
int main()<br />
{<br />
DDRD = 0xFF; // irgendein Port, damit wir auch was sehen<br />
<br />
TIMSK |= (1<<TOIE0); // den Overflow Interrupt des Timers freigeben<br />
TCCR0 = (1<<CS01); // Vorteiler 8, jetzt zählt der Timer bereits<br />
<br />
<br />
sei(); // und Interrupts generell freigeben<br />
<br />
<br />
while( 1 )<br />
{ // hier braucht nichts mehr gemacht werden.<br />
} // der Timer selbst sorgt dafür, dass die ISR Funktion<br />
} // regelmäßig aufgerufen wird<br />
</syntaxhighlight><br />
<br />
== CTC Modus ==<br />
Manchmal reicht das aber nicht. Benötigt man zb nicht 488 sondern möglichst genau 500 ISR Aufrufe in der Sekunde, so wird man weder mit Vorteiler noch durch Softwaremässiges Weiterteilen in der ISR zum Ziel kommen. Man kann natürlich die Taktfrequenz des kompletten Systems soweit umstellen, dass sich das alles ausgeht, aber oft ist das einfach nicht möglich. Was tun?<br />
<br />
Die Sache wäre einfacher, wenn man dem Timer vorschreiben könnte, nicht einfach nur von 0 bis 255 zu zählen, sondern wenn man ihm eine Obergrenze vorgeben könnte. Denn dann könnte man sich eine Obergrenze so bestimmen, dass dieser neue Zählbereich in 1 Sekunde ganz genau so oft durchlaufen werden kann, wie man es benötigt. Und hier kommt der sog. <b>CTC</b> Modus ins Spiel. Denn genau darin besteht sein Wesen: Man gibt dem Timer eine Obergrenze vor. Erreicht seine Zählung diesen Wert, so wird der Timer auf 0 zurückgesetzt und beginnt wieder von vorne. Genau das was wir benötigen. Wollen wir exakt 500 ISR Ausfrufe in der Sekunde haben (bei 1 Mhz Systemtakt), dann wählen wir einen Vorteiler von 8 und setzen die Obergrenze auf 250-1 (nicht vergessen: wir brauchen 250 Zählschritte, das bedeutet der Timer muss von 0 bis 249 zählen, denn auch der Überlauf von 249 zurück auf 0 ist ein Zählschritt). Der Timer taktet dann mit 1Mhz / 8 = 125kHz und da nach jeweils 250 Zählschritten die Obergrenze erreicht ist, wird diese Obergrenze in 1 Sekunde 125000 / 250 = 500 mal erreicht. Genau so wie wir das wollten.<br />
Wie wird dem Timer nun mitgeteilt, dass er eine spezielle Obergrenze benutzen soll? Nun, jeder Timer hat verschiedene Modi. Welche das bei einem konkreten µC und bei einem konkreten Timer genau sind, findet sich im Datenblatt im Abschnitt über die Timer. Normalerweise ist immer eines der letzteren Abschnitte eines jeden Kapitels im Datenblatt das interessantere: "Register Summary" oder "Register Description" genannt (die Datenblätter sind da nicht ganz einheitlich). So auch hier.<br />
[[Bild:FAQ_Datenblatt_Timer_Mega16.png|framed|center|Auszug aus dem Atmel Datenblatt für den Mega16 - Suche im Datenblatt]]<br />
In jedem Atmel Datenblatt findet sich bei jedem Timer immer auch besagter Abschnitt (im Inhaltsverzeichnis beim Kapitel über den jeweiligen Timer suchen. Im Datenblatt-PDF daher immer das Inhaltsverzeichnis anzeigen lassen!), und in diesem Abschnitt gibt es eine Tabelle, aus der hervorgeht, welche Modi es gibt, welche Bits dazu in den Konfigurationsregistern gesetzt werden müssen, wie sich dann die Obergrenze des Timer-Zählbereichs zusammensetzt und noch ein paar Angaben mehr. Beim Mega16 findet sich diese Tabelle für den Timer 0 zb auf Seite 83 und dort ist es die Tabelle 14.2. Diese Tabelle sieht im Datenblatt so aus<br />
<br />
<center><br />
{| {{Tabelle}} <br />
|- style="background-color:#ffddcc"<br />
! Mode || WGM01 || WGM00 || Timer/Counter || TOP || Update of || TOV0 Flag<br />
|- style="background-color:#ffddcc"<br />
! || (CTC0) || (PWM0) || Mode of Operation || || OCR0 || Set-on<br />
|-<br />
| 0 || 0 || 0 || Normal || 0xFF || Immediate || MAX<br />
|-<br />
| 1 || 0 || 1 || PWM, Phase Correct || 0xFF || TOP|| BOTTOM<br />
|-<br />
| 2 || 1 || 0 || CTC || OCR0 || Immediate || MAX<br />
|-<br />
| 3 || 1 || 1 || Fast PWM || 0xFF || BOTTOM || MAX<br />
|}<br />
</center><br />
<br />
Dort beginnt man mit der Recherche und sucht sich die Bits für den gewünschten Modus raus. Mit diesen Bits sieht man dann in den Konfigurationsregistern nach, welches Bit zu welchem Register gehört und setzt es ganz einfach. In unserem Fall möchten wir den CTC Modus, also den Modus 2. Dazu muss das Bit WGM01 gesetzt werden und WGM00 muss auf 0 bleiben. Im Datenblatt ein wenig zurückscrollen bringt ans Licht, dass das Bit WGM01 im Konfigurationsregister TCCR0 angesiedelt ist. Weiters entnehmen wir der Tabelle, dass der Timer bis zum Wert in OCR0 zählen wird (die Spalte <b>TOP</b>). Dort hinein müssen also die 250-1 als Obergrenze geschrieben werden.<br />
<br />
Wichtig ist auch noch: Dieser Spezialmodus CTC löst keinen Overflow Interrupt aus, sondern einen sog. Compare Match Interrupt. Dies deshalb, weil die gewünschte Obergrenze ja laut Datenblatt in eines der sog. Compare Match Register geschrieben werden muss (OCR0). Das sind Spezialregister, die nach jedem Zählvorgang mit dem Zählregister verglichen werden. Stimmt ihr Inhalt mit dem des Zählregisters überein, so hat man einen Compare-Match und kann daran wieder eine Aktion (ISR) knüpfen. In diesem speziellen Fall des CTC Modus beinhaltet dieser Compare Match dann auch noch das automatische Rücksetzen des Timers auf 0.<br />
<br />
Und natürlich können auch mehrere Techniken kombiniert werden. Z. B. CTC Modus und zusätzliches weiteres softwaremässiges Herunterteilen in der ISR sieht dann so aus:<br />
<br />
<syntaxhighlight lang="c"><br />
// Für einen Mega16.<br />
// Andere Prozessoren: siehe Datenblatt wie die Timerkonfiguration einzustellen ist<br />
<br />
#define F_CPU 1000000UL<br />
#include <avr/io.h><br />
#include <avr/interrupt.h><br />
<br />
ISR( TIMER0_COMP_vect ) // Compare Match Interrupt Vector<br />
{<br />
static uint8_t swTeiler = 0;<br />
<br />
swTeiler++;<br />
if( swTeiler == 200 ) {<br />
swTeiler = 0;<br />
PORTD = PORTD ^ 0xFF;<br />
}<br />
}<br />
<br />
int main()<br />
{<br />
DDRD = 0xFF; // irgendein Port, damit wir auch was sehen<br />
<br />
TIMSK = (1<<OCIE0); // den Output Compare Interrupt des Timers freigeben<br />
OCR0 = 250 - 1; // nach 250 Zaehlschritten -> Interrupt und Timer auf 0<br />
TCCR0 = (1<<WGM01) | (1<<CS01); // Vorteiler 8, CTC Modus<br />
<br />
<br />
sei(); // und Interrupts generell freigeben<br />
<br />
<br />
while( 1 )<br />
{ // hier braucht nichts mehr gemacht werden.<br />
} // der Timer selbst sorgt dafür, dass die ISR Funktion<br />
} // regelmässig aufgerufen wird <br />
</syntaxhighlight><br />
<br />
== Fast PWM ==<br />
<br />
Aber der Timer kann noch mehr. Wenn der Timer so vor sich hinzählt, dann kann man bestimmte Output-Pins des µC an diesen Timer koppeln. Der Timer kann dann diesen Pin bei erreichen von bestimmten Zählerständen ganz von alleine wahlweise auf 0 schalten, auf 1 schalten oder umdrehen.<br />
<br />
Was passiert da genau? Im folgenden sei von der einfachsten Form der PWM auf einem Mega16 ausgegangen: Wenn der Timer in seiner Zählerei bei 0 ist, dann schaltet er den Pin auf 1, bei einem bestimmten Zählerstand soll er den Pin wieder auf 0 zurücksetzen und ansonsten soll der Timer wie gewohnt laufend von 0 bis 255 durchzählen.<br />
Es ist die Rede vom Timer-Modus 3, siehe die vorhergehende Tabelle aus dem Datenblatt.<br />
<br />
Der Tabelle entnehmen wir wieder: Die Bits WGM01 und WGM00 müssen auf 1 gestellt werden. Der TOP Wert (also der Wert, bis zu dem der Timer zählt) ist 0xFF (also 255) und das OCR0 Register steuert, bei welchem Zählerstand die Timerhardware den Pin wieder auf 0 zurück schaltet. Das Einschalten auf 1 ist vorgegeben (auch das kann man ändern, dazu später mehr).<br />
<br />
Stellt man also genau diese Konfiguration her und schreibt in das Register OCR0 beispielsweise den Wert 253, dann beginnt der Timer bei 0 zu zählen, wobei der den Ausgangspin auf 1 schaltet. Wird der Zählerstand 253 erreicht, dann schaltet der Timer den Pin wieder auf 0 zurück und zählt weiter bis 255 um dann wieder erneut bei 0 zu beginnen (und den Ausgansg-Pin wieder auf 1 zu schalten). Der Ausgangspin ist in diesem Fall also die meiste Zeit auf 1 und nur ganz kurz (während der Timer von 253 bis 255 zählt) auf 0.<br />
<br />
Schreibt am auf der anderen Seite in das Register OCR0 den Wert 3, dann passiert konzeptionell genau dasselbe nur mit anderen Zahlenwerten. Der Timer beginnt bei 0 zu zählen und schaltet den Ausgansgpin auf 1. Aber diesmal ist es bereits beim Zählerstand 3 so weit: Der Zählerstand stimmt mit dem Wert in OCR0 überein und als Folge davon wird der Ausgangspin wieder auf 0 gestellt. Der Timer zählt natürlich wie immer weiter bis 255 ehe dann das ganze Spiel wieder von vorne beginnt. In diesem Fall war also der Ausgangspin nur ganz kurze Zeit auf 1 (nämich in der Zeit, die der Timer benötigt um von 0 bis 3 zu zählen) und dann die meiste Zeit auf 0. Und genau darum geht es bei PWM: Mit dem Register OCR0 lässt sich daher der zeitliche Anteil steuern, in dem der Pin auf 1 liegt.<br />
<br />
<br />
<syntaxhighlight lang="c"><br />
// Für einen Mega16.<br />
// Andere Prozessoren: siehe Datenblatt wie die Timerkonfiguration einzustellen ist<br />
<br />
#define F_CPU 1000000UL<br />
#include <avr/io.h><br />
#include <util/delay.h><br />
<br />
int main()<br />
{<br />
uint8_t i;<br />
<br />
DDRB |= (1<<PB3); // PB3 auf Ausgang stellen. Dieser Pin<br />
// trägt auch die Bezeichnung OC0 und ist der Pin<br />
// an dem der Timer 0 seine PWM ausgibt<br />
<br />
OCR0 = 250;<br />
TCCR0 = (1<<WGM01) | (1<<WGM00) | (1<<CS01); // Vorteiler 8, Fast PWM 8 Bit<br />
TCCR0 = (1<<COM01); <br />
<br />
while( 1 )<br />
{ // hier braucht nichts mehr gemacht werden.<br />
// der Timer selbst sorgt dafür, dass die PWM läuft<br />
// wir wollen aber ein wenig Action haben.<br />
// Also setzen wir das OCR0 Register auf verschiedene<br />
// Werte, damit eine angeschlossene LED unterschiedliche<br />
// Helligkeiten zeigt<br />
OCR0 = 30;<br />
_delay_ms( 1000 );<br />
OCR0 = 200;<br />
_delay_ms( 1000 );<br />
<br />
for( i = 0; i < 255; ++i ) {<br />
OCR0 = i;<br />
_delay_ms( 10 );<br />
}<br />
}<br />
}<br />
</syntaxhighlight><br />
<br />
Bleibt noch das gesetzte Bit COM01. Was hat es damit auf sich? Bisher war immer die Rede davon, das der Ausganspin bei einem Zählerstand von 0 auf 1 geschaltet wird usw. Das regelt genau dieses Bit. Im Datenblatt findet sich die Tabelle 14.4 auf der Seite 83, die genau regelt welche Bedeutung die Bits COM01 bzw COM00 haben, wenn der Timer Modus auf Fast-PWM eingestellt ist. Achtung: Je nach Timer-Modus haben diese Bits andere Bedeutungen! Man muss sich also immer die zum jeweiligen Timer-Modus gehörende Tabelle im Datenblatt suchen.<br />
<br />
<br />
<references /><br />
<br />
<br />
<br />
[[Kategorie:C]]<br />
[[Kategorie:avr-gcc]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=Richtiges_Designen_von_Platinenlayouts&diff=74575Richtiges Designen von Platinenlayouts2013-03-09T21:43:24Z<p>Pappnase: /* Vorgehen bei der Layouterstellung */</p>
<hr />
<div>Beim Erstellen von Platinenlayouts muss man vieles beachten. Dieser Artikel zählt auf, was man machen sollte (Dos), und was man keinesfalls machen sollte (Don'ts).<br />
<br />
== Gutes Platinenlayout (Dos) ==<br />
<br />
* Berechne vor dem Zeichnen, welche Ströme über die Leiterbahn fließen werden und bestimme anhand dessen ihre Breite. Faustformel: 0,35mm können mit einem Ampere belastet werden. Weiteres siehe unter [[Leiterbahnbreite]].<br />
* Halte die Leiterplatte klein und kompakt. Jeder Leiterzug wirkt wie eine Antenne, welche Störungen aussendet oder empfängt. Je länger um so intensiver. Zumindest auf der Eingangsseite.<br />
* Nutze die Flächen zwischen den Leiterzügen und verbinde sie mit Masse (Polygone). So kann man Strahlung von außen abschirmen und Abstrahlung minimieren. Vermeide aber freie Kupferflächen, die nicht an GND angeschlossen sind. <br />
* Geize nicht mit Blockkondensatoren. Für jeden VCC-Pin o.ä. ist mindestens ein 100nF Kondensator, bei schnelleren Sachen evtl. ein kleinerer (z.&nbsp;B. 10nF) einzusetzen. Ausserdem kann es meist notwendig sein, pro IC noch zusätzlich einen 10µF Kondensator und eine Ferritperle (engl. bead) zur Entkopplung von Vcc zu spendieren.<br />
* Digitale und analoge Signale getrennt routen und nur in einem Punkt verbinden. Und zwar idealerweise am [[AD-Wandler]], wenn dieser vorhanden ist, sonst in der Nähe des Spannungsreglers. Eine Massefläche für analoge und digitale Schaltungsteile sollte durchgängig sein, getrennte Masseflächen sind nur in sehr seltenen Fällen sinnvoll. <br />
* Nutze die Anschlüsse der bedrahteten Bauelemente für Durchkontaktierungen.<br />
* Wenn es sich nicht vermeiden lässt 230V (400V) Netzspannung auf die Platine zu führen, so trenne die Bereiche der Kleinspannung und Netzspannung deutlich voneinander und mit vieeel Platz. Dabei unterscheidet man zwischen Luft- und Kriechstrecken. Eine Kriechstrecke ist die Strecke auf der Oberfläche einer Leiterplatte oder eines Bauteils. Die Luftstrecke ist sozusagen die kürzeste Verbindung zwischen den beiden Potentialen. Die Luft- und Kriechstrecken betragen zwischen 3 und 8 mm. Der notwendige Abstand hängt von der Gefährdung ab, siehe auch [[Leiterbahnabstände]].<br />
* Mögichst sternförmige Verbindungen für Masse und Versorgungsspannungen bei Schaltungen mit hohen Strömen und empfindliche Analogschaltungen<br />
<br />
== Schlechtes Platinenlayout (Don'ts) ==<br />
<br />
* Analoge und digitale Schaltungsteile direkt ohne Filter aus der gleichen Stromquelle versorgen.<br />
* Digitale Signalleitungen in unmittelbarer Nachbarschaft analoger Signale.<br />
* Zu wenig Abstand zwischen Leiterplattenrand und Leiterzügen.<br />
* 90° oder spitze Winkel beim Routen von Leiterbahnen. Entgegen der weit verbreiteten Annahme hat das nur sehr wenig Auswirkungen auf die HF-Eigenschaften. Es sind mehr mechanische (Ablösung von Ecken) und ästhetische Gründe (Aussehen, Packungsdichte der Leitungen). Mehr dazu im Artikel [[Wellenwiderstand#Leitungsf.C3.BChrung_und_Layout | Wellenwiderstand]].<br />
* Durchkontaktierungen auf SMD-Pads. Beim maschinellen Löten läuft das Flussmittel bzw. das Lötzinn in die Bohrung. Die Fehlerhäufigkeit steigt.<br />
* Durchkontaktierungen von beiden Seiten mit Stopplack verschließen. Es könnte Feuchtigkeit oder gar Aetzrückstaende darin zurückbleiben und beim Löten der Stopplack abplatzen oder Korrosion auftreten.<br />
<br />
== Vorgehen bei der Layouterstellung ==<br />
* Umrisse festlegen<br />
* Befestigungsbohrungen festlegen, dabei ausreichend Platz für Schraubenköpfe und Werkzeuge freihalten (Sperrflächen)<br />
* Steckverbinder platzieren<br />
* Bauteile platzieren, dabei möglichst zusammengehörige Bauteil nebeneinander platzieren. Die Verbindungen (Luftlinien, engl. air wires) möglichst kurz und kreuzungsarm halten.<br />
* Stromversorgung der ICs layouten<br />
* Kritische Signale layouten wie Takte, Sensoreingänge etc.<br />
* Restliche Signale layouten<br />
* Masseflächen füllen.<br />
** Masseflächen können eine Schaltung deutlich verbessern, wenn sie richtig benutzt werden. Sie können aber auch genau das Gegenteil bewirken, wenn sie als automatisches Wundermittel betrachtet werden.<br />
** Die Masseverbindung aller ICs muss zunächst direkt layoutet werden.<br />
** Erst wenn die Masse komplett layoutet ist, kann man die Massefläche auffüllen. Damit verhindert man, dass vielleicht ein IC nur über eine sehr dünne Verbindung angeschlossen wird, welche man in der Massefläche übersieht.<br />
** Masseflächen sind nur dann wirklich wirksam, wenn sie möglichst durchgängig sind. Wenn sie durch viele Leitungen zerschnitten werden, sinkt ihre Wirksamkeit massiv und sie können sich zu einem EMV-Problem entwickeln (Abstrahlung von Energie, Streifen- und Schlitzantennen)<br />
<br />
== Siehe auch ==<br />
<br />
*[http://www.mikrocontroller.net/forum/read-6-178710.html#254235 Forumsbeitrag]: Regeln beim Platinenentwurf<br />
* [http://www.mikrocontroller.net/topic/93602#804338 Forumsbeitrag]: Vorschlag für Lötpads bei Hobbyeinsteigerplatinen<br />
* [[EMV]]<br />
* [[Eagle im Hobbybereich]]<br />
<br />
==Links==<br />
* [http://www.ilfa.de/download/C/pb-id=wd7c584f8c9d73e6df32d52cabe12bcbbba8624a4eci8/453/Design-Optimierung.pdf Optimierung von Layouts]<br />
*[http://www.ilfa.de/designrichtlinien Weitere Dokumente zum Thema professionelle Platinenherstellung]<br />
* [http://www.analog.com/library/analogDialogue/Anniversary/12.html Grounding (Again)], Ask The Applications Engineer - 12, Fa. Analog Devices, (englisch)<br />
<!-- * http://edaboard.com --><br />
* [http://www.sparkfun.com/commerce/tutorial_info.php?tutorials_id=115 Designing a Better PCB] von Sparkfun (engl.)<br />
* [http://www.hottconsultants.com/tips.html Tech Tips] von Henry Ott (engl.)<br />
* [http://www.ultracad.com/articles/90deg.pdf Messung] von verschiedenen Winkeln von Leiterbahnen mit 17ps TDR, keinerlei Unterschiede!<br />
<br />
[[Category:Platinen]]<br />
[[Kategorie:Schaltplaneditoren]]</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=Benutzer:Pappnase&diff=69249Benutzer:Pappnase2012-11-20T05:51:30Z<p>Pappnase: Die Seite wurde neu angelegt: „Вэссиз, диэ дас нихт лэзэн коэннэн, зинд дооф!!!“</p>
<hr />
<div>Вэссиз, диэ дас нихт лэзэн коэннэн, зинд дооф!!!</div>Pappnasehttps://www.mikrocontroller.net/index.php?title=Atmel_Studio&diff=68906Atmel Studio2012-10-31T16:46:06Z<p>Pappnase: /* Atmel QTOuch */</p>
<hr />
<div>Das '''Atmel Studio''' (vor Version 6: "'''AVR Studio'''") ist eine kostenlose Entwicklungsumgebung ([[Editoren/IDEs|IDE]]) für die Programmierung der [[AVR]]-[[Mikrocontroller]] und [[ARM]]-[[Mikrocontroller]] (ab Version 6) von Atmel. Sie basiert ab Version 5 auf der Visual Studio Shell von Microsoft und besteht aus einer Projektverwaltung, einem [[Editoren/IDEs#Texteditoren für Programmierer|Editor]], einem [[AVR-Studio#Debugger|Debugger]] und Werkzeugen zum beschreiben der Mikrocontroller.<br />
<br />
Mit dem Atmel Studio kann in [[Assembler]] sowie in [[C]]/[[C-Plusplus|C++]] programmiert werden. Für die Unterstützung von C/C++ musste bis einschließlich Version 4 vor der Installation des AVR Studio der GNU C Compiler für AVRs [[WinAVR]] installiert werden. Ab AVR Studio 5 ist eine vollständige Toolchain zur Entwicklung von C-Projekten enthalten. Atmel bietet weiterhin eine Erweiterung zwecks Erstellung von Projekten mit eingeschränkter C++-Unterstützung an (siehe [[AVR_Studio#Tipps_.26_Tricks|Tipps & Tricks]]).<br />
<br />
== Debugger ==<br />
Die Atmel-Studio-Umgebung sieht unabhängig von der speziellen Debug-Plattform größtenteils identisch aus. Es existieren folgende Debug-Möglichkeiten:<br />
# [[AVR-Simulation#AVR_Studio|AVR Simulator]]<br />
# AVR In-Circuit Emulator / [[JTAG]]-Adapter: AVR Dragon, AVR ONE!, JTAGICE3, JTAGICE mkII, SAM-ICE<br />
'''Simulation'''<br />
* die meisten AVR-Mikrocontroller werden unterstützt<br />
* z.T langsamer als eine Emulation (insbesondere bei größeren Projekten)<br />
* Wechselwirkung mit Peripherie nur über vordefinierte Stimuli möglich<br />
* Anzeige aller Register zu jeder Zeit möglich<br />
'''Emulation'''<br />
* Unterstützung von Mikrocontrollern plattformabhängig eingeschränkt<br />
* z.T. schneller als Simulation<br />
* Debugging in tatsächlicher Hardwareumgebung<br />
* Register nicht uneingeschränkt lesbar<br />
<br />
== Tipps & Tricks ==<br />
<br />
* [[AVR-Studio Bugs]]<br />
<br />
* [[AVR-Simulation]]<br />
<br />
* [http://www.mikrocontroller.net/topic/193587#1894280 Pfad zum Hexfile]<br />
<br />
* [http://www.mikrocontroller.net/topic/237681#2411339 Anzeige der Größe benutzter Speicherbereiche in AVR Studio 5]<br />
<br />
* [http://www.mikrocontroller.net/topic/236601#2413654 C++ Templates (beta) für AVR Studio 5] (Vorsicht: kein vollständiger Funktionsumfang, siehe [http://support.atmel.no/bin/customer.exe?=&action=viewKbEntry&id=1001 FAQ])<br />
<br />
* [http://www.rn-wissen.de/index.php/AVR_Studio_5#Eigene_Templates_erzeugen Erstellung eigener Templates in AVR Studio 5]<br />
<br />
* [http://www.mikrocontroller.net/topic/267384#new Nicht Atmel Programmer direkt unter AVR Studio 5 oder 6 verwenden z.B. USBasp]<br />
<br />
== Downloads ==<br />
<br />
=== Offizielle Seite ===<br />
* http://www.atmel.com/atmelstudio<br />
* http://www.atmel.no/beta_ware/ (gelegentlich Aktualisierungen und Testversionen)<br />
<br />
=== Direktlinks Installer ===<br />
<br />
Anm.: Die MD5 Checksumme dient zum Überprüfen der Downloads auf Vollständigkeit. Die aktuelle Version ist '''fett''' markiert.<br />
<br />
Im Falle nicht eingepflegter Updates hier der Direktlink-Präfix (entsprechenden Dateinamen aus dem Formularlink kopieren und hinter dem letzten Schrägstrich einfügen):<br />
<br />
http://www.atmel.com/Images/<br />
<br />
*[http://www.atmel.com/Images/as6installer-stable-servicepack1-6.0.1938-with-asf.exe as6installer-stable-servicepack1-6.0.1938-with-asf.exe] '''Atmel Studio 6.0 Service Pack 1 with ASF update (407 MB, updated 2012/09)''' Upgrade auf 6.0.1938.<br />
<br />
*[http://www.atmel.com/Images/as6installer-stable-servicepack1-6.0.1938.exe as6installer-stable-servicepack1-6.0.1938.exe] '''Atmel Studio 6.0 Service Pack 1 (148 MB, updated 2012/09)''' Upgrade auf 6.0.1938.<br />
<br />
*[http://www.atmel.com/Images/as6installer-patch-6.0.1882.exe as6installer-patch-6.0.1882.exe] '''Atmel Studio 6.0 Patch 2 (25MB, updated 2012/08)''' Patch update für Atmel Studio 6.0 (build 1843) und (6.0.1863) kleinere Bugfixes und Upgrade auf 6.0.1882.<br />
<br />
*[http://www.atmel.com/Images/as6installer-6.0.1843.exe as6installer-6.0.1843.exe] '''Atmel Studio 6 Release installer (743MB, updated 2012/05)'''<br />
Achtung: Atmel Studio 6.0 zeigt die RAM-Nutzung falsch an, egal welche Toolchain genutzt wird. Es addiert die EEPROM-Belegung dazu. [http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=963389#963389 Work-Around]<br />
<br />
*[http://www.atmel.com/Images/as6installer-6.0.1843.noVSSnoDotNet.exe as6installer-6.0.1843.noVSSnoDotNet.exe] '''Atmel Studio 6 Release installer (528MB, updated 2012/05)'''<br />
<br />
*[http://www.atmel.com/Images/as6installer-6.0.1703-full.exe as6installer-6.0.1703-full.exe] Atmel Studio 6 BETA installer (731MB, updated 2012/03)<br />
<br />
*[http://www.atmel.com/Images/as6installer-6.0.1703-small.exe as6installer-6.0.1703-small.exe] Atmel Studio 6 BETA installer (516MB, updated 2012/03)<br />
<br />
*[http://www.atmel.com/Images/as5installer-stable-5.1.208-full.exe as5installer-stable-5.1.208-full.exe] AVR Studio 5.1 installer (includes VSS, .NET4.0, ASF 2.11.0 and Toolchain 3.3.1) (616MB, updated 2012/02)<br />
<br />
* [http://www.atmel.com/Images/as5installer-stable-5.1.208-small.exe as5installer-stable-5.1.208-small.exe] AVR Studio 5.1 installer (includes ASF 2.11.0 and Toolchain 3.3.1) (396 MB, updated 2012/02)<br />
<br />
*[http://www.atmel.com/Images/as5installer-5.1.148.beta-full.exe as5installer-5.1.148.beta-full.exe] AVR Studio 5.1 Beta installer (includes VSS and .NET) (523 MB, updated 2011/12)<br />
<br />
*[http://www.atmel.com/Images/as5installer-5.1.148.beta-small.exe as5installer-5.1.148.beta-small.exe] AVR Studio 5.1 Beta installer (308 MB, updated 2011/12)<br />
<br />
*[http://www.atmel.com/Images/AvrStudio4Setup.exe AvrStudio4Setup.exe] '''AVR Studio 4.19 (build 730) (124 MB, updated 2011/09/11)''' MD5:609209DB9A1C6191945421299101DC15<br />
<br />
*[http://www.atmel.com/Images/AVRStudio4.18Setup.exe AVRStudio4.18Setup.exe] AVR Studio 4.18 (build 684) (117 MB, updated July 2009)<br />
<br />
*[http://www.atmel.com/Images/AVRStudio4.18SP3.exe AVRStudio4.18SP3.exe] Service Pack 3 for AVR Studio 4.18 (build 716) (33 MB, updated July 2009)<br />
<br />
*[http://www.atmel.com/Images/AvrStudio417Setup.exe AvrStudio417Setup.exe] AVR Studio 4.17 (build 666) (112 MB, updated 07/09)<br />
<br />
*[http://www.atmel.com/Images/AvrStudio416Setup.exe AvrStudio416Setup.exe] AVR Studio 4.16 (build 628) (126 MB, updated 02/09) (last version for Win98)<br />
<br />
*[http://www.atmel.com/Images/aStudio4b589.exe aStudio4b589.exe] AVR Studio 4.14 (build 589) (89 MB, updated 04/08)<br />
<br />
*[http://www.atmel.com/Images/aStudio4b528.exe aStudio4b528.exe] AVR Studio 4.13 (build 528) (73 MB, updated 03/07)<br />
<br />
=== Direktlinks Zusatzsoftware ===<br />
====Atmel QTouch====<br />
* [http://www.atmel.com/Images/AVRQTouchStudioSetup_VSS_dotNET.exe AVRQTouchStudioSetup_VSS_dotNET.exe] AVR QTouch Studio mit .NET (373 MB, updated 03/10)<br />
<br />
* [http://www.atmel.com/Images/Atmel_QTouch_Libraries_5.0.exe Atmel_QTouch_Libraries_5.0.exe] Atmel QTouch Library 5.0 (34.3MB, updated April 2011)<br />
<br />
====AVR Toolchain====<br />
* [http://www.atmel.com/Images/avr-toolchain-installer-3.4.1.1195-win32.win32.x86.exe avr-toolchain-installer-3.4.1.1195-win32.win32.x86.exe] '''AVR Toolchain 3.4.1 (95.9 MB, AVR-GCC: 4.6.2, AVR-LIBC: 1.8.0, updated 2012/08)'''<br />
<br />
* [http://www.atmel.com/Images/avr-toolchain-installer-3.4.0.1146-win32.win32.x86.exe avr-toolchain-installer-3.4.0.1146-win32.win32.x86.exe] AVR Toolchain 3.4.0 (91 MB, AVR-GCC: 4.6.2, AVR-LIBC: 1.8.0, updated 2012/06)<br />
<br />
* [http://www.atmel.com/Images/avr-toolchain-installer-3.3.1.1020-win32.win32.x86.exe avr-toolchain-installer-3.3.1.1020-win32.win32.x86.exe] AVR Toolchain 3.3.1 (94 MB, AVR-GCC: 4.5.1, AVR-LIBC: 1.7.1, updated 2012/04)<br />
<br />
* [http://www.atmel.com/Images/avr-toolchain-installer-3.3.0.710-win32.win32.x86.exe avr-toolchain-installer-3.3.0.710-win32.win32.x86.exe] AVR Toolchain 3.3.0 (94 MB, AVR-GCC: 4.5.1, AVR-LIBC: 1.7.1, updated 2011/09/11)<br />
<br />
* [http://www.atmel.com/Images/avr-toolchain-installer-3.2.3.579-win32.win32.x86.exe avr-toolchain-installer-3.2.3.579-win32.win32.x86.exe] AVR Toolchain 3.2.3 (95 MB, AVR-GCC: 4.5.1, AVR-LIBC: 1.7.1, updated 2011/06/11)<br />
<br />
* [http://www.atmel.com/Images/avr-toolchain-installer-3.0.0.240-win32.win32.x86.exe avr-toolchain-installer-3.0.0.240-win32.win32.x86.exe] AVR Toolchain 3.0.0 (87 MB, AVR-GCC: 4.4.3, AVR-LIBC: 1.7.0, updated 2010/09/10) <br />
<br />
====Atmel Software Framework====<br />
* [http://www.atmel.com/Images/as-asf330-msi-stable-6.0.0.144-win32.win32.x86.zip as-asf330-msi-stable-6.0.0.144-win32.win32.x86.zip] '''Atmel Software Framework 3.3.0 update for Atmel Studio 6'''<br />
<br />
* [http://www.atmel.com/Images/asf-standalone-archive-3.3.0.zip asf-standalone-archive-3.3.0.zip] '''AVR Softwware Framework 3.3.0'''<br />
<br />
* [http://www.atmel.com/Images/asf-standalone-archive-2.10.0.zip asf-standalone-archive-2.10.0.zip] '''AVR SoftwareFramework 2.10.0 - drivers and libraries (87 MB, revision 2.10.0, updated 2012/1/12)'''<br />
<br />
* [http://www.atmel.com/Images/asf-standalone-archive-2.9.0.zip asf-standalone-archive-2.9.0.zip] AVR SoftwareFramework 2.9.0 - drivers and libraries (79 MB, revision 2.9.0, updated 2011/12/11)<br />
<br />
* [http://www.atmel.com/Images/as5.1-asf-vsix-stable-2.11.1.30-win32.win32.x86.zip as5.1-asf-vsix-stable-2.11.1.30-win32.win32.x86.zip] '''AVRStudio5-ASF-Update-2.11.1.30 (174 MB, revision 2.11.1, updated 2012/02/)'''<br />
<br />
* [http://www.atmel.com/Images/AVRStudio5-ASF-Update-2.8.1.76.exe AVRStudio5-ASF-Update-2.8.1.76.exe] AVRStudio5-ASF-Update-2.8.1.76 (222 MB, revision 2.8.1, updated 2011/10/11)<br />
<br />
== Weblinks ==<br />
* [http://www.youtube.com/user/AtmelCorporation#g/c/8F325BE889E62E50 YouTube-Playlist: AVR Studio 5 Tutorial]<br />
<br />
* [http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=82994 How to install JTAGICE mkII (and AVR Dragon and AVRISP mkII) on Windows 7 x64] auf avrfreaks.net (ggf. kostenlos registrieren). Siehe auch Hinweis von Denny [http://www.mikrocontroller.net/topic/146857#1476962] im Forum.<br />
<br />
*[http://avr-eclipse.sourceforge.net/wiki/index.php/The_AVR_Eclipse_Plugin AVR Eclipse Plugin]<br />
<br />
*[http://avrstudio5.wordpress.com/ AVR Studio 5 Blog] - Useful hints and tips for installation troubleshooting with the new AVR Studio 5<br />
<br />
[[Category:AVR]]<br />
[[Kategorie:Entwicklungstools]]</div>Pappnase