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 mein 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)
    {
        delay(500);
        led_on();
        puts_P(PSTR("Hello World"));

        delay(500);
        led_off();
        printf("%d",milliseconds());
    }
}

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ührt 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 einen Probeaufbau, mit dem du das Hello-World Projekt testen kannst. 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.

Schaltung

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.

Der oben gezeigte Programmer kann sich automatisch an die Versorgungsspannung des Mikrocontrollers anpassen. Viele billigere Modelle unterstützen nur 5V. Wer mit Batterien oder mit 3,3V 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.

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 zum Schutz gegen Überspannung und Kurzschluss zwei 1kΩ Widerstände in die RxD und TxD Leitungen eingefügt.

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-II, Diamex ALL-AVR ISPavrdude -c avrispmkII -P usb -B16 -p attiny2313
USBASPavrdude -c usbasp -P usb -B16 -p attiny2313
STK500, Diamex USB ISPavrdude -c stk500 -P COM6 -B16 -p attiny2313
Arduino Boards altavrdude -c arduino -P COM8 -b 57600 -p atmega328
Arduino Boards neuavrdude -c arduino -P COM8 -b 115200 -p atmega328
ICprog-AVR2.0avrdude -c avr910 -P \\.\COM10 -b 115200 -p attiny2313

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 Mikrocontroller 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 schon einmal 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

# Optional fuse settings, for example
# 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 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 setzen. 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 kannst.

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.

Quelltext an die Hardware anpassen

Finde 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. Passe auch MCU und F_CPU entsprechend deinem Mikrocontroller an.

In main.c musst du in den Funktionen led_on() und led_off() angeben, wo die LED angeschlossen ist:

void led_on()
{
    DDRB  |= (1<<PB1); 
    PORTB |= (1<<PB1);
}

void led_off()
{
    PORTB &= ~(1<<PB1);
}

Werfe einen Blick in die Datei driver/serialconsole.h, dort steht die Baudrate und welcher serielle Port verwendet werden soll. Beispiel für die Variante mit UART:

// Serial bitrate
#define SERIAL_BITRATE 2400

// Select the serial port
#define USE_SERIAL0

// In the software, lines are always terminated with \n.
// If terminal mode is enabled, line breaks are converted automatically:
// - in sending direction, line breaks are sent as \r\n.
// - in receiving direction, line breaks may be terminated by \r, \n or \r\n.
// - backspace characters in receiving direction delete the last received character from the input buffer.
#define TERMINAL_MODE 1

// Enable echo on serial interface
#define SERIAL_ECHO 0

// Size of input buffer in chars, max 254 (output is not buffered).
// Change it carefully, you get a stack overflow if the buffer occupies too much RAM.
#define SERIAL_INPUT_BUFFER_SIZE 100

Beispiel für die soft-serial Variante:

// Serial bitrate
#define SOFTSERIAL_BITRATE 2400

// Macros to control the TxD Pin
#define SOFTSERIAL_TXD_INIT   { DDRB  |=  (1<<PB3); }
#define SOFTSERIAL_TXD_HIGH   { PORTB |=  (1<<PB3); }
#define SOFTSERIAL_TXD_LOW    { PORTB &= ~(1<<PB3); }

Compilieren und Ausführen

Danach müsste die LED Blinken. Wenn du jetzt auch noch das USB-UART Kabel anschließt und ein Terminalprogramm (z.B. Hammer Terminal, Cutecom, Putty) 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 UART des Mikrocontrollers 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.