Startseite

AVR Hello-World Vorlage

Hier zeige ich, wie man Programme für AVR Mikrocontroller ohne Abhängigkeit zu einer bestimmten IDE erstellt.

Ich benutze gerne integrierte Entwicklungsumgebungen als "besserer 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 Projektvorlage auf einem altmodischen Makefile.

Die Kopiervorlage enthält ein minimales Beispielprojekt welches eine LED blinken lässt und "Hello World" auf die serielle Konsole ausgibt. Damit beginne ich praktisch jedes neue AVR Projekt.

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 messen Sie 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

Foto vom Steckbrett

Steckbrett mit den Bauteilen

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

Stromversorgung

Foto von der Batterie

Die Batterie wird an VCC und GND gesteckt. Ich habe mich für 3,6V aus Akkus entschieden, es darf auch ruhig etwas mehr sein (bis zu 5,5V).

Programmieradapter

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

Foto vom Programmieradapter

Das Foto zeigt den "originalen" Atmel AVRISP MK-II, das ist quasi das Standard Gerät zur Programmierung von AVR Mikrocontrollern. 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!

Serielles Kabel

Foto vom seriellen 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. Im Online Handel werden diese Kabel oft fälschlicherweise "USB TTL Cable" genannt.

Ich habe 2,2k Ohm Widerstände in die Leitungen RxD und TxD Leitungen eingebaut. So ist das Kabel Kurzschlussfest und ich kann es sowohl für 3,3V als auch für 5V Schaltungen verwenden.

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.

Für den "originalen" Atmel AVRISP MK-II benötigt man die Optionen: avrdude -c avrispmkII -P usb -B16 -p attiny2313.

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.

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

Für die Geräte mit AVR910 und STK500 Protokoll, sowie bei Arduino Modulen muss man hingegen angeben, welchen virtuellen seriellen Port das Betriebssystem dem Gerät zugewiesen hat und welche Baudrate zu verwenden ist. Unter Windows hilft dazu ein Blick in den Gerätemanager. Unter Linux gibt man dmesg ein.

Beispiel für den ICprog-AVR2.0 von In-Cicuit unter Linux: avrdude -c avr910 -P /dev/ttyUSB0 -b 115200 -p attiny2313

Beispiel für Arduino unter Windows: avrdude -c arduino -P COM3 -b 115200 -p attiny2313

Falls die Nummer des seriellen Portes zweistellig ist (z.B. COM10) muss man sie so schreiben: \\.\COM10.

Unter Linux benötigt man unter Umständen Root Rechte, damit man auf USB Geräte zugreifen darf. Bei Ubuntu und Debian erreicht man das mit dem Befehl sudo avrdude ....

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

Bildschirmfoto von avrdude

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 sie soweit sind, dass Avrdude die Signatur erfolgreich ausliest, können sie versuchen, den Programmspeicher zu beschreiben. Aber das machen sie 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.

Schauen sie sich 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 sie make aufrufen können. Zum Beispiel geben sie make code ein, um den Quelltext zu compilieren. Als Ergebnis erhalten sie eine HEX Datei, die das für den Mikrocontroller ausführbare Programm enthält.

Mit sudo make program übertragen Sie die Firmware in den Mikrocontroller. Dabei wird avrdude aufgerufen. Und damit avrdude die richtigen Aufrufparameter für ihren Programmieradapter erhält, geben Sie diese im Makefile mit AVRDUDE_HW = ... an.

Mit make fuses "brennen" sie die Fuses entsprechend der angegebenen Fuse settings. Sie sollten die Fuses nur dann brennen, wenn sie ganz sicher sind, 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 brauchen sie 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 entfernen sie alle vom Compiler erzeugten Dateien wieder. Sie brauchen diesen Befehl, wenn sie Header Dateien (*.h) oder das Makefile verändert haben. Ä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 finden sie für jedes Target einen Block Anweisungen, was dabei passieren soll. Diese Anweisungen müssen sie nicht unbedingt im Detail verstehen. Freuen sie sich einfach darüber, hiermit eine funktionierende Vorlage zu haben, die sie nach Bedarf anpassen können.

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

Mit PRG = HelloWorld geben Sie dem Projekt einen Namen. Dementsprechend heisst die erzeugte HEX Datei HelloWorld.hex.

Mit MCU=... und F_CPU=... geben Sie 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 sie hier einen falschen Wert angeben, 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=... Listen sie 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 Sie erstmal die richtigen Parameter für avrdude heraus, wie oben erklärt. Diese tragen Sie dann in das Makefile ein. Aber lassen Sie den Mikrocontroller-Typ weg, denn der wird durch die Anweisungen ganz unten im Makefile automatisch angehängt.

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

Werfen Sie 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 sie jetzt auch noch das USB-UART Kabel anschließen und ein Terminalprogramm mit der richtigen Baudrate starten, sehen sie außerdem jede Sekunde den Text "Hello World" und den Zählerstand des Systemtimers. Mit Cutecom unter Linux sieht das so aus:

Bidschirmfoto von Cutecom

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.