AVR-Tutorial
Ausser diesem Tutorial gibt es noch das AVR-GCC-Tutorial sowie die Artikel in der Kategorie:avr-gcc Tutorial.
Aufbau des Tutorials
- Einleitung: Worum geht es überhaupt?
- Benötigte Ausrüstung: Welche Hard- und Software brauche ich, um AVR-Mikrocontroller zu programmieren?
- I/O-Grundlagen: Wie kann ich Taster und LEDs an einen AVR anschließen und benutzen?
- Logik: Verschiedene Grundoperationen und Verknüpfungen
- Arithmetik: Verschiedene Grundoperationen
- Der Stack: Was ist der Stack und wie funktionieren Unterprogrammaufrufe?
- LCD: Ansteuerung eines LC-Displays im 4bit-Modus
- Interrupts: Was sind Interrupts und wie kann ich sie verwenden?
- Vergleiche: Wie werden Entscheidungen getroffen?
- Mehrfachverzweigung: Eine Variable auf mehrere Werte prüfen.
- Der UART: Wie kann ich Daten zwischen einem Mikrocontroller und einem PC austauschen?
- Flash, EEPROM, RAM: Die verschiedenen Speicherarten des AVR und ihre Anwendung.
- Die Timer: in regelmäßigen Zeitabständen Dinge tun.
- Die Timer: Uhr und CTC Modus.
- Der ADC: Die Brücke von der analogen zur digitalen Welt.
- Tasten: Einzelne Tastendrücke und Entprellen.
- PWM: Ein Timer dimmt eine LED.
- Schieberegister: Ausgabe-/Eingabeport erweitern.
- SRAM: Wenn die vorhandenen Register nicht mehr reichen.
- 7-Segment Anzeigen: Was ist Multiplexing?
- weltbewegende Dinge oder: wie ist das mit Servos?
- Der Watchdog und dessen Wirkungsweise.
- Power Management: Strom sparen
Falls ihr irgendwelche Fragen habt, stellt diese bitte im Forum!
Was ist ein Mikrocontroller?
Ein Mikrocontroller ist ein Prozessor. Der Unterschied zu PC-Prozessoren besteht darin, dass bei einem Mikrocontroller Speicher, Digital- und Analog-Ein- und -Ausgänge etc. meist auf einem einzigen Chip integriert sind, so dass eine Mikrocontroller-Anwendung oft mit wenigen Bauteilen auskommt.
Mikrocontroller werden als erstes an der Bit-Zahl des internen Datenbusses unterschieden: 4bit, 8bit, 16bit und 32bit. Diese Bit-Zahl kann man als die Länge der Daten interpretieren, die der Controller in einem Befehl verarbeiten kann. Die größte in 8 Bit (= 1 Byte) darstellbare Zahl ist die 255, somit kann ein 8-Bit-Mikrocontroller z. B. in einem Additionsbefehl immer nur Zahlen kleiner-gleich 255 verarbeiten. Zur Bearbeitung von größeren Zahlen werden dann jeweils mehrere Befehle hintereinander benötigt, was natürlich länger dauert. Ein Mikrocontroller braucht zum Betrieb, wie jeder andere Prozessor auch, einen Takt. Die maximale Taktfrequenz mit der ein Controller betrieben werden kann, reicht von 1 MHz bei alten Controllern bis hin zu über 100 MHz bei teuren 32-Bittern. Diese Taktfrequenz sagt jedoch noch nichts über die tatsächliche Geschwindigkeit eines Prozessors aus. So wird z. B. bei den meisten 8051-Controllern die Frequenz intern durch 12 geteilt, ein mit 24 MHz getakteter 8051 arbeitet also eigentlich nur mit 2 MHz. Benötigt dieser dann für einen Befehl durchschnittlich 2 Taktzyklen, so bleiben "nur" noch 1 Mio. Befehle pro Sekunde übrig - ein AVR, der ungeteilt mit 8MHz arbeitet und für die meisten Befehle nur einen Zyklus braucht, schafft dagegen fast 8 Mio. Befehle pro Sekunde.
Wozu ist ein Mikrocontroller gut?
Hier ein paar Beispiele, für welche Aufgaben Mikrocontroller verwendet werden (können):
- Ladegeräte, komplexere Stromversorgungen, Umrichter
- Motorsteuerungen, CNC-Maschinen
- Roboter
- Messwerterfassung (z. B. Drehzahlmessung im Auto), Datenlogger
- Temperaturregler, Drehzahlregler
- MP3-Player, Uhren, Armbanduhren (etwa mit ausgefallenen Extras)
- Schaltuhren, SPS-Ersatz
- Alarmanlagen
- LED-Matrizen (Blinkmuster etc.)
- Zur Steuerung und Regulierung von Flüssigkeiten
- „Konsumgüter“-Steuerungen, etwa im Eigenbau-Radio oder Amateurfunkgerät, Fernbedienungen, PC-Zubehör
- ...
Welchen Mikrocontroller soll ich verwenden?
Typische Anforderungen an einen Mikrocontroller für Hobbyanwender (einige davon konkurrieren miteinander):
- Gute Beschaffbarkeit und geringer Preis
- Handliche Bauform: Ein Controller mit 20 Pins ist leichter zu handhaben als einer mit 128
- Flash-ROM: Der Controller sollte mindestens 1000 mal neu programmiert werden können
- In-System-Programmierbarkeit (ISP): Man benötigt kein teures Programmiergerät und muss den Controller zur Programmierung nicht aus der Schaltung entfernen
- Kostenlose Software verfügbar: Assembler bekommt man praktisch immer kostenlos
Weitere Entscheidungskriterien sind im Artikel Entscheidung Mikrocontroller zusammengefasst.
Viele dieser Anforderungen werden von den 8-bit-AVR-Controllern von Atmel erfüllt. Deshalb werde ich einen AVR, genauer gesagt den ATmega8, in diesem Tutorial einsetzen.
Und damit kein Missverständnis aufkommt: So etwas wie den "besten" Controller gibt es nicht. Es hängt immer von der Aufgabenstellung ab, welcher Controller gut dafür geeignet ist. Natürlich haben sich einige Controller als Standardtypen in der Praxis durchgesetzt, mit denen man in vielen Fällen ein gutes Auslangen hat und die mit ihrer Leistungsfähigkeit einen weiten Bereich abdecken können. Der ATmega8 ist z. B. so einer. Aber daneben gibt es noch viele andere.
In welcher Sprache soll programmiert werden?
Je nach Anforderungsfall bieten sich verschiedene Sprachen an:
Vorbemerkung
Warum ist dieses Tutorial für Assembler geschrieben, wo es doch einen kostenlosen C-Compiler (WinAVR, AVR-GCC) und einen billigen Basic-Compiler gibt?
Assembler ist für den Einstieg "von der Pike auf" am besten geeignet. Nur wenn man Assembler anwendet, lernt man den Aufbau eines Mikrocontrollers richtig kennen und kann ihn dadurch besser nutzen; außerdem stößt man bei jedem Compiler irgendwann mal auf Probleme, die sich nur oder besser durch das Verwenden von Assemblercode lösen lassen und sei es nur, dass man das vom Compiler generierte Assemblerlisting studiert, um zu entscheiden, ob und wie man eine bestimmte Sequenz im C-Code umschreiben soll, um dem Compiler das Optimieren zu ermöglichen/erleichtern.
Allerdings muss auch erwähnt werden, dass das Programmieren in Assembler besonders fehleranfällig ist und dass es damit besonders lange dauert, bis das Programm erste Ergebnisse liefert. Genau aus diesem Grund wurden "höhere" Programmiersprachen erfunden, weil man damit nicht immer wieder "das Rad neu erfinden" muss. Das gilt besonders, wenn vorbereitete Programmblöcke zur Verfügung stehen, die man miteinander kombinieren kann. Auch der Geschwindigkeitsvorteil ist selten und nur bei kritischen Anwendungen von Bedeutung. Heutige Compiler generieren zudem oft schnelleren oder kleineren Code als handgeschriebene Assemblerroutinen. Wer regelmäßig programmieren und auch längere Programme schreiben möchte, dem sei deshalb geraten, nach diesem Assembler-Tutorial C zu lernen, zum Beispiel mit dem AVR-GCC-Tutorial.
Wer C schon kann, für den bietet es sich an, das Tutorial parallel in C und Assembler abzuarbeiten. Die meisten hier vorgestellten Assemblerprogramme lassen sich relativ einfach in C umsetzen. Dabei sollte großes Augenmerk darauf gelegt werden, dass die dem Programm zugrunde liegende Idee verstanden wurde. Nur so ist ein vernünftiges Umsetzen von Assembler nach C (oder umgekehrt) möglich. Völlig verkehrt wäre es, nach sich entsprechenden 'Befehlen' zu suchen und zu glauben, damit hätte man dann ein Programm von Assembler nach C übersetzt.
Assembler
Die Vorteile von Assembler wurden bereits genannt:
- direkter Einstieg in die Hardware
- keine Abhängigkeit von Compilern und deren Fehlern, bzw. Missinterpretation, keine Seiteneffekte
- optimaler Code erzeugbar
- sehr schnell in der Ausführung
- Feintuning der Geschwindigkeitsreserven
- kurzer Weg zu hardwarenahen Funktionen
- sehr langer Weg zu komplexeren Funktionen
Basic
Basic bietet den einfachsten Einstieg, wenn man bereits eine höhere Programmiersprache beherrscht und wenig Kenntnisse über die Hardware hat und sich zudem auf komplexere Steuerungen ohne optimale Ausschöpfung der HW-Resourcen beschränkt.
- direkter Einstieg in komplizierte Abläufe
- einfacher Einstieg in die Programmiersprache
- Abhängigkeit von Compilern und deren Fehlern
- Code ist schnell erzeugbar
- sehr langsam in der Ausführung
- kurzer Weg zu komplexeren Funktionen
- keine hardwarenahen Funktionen verfügbar
Fast immer beziehen sich BASIC-Programmierer auf BASCOM, eine kostenpflichtige Softwarelösung zum einfachen Erstellen von Mikrocontroller-Programmen. Während damit gewisse Standardaufgaben in null-komma-nix erschlagen sind, muss man für alles andere die Materie genauso durchdringen wie der Assembler- oder C-Programmierer. Im Gegensatz zum Assembler-Programmierer muss man mit Seiteneffekten rechnen.
C / C++
C bietet den optimalen Kompromiss, da man durch Funktionen und Prozeduren sehr leicht hochsprachliche Strukturen und Datentypen nutzen kann und dennoch sehr effektiven Code produzieren (lassen) kann. Allerdings ist C strukturell am schwierigsten zu verstehen.
- schwieriger Einstieg in die Programmiersprache
- Abhängigkeit von Compilern und deren Fehlern, allerdings verifizierbar
- Code ist automatisch erzeugbar, manuell aber kompliziert
- sehr schnell in der Ausführung
- akzeptabler Weg zu komplexeren Funktionen
- hardwarenahe Funktionen verfügbar
- mit Assembler kombinierbar (zumeist mit Inline-Assembler, ist jedoch nicht C-standardkonform)
Generell kann man zu C++ raten, um dessen Vorteile zu nutzen, aber dann sollte man keinesfalls alle Fähigkeiten von C++ ausreizen!
Nutzbar sind:
- Variablendeklaration an beliebigen Stellen
- bool-Datentyp
- Referenzen und Referenz-Parameter („& statt *“)
- Initialisierung von Strukturen mit Nicht-Konstanten
- Klassen mit Einfachvererbung
- Überladene Funktionen
- Einfache Templates
- Statische Konstruktoren
Nicht unbedingt nutzen sollte man (oder man muss nachsehen, wie blöd sich der Compiler anstellt):
- STL (Standard Template Library)
- Mehrfachvererbung
- Virtuelle Methoden (muss man fallweise entscheiden, zumindest bei avr-gcc umständlich)
- Allzu verrückte oder neue Features wie
- Verschachtelte Klassen
- Automatische Typerkennung (auto-Schlüsselwort)
- Laufzeit-Typprüfung
- Anonyme Funktionen (Lambda-Ausdrücke)
- new- und delete-Operator (Speicherverwaltung allgemein)
Anfängern ist nicht zu C++ zu raten, weil die Fehlermeldungen (bei Tippfehlern) wesentlich unverständlicher sind.
Deutsch oder englisch?
Die Entscheidung über die Sprache der Kommentierung und Ausgabetexte ist je nach Problem und Audienz zu fällen. In der Regel kommentiert man deutsch, damit sich Franzosen über die Unlesbarkeit beschweren, oder englisch, damit man sich selbst schwer tut und Briten sich über das unverständliche Englisch beschweren:-)
Gerade als Anfänger macht man den Fehler, in den Kommentar hineinzuschreiben, was eine Standardfunktion tut (das steht in der Dokumentation und nach 10 Aufrufen im Kopf), aber nicht, warum man diese aufgerufen hat. Bei eigenen Funktionen sollten stets alle Parameter in ihrer Wirkung erläutert werden. Zu nichts nütze ist ein Kommentar wie „bool silentmode switches the function to silent mode“.
Nichtsdestotrotz muss man sich mit dem verwendeten Zeichensatz auseinander setzen. Denn früher oder später will man Umlaute, „°C“ oder „kΩ“ ausgeben, und hier sind alle klassischen 1-Byte-Zeichensätze am Ende. Am zukunftssichersten benutzt man die Mehrbyte-Kodierung UTF-8 und implementiert geeignete Transkodierungsroutinen bspw. für HD44780-kompatible LC-Anzeigen. Die Beschränkung auf BMP (< 65000 Zeichen) genügt für Mikrocontroller-Projekte, sodass ein UTF-8-Zeichen maximal 3 Bytes lang ist.
Mit Betriebssystem oder ohne?
Betriebssysteme erfreuen sich auch auf Mikrocontrollern immer größerer Beliebtheit. Multitasking- und Echtzeitanwendungen lassen sich so manchmal viel einfacher implementieren, da standardisierte Schnittstellen und Zugriffsmethoden verwendet werden und die zur Verfügung stehenden Bibliotheken und Software-Pakete genutzt werden können. Es ist jedoch stets abzuwägen, ob der Mehraufwand der Einarbeitung in ein Multitasking-Betriebssystem und der damit in Verbindung stehende Programmieraufwand mit dem potenziellen Ersparnis an Denk- und Planungszeit, die zur Realisierung der benötigten Funktionen „zu Fuß“ benötigt würde, im Verhältnis steht. Oftmals wird in der Industrie nach wie vor aus Kostengründen auf ein Betriebssystem verzichtet, weil es das Risiko eines nicht zu vertretenden Mehraufwands birgt und die Ausnutzung der Rechenleistung — gerade kleiner Controller — stark herabsetzt, was wiederum die Echtzeitfähigkeit verschlechtert.
Mit Betriebssystem
- Einarbeitung kostet Zeit und Geld
- Echtzeitfunktion im Millisekundenbereich einfacher möglich, da das Multitasking die parallele Reaktion des Systems auf äußere Einflüsse erleichtert
- Multitaskingfunktion ist vorimplementiert - muss nur genutzt werden
- Implementierung des Multitaskings kostet weniger Zeit
- Die Multitasking-relevanten Teile des Codes sind betriebssystem-spezifisch und nicht übertragbar
- Der gesamte Code ist weniger gut auf andere Controller portierbar (wieso?)
- Der Controller ist pauschal mit viel nicht nutzbarem Overhead belastet
- Es muss tendenziell ein teuerer Controller eingesetzt werden
Zu einem Betriebssystem greift man gern wenn TCP/IP zu implementieren ist. Das ist typischerweise ein Controller mit Ethernet-Anschluss. Oder der Zugriff auf ein Dateisystem, etwa USB-Sticks. Das wiederum erfordert einen USB-Hostcontroller. (Die meisten Mikrocontroller implementieren nur den einfacheren USB-Devicecontroller.) Auch für den Zugriff auf eine SD-Karte bietet sich ein Betriebssystem an; will man jedoch nur FAT16/FAT32 lesen, gibt es einfachere Lösungen. Auf den Punkt gebracht: Betriebssysteme nimmt man, wenn man hochgradig softwarelastige Standardaufgaben lösen will.
Linux eignet sich praktisch nur bei Controllern mit externem RAM und Flash und eignet sich daher kaum für selbst gebastelte Mikrocontroller-Boards. Für kleinere Controller gibt es angepasste Betriebssysteme, etwa NutOS. Als grobe Schranke gilt: Unter 64 KByte Flash braucht man über ein Betriebssystem nicht nachzudenken.
Hieran sieht man auch, warum sich Toaster und Kühlschränke mit Internetanschluss nicht durchsetzen: Der hierfür benötigte Controller ist einfach viel größer und energiehungriger als wenn nur eine Temperatur zu regeln bzw. der Bräunungsgrad der Toastscheibe zu messen ist!
Will man neben dem Betriebssystem Echtzeitanforderungen im Mikrosekundenbereich realisieren benötigt man ein tiefes Verständnis in das Betriebssystem sowie den Quellcode, um an den richtigen Stellen eingreifen zu können, etwa um Interruptsperrzeiten im Task-Switcher zu minimieren. Im Allgemeinen verzichtet man auf solche Extras und erledigt solche Aufgaben in einem separaten Mikrocontroller ohne Betriebssystem.
Das Betriebssystem legt die Wahl der Programmiersprache fest. Dies ist ausnahmslos C oder C++.
Ohne Betriebssystem
- Kein zusätzlicher Einarbeitungsaufwand
- Multitasking muss selbst implementiert werden (ist nicht so schwer, aber man benötigt etwas Übung)
- Implementierung des Multitaskings kostet mehr Zeit
- die Multitasking-relevanten Teile des Codes sind voll übertragbar
- Weniger Overhead, da nur benötigte Funktionen eingebaut werden
- Echtzeitfunktion ebenfalls möglich, insbesondere im Mikrosekundenbereich, wenn auch schwieriger zu implementieren
- Es kann tendenziell ein preiswerterer Controller eingesetzt werden
Die meisten Projekte werden ohne Betriebssystem erstellt. Auch für Anfänger im Bereich der Mikrocontroller ist dies der bessere Weg. Denn je kleiner der verwendete Controller ist, desto weniger kann er, und desto besser gelingen bspw. Echtzeitfunktionen im Mikrosekunden-Bereich.
Weitere Informationen
Weiterführende Informationen u. A. zu den berüchtigten Fuse-Bits, zu Programmier-Hard- und Software, dem AVR Softwarepool und einer Checkliste mit Hinweisen zur Lösung üblicher Probleme finden sich im Hauptartikel AVR.
- Datenblatt des ATmega8, deutsche Übersetzung
- Datenblatt des ATmega8A
- Datenblatt der ATmega48/88/168
- Datenblatt der ATmega48(P)A/88(P)A/168(P)A/328(P), deutsche Übersetzung (bspw. ATmega328 auf Arduino Uno; basierend auf o. a. Übersetzung), auch als .CHM herunterladbar