Erste Schritte mit dem STK200

AT90Sxxx & Linux

von Tjabo Kloppenburg



Evaluation Kits für Microcontroller sind meist nur mit Windows-Software ausgestattet. Dass man auch unter Linux Software für Microcontroller entwickeln kann, und auch die Programmierhardware ansprechen kann, sollt dieser Artikel am Beispiel des STK200-Kits zeigen.


Versucht man, einen Einstieg in die Microcontroller-Welt zu bekommen, stellt sich immer die Frage nach der Programmierhardware. Man kann versuchen, sie selbst zu bauen, mit dem Vorteil des geringeren Preises, und dem Nachteil gewisser Unsicherheiten im Betrieb, oder auf ein Evaluation Kit zurückgreifen, bei dem man sich sicher sein kann, dass es funktioniert. Dummerweise sind diese Kits meist für Hobbybastler unerschwinglich. Nicht so das STK200 für die Atmel AT90Sxxxx-Controller, dessen Betrieb unter Linux Thema dieses Artikels ist.

Das STK200 ist eine Experimentierplatine, auf der sich neben Stecksockeln für alle Ports auch eine LED- und eine Taster-Reihe befinden. Die Platine lässt sich die Platine über das mitgelieferte Kabel einfach an den Parallelport anschließen, um ihn mit der mitgelieferten Software zu programmieren - natürlich handelt es sich nur um Software für Windows. Aber es ist durchaus möglich, das STK200 auch unter Linux einzusetzen, und Programme für die AT90Sxxxx-Controller zu entwickeln.

Die elementarsten Software-Komponenten eines Entwicklungssystems sind ein Assembler, ein STK200-kompatibler ISP-Programmierer, und vielleicht noch ein C-Compiler. Die Installation dieser - kostenlosen - Linuxsoftware wird im Folgenden beschrieben.

Ein Assembler

Einen guten Assembler für die Console, ava, findet man auf [1]. Auf manchen Webseiten, wie der »Micro Tools for Linux«-Seite [2], wird ava als von avr-gcc unterstützt genannt. Für die aktuelle Version von avr-gcc stimmt dies allerdings nicht mehr. Trotzdem sollte man die aktuellste Version des Sourcearchivs von ava (0.3b-0815) herunterladen. Das geht wie gewohnt mit einem Browser, oder besser und schneller mit wget. Die Installation ist unkompliziert, setzt aber natürlich ein Linuxsystem mit installiertem C/C++-Compiler voraus:

[user@totekuh]:downloads/> wget http://www.blabla.com/ava-0.3b-0815.src.tar.gz
[user@totekuh]:downloads/> tar xzvf ava-0.3b-0815.src.tar.gz
[user@totekuh]:downloads/> cd ava-0.3b-0815
[user@totekuh]:ava-0.3b-0815/>
Im neuen Verzeichnis ./ava-0.3b findet sich eine Datei INSTALL, in die man auf jeden Fall einen Blick werfen sollte. Danach wechseln wir ins src-Verzeichnis, um im Makefile Anpassungen bei den Zielverzeichnissen für die Installation vorzunehmen. Wichtig sind dabei diese Einträge:

# Directory Setup
    AVA_BIN = /usr/local/bin
    AVA_LIB = /usr/local/lib/uTools/ava
Man beachte, dass AVA_BIN nur ganz unten im Makefile Verwendung findet in der Zeile:

cp ava $(AVA_BIN)
Gibt man bei AVA_BIN ein nicht existierendes Verzeichnis an, so wird ava auf den Namen des (nicht existierenden) Verzeichnisses kopiert - unter Umständen kann also ava mit dem Namen eines Verzeichnisses enden. Man kann nun entweder selbst sicherstellen, dass alle Zielverzeichnisse existieren, oder man passt das Makefile entsprechend an:

# Directory Setup
    AVA_DIR = /home/hansolo/bin/avr_asm
    AVA_BIN = $(AVA_DIR)/ava
    AVA_LIB = $(AVA_DIR)/lib
Achtung, vor den Einträgen MUSS ein Tabulator stehen! Das gilt auch für den install-Block, bei dem wir die oberste makedir-Zeile hinzufügen:

install:
    mkdir -p $(AVA_DIR)
    mkdir -p $(AVA_LIB)
    rm -r $(AVA_LIB)
    cp -r ../avalib $(AVA_LIB)
    cp ava $(AVA_BIN)
Nun können wir das Programm kompilieren und installieren:

[user@totekuh]:src/> make
[user@totekuh]:src/> make install  (ggf. als root)
Um das Programm bequem aufrufen zu können, sollten wir einen Link in einem Verzeichnis des PATH anlegen, der auf das Binary ava zeigt, oder den PATH anpassen. Wie man der ava-Dokumentation entnehmen kann, muss ava zweimal aufgerufen werden, um ein Assembler-Programm in eine HEX-Datei für den ISP-Prommer umzuwandeln. Dazu kommen noch ein paar lästige Parameter - ein hübsches Perl-Script, siehe Listing 1, nimmt uns die lästige Tipparbeit ab:

Listing 1: Perlscript für den Assembleraufruf
#!/usr/bin/perl -w

# Dieses Script startet den ava-Assembler:# Aufruf: avrassembler source.s
$ava = "/usr/local/bin/avr_asm/ava";
if ($#ARGV != 0) {
  print "\nUsage: avrassembler source.s\n";
  print "\nHilfe zu ava: $ava -h\n\n";
  exit 1;
}

$basename = substr($ARGV[0],0,rindex($ARGV[0],"."));
$asmopts  = "--intel -favr_noendianbug";
$linkopts = "$asmopts --multiple-output-files";

print "Assembliere...\n";
system("$ava $asmopts $basename.s");

print "Linke...\n";
system("$ava $linkopts $basename.o -o $basename.hex");

Wer mag, kann auch die Programm-Dokumentation von ava installieren (siehe INSTALL-Datei). Sie liegt als TeX- und Postscriptdatei vor. Die Ansicht der ersteren setzt ein installiertes TeX-System voraus, die der zweiten Datei einen guten PS-Viewer. Ich habe bei mir die Datei ava.ps mit latex2html nach html konvertiert, da der Text im WebBrowser wesentlich besser zu lesen ist. Wer sich dafür interessiert, kann es sich die HTML-Doku bei [3] downloaden.

Assembler testen

Nachdem der Assembler kompiliert und übersetzt ist, werden wir nun ein Testprogramm schreiben und assemblieren. Es bietet sich an, einen einfachen Zähler zu programmieren, dessen Zählerstand mit den LEDs an Port B des STK200 angezeigt wird. Da der AT90S8515 mit 4MHz getacktet wird, bauen wir eine Verzögerungssschleife ein, die die Zählgeschwindigkeit auf ein sehbares Maß reduziert. Geben Sie dazu Listing 2 ein, und speichern Sie es unter dem Namen counter.s ab.

Listing 2: Testprogramm
; Testprogramm: "Hübsch blinkende LEDs"
;* Ziel MCU : AT90S8515

#arch AT90S8515
#include "avr.inc"

; Sprungtabelle mit RESET-Vektor:
  rjmp RESET ;Reset Handle
  reti
  reti
  reti
;***** Code

RESET:


  ldi R16,low(RAMEND)   ; STACK-PTR einstellen:
  out SPL,R16
  ldi R16,high(RAMEND)
  out SPH, R16

  ldi r16, $ff          ; PortB -> Ausgang
  out DDRB, r16

  ldi r20, $00          ; Startwert f. Zaehler

forever:
; =============================
; Warteschleifen-Generator
; 1999994 Zyklen:
; -----------------------------
; Warte 1999980 Zyklen:
  ldi r17, $29
  WGLOOP1: ldi r18, $47
  WGLOOP2: ldi r19, $E4
  WGLOOP3: dec r19
  brne WGLOOP3
  dec r18
  brne WGLOOP2
  dec r17
  brne WGLOOP1
; -----------------------------
; Warte 12 Zyklen:
  ldi r17, $4
  WGLOOP6: dec r17
  brne WGLOOP6
; -----------------------------
; Warte 2 Zyklen:
  nop
  nop
; =============================
; Ausgabe des Wertes r20 auf Port B (invertiert, 0 = LED AN):
  mov r18, r20    ; 1 Takt, Zaehlerstand holen:
  com r18         ; 1 Takt, Zaehlerstand invertieren:
  out PORTB, r18  ; 1 Takt, r18 -> PORT B (LEDs):
  inc r20         ; 1 Takt, Zaehlerstand erhoehen:
rjmp forever      ; 2 Takte

Mit den Zeilen

#arch AT90S8515
#include "avr.inc"
wird dem ava-Assembler mitgeteilt, für welchen Chip der Code erzeugt werden soll. Es empfiehlt sich, einen Blick in das lib-Verzeichnis von ava zu werfen - dort finden sich avr.inc und alle chipspezifischen Includes.

Das Initialisieren des STACKS hätten wir uns u.U. sparen können, ist aber notwendig, sobald wir es mit Sprüngen in Unterprogramme zu tun bekommen.

Der Code ab der Marke forever: wird in endloser Wiederholung ausgeführt. Die Warteschleife habe ich mir mit einem selbstgeschriebenen Perl-Script erzeugt, dass der geneigte Leser unter der Url [2] findet. Natürlich kann jeder auch selbst versuchen, passende Werte für die Schleifeniteration zu finden. Viel Spaß dabei! ;-). Die Warteschleife ist ingesamt 1'999'994 Taktzyklen lang, zusammen mit den 6 Taktzyklen der Befehle zum Ausgeben des Zählerstandes ergibt sich für die forever-Schleife eine Taktlänge von 2'000'000 Taktzyklen - bei 4MHz leuchtet also die rechteste LED pro Sekunde einmal auf und erlischt wieder.

Die Übersetzung des Programms erfolgt entweder von Hand durch Aufruf von ava mit allen notwendigen Optionen:

ava --intel -favr_noendianbug counter.s
ava --intel -favr_noendianbug --multiple-output-file counter.o -o counter.hex
oder einfacher mit dem Perl-Script aus Listing 1:
avrassembler counter.s
Wenn alles fehlerfrei lief, finden wir nun im aktuellen Verzeichnis eine Datei counter.hex - das ist die hex-Datei, die wir in den AT90S8515 programmieren werden, sobald die ISP-Software installiert ist.

ISP-Programmierer 1: uisp

An kostenloser STK200-kompatibler ISP-Software gibt es zwei Programme, uisp [3] und avr1 [4]. Bei beiden Programmen ist bei der Installation auf Feinheiten zu achten. Sehen wir uns zuerst die Installation von uisp-02b an:

[user@totekuh]:downloads/> tar xzvf uisp-0.2b-1026.src.tar.gz
[user@totekuh]:downloads/> cd uisp-0.2b
[user@totekuh]:uisp-0.2b/> less INSTALL
[user@totekuh]:uisp-0.2b/> cd src
[user@totekuh]:src/>
Laut INSTALL-Datei reicht ein einfacher Aufruf von make und make install, um uisp zu compilieren. Zumindest auf meinem Suse6.3-System war es damit nicht getan. Es kommt eine Fehlermeldung mit "LP_PSTROBE". Auf meinem System rührt der Fehler daher, dass die includierte Headerdatei sys/lp.h zwar eine Definition von LP_PSTROBE enthält - allerdings in einem Block, der der Kernelcompilierung vorbehalten ist. Meine Problemlösung besteht darin, die Datei /usr/include/linux/lp.h lokal ins uisp-Sourcedirectory zu kopieren (mein-lp.h), und die Zeile

#ifdef __KERNEL__
sowie ganz unten die Zeile:
#endif
zu löschen. Diese veränderte Includedatei mein_lp.h includiere ich dann in DAPA.C anstelle der sys/lp.h:
Ersetze:
#include <linux/lp.h>
Durch:
#include "mein_lp.h"
Nun sollte die Compilierung fehlerfrei klappen:
[user@totekuh]:src/> make
[user@totekuh]:src/> su
Passwort: *********
[user@totekuh]:src/> make install ; exit
Damit wird uisp im Verzeichnis /usr/local/bin installiert. Soll in ein anderes Zielverzeichnis installiert werden, so muss man halt des Makefile entsprechend anpassen.
Welche Optionen uisp kennt, sieht man bei einem Aufruf von uisp -h. Sollte jetzt keine mindestens eine Bildschirmseite lange Liste auf dem Bildschirm zu sehen sein, in der u.a. die --verify-Option erklärt ist, so muss man man eben in der Datei Main.C die Optionen nachlesen. Bei mir trat dieses Phänomen auf, ohne dass ich herausfinden konnte, wodran es lag. Das ist halt die oftmals überraschende Welt von Linux... :-)

Der ISP-Programmierer uisp bietet nur vier interessante Optionen: erase, verify, program und segment. Mehr Funktionen bieten die avr1-Tools, deren Installation im folgenden Kapitel behandelt werden:

ISP-Programmierer 2: avr1

Der andere kostenlose STK200-kompatible Programmierer ist avr1, zusammen mit dem STK200-Patch. Beides findet man auf [4].Zuerst entpackt man die Archive:
[user@totekuh]:downloads/> tar xzvf avr1-0.50.tar.gz
[user@totekuh]:downloads/> tar xzvf stk200-1.tar.gz
Nun kopiert man die Dateien aus dem Verzeichnis stk200 in das Verzeichnis avr1-0.50, nimmt eine kleine Anpassung vor, und startet die Compilierung:
[user@totekuh]:downloads/> cp stk200/libstk200.* avr1-0.50/
[user@totekuh]:downloads/> cd avr1-0.50
[user@totekuh]:avr1-0.50/> cp libstk200.c libavr1.c
[user@totekuh]:avr1-0.50/> make
Nach erfolgreicher Compilierung finden sich im Unterverzeichnis ./bin/ die fertigen Programme. Am besten kopiert man sie als root in das Verzeichnis /usr/local/bin. Das README von avr1 gibt leider nicht viele Informationen über die Funktion der Programme her, allerdings folgt die Namensgebung einem klaren Schema:

avrlock Lockbits setzen usw.
avreep EEPROM programmieren
avreer EEPROM lesen (read)
avrerase Flash löschen
avrid Controller-ID auslesen
avrprg Flash programmieren
avrrd Flash auslesen

Controller programmieren

Die Programmierung des Controllers im STK200 erfolgt über die parallele Schnittstelle. Dazu sind unter Linux root-Rechte erforderlich. Man muss die Programmierprogramme also als root aufrufen, oder das SUID-Flag setzen, oder ein Tool wie sudo einsetzen. Letzteres hat gegenüber SUID den Vorteil, dass die Ausführung der Programme auf bestimmte Benutzer beschränkt werden kann. Infos über die Konfiguration von sudo entnehme man der sudo-Dokumentation.

Auf einem Heimrechner, auf man man eigentlich nur selbst arbeitet, ist das Setzen des SUID-Flags der einfachste Weg. Dazu stellt man sicher, dass das entsrechende Programm dem user root und der gruppe root gehört, für alle User ausführbar ist, und setzt dann das s-Flag. Wie gesagt, auf einem Multiusersystem ist das keine gute Lösung, da dann JEDER Benutzer - absichtlich oder versehentlich - die Programmierprogramme starten kann.

Setzen des SUID-Flags (Programm wird mit der userid des Besitzers (root) ausgeführt):

su -
Passwort: ********
chown root:root /usr/local/bin/avrprg
chmod a+rx /usr/local/bin/avrprg
chmod u+s /usr/local/bin/avrprg
exit

Damit ist die Installation der ISP-Software abgeschlossen. Testen können wir die Funktion am einfachsten mit dem Programm avrid, das die ID des Controllers ausliest. Dazu starten wir avrid mit root-Rechten, bei eingeschaltetem betriebsbereitem STK200. Die Ausgabe auf dem Bildschirm sollte sinnvoll aussehen :-). Sollte das nicht klappen, müssen wir uns auf den Weg der Fehlersuche begeben: Falls eine Meldung zum Thema "IOPERM" kommt, dann haben wir versucht, den Parallelport ohne root-Recht zu benutzen. Überprüfen Sie auch, ob das STK200 wirklich an ist, und der Stecker wirklich im Parallelport steckt. Verwenden Sie Port 2 statt 1, so müssen Sie in mein_lp.h die Portadresse anpassen und die Programme neu compilieren.

Als nächstes werden wir unseren Zähler aus dem Kapitel über den Assembler in den Controller programmieren, allergings nicht ohne vorher das Programm, dass sich bereits im Controller befindet, auszulesen und zu sichern. Das ist dann gleichzeitig noch ein Funktionstest, bei dem das aktuelle Programm im Controller nicht zerstört werden kann. Mit dem folgenden Kommando lesen wir das Programm aus dem FLASH-Speicher des Controllers im STK200 aus:

avrrd
bzw.:
[user@totekuh]:.../> avrrd >sicherheitskopie.lst
Sollte auch hier kein Fehler aufgetreten sein, können wir guten Mutes unseren assemblierten Zähler in den Controller uploaden:
[user@totekuh]:.../> avrprg -f counter.hex.flash

Nach einem erfolgreichem Upload wird übrigends automatisch ein Controller-RESET durchgeführt, und unser Programm startet mit lustigem Geblinke.

C-Compiler

Die großartige Welt des Internet hält auch einen "kostenlosen" C-Compiler für und bereit - solange man es schafft, die fast 9MB Sourcecode kostenfrei über das WWW zu beziehen :-). Die Sourcen für den gcc-core sowie den avr-patch. findet man auf der "Micro Tools for Linux" - Webseite [5]. Wie eingangs erwähnt, ist der gcc nicht mehr zum ava-Assembler kompatibel, daher muss man auch die binutils mitsamt patch downloaden und installieren. Und nicht zuletzt die avr-libc. Bei allen diesen Programmen sollte man auf jeden Fall auch die BUILD-Readmes downloaden, da diese eine recht präzise knappe Anleitung enthalten, wie man sich seinen avr-gcc zurechtstricken kann.

Das Einarbeiten der jeweiligen Patches ist eine simple Sache, die so funktioniert:

[user@totekuh]:.../> tar xIvf binutils-2.9.5.0.13.tar.bz2
[user@totekuh]:.../> cd binutils-2.9.5.0.13
[user@totekuh]:binutils-2.9.5.0.13/>
       gzip -dc ../binutils-2.9.5.0.13-avr-patch-1.1.gz | patch -p1
Das "-dc" bedeutet "auspacken nach STDOUT", die Ausgabe wird direkt zu "patch -p1" gepipet. Man sollte darauf achten, wirklich "patch" und nicht "path" einzutippen - zumindest bei mir verschwindet das "c" immer auf geheimnisvolle Weise... ;-)

Die weiteren Installationsschritte für die binutils sind:

[user@totekuh]:binutils-.../> configure --target=avr
[user@totekuh]:binutils-.../> make
[user@totekuh]:binutils-.../> make install  (als root)
Zu den avr-binutils zählen diverse Programme wie avr-as (Assembler), avr-ld (Linker), und weiter. Diese werden vom Compiler, den wir gleich bauen, aufgerufen, um den AVR-Binärcode zu erzeugen. Den Compiler entpacken, patchen und compilieren wir (fast) äquivalent zu den binutils:
[user@totekuh]:.../> tar xzvf gcc-core-2.95.2.tar.gz
[user@totekuh]:.../> cd gcc-core-2.95.2
[user@totekuh]:gcc-2.95.2>
       gzip -dc ../gcc-core-2.95.2-avr-patch-1.1.gz | patch -p1
[user@totekuh]:gcc-2.95.2> configure --target=avr
[user@totekuh]:gcc-2.95.2> make
[user@totekuh]:gcc-2.95.2> make install  (als root)
Die Compilierung der 41MB kann je nach Computersystem etwas Zeit in Anspruch nehmen. Wenn, wie zu erwarten ist, die Compilierung fehlerfrei verlaufen ist, steht ab sofort der C-Compiler avr-gcc zur Verfügung.

Bevor wir ein erstes C-Programm compilieren, sollten wir noch die aktuelle avr-libc installieren - das geht schnell und unkompliziert:

[user@totekuh]:.../> tar xzvf avr-libc-20000201.tar.gz
[user@totekuh]:.../> cd avr-libc-20000201
[user@totekuh]:avr-libc-.../> cd src
[user@totekuh]:src/> make
[user@totekuh]:src/> make install  (als root)

Testprogramm für avr-gcc

Für einen ersten Test des avr-gcc schreiben wir uns ein klitzekleines C-Programm. Vorher jedoch sollten wir das kleine Makefile aus Listing 3 abtippen, dass uns die Compilierung und die Einprogrammierung in den Controller erleichtert. Achtung: die eingerückten Zeilen müssen mit einem Tabulatorzeichen beginnen!

Listing 3: Simples Makefile
.c.o:
        avr-gcc -mmcu=at90s8515 -O -c $<
ispload:  test2.rom
        uisp -dstk200 --erase --upload --verify if=test2.rom

test2.rom: test2.out
        avr-objcopy -O srec test2.out test2.rom

test2.out: test2.o
        avr-gcc test2.c -o test2.out

Mit dem Makefile aus Listing 3 können wir recht einfach eine Datei test2.c compilieren. Um Makefiles für andere Sourcefiles zu erstellen, passen wir Listing 3 entweder an, oder wir schreiben uns z.B. mit Perl einen Makefile-Generator, wobei wir uns an Listing 1 orientieren können. Als Tip mag der Hinweis genügen, dass ein "\t" in der print-Anweisung einem Tabulatorzeichen entspricht. Aber vielleicht ist es auch einfacher, ein kleines Bash oder Perlscript zu schreiben, dass alle notwenigen Kommandos in der richtigen Reihenfolge startet, wenn man ihm den Namen des Quelltextes übergibt.

Als erstes Testprogramm für avr-gcc reicht jedes einfache C-Programm (test1.c), wie z.B. das aus Listing 4:

Listing 3: Simples Makefile
main() {
  int i;
  for (i = 0; i <= 100; i++) {    /* Zaehlt von 1..100 */
    ;
  }
}

Da es kaum Sinn macht, dieses Programm in den Controller zu schreiben, reicht vorerst ein

  avr-gcc test1.c -o test1.out
um zu sehen, ob der Compiler läuft. Wenn alles geklappt hat, sollte jetzt eine neue Datei test1.out existieren - sie enthält ein statisch gelinktes AVR-Binary. Mit dem binutil avr-objdump kann man den Inhalt disassemblieren:
[user@totekuh]:.../> avr-objdump -d test1.out
Die Ausgabe auf dem Bildschirm bestärkt mich persönlich in der Meinung, dass man so einen überschaubaren Controller doch besser in Assembler programmiert... ;-)

Wie gehts weiter?

Der geneigte Leser kann nun versuchen, unser Assemblerprogramm aus Listing 2 nach C zu konvertieren (test2.c), und mit dem Makefile zu übersetzen. Um die .rom-Datei in den Controller zu programmieren, muss eventuell die ispload-Zeile im Makefile angepasst werden, je nachdem, ob uisp oder avr1 zum Programmieren des STK200 verwendet werden soll. Wer Probleme mit dem avr-gcc hat, kann sich auch in die avr-gcc-Mailingliste eintragen. Hinweise dazu finden sich auf der "Micro Tools for Linux"-Seite [3].

Bleibt dann nur noch zu hoffen, dass es nicht zu lange dauert, bis es einen AVR-Perlcompiler gibt... ;-)


[1]: http://www.colint.com/phil/avr1/
[2]: http://www.home.unix-ag.org/tjabo/avr/
[3]: http://medo.fov.uni-mb.si/mapp/uTools/index.html
[4]: http://www.colint.com/phil/avr1/

Autorinfo: Der Autor studiert Technische DV an der Uni Siegen. Aktuelles Lieblingsprojekt: Sein Abalone-Server (Perl,PHP3,JavaScript).