Mikrocontrollerprogrammierung mit dem ATMEGA

Achtung: Diese Einführung ist alt, und ein wenig unordentlich. Die »Library« kann ganz praktisch sein, sticht allerdings nicht durch guten Stil heraus. Auf diesen Seiten pflege ich nur noch kleine Korrekturen ein, keine umfassenden Verbesserungen oder Ergänzungen mehr. Aber vielleicht ist das hier trotzdem für viele ein guter Einstieg. Im Text wird uisp verwendet, was nicht mehr zeitgemäß ist. Ich bitte darum, selbst von Anfang an die simple Verwendung von avrdude zu recherchieren.

Einführung

Worum geht's hier?

Hier geht es um die Programmierung eines ATMEGAs mit C. Ein ATMEGA ist ein AVR-Mikrocontroller. Ich erkläre den schnellen Einstieg mit Installation der zur Programmierung benötigten Software, Aufbau der Basis-Hardware und das Schreiben einer Software, die ein paar Ausgänge ansteuert. Der Text ist nicht sehr ausführlich, nicht der Beste zum Thema (da es im Netz sehr viele Seiten darüber gibt). Ich möchte mit diesem Text jedoch die wichtigsten Infos zusammenfassen, die man benötigt, um möglichst schnell seine erste Mikrocontroller-Schaltung zum Laufen zu bringen und Basis-Komponenten (LEDs, Taster, serielle Schnittstelle) zu verwenden. Ich versuche den Text hier so zu schreiben, dass die Probleme, die ich hatte, nicht auftreten, und werde weniger auf die Detaills als auf die praktische Anwendung eingehen.

Benötigtes Vorwissen

Die Elektronik muss aufgebaut werden. Ich löte ziemlich schlecht, und mache öfters Kurzschlüsse rein. Wenn man schlecht lötet muss man also die Ausdauer haben, die Kurzschlüsse danach zu finden… Man muss einigermaßen löten können, sollte folglich Lötkolben und Messgerät (theoretisch nicht notwendig, praktisch aber extrem hilfreich um Fehler zu finden), sowie eine dritte Hand und einen Seitenschneider besitzen... Zur Inbetriebnahme der Schaltung ist ein Netzteil oder z. B. eine 9V-Batterie erforderlich — ich werde darauf in dieser Anleitung nicht näher eingehen, sondern nur erklären, wo der Strom rein muss. Einfache Netzteile kann man für 5 bis 20 € bei Reichelt, Conrad und diversen anderen Geschäften erwerben. Für die Programmierung sind grundlegende Kenntnisse der Programmiersprache C erforderlich! Der ATMEGA lässt sich zwar auch mit Assembler, C++ (für 'nen Atmel etwas überladen?), Bascom (Basic-Dialekt), Ada (seltsame Sprache) und einigen anderen Programmiersprachen verwenden, ich gehe darauf jedoch nicht ein!

Fertige Schaltungen

Bei shop.mikrocontroller.net kann man fertige Entwicklungsplatinen und ein Programmierkabel für insg. ca. 40 € erwerben. Die haben dann einen Mikrocontroller, einen seriellen Anschluss, einen Stecker zum Programmieren und eine LED schon fertig draufgebaut. Das ist für faule Menschen eine nette Lösung. Ich denke aber, dass es nicht schadet, wenn man so eine Schaltung mal selbst aufbaut, und da für eine einfache Schaltung mit einem Mikrocontroller schon 15 Bauteile ausreichen, ist das auch nicht sehr aufwendig. Außerdem zahlt man nur ca. ¼ wenn man selbst lötet. Ich habe noch nie ein fertiges Entwicklerboard gekauft, ich weiß folglich nicht ob es sich ohne Probleme unter Linux programmieren lässt.

Gliederung

Der Text ist in 4 Kategorien gegliedert:

«Benötigte Software»
Nennt die zur Programmierung benötigte Software und gibt Tipps, wie man sie installiert.
«Hardware»
Was für Hardware muss zusammengelötet werden, damit man den ATMEGA programmieren kann?
«Software»
Umgang mit Bits und Registern, das Verwenden der Ausgänge und verschiedene mit der Hardware zusammenhängende Eigenheiten eines Atmel-Mikrocontrollers. C-Grundkenntnisse werden vorausgesetzt.
«Meine Library»
Erklärt die Funktions-Library, die ich geschrieben habe, mit der man die IOs des Mikrocontrollers komfortabler verwenden kann und einige Vereinfachungen im Hauptprogramm erfährt. Sie ist natürlich rein optional, kann die Programmierung jedoch vereinfachen.

Beispielprojekt: Binäruhr

Einfache Binäruhr, die die Uhrzeit im Zweiersystem anzeigt

Einfache Binäruhr, die die Uhrzeit im Zweiersystem anzeigt

Ich habe mir zur Vorstellung einer einfachen Mikrocontrollerschaltung beim LUSC-Workshop-Weekend 2005 eine Binäruhr gebaut. Diese besteht aus einer primitiven Basisschaltung mit einem ATMEGA8 und 17 LEDs sowie 2 Tastern.

Beispielprojekt: Umgebaute — ehemals ferngesteuerte — Autos

Zu dritt bei Jugend Forscht in Erlangen

Zu dritt bei Jugend Forscht in Erlangen

Zusammen mit Michele Collodo und Marcel Neunhoeffer bastelte ich 9 Monate an einer Ampelschaltung, veranschaulicht durch ein 3m²-großes Modell, auf dem 8 Ampeln und 3 Autos drauf sind. (Original waren es ferngesteuerte 20 €-Autos mit äußerst schlechter Mechanik.) Wir haben die Elektronik ersetzt und Fototransistoren integriert, damit sie eine Linie auf der Platte selbstständig verfolgen können. Anhand von Strichcodes auf der Platte erkennen die Autos ihre Position, schicken diese per Infrarot zur Steuerungselektronik in der Mitte, diese wiederum schaltet dann die Ampeln sinnvoll und teilt den Autos mit, wann sie anhalten sollen. Mehr infos auf der Projektseite.

URLs mit weiteren Informationen

Unklarheiten oder Probleme mit meinem Text?

Ich gehe mal davon aus. Irgendeine Unklarheit gibt es immer. Ich habe einige Zeit gebraucht bis meine erste Schaltung lief. Da ich dir bei Hardwareproblemen kaum helfen kann, ist es sinnvoller, du findest jemanden, der sich damit auskennt, als dass du mich anschreibst. Ich bin jedoch bereit Softwareprobleme aus meinen Beispielen zu bearbeiten. Außerdem nehme ich natürlich gerne Verbesserungsvorschläge, Kritik und Hinweise auf Fehler zu diesem Text entgegen. Kontakt

Unvollständigkeiten der Dokumentation

Wenn besonderes Interesse an einem der Punkte besteht würde ich mich über eine Mail freuen, dann kann ich priorisieren.

Benötigte Software

Die benötigte Software beschränkt sich auf avr-gcc, eine «gcc 3.x»-Version die C mit einem Atmel als Zielplattform kompiliert, der avr-libc, die Funktionen bereitstellt, die man für die komfortable Ansteuerung der IOs und Eigenheiten des Atmels benötigt und eine Software zum Übertragen des Programms auf den Atmel, z. B. Ponyprog, uisp oder avrdude.

Installation unter Windows

WinAVR enthält die drei avr-Tools, die unter Linux einzelnd installiert werden, in einem Paket.

Installation unter Linux

Alle Programme lassen sich per «apt-get» installieren (Ubuntu/Debian: apt-get install uisp avr-libc binutils-avr gcc-avr). Die RPMs sind teilweise veraltet, insbesondere aktuelle Versionen der avr-libc sind empfehlenswert.

Installation aus dem Quellcode (=> aktuelle Versionen, relativ problemlose Installation, keine Probleme mit Repositorys & RPMs): man lade sich die oben verlinkten Pakete herunter, entpacke sie (tar jxf <file> für .bz2-Dateien bzw. tar zxf <file> für .tar.gz-Dateien), führe ein ./configure --prefix=/usr/local/avr in den jeweiligen Verzeichnissen aus (bei avr-binutils und avr-gcc zusätzlich mit dem Parameter --target=avr), dann noch ein make && make install und fertig. cd binutils-*
./configure --prefix=/usr/local/avr --target=avr
make
make install
cd ../gcc-*
./configure --prefix=/usr/local/avr --target=avr
make
make install
cd ../avr-libc-*
./configure --prefix=/usr/local/avr
make
make install
cd ../uisp
./configure --prefix=/usr/local/avr
make
make install
Statt «uisp» kann man unter Linux ebenfalls «Ponyprog» verwenden (welches im Gegensatz zu uisp eine grafische Oberfläche bietet) ? ich habe es jedoch auf keinem Linuxsystem installieren können, da es etwas anspruchsvoll ist, was den verwendeten Kernel betrifft.

Der Programmaufruf kann über ein Bash-Skript abgekürzt werden, da zum Programmieren eines Mikrocontrollers der Aufruf von «avr-gcc» und «uisp» etwas stressig ist. Alternativ (eleganter) kann für jedes neue Programm ein Makefile erstellt werden. Ich gehe darauf nicht ein. Eine ausführlichere Erklärung der Softwareinstallation und ein Beispiel für ein Makefile gibt es bei Linuxfocus.

Installation am Mac

Theoretisch geht die Installation der Software am Mac wohl ähnlich wie unter Linux, in der Praxis weiß ich, dass es schon gemacht wurde, habe es aber selbst nie probiert.

Hardware

Eigenschaften des ATMEGA8/168

Ich bevorzuge zwei unterschiedliche ATMEGA-Typen, obwohl es deutlich mehr gibt. Die Unterschiede liste ich tabellarisch auf. Für beide Atmel-Varianten gelten folgende Daten:

Pins
28. 23 davon als IOs nutzbar, d. h. für das Ausgeben oder Einlesen eines Signals.
Flash
Im Flash wird das Programm selbst gespeichert. Obwohl sich das so nicht sagen lässt, schätze ich, dass 1.000 Zeilen C ca. 8kb Flash brauchen.
SRAM
1kb
EEPROM
Im EEPROM können Daten gespeichert werden, die bei Stromverlust nicht verloren gehen. Beide Prozessoren haben 0.5kb EEPROM. Jede EEPROM-Zelle lässt sich nur ca. 100.000 mal beschreiben, wechselt man geschickt zwischen den Zellen durch kann man die Lebensdauer erhöhen.
ATMEGA8ATMEGA168
Flash8kb16kb
Max. Taktfrequenz16 Mhz20Mhz
Anzahl der Puls-Width-Modulation-Ports36
Preis (bei Reichelt)1,65 €3,15 €

Die Daten hören sich im Vergleich zu einem PC mit mehreren Ghz etwas schwach an. Programme brauchen am PC oft über 1GB an Speicher und würden niemals mit 1kb RAM auskommen. Das ist jedoch mehr als es sich anhört. Ein Atmel muss in der Regel keine grafischen Oberflächen anzeigen, braucht kein riesiges Betriebssystem, nicht zwingend Multitasking usw. Diese Atmels kann einfache Aktionen deutlich schneller als ein Personal Computer ausführen. So kann er in 1/10.000 Sekunden Berechnungen durchführen und in wenigen Millisekunden problemlos auf Aktionen von Außen reagieren. Diese Geschwindigkeit reicht für praktisch jede Anwendung mit einem Mikrocontroller dieser Größe aus.

Pin-Belegung

ATMEGA8

(OC1A) PB1 15 ATMEGA8 14 PB0 (ICP1)
(SS/OC1B) PB2 16 13 PD7 (AIN1)
(MOSI/OC2) PB3 17 12 PD6 (AIN0)
(MISO) PB4 18 11 PD5 (T1)
(SCK) PB5 19 10 PB7 (XTAL2/TOSC2)
AVCC 20 9 PB6 (XTAL1/TOSC1)
AREF 21 8 GND
GND 22 7 VCC
(ADC0) PC0 23 6 PD4 (XCK/T0)
(ADC1) PC1 24 5 PD3 (INT1)
(ADC2) PC2 25 4 PD2 (INT0)
(ADC3) PC3 26 3 PD1 (TXD)
(ADC4/SDA) PC4 27 2 PD0 (RXD)
(ADC5/SCL) PC5 28 1 PC6 (RESET)
Kerbe auf dieser Seite

ATMEGA168

(OC1A/PCINT1) PB1 15 ATMEGA168 14 PB0 (PCINT0/CLK0/ICP1)
(SS/OC1B/PCINT2) PB2 16 13 PD7 (PCINT23/AIN1)
(MOSI/OC2A/PCINT3) PB3 17 12 PD6 (PCINT22/OC0A/AIN0)
(MISO/PCINT4) PB4 18 11 PD5 (PCINT21/OC0B/T1)
(SCK/PCINT5) PB5 19 10 PB7 (PCINT7/XTAL2/TOSC2)
AVCC 20 9 PB6 (PCINT6/XTAL1/TOSC1)
AREF 21 8 GND
GND 22 7 VCC
(ADC0/PCINT8) PC0 23 6 PD4 (PCINT20/XCK/T0)
(ADC1/PCINT9) PC1 24 5 PD3 (PCINT19/OC2B/INT1)
(ADC2/PCINT10) PC2 25 4 PD2 (PCINT18/INT0)
(ADC3/PCINT11) PC3 26 3 PD1 (PCINT17/TXD)
(ADC4/SDA/PCINT12) PC4 27 2 PD0 (PCINT16/RXD)
(ADC5/SCL/PCINT13) PC5 28 1 PC6 (PCINT14/RESET)
Kerbe auf dieser Seite

Bedeutung der Pins

Die folgenden Pins haben eine besondere Bedeutung:

Normale IOs (= Input/Outputs)
Alle nicht anderweitig belegten Pins sind als normaler IO, d. h. als konfigurierbarer digitaler Ein- bzw. Ausgang verwendbar.
RESET/SS/MOSI/MISO/SCK: Das Programmierinterface
Diese Pins muss man über die parallele Schnittstelle mit dem Computer verbinden, damit man den Atmel programmieren kann. Der Reset-Pin muss über einen Widerstand mit dem Pluspol und dem Programmierinterface verbunden werden — liegt Spannung an, läuft der Atmel, schließt der Computer über die parallele Schnittstelle den Pin kurz, wird der Atmel neu gestartet. Alle Pins, die nicht der Reset-Pin sind kann man zusätzlich als normale IOs verwenden, d. h. die parallele Schnittstelle und z. B. einen Taster parallel anlöten.
RXD/TXD: Der USART
Über den sog. USART kann man mit einem anderen Gerät per RS232 (= serielle Schnittstelle) kommunizieren — das ist sehr hilfreich, um sich von seinem Mikrocontroller beispielsweise Nachrichten auf den Computer schicken zu lassen. RXD ist der Empfangspin, TXD wird zum Senden verwendet.
VCC, GND, AVCC, AREF
VCC muss an stabilisierten 2.25 bis 5.5V anliegen, GND an der Masse (= Minus-Pol). AVCC ist die Spannungsversorgung für den Analog-Digital-Konverter, dort sollten +5V anliegen. AREF definiert die maximal messbare Spannung gegenüber 0V (die nicht höher 5V liegen sollte), welche bei Verwendung des ADCs den Wert 1023 darstellt. Man verbindet den Pin demnach häufig mit 5V (oder einer niedrigeren Spannung, um eine höhere Auflösung zu erreichen). Ein 100n-Kondensator gegen Masse stabilisiert die Messwerte.
XTALx-Pins: Der Quarz
An diesen beiden Pins kann man (muss man aber nicht) einen Quarz anschließen, der die Taktgenauigkeit des Atmels erhöht. Das ist wichtig, wenn man eine Uhr baut oder den USART verwendet ;) Der Atmel hat einen einstellbaren internen Oszillator, der alternativ verwendet werden kann, jedoch nicht so gute Ergebnisse bringt.
OCxx-Pins: Puls-Width-Modulation
Über die Pulsbreitenmodulation kann man analoge Signale ausgeben, d. h. Spannungen zwischen 0 und 5V. Im Endeffekt ist die Pulsbreitenmodulation nichts anderes als hardwarebeschleunigtes, gaaanz schnelles An- und Ausschalten des Ausgangs, wobei dahinter ein interner Kondensator zur Stabilisierung aktiviert wird. Schaltet man z. B. gleichmäßig zwischen 0 und 5V sehr schnell hin- und her, und stabilisiert diesen ständigen Wechsel mit einem Kondensator entstehen 2,5V. So kann man mit einem digitalen Pin analoge Signale erzeugen.
ADCx-Pins: Der Analog-Digital-Wandler
Möchte man analoge Signale einlesen kann man den ADC verwenden.

Den USART, den ADC und die PWM könnte man auch ohne die vom Atmel bereitgestellten Möglichkeiten implementieren, wenn auch etwas umständlicher/langsamer (in der Ausführung).

Basisschaltung

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 UP 832EP EPOXYD-Lochrasterplatine, 16x10cm Platine zum Draufbauen der Elektronik
1 D-SUB ST 25 25-Poliger DSUB-Stecker für Parallelport Programmierung
3 1/4W 470 470 Ohm-Widerstand Programmierung
1 1/4W 220 220 Ohm-Widerstand Programmierung
1 1/4W 10K 10K Ohm-Widerstand Reset-Pin
1 BL 1X10G 2,54 Buchsenleiste Steckverbindung zum Parallelport
1 SL 1X40G 2,54 Steckerleiste Steckverbindung zum Parallelport
2 GS 14P 14 Pin-Sockel mit 7.62 mm Breite Sockel, zwei Stück, aneinander gelegt geben sie die 28 Pins — 28 Pin-Sockel in der passenden Breite sind schwieriger zu kriegen
1 ATMEGA 8-16 DIP ATMega8 Mikrocontroller höchst persönlich (der ATMEGA 8L8 DIP, welcher mit weniger Spannung auskommt, jedoch nur 8 Mhz schafft, kann alternativ verwendet werden) — oder eben ein ATMEGA 168-20DIP
Grundschaltung um einen Atmel programmieren zu können

Grundschaltung um einen Atmel programmieren zu können

+5V und Masse müssen mit der Stromversorgung verbunden werden, die ich gleich genauer erkläre.

Stromversorgung

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 µA 7805 5V-Spannungsregler Stromversorgung
1 RAD 10/35 10µ-Kondensator Stromversorgung
1 KERKO 100N 100 Nano-Kondensator Stromversorgung
Der Spannungsregler erzeugt stabilisierte 5V

Der Spannungsregler erzeugt stabilisierte 5V

Der L7805 ist ein Spannungsregler der die Spannung auf 5V runterregelt. Als Eingangsspannung sind ca. 7 bis 30 Volt in Ordnung.

In manchen Anwendungsfällen muss man diese Stromversorgung etwas abändern, da an dem Spannungsregler ca. 1V Spannung abfällt (d. h. man muss mind. mit 6V an den Eingang), in manchen Anwendungen die Spannung jedoch sehr knapp ist (z. B. Akku-betriebene Modellautos).

Quarz

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 4-HC18 8Mhz -uarz Quarz um Genauigkeit des Atmels zu erhöhen (für USART oder eine Uhr z. B.); Es können bis zu 16MHz-Quarze verwendet werden (bzw. beim ATMEGA168 auch 20MHz-Quarze)
2 KERKO 27P Kondensator Kondensatoren zum Einschwingen des Quarzes
Durch den Quarz wird das Zeitgefühl des Atmels verbessert

Durch den Quarz wird das Zeitgefühl des Atmels verbessert

Der an Pin 9 und 10 (XTAL1/2) anzuschließende externe Quarz dient dazu, dem Mikrocontroller ein «Taktgefühl» zu spendieren, damit er Zeiten einschätzen kann. Der Atmel hat einen internen Oszillator, der das schon ganz gut macht. Der ist jedoch ungenauer. Schließt man einen externen Quarz an, ist der Atmel sogar genau genug, um als Uhr verwendet zu werden. Der Quarz legt auch fest, wie schnell der Atmel läuft. Schließt man einen 16Mhz-Quarz an, ist der Atmel schneller, als wenn man einen 4Mhz-Quarz anschließt. Die Polung des direkt anschließbaren Quarzes ist irrelevant. Beide Pins des Quarzes werden zusätzlich über einen 27Pico-Kondensator mit der Masse verbunden.

Tipps zum Zusammenbauen der Hardware

Es ist darauf zu achten, dass der Pin 1 rechts von der Kerbe des Sockels liegt, dann hochgezählt wird bis zu Pin 14, links oben mit Pin 15 fortgesetzt und dann wieder heruntergezählt wird, bis man bei Pin 28 ankommt.

Wenn alles fertig gelötet ist, sollte man den Atmel noch nicht aufstecken, sondern erst die Stromversorgung anschließen und direkt am Sockel des Mikrocontrollers messen, ob die Stromversorgung korrekt funktioniert. Sollte irgendwo ein Kurzschluss versteckt sein wird der Spanungsregler heiß und riecht komisch … Der Spannungsregler hält ziemlich viel aus, die anderen Bauteile sind sowieso unempfindlich. Steckt man den Atmel jedoch auf die Elektronik, bevor man alles sorgfältig geprüft hat, kann er kaputt gehen!

Damit der Atmel in den Sockel passt, müssen evtl. die Beine leicht gebogen werden. Dabei vorsichtig vorgehen, damit kein Bein abbricht. Das Aufstecken vom Atmel nur vornehmen, während der Strom abgesteckt ist! Es ist darauf zu achten, dass er richtig rum eingesetzt wird. Das heißt dass die Kerbe auf die Kerbe des Sockels muss.

Wenn das Programmieren nicht funktioniert ist es gut möglich, dass man einfach das Programmierinterface falsch an den Atmel angeschlossen hat, dort verwechselt man die Pins relativ leicht.

Testen der Hardware & setzen der Fuse-Bits

Das Kabel vom Parallelport kann auf die Elektronik aufgesteckt werden, es ist zum Programmieren notwendig.

Du glaubst, deine Schaltung ist fertig? Du hast sie mit Strom versorgt und sie an den Parallelport angeschlossen? Die Software ist installiert? Gut! Ich gehe nicht auf die Programmierung mit Ponyprog ein. Mir tut das leid, aber ich habe unter Windows noch nie einen Mikrocontroller programmiert und kann dir dabei auch nicht helfen. Ich erkläre aber uisp. Man kann uisp auch irgendwie mit Windows verwenden.

Prüfe die Hardware in dem du als root unter Linux in die Konsole uisp -dplt=/dev/parport0 -dprog=dapa eingibst. Im Idealfall erscheint die Rückmeldung Atmel AVR ATmega8/168 is found. Wenn nicht, musst du deine Hardware nochmal überprüfen.

Du solltest jetzt den internen Oszillator einstellen bzw. auf den externen umschalten. Per uisp -dlpt=0x378 -dprog=dapa --wr_fuse_l=0xe1 kann der interne Oszillator auf 1Mhz eingestellt werden. uisp -dlpt=0x378 -dprog=dapa --wr_fuse_l=0xe3 stellt ihn auf 4Mhz ein, uisp -dlpt=0x378 -dprog=dapa --wr_fuse_l=0xe4 auf 8Mhz. uisp -dlpt=0x378 -dprog=dapa --wr_fuse_l=0xee legt fest, dass der externe Quarz verwendet wird. Wenn der externe Quarz nicht funktioniert, kann man den Atmel nicht mehr zurückstellen, da der eingestellte Oszillator funktionieren muss!

Wenn man einmalig als root chmod 666 /dev/parport0 aufruft müsste das Programmieren in Zukunft auch für normale User erlaubt sein.

LED

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 1/4W 470 470 Ohm-Widerstand Vorwiderstand für die LED
1 LED 5-4500 RT Superhelle LED Digitaler Ausgang
Um eine LED anzusteuern muss man diese lediglich über einen Vorwiderstand gegen den Pluspol oder die Masse schalten. Je nach Polung ist zu beachten dass man dann den Port ausschalten muss, wenn man die LED gegen den Pluspol schaltet, oder dass man den Port einschalten muss wenn die LED gegen Masse verbunden ist, damit selbige leuchtet.

Um eine LED anzusteuern muss man diese lediglich über einen Vorwiderstand gegen den Pluspol oder die Masse schalten. Je nach Polung ist zu beachten dass man dann den Port ausschalten muss, wenn man die LED gegen den Pluspol schaltet, oder dass man den Port einschalten muss wenn die LED gegen Masse verbunden ist, damit selbige leuchtet.

Zur Ansteuerung siehe unten wie das Ansteuern digitaler Ausgänge funktioniert.

Taster

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 TASTER 1022 Taster Digitaler Eingang
Den Taster gegen Masse schalten und fertig!

Den Taster gegen Masse schalten und fertig!

Zum Einlesen siehe unten wie das Lesen digitaler Eingänge funktioniert.

Serielle Schnittstelle

Serielle Schnittstelle? Sowas altmodisches? Heutzutage gibts USB? USB ist unpraktisch! USB ist viel zu kompliziert. USB-Module sind teuer. Fast alle Desktop-PCs und viele Notebooks haben außerdem immer noch eine serielle Schnittstelle. Aufjedenfall ist diese gut zur Kommunikation mit dem Atmel geeignet. Auch verschiedene Atmels untereinander können so kommunizieren.

Es gibt aber auch Chips, die das RS232-Signal auf eine USB-Schnittstelle oder gar auf eine Ethernet-Schnittstelle übertragen.

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 D-SUB BU 09 9-Polige DSUB-Buchse Buchse für serielle Schnittstelle
2 BC 550B Transistoren Invertierung von Pins
2 1/4W 4,7K 4,7 Kiloohm-Widerstand Schutzwiderstände
2 1/4W 10K 10 Kiloohm-Widerstand Schutzwiderstände
Die zwei Transistoren invertieren das Signal, da ein PC unter einer 1 exakt das Gegenteil wie der Atmel versteht; kommunizieren zwei Atmels direkt miteinander, benötigt man keine zusätzliche Elektronik, sondern kann die Pins direkt gekreuzt verbinden

Die zwei Transistoren invertieren das Signal, da ein PC unter einer 1 exakt das Gegenteil wie der Atmel versteht; kommunizieren zwei Atmels direkt miteinander, benötigt man keine zusätzliche Elektronik, sondern kann die Pins direkt gekreuzt verbinden

An Pin 2 kann der Atmel serielle Daten empfangen, an Pin 3 welche senden. Da sich Atmel und PC nicht ganz einig sind, was eine 1 und was eine 0 ist, müssen die Pins invertiert werden, d. h. wenn der PC eine 1 schickt schickt man dem Atmel eine 0 und andersrum. Dazu sind die Transistoren da. Die gängige Lösung würde dafür einen MAX232-IC vorsehen, ich halte das jedoch für komplizierter. Außerdem hat man die Teile für diese Transistorschaltung oft schon da — es lässt sich statt dem BC550B praktisch jeder andere NPN-Transistor einsetzen.

Um eine ausreichende Genauigkeit zu erreichen, ist ein externer Quarz anzuschließen.

Wenn du nicht weißt, wie die Pin-Belegung eines Transistors aussieht, schau dir doch diese Skizze an:

Ansicht eines Transistors von unten

Ansicht eines Transistors von unten

Die Software um den USART zu verwenden gibts weiter unten.

Servo

Ein Servo ist ein Gleichstrommotor mit integriertem Potentiometer, um die aktuelle Position zu überwachen. Man kann ihm ein Signal schicken, das bestimmt, welche Position angefahren werden soll. Servos kann man recht günstig im Internet kaufen. Um einen Servo zu verwenden benötigt man hardwareseitig garkeine Elektronik — der Servo hat vermutlich einen Stecker mit drei Pins, zwei für die Stromversorgung, die direkt angeschlossen werden können (wenn von der Spannung die das Netzteil liefert her möglich idealerweise vor den Spannungsregler, damit dieser nicht unnötig belastet wird) und ein Pin, den man direkt an einen IO des Atmels anschließen kann. Die Drehung des Servos regelt man durch ein moduliertes Signal, dazu später mehr.

Gleichstrommotor

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 L 293 B Motortreiber, maximale Gesamtbelastung von 4A Steuern von Gleichstrommotoren
8 1N 4007 Dioden Schutzdioden für 2 Motoren
Zu jedem Ausgang des Motortreibers muss eine Diode von der Masse aus hinlaufen und eine in Richtung Pluspol weglaufen; die IOs des Atmels müssen mit den Eingängen des Motortreibers verbunden werden; die Ausgänge des Motortreibers gehen zu den Motoren selbst; in den Ecken der Motortreiber befindet sich das Chip-Enable und die Spannungsversorgung, d. h. alle vier Ecken müssen an die Spannungsquelle gehen; die vier mittleren Pins gehören an die Masse

Zu jedem Ausgang des Motortreibers muss eine Diode von der Masse aus hinlaufen und eine in Richtung Pluspol weglaufen; die IOs des Atmels müssen mit den Eingängen des Motortreibers verbunden werden; die Ausgänge des Motortreibers gehen zu den Motoren selbst; in den Ecken der Motortreiber befindet sich das Chip-Enable und die Spannungsversorgung, d. h. alle vier Ecken müssen an die Spannungsquelle gehen; die vier mittleren Pins gehören an die Masse

Um einen Gleichstrommotor ansteuern zu können benötigt man zwei IOs und ein paar Schutzdioden und einen Motortreiber, der mit dem geringen Strom der vom Atmel kommt den großen Strom für den Motor steuert, der möglichst direkt von der Spannungsquelle, nicht vom Spannungsregler bezogen werden sollte (da dieser eine solche Belastung nicht lange aushält) — dabei muss natürlich auch einbezogen werden, welche Spannung vor dem Spannungsregler anliegt und welche Spannung von den Motoren benötigt wird. Der Spannungsregler schafft bis zu 36V bei 1A pro jedem der vier Kanäle. Zwei IOs pro Motor benötigt man, um die Drehrichtung einstellen zu können. Der Motortreiber «L 293 B» kann sogar zwei Motoren steuern.

Pro Motor muss einer der beiden IOs vom Atmel ein PWM-Port sein (OC1xx), damit sich die Geschwindigkeit der Motoren regeln lässt!

Es gibt einen extra Abschnitt nur über die Ansteuerung von Gleichstrommotoren, es lohnt sich aber vorher den PWM-Teil zu lesen.

Fototransistor

Mit einem Fototransistor kann man die Helligkeit messen. Das ist u. A. dafür gut, auf eine Oberfläche zu leuchten und mit dem Fototransistor die Reflexion zu bestimmen, um z. B. zwischen weißem und schwarzem Untergrund unterscheiden zu können.

Anzahl Bezeichnung Bauteilart Verwendungszweck
1 BPW 17N Fototransistor Messen der Helligkeit
1 1/4W 68K 68k-Widerstand Pullup
Der Fototransistor geht vom Atmel zur Masse, über einen Pullup-Widerstand auf der Atmel-Seite wird das Signal hochgezogen

Der Fototransistor geht vom Atmel zur Masse, über einen Pullup-Widerstand auf der Atmel-Seite wird das Signal hochgezogen

Einlesen analoger Signale

Software

Übertragen eines Programms auf den Atmel

Um ein Programm unter Linux zu kompilieren und zu übertragen benötigt man folgende Befehlsabfolge: # Programm kompilieren und vorbereiten
avr-gcc -g -mmcu=atmega8 -Wall -Wstrict-prototypes -Os -mcall-prologues -Os -c main.c
avr-gcc -g -mmcu=atmega8 -Wall -Wstrict-prototypes -Os -mcall-prologues -o main.out -Wl,-Map,main.map main.o
avr-objcopy -R .eeprom -O ihex main.out main.hex
# Den Flash-Speicher auf dem Atmel leeren
uisp -dplt=/dev/parport0 -dprog=dapa --erase
# Neues Programm übertragen
uisp -dplt=/dev/parport0 --upload if=main.hex -dprog=dapa -v=3 --hash=32
# Temporäre Dateien entfernen
rm -f *.o *.map *.out *.hex
Möchte man mehrere Dateien in ein Programm einkompilieren, müssen die ersten zwei Zeilen zum Kompilieren so aussehen: avr-gcc -g -mmcu=atmega8 -Wall -Wstrict-prototypes -Os -mcall-prologues -Os -c main.c Liste weiterer Dateien mit Leerzeichen getrennt
avr-gcc -g -mmcu=atmega8 -Wall -Wstrict-prototypes -Os -mcall-prologues -o main.out -Wl,-Map,main.map main.o Liste weiterer Dateien mit Leerzeichen getrennt, jeweils mit der Dateiendung .o
Um diesen umständlichen Kompilierungs- und Übertragungsprozess zu umgehen habe ich mir ein kleines Programm geschrieben, dass das alles automatisch macht. Ich kann nur empfehlen, ein Bash-Skript zu basteln, das sowas allgemeingültig macht, oder ein Makefile zu verwenden. Linuxfocus bietet gegen Ende der Seite ein Beispielprogramm mit Makefile zum Download an.

Bits lesen und schreiben

Was ist ein Bit? Ein Bit ist eine 1 oder eine 0. Ein Bit ist die kleinste Einheit, die ein Computer kennt. 1 Bit sagt nur „Ja” oder „Nein”, „Wahr” oder „Falsch”.

Bits lesen und schreiben macht man, wenn man am PC programmiert, praktisch nie. Auf einem Mikrocontroller braucht man das aber dauernd — alle wichtigen Informationen stehen in einem Bit. Ob ein Pin Ein- oder Ausgang ist, ob der Pullup an ist, ob eine 0 oder 1 an einem Eingang anliegt, ob ein Ausgang an oder aus ist usw. Deshalb ist es notwendig, zu wissen, wie man ein Bit leert, ein Bit schreibt und ein Bit liest. Die avr-libc bietet für alle drei Aufgaben fertige Funktionen an. Ich erwähne sie aber nicht. Ich erkläre die normale C-Syntax, weil das Programm dann ähnlicher zu C-Programmen am Computer ist.

Bit 3 der Variable foobar leeren: foobar &= ~(1 << 3); Alle Bits von foobar leeren: PORTC = 0x00; Bit 7 des Registers PORTC setzen: PORTC |= (1 << 7); Bit 7 und 8 von PORTC auf einmal setzen: PORTC |= (1 << 7) | (1 << 8); Bit 7 und 8 von PORTC auf einmal setzen, alle anderen Bits leeren: PORTC = (1 << 7) | (1 << 8); Alle Bits von PORTC setzen: PORTC = 0xff; Bit 7 der Variable foobar auf Wert 1 überprüfen: if (foobar & (1 << 7)) { ... } Bit 7 der Variable foobar auf Wert 0 überprüfen: if (!(foobar & (1 << 7))) { ... }

Grundgerüst

Das Grundgerüst eines C-Programms für einen Atmel sieht so aus: #include <avr/io.h>
// noch mehr header-dateien einbinden
// globale variablen definieren
// funktionen definieren
int main(void)
{
    // hauptprogramm
}
Endlosschleifen auf einem Atmel (wie mit while(1)) sind nicht schlimm — schließlich läuft ja ein und das selbe Programm unendlich lang, und es laufen keine anderen Prozesse parallel. Man kann die gesamte Rechenleistung für sich nutzen.

Ansteuerung von digitalen Ausgängen

Um einen Pin als Ausgang zu setzen muss man in einem Register (DDRx) vermerken, dass es sich bei dem Pin um einen Ausgang und keinen Eingang handelt und in einem anderen Register (PORTx) einstellen, ob der Ausgang aktiviert sein soll oder nicht: DDRD |= (1 << PD0); // PD0 als Ausgang festlegen
PORTD |= (1 << PD0); // PD0 aktivieren
Alle angeschlossenen, gegen Masse geschalteten LEDs leuchten lassen: DDRB = 0xff; DDRC = 0xff; DDRD = 0xff;
PORTB = 0x00; PORTC = 0x00; PORTD = 0x00;
LED an PD0 blinken lassen: #include <avr/io.h>
#define F_CPU 4000000UL /* 4 Mhz-Takt; hier richtigen Wert eintragen */
#include <util/delay.h>
int main(void)
{
    DDRD |= (1 << PD0); // PD0 als Ausgang festlegen
    while(1) // Unendlich lang wiederholen
    {
        PORTD |= (1 << PD0); // PD0 aktivieren
        _delay_ms(63);
        PORTD &= ~(1 << PD0); // PD0 deaktivieren
        _delay_ms(63);
    }
}

Ansteuern von Puls-Width-Modulation-Ports

Todo: PWM.

Vorläufige Notiz: Um Pulsbreitenmodulation zu nutzen muss der Pin erstens als Ausgang definiert werden und zweitens müssen einige Register gesetzt werden, um die Pulsbreitenmodulation zu initialisieren. Setzt man OCRxx = 255 (mit obiger Schaltung entspricht das 5V) ist das die maximale Frequenz, 0 ist das Minimum, 128 Mittelwert. Der folgende Code kann alle 6 PWM-Ports nutzen, im Normalfall reichen natuerlich weniger Ports, d. h. man kann abhängig vom Einsatzzweck nur ein paar der folgenden Abschnitte verwenden.
/* Timer 0 für PWM-Nutzung initialisieren */
    TCCR0A = (1 << WGM00)  // mode 3, fast pwm, update at top
           | (1 << WGM01); // tov1 flag set on top, top=0xff
    TCCR0B = (1 << CS01)   // clock select clk/64
           | (1 << CS00);
/* OC0A als PWM-Port setzen */
    DDRD |= (1 << PD6);
    TCCR0A |= (1 << COM0A1);
    OCR0A = 128;
/* OC0B als PWM-Port setzen */
    DDRD |= (1 << PD5);
    TCCR0A |= (1 << COM0B1);
    OCR0B = 128;
/* Timer 1 für PWM-Nutzung initialisieren */
    TCCR1A = (1 << WGM11); // mode 14, fast pwm, update at top
    TCCR1B = (1 << WGM13)  // tov1 flag set on top
           | (1 << WGM12)  // top = ICR1
           | (1 << CS11)   // clock select clk/64
           | (1 << CS10);
    ICR1 = 255;
/* OC1A als PWM-Port setzen */
    DDRB |= (1 << PB1);
    TCCR1A |= (1 << COM1A1);
    OCR1A = 128;
/* OC1B als PWM-Port setzen */
    DDRB |= (1 << PB2);
    TCCR1A |= (1 << COM1B1);
    OCR1B = 128;
/* Timer 2 für PWM-Nutzung initialisieren */
    TCCR2A = (1 << WGM20)  // mode 3, fast pwm, update at top
           | (1 << WGM21); // tov1 flag set on top, top=0xff
    TCCR2B = (1 << CS22);  // clock select clk/64
/* OC2A als PWM-Port setzen */
    DDRB |= (1 << PB3);
    TCCR2A |= (1 << COM2A1);
    OCR2A = 128;
/* OC2B als PWM-Port setzen */
    DDRD |= (1 << PD3);
    TCCR2A |= (1 << COM2B1);
    OCR2B = 128;

Gleichstrommotor mit L293B

Todo: Welche Signale führen zu welcher Drehung der Motoren? Wie regelt man Richtung und Geschwindigkeit?

Vorläufige Notiz: Ist der digitale Port auf High und der PWM-Port auf eine niedrige Frequenz gesetzt dreht sich der Motor in eine Richtung, ist der PWM-Port auf eine hohe Frequenz gesetzt und der Digital-Port auf Low dreht er sich in die andere. Um so näher die Frequenz ans Maximum/Minimum kommt, desto langsamer dreht sich der Motor.

Lesen von digitalen Eingängen

Entfernt man das entsprechende Bit im DDRx-Register hat man einen Pin als Eingang konfiguriert. PORTx kann dann gesetzt werden, um festzulegen, dass der Pullup aktiviert werden soll. (Das wird z. B. bei Tastern gebraucht, damit die sinnvolle Werte lesen.) Im Register PINx stehen die Messwerte, d. h. ein Bit ist z. B. dann gesetzt, wenn der Taster gedrückt ist: DDRD &= ~(1 << PD0); // PD0 als Eingang festlegen
PORTD |= (1 << PD0); // Pullup für PD0 aktivieren
if (PIND & (!(1 << PD0))) { ... } // Wert von PD0 prüfen
In der Praxis hat man beim Einlesen von Tastern ein Problem damit, dass diese prellen. Das bedeutet, dass man nicht kurz drückt, und der Taster ist kurz an, sondern dass dieser Anfangs ganz schnell an und aus geht. Um das Problem zu umgehen kann man den Wert des Tasters lesen, eine zehntel Sekunde warten und prüfen, ob der Taster immernoch an ist. Das geht mit der Funktion _delay_ms(), Headerdatei «utils/delay.h».

Lesen von analogen Eingängen

uint16_t adc(uint8_t admux)
{
    ADCSRA  =  (1<<ADEN)  | (1<<ADPS1) | (1<<ADPS0);
    ADMUX   =  admux;
    ADMUX  |=  (1<<REFS1) | (1<<REFS0);
    ADCSRA |=  (1<<ADSC);
    while      (ADCSRA    & (1<<ADSC));
    uint16_t val     = ADCW;
    ADCSRA &= ~(1<<ADEN);
    return val;
}
Die ADC-Funktion kann so aufgerufen werden: uint16_t sensor = adc(5); // Lesen von dem an Pin 28 anliegendem analogen Signal Ein kompliziertes Beispielprogramm, das die ADC-Werte über den USART des Atmels leicht lesbar macht, findet sich bei den Downloads.

Ansteuern von Servos

Um dem Servo zu sagen welchen Winkel er anfahren soll muss man alle 20ms ein Signal mit einer Dauer zwischen 1 und 2ms ausgeben, welches den anzufahrenden Winkel bestimmt. Implementiert ist das in meiner Library.

Datenübertragung mit der seriellen Schnittstelle

Folgende Funktionen sind ins Programm einzufügen: #include <avr/interrupt.h>
#define UART_UBRR_CALC(BAUD_,FREQ_) ((FREQ_)/((BAUD_)*16L)-1)
#define UART_BAUD_RATE      2400
#define UART_BUFFER            10
uint8_t uart_buffer[UART_BUFFER];
int8_t  uart_count  = 0         ;
uint8_t uart_getc(void)
{ /* output next char in cache
*/
    if (uart_count == 0) return 0;
    return uart_buffer[--uart_count];
}
ISR(SIG_UART_RECV)
{ /* INTERRUPT: receive char from UART and save it the buffer
*/
    if (uart_count < UART_BUFFER) uart_buffer[uart_count++] = UDR;
}
inline void uart_init(uint8_t tx, uint8_t rx)
{ /* initializes UART communication;
   * tx: sending should be activated
   * rx: receiving should be activated
   */
    uint16_t baudrate;
    // Baud-Rate setzen
    baudrate = UART_BAUD_RATE/2;
    UBRRH    = (uint8_t) (UART_UBRR_CALC(baudrate,F_CPU)>>8);
    UBRRL    = (uint8_t) UART_UBRR_CALC(baudrate,F_CPU);
    // Ggf. TX und RX anschalten
    UCSRA |= (1<<U2X);
    UCSRB |= (1<<RXCIE);
    if(tx) UCSRB |= (1<<TXEN);
    if(rx) UCSRB |= (1<<RXEN);
    sei();
    pause(1);
}
void uart_putc(uint8_t c)
{ /* send one char per UART
   */
    while (!(UCSRA & (1<<UDRE)));
    UDR = c;
}
void uart_puts(uint8_t *data)
{ /* send string per UART
   */
    while(*data)
    {
        uart_putc(*data);
        data++;
    }
}
void uart_puti(uint16_t data)
{ /* send integer per UART
   */
    uint8_t buffer[7];
    uart_puts(utoa(data, buffer, 10));
}
void uart_putf(float data)
{ /* send float per UART
     BUG: unnötige Leerzeichen am Anfang?
   */
    uint8_t buffer[7];
    uart_puts(dtostrf(data, 6, 2, buffer));
}
«UART_BUFFER» stellt ein, wie viele Zeichen gecached werden können. Wird uart_getc() zu selten aufgerufen, werden alte Zeichen verworfen. «UART_BAUD_RATE» stellt die Kommunikationsgeschwindigkeit ein.

Per uart_init(1, 1) wird der USART initialisiert. Per uin8t_t zeichen = uart_getc() lassen sich empfangene Zeichen auslesen. uart_puts("Hello World!\n") sendet den String raus. uart_putc(), uart_puti(), uart_putf() können einzelne Zeichen, Integer, Floats rausschicken.

Verwendung des EEPROMs

Siehe mikrocontroller.net — der Inhalt ist dort gut und verständlich dargestellt.

Meine Library

Wozu ist sie gut?

Meine Library enthält Funktionen zur Verwendung des ADC, des USARTs, von bis zu 10 Servos und zur Abstraktion von IOs: man spricht sie nicht mehr über zahlreiche Register, sonder nur noch über eine einzige Nummer an. Diese entspricht der Pinnummer. So können in einer Konfigurationsdatei den Nummern Konstanten die die an den Pin angeschlossene Elektronik benennen definiert werden, und später über diese Konstante der IO gesetzt, gelesen (analog/digital) oder der IO als Servo definiert werden. Diese zusätzliche Abstraktionsebene macht die Programmierung im Hauptprogramm deutlich einfacher und übersichtlicher, da man nicht mehr ständig mit den Registern der IOs herumhantieren und diese umständlich zwischen Funktionen hin- und herschicken muss. Weiterhin hat man es leichter, wenn man den Programmcode mal auf einen anderen Prozessor portieren muss.

Wie verwende ich sie?

Die unten verlinkten Dateien sind alle vier in einem Verzeichnis zu speichern. In der «config.h» muss F_CPU angepasst werden, weiterhin sollten die Einstellungen für den UART und die Servos definiert werden. Die Konstanten für die natürlichsprachliche Bezeichnung der IOs kommem ebenfalls dorthin, ein Beispiel ist als Kommentar notiert. «UART_BUFFER» sollte nur definiert werden, wenn man mit dem USART auch Daten empfangen möchte. Wenn die Konstante «SERVOS» definiert ist, wird der entsprechende Interrupt für die Software-Pulsbreitenmodulation aktiviert. Die 10 Konstanten («SERVO0» bis «SERVO9») bekommen 255 zugewiesen, wenn der Servo ungenutzt ist, andernfalls die Pin-Nummer, an der der Servo angechlossen ist. Um die Servos zu nutzen muss man sie erst initialisieren und kann anschließend einen Wert zwischen 0 und 255 als Winkel setzen, der angefahren werden soll. Viele Servos sind von der Mechanik her nicht perfekt, weswegen sie nicht bis zum Anschlag fahren können. Es empfiehlt sich deshalb, nur den Wertebereich von 5 bis 250 zu nutzen. servo_init();
servo_set(SERVO0, 128); // Mittelstellung
Die UART-Funktionen sind oben beschrieben. Um einen IO zu setzen, verwendet man die folgende Syntax: io_set(23, 0); // deaktivierter Ausgang
io_set(24, 1); // aktivierter Ausgang
io_set(25, 2); // Eingang mit aktiviertem Pullup
io_set(26, 3); // Eingang mit deaktiviertem Pullup
Die Funktion io_get() liest den Zustand eines digitalen Eingangs ein: if (io_get(25))
{
    // Taster gedrückt
}
Die adc()-Funktion ruft man ebenfalls mit einer Pinnummer als Parameter auf, sie gibt dann einen Wert zwischen 0 und 1024 abhängig vom anliegenden analogen Signal zurück.

Beim Kompilieren muss die Datei «lib.c» zusätzlich zur «main.c» einbezogen werden.

Download

main.c
Quellcode ansehen | Download
config.h
Quellcode ansehen | Download
lib.h
Quellcode ansehen | Download
lib.c
Quellcode ansehen | Download
Beispielprogramm: ADC-Werte lesen und über die serielle Schnittstelle ausgeben
Quellcode ansehen | Download

© 2009 Julian von Mendel (http://derjulian.net) | Datum: 27.05.2017