Startseite

AVR Hello World! Vorlage

Dies ist eine Kopiervorlage zum Starten neuer Projekte. Es gibt "Hello World" auf die serielle Konsole aus und lässt eine LED blinken. Außerdem stellt es einen Millisekunden-Zähler bereit.

Ich benutze gerne integrierte Entwicklungsumgebungen als "besseren Editor", allerdings möchte ich meine Projekte nach diversen Problemen mit dem Atmel Studio nicht mehr fest an eine bestimmte IDE binden. Daher basiert meine Beispiel auf einem Makefile.

HelloWorld.zip nutzt den Hardware UART bidirektional. Für ATtiny 2313, ATtiny 4313, alle ATmega und alle Xmega.

HelloTiny.zip mit soft-serial Emulation, nur Ausgabe. Für alle ATtiny, alle ATmega und alle Xmega.

Auf der AVR Tools Seite findest du den C-Compiler (Toolchain) und weitere Downloads.

int main(void) 
{
    initSerialConsole();
    initSystemTimer();
    
    while(1)
    {
        LED_ON;
        puts_P(PSTR("Hello World"));
        _delay_ms(500);    

        LED_OFF;
        char buffer[6];
        utoa(milliseconds(),buffer,10);
        puts(buffer);
        _delay_ms(500);
    }
}

Das Ausgeben von Text mit puts und printf ist bei der Fehlersuche sehr hilfreich. Mikrocontroller mit UART können auch Text empfangen, so dass praktisch alle Funktionen der stdio.h Library mit der seriellen Konsole verwendbar sind.

Timer 0 wird benutzt, um Millisekunden zu zählen. Damit misst man Zeitabstände von Ereignissen oder führen Aktionen zu bestimmten Zeitpunkten aus. Man kann sowohl den Timer als auch die Konsole einfach entfernen, wenn sie nicht benötigt werden.

Probeaufbau

In den folgenden Absätzen beschreibe ich den Probeaufbau, mit dem ich das Hello-World Projekt getestet habe. Ich glaube, es eignet sich gut für Anfänger, um sich mit den Tools vertraut zu machen. Deswegen beschreibe ich den Aufbau sehr detailliert. Mein Probeaufbau besteht aus folgenden Komponenten:

Schaltung

Steckbrett mit den Bauteilen

Die LED ist an Pin 4 angeschlossen, das ist Port A1.

Programmieradapter

Der ISP Programmer dient dazu, das selbst geschriebene und compilierte Programm in den Flash Speicher des Mikrocontrollers zu übertragen.

Das Foto zeigt den "originalen" Atmel AVRISP MK-II, ein sehr robustes und flexibles Gerät. Damit ich es mit dem Steckbrett verbinden kann, habe ich an das Ende des Flachband-Kabels eine 2x3 polige Stiftleiste gesteckt und dort bunte Litzen angelötet.

Im Internet gibt es noch alte Anleitungen, wo man den Mikrocontroller mehr oder weniger direkt an einen parallel-Port oder an einen seriellen Port angeschlossen hatte. Diese Methode funktioniert nur mit sehr alten Computern und seit Windows 7 gar nicht mehr!

Der oben gezeigte Programmer kann sich automatisch an die Versorgungsspannung des Mikrocontrollers anpassen. Viele billigere Modelle können das nicht. Sie unterstützen meistens nur 5V, manchmal auch 3,3V. Wer viel mit Batterien arbeitet sollte das beim Kauf berücksichtigen.

Stromversorgung

Die Stromversorgung wird an VCC und GND gesteckt. Ich habe mich für 3,6V aus Akkus entschieden. Für einen Programmieradapter der nur 5V unterstützt, müsste man 4 Akkus (=4,8V) verwenden, weil die Spannungen maximal 0,5V abweichen dürfen.

Serielles Kabel

Mit einem sogenannten USB-UART Kabel verbinde ich den seriellen Port des Mikrocontrollers mit meinem Computer. Dort starte ich ein Terminal-Programm, welches die übertragenen Texte in einem Fenster anzeigt. Auf diese Weise kann ich mit puts oder printf Meldungen ausgeben, die mir helfen, die Funktion des Programmes zu untersuchen. Man braucht dazu nur zwei Drähte anzuschließen:

Die USB-UART Kabel haben üblicherweise einen der folgenden Chips eingebaut: PL2303, CP2102, FT232, CH340, CH341. Im Online Handel werden diese Kabel oft fälschlicherweise "USB TTL Cable" genannt. Für den PL2303 habe ich einen alten Windows Treiber aufbewahrt, der mit alten und gefälschten Chips besser klar kommt.

Ich habe 1kΩ Widerstände in die RxD und TxD Leitungen, sowie 47Ω in die GND Leitung eingefügt. So ist das Kabel Kurzschlussfest und universell für alle Spannungen von 3,3V bis 5V verwendbar.

Alternativ gibt es Mikrocontroller Boards mit eingebautem USB-UART Chip, zum Beispiel Arduino Nano und die Crumb Module von der Firma Chip45.

Avrdude

Wer vorher nur mit dem Atmel Studio gearbeitet hat, dem wird die Bedienung von Avrdude sehr ungewohnt vorkommen. Es hat keine grafische Oberfläche, sondern man muss es durch zahlreiche Kommandozeilen-Optionen steuern.

Doch diese Kommandos kann man in Scripte (oder auch dem Makefile) festschreiben, so dass man sich die Kommandos nicht merken muss. Diese Vorgehensweise ist vorteilhaft, wenn man den Quelltext an andere Leute übergibt. Man muss dann nicht großartig erklären, welche Klicks in der GUI nötig sind. Stattdessen sagt man einfach: Führe das Script xyz aus, der Rest läuft dann automatisch ab.

Die richtigen Kommandozeilen-Optionen findet man recht schnell mit Google, indem man nach dem Namen des Programmieradapters und "avrdude" sucht. Beispiele:

ProgrammieradapterBefehl
Atmel AVRISP Mk-IIavrdude -c avrispmkII -P usb -B16 -p attiny2313
USBASPavrdude -c usbasp -P usb -B16 -p attiny2313
STK500avrdude -c stk500 -P COM6 -B16 -p attiny2313
ICprog-AVR2.0avrdude -c avr910 -P COM7 -b 115200 -p attiny2313
Arduino Boardsavrdude -c arduino -P COM8 -b 57600 -p atmega328
Arduino Boards mit neuem "OptiBoot" loaderavrdude -c arduino -P \\.\COM10 -b 115200 -p atmega328

Bei Programmieradaptern, die im Gerätemanager mit einem virtuellen COM-Port erscheinen, muss man diesen hinter -P angeben. Zweistellige COM-Port Nummern muss man wie im letzten Beispiel schreiben. Bei Geräten ohne COM-Port gibt man "-P usb" an.

Unter Linux heißen die seriellen Ports /dev/ttyUSB0 oder so ähnlich. Im Zweifelsfall hilft die Ausgabe vom Befehl dmesg, wenn man ihn direkt nach dem Einstecken des USB Steckers eintippt. Unter Umständen brauchen Linux Benutzer Root Rechte, damit sie auf USB Geräte zugreifen dürfen (sudo avrdude ... eingeben).

Der Parameter -B16 reduziert die Geschwindigkeit der Kommunikation zwischen Programmieradapter und Mikrocontroller. Ohne diesen Parameter würde er den mit nur 1Mhz getakteten Mikrocontroller überfordern. Manche USBASP Programmieradapter unterstützen diesen Parameter nicht, haben aber einen entsprechenden Schalter (oder Jumper) oder verfügen wie der ICprog-AVR2.0 über eine automatische Erkennung der Geschwindigkeit.

Der Parameter -p attiny2313 gibt an, welcher Mikrocontroller-Typ angeschlossen ist.

So teste ich avrdude mit einem Atmel AVRISP MK-II unter Ubuntu Linux:

Avrdude liest die Signatur und die Fuses des Mikrocontrollers aus. Wenn Avrdude (wie in diesem Fall) meckert, dass die Signatur falsch sei, dann hat man entweder den Mikrocontroller-Typ falsch angegeben, oder die Übertragung zwischen Programmieradapter und Mikrcocontroller ist gestört. Die häufigsten Ursachen für gestörte Übertragungen sind:

Wenn du so weit bist, dass Avrdude die Signatur erfolgreich ausliest, kannst du versuchen, den Programmspeicher zu beschreiben. Aber mache das besser mit Hilfe des Makefiles, nicht manuell.

Makefile

Das Programm make hat die Aufgabe, herauszufinden, welche Dateien verändert wurden um dementsprechend den Compiler aufzurufen. Anstatt immer den gesamten Quellcode zu compilieren, macht make das nur für die Dateien, wo es benötigt wird. Wer schonmal große Sachen wie den Linux Kernel compiliert hat, weiss dieses Features sehr zu schätzen.

Schaue dir mal den oberen Teil des Makefiles vom Hello-World Projekt an:

# Makefile for this AVR project

# make code       Compiles the source code
# make fuses      Program fuses
# make program    Program flash and eeprom

# make list       Create generated code listing
# make clean      Delete all generated files

# Programmer hardware settings for avrdude
AVRDUDE_HW = -c avrispmkII -P usb -B16

# Name of the program without extension
PRG = HelloWorld

# Microcontroller type and clock frequency
MCU = attiny2313
F_CPU = 1000000

# Fuse settings
LFUSE = 0x64
HFUSE = 0xDF
EFUSE = 0xFF

# Objects to compile (*.c and *.cpp files, but with suffix .o)
OBJ = driver/serialconsole.o driver/systemtimer.o main.o

Hier stehen zuerst Hinweise, mit welchen Parameter man make aufrufen kann. Zum Beispiel gebe make code ein, um den Quelltext zu compilieren. Als Ergebnis kommt eine HEX Datei heraus, die das für den Mikrocontroller ausführbare Programm enthält.

Mit sudo make program überträgt man die Firmware in den Mikrocontroller. Dabei wird avrdude aufgerufen. Damit avrdude die richtigen Aufrufparameter für deinen Programmieradapter erhält, gebe diese im Makefile mit AVRDUDE_HW = ... an.

Mit make fuses "brennen" man die Fuses entsprechend der angegebenen Fuse settings. Du solltest die Fuses nur dann brennen, wenn du ganz sicher bist, dass die angegebenen Werte richtig sind. Denn bei falschen Werten passiert es allzu leicht, das der Mikrocontroller unbrauchbar wird. Und Achtung: Werte die für einen Chip richtig sind, können für einen anderen Chip völlig falsch sein. Denn die Bedeutung der einzelnen Bits ist bei jedem Mikrocontroller-Typ anders.

Für das vorliegende Hello-World Projekt brauchst du die Fuses nicht zu ändern. Die Vorgabewerte, mit denen der Chip verkauft wird, sind bereits passend. Die Online Fuse-Kalkulatoren von Engbedded und Eleccelator helfen bei der Berechnung der Hexadezimalzahlen.

Mit dem Befehl make clean entfernst du alle vom Compiler erzeugten Dateien wieder. Du brauchst diesen Befehl, wenn du Header Dateien (*.h) oder das Makefile verändert hast. Änderungen an den *.c Dateien erkennt make hingegen automatisch.

Die Schlüsselwörter "code", "program", "fuses" und "clean" heissen in der Sprache von make "Targets". Etwas weiter unten im Makefile findest du für jedes Target einen Block Anweisungen, was dabei passieren soll. Diese Anweisungen musst du nicht unbedingt im Detail verstehen. Freue dich einfach darüber, hiermit eine funktionierende Vorlage zu haben, die du nach Bedarf anpassen können.

Man kann übrigens mehrere Targets mit einem Befehl abarbeiten, zum Beispiel: make clean code program. Wenn du einfach nur make ohne Parameter eingeben, wird immer das erste Target ausgeführt, was in diesem Fall "code" ist.

Mit PRG = HelloWorld gibst du dem Projekt einen Namen. Dementsprechend heißt die erzeugte HEX Datei HelloWorld.hex.

Mit MCU=... und F_CPU=... gibst du an, für welchen Mikrocontroller das Projekt compiliert werden soll und mit welcher Taktfrequenz er betrieben wird. Die Taktfrequenz ist für Warteschleifen mittels _delay_ms() und für den seriellen port wichtig. Wenn du hier einen falschen Wert angibst, stimmen die Timings nicht. Die LED wird dann mit der falschen Geschwindigkeit blinken und der Computer wird vom seriellen Port nur Hieroglyphen empfangen, statt den erwarteten Text.

Ein häufiger Anfängerfehler ist die Annahme, dass man mit F_CPU die Taktfrequenz verändern könne. Das ist nicht der Fall! Die Taktfrequenz wird mit Fuses eingestellt und hängt ggf. von einem externen Taktgeber oder Quarz ab.

In der Zeile OBJ=... Listest du alle Objekte auf, die der C-Compiler compilieren soll. Jede *.c (oder *.cpp) Datei wird in eine Objekt-Datei compiliert.

Hardware Makros

Ich benutze gerne Makros, um den Zugriff auf I/O Pins zu definieren. Dadurch wird der Quelltext besser lesbar und man kann den Code nach kleinen Änderungen an der Schaltung leichter anpassen.

Um die LED einmal an und wieder aus zu schalten, würde ich nach herkömmlicher Methode schreiben:

{
    DDRA |= (1<<PA1);
    PORTA |= (1<<PA1);
    PORTA &= ~(1<<PA1);
}

Das sieht ziemlich Kryptisch aus. Ohne Schaltplan im Kopf kann man gar nicht erkennen, was dort passiert. Mit dem Makros (aus hardware.h) wird es viel deutlicher:

{
    LED_ON;
    LED_OFF;
}

Compilieren und Ausführen

Finden zuerst die richtigen Parameter für avrdude heraus, wie oben erklärt. Diese trägst du dann in das Makefile ein. Aber lasse den Mikrocontroller-Typ weg, denn der wird durch die Anweisungen ganz unten im Makefile automatisch angehängt.

Wenn du nicht den ATtiny2313 verwenden, passe die MCU und F_CPU im Makefile entsprechend an. Außerdem passe ggf. die beiden Makros in hardware.h an, so dass der richtige Pin angesteuert wird, wo die LED angeschlossen ist.

Werfe einen Blick in die Datei driver/serialconsole.h, dort steht die Baudrate der seriellen Schnittstelle und noch ein paar andere interessante Parameter. Ich habe hier 2400 Baud vorgegeben.

Danach müsste die LED Blinken. Wenn du jetzt auch noch das USB-UART Kabel anschließt und ein Terminalprogramm mit der richtigen Baudrate startest, siehst du außerdem jede Sekunde den Text "Hello World" und den Zählerstand des Systemtimers. Mit Cutecom unter Linux sieht das so aus:

Serielle Baudraten

Mit einem 7,3728 Mhz oder 14,7456 Mhz Quarz kann der Mikrocontroller alle gängigen Baudraten exakt erzeugen. Diese beiden Quarze sind für den seriellen Port als Idealfall zu empfehlen. Beim obigen Testaufbau nutzen wir jedoch den internen R/C Oszillator, dessen Frequenz von der Versorgungsspannung und Temperatur abhängt. Es kann daher zu Übertragungsstörungen kommen - vor allem, wenn der Chip warm wird oder die Versorgungsspannung weit von 3,3V abweicht.

Soweit die Theorie. In der Praxis kommen die meisten Hobbyelektroniker mit R/C Oszillator und niedrigen Baudraten wie 2400 oder 9600 gut zurecht. Ich hatte damit bisher nur ein einziges mal Schwierigkeiten, und da half es einfach, den Mikrocontroller durch einen anderen baugleichen auszutauschen.