28.10.2007

CLI (Command Line Interface)

Inhalt:

  1. Motivation
  2. echo-Funktion am Serial-Port
  3. Interner Oscillator und Fuse-Bits
  4. CLI am Serial-Port
  5. Erweiterung der CLI

Motivation:

Für die folgenden Projekte mit dem Atmel μC und zur schnelleren Fehlersuche vor allem in Assemblerprogrammen brauchte ich erstmal eine vernünftige Möglichkeit, interne Daten (Registerwerte, Zustandswerte, etc.) anzeigen zu lassen. Und für z.B. eine Motorsteuerung möchte ich als von der bash-Shell verwöhnter Linux-User auch Befehle eingeben können (Bsp. Motor einschalten) und ein Ergebnis als Output zurückbekommen.

Die einfachste Lösung für die eben geschilderte Problemstellung findet man unter dem Begriff "Command Line Interface (CLI)" zusammengefaßt, wofür sich die serielle Schnittstelle zur Kommunikation zwischen Microcontroller und PC anbietet (man kann natürlich auch eine LCD-Anzeige und Keyboardeingabe implementieren, wenn einem das nicht genügt ;-). Der ATtiny2313 ist erfreulicherweise mit einem "Full Duplex USART" (USART - Universal Synchronous and Asynchronous serial Receiver and Transmitter) ausgestattet.

echo-Funktion am Serial-Port:

Als Vorarbeit für die doch etwas komlexere CLI habe ich zuerst eine simple echo-Funktion geschrieben, die alle am seriellen Port empfangenen Zeichen (Characters) einfach wieder zurückschickt. Die sog. Daten-Frames kommen am seriellen Port über die RxD-Leitung (Receive Data) an und werden über die TxD-Leitung (Transmit Data) wieder zurückgesendet.

Auf dem PC gibt es für die Kommunikation über den Serial-Port genügend Tools, z.B. unter Linux das minimalistische picocom oder das etwas komfortablere minicom. Unter Windows kann man das Hyperterminal z.B. auf Serial-Port COM1 verwenden.

Bevor man den Serial-Port auf dem Atmel μC benutzen kann, muß man zuerst den USART-Baustein initialisieren, d.h. die Baudrate konfigurieren, Frame-Format festlegen und Receiver/ Transmitter aktivieren. Dann kann man die Interrupts freischalten (RX Complete Interrupt Enable und Global Interrupt Flag), wenn man jedes empfangene Byte Interrupt-gesteuert auswerten möchte. Als Baudrate ist 9600 (Bits/sec) üblich. Der Standard für das Frame-Format ist 8N1, d.h. 8 Data bits, no Parity bit, 1 Stop bit. Die genaue Prozedur kann man im ATtiny2313-Manual, Seite 115 ff. nachlesen.

Das fertige Assembler-Programm für die echo-Funktion ist hier:
usart_echo.asm

Dazu gehört noch die Include-Datei mit den Definitionen der Register und Bitvalues vom ATtiny2313 (für den avra-Assembler modifizierte Version von tn2313def.inc, gefunden in einem Forum auf www.mikrocontroller.net):
tn2313def.avra.inc

Und wie immer das Makefile dazu:
Makefile

Das Makefile ist inzwischen auch etwas umfangreicher geworden. Ein make help zeigt alle wichtigen targets. Mit make wird nur das Hex-File assembliert, mit make upload dagegen assembliert, der Programm-Flash auf dem Atmel gelöscht (uisp --erase) und anschließend der Hex-Code vom Assembler-File auf den Flash hochgeladen (uisp --upload) und verifiziert (uisp --verify). Ein make test hingegen started zusätzlich noch ein picocom auf dem Serial-Port (Makefile-Variable PICO_TTY). Zu den Targets init_chip und show_fuses mehr im nächsten Abschnitt. Mit make clean kann man aufräumen und alle alle generierten Dateien löschen. Das Makefile automatisiert nun die gesamte Flash-Prozedur.

Interner Oscillator und Fuse-Bits:

Den Serial-Port zum Laufen zu bringen, stellte sich als große Geduldsprobe heraus. Mein Programm tat einfach nicht, was es sollte, es kam nichts auf dem Serial-Port zurück. Ich war schon fast wieder am Aufgeben.

Dann habe ich zusammen mit meinem Kollegen die Receive- und Transmit-Leitungen vom Serial-Port mit einem Oszi durchgemessen und festgestellt, daß nur ein Rx-Interrupt ausgelöst wurde. Schließch hatte er die zündende Idee, daß der Atmel-μC einen internen Oszillator hat, der standardmäßig eingeschaltet ist. Ich hatte erwartet, daß der Atmel-μC automatisch mit dem externen Quarz getaktet wird, wenn der an XTAL1 und XTAL2 angeschlossen wird, was bei dem μPSD der Fall war, mit dem ich vorher experimentiert hatte.

Das war aber nur die halbe Miete. Der interne Oszillator hatte nämlich auch die Frequenz 8Mhz. Daran lag es also nicht unbedingt. Wie sich herausstellte, gibt es noch einen Takt-Teiler, der defaultmäßig eingeschaltet ist und den Wert 8 hat! Damit konnte natürlich nichts mit dem USART funktionieren, denn der hatte damit eine Baudrate von 9600/8.

Im Manual (Seite 24 ff., System Clock and Clock options), fand sich dann der Hinweis, wo man auf den externen Quarz umschalten (CKSEL3:0 & CKOUT) und den Takt-Teiler (CKDIV8) umstellen konnte: im Flash Fuse Low Byte, das man mit dem uisp mit der Option --wr_fuse_l umprogrammieren kann. Mit dem 8Mhz-Quarz ergibt sich dann der Wert (CKOUT = 1, CKSEL3:0 = 1111, CKDIV8 = 1, SUT1:0 = 10) 0xef für das Fuse Low Byte. Das "Initialisieren" des Atmel-μC mit uisp --wr_fuse_l=0xef habe ich daher im Makefile im Target init_chip festgehalten:

make init_chip
/home/akagisan/uC/tools/uisp/uisp-20050207/src/uisp -dprog=dasa2 -dserial=/dev/ttyS0 --wr_fuse_l=0xef
Atmel AVR ATtiny2313 is found.

Fuse Low Byte set to 0xef
Mit uisp --rd_fuses kann man die Werte aus den Flash Fuses auch auslesen, das habe ich im Makefile ins Target show_fuses geschrieben:
make show_fuses
/home/akagisan/uC/tools/uisp/uisp-20050207/src/uisp -dprog=dasa2 -dserial=/dev/ttyS0 --rd_fuses
Atmel AVR ATtiny2313 is found.

Fuse Low Byte      = 0xef
Fuse High Byte     = 0xdf
Fuse Extended Byte = 0xff
Calibration Byte   = 0x54  --  Read Only
Lock Bits          = 0xff
    BLB12 -> 1
    BLB11 -> 1
    BLB02 -> 1
    BLB01 -> 1
      LB2 -> 1
      LB1 -> 1
Also eine Menge Stolperfallen, die einem in den Weg gelegt werden, wenn man sich das erste Mal mit dem Atmel beschäftigt. Aber jetzt war der Weg frei, um ein Command-Line-Interface für den Serial-Port zu realisieren.

CLI am Serial-Port:

Ich habe mich dafür entschieden, die CLI (usart-cli.asm) in Assembler und nicht in C zu schreiben, da ich mir ausmalen konnte, daß der Code viel umfangreicher als bei echo-Funktion werden würde, und beim ATtiny2313 nur 2KB Flash als Programm-Speicher zur Verfügung stehen. Bei der Erweiterung des Befehlsumfangs der CLI würde der Code sicher schnell an die Grenzen des Flash-Speichers stoßen (OK, ich könnte auch einfach auf einen anderen Atmel μC mit mehr Flash umsteigen, z.b. ATmega). Außerdem wollte ich noch etwas mehr vom Assembler kennenlernen.

Dann mußte ich mir noch über den Funktionsumfang der CLI klarwerden. Nach einem Reset sollte ein Header-Text mit einer kurzen Identifikation, der Versionsnummer, dem Autor (ich!) und einem Prompt in der folgenden Zeile ausgegeben werden, z.B.:

Atmel Evaluations-Board by Pollin, Version 2.0
Atmel AVR ATtiny2313 console, version 0.1
written by Thomas Hoehn, Erding, 10/2007
cli >
Die Eingabezeile (Command Line) wird auf 48 Zeichen begrenzt (reicht aus, Variable INPUT_BUF_LEN), danach stoppt die Eingabe. Eine minimale Editier-Funktion wird mit dem Backspace-Key geboten, der das letzte Zeichen löscht, bis zum Anfang der Eingabezeile. Mit Enter wird die Eingabe abgeschlossen und der eingegebene Befehl ausgewertet. Ist der Befehl nicht implementiert, wird eine Fehlermeldung ausgegeben. Nur 3 Commands werden hier implementiert:
  • help - zeigt verfügbare Befehle
  • sysinfo - Info zum μC-Device, d.h. SRAM-, EEPROM- & Flash-Size, μC-Takt
  • led1 - toggle LED1 (LED1 einschalten/ausschalten)
Die ganze Implementierung hat gerade mal 2 Tage gedauert. Hier das fertige Assembler-Programm, die Include-Dateien und das Makefile für die minimalistische Atmel-CLI: Das Include-File bcd-convert.inc (leicht modifizierte Version von konvert.asm, Quelle: www.avr-asm-tutorial.net) ist notwendig für den sysinfo-Befehl zur Umwandlung der Hex-Werte für SRAM/EEPROM/Flash-Size in Dezimal-Werte.

Der assemblierte Programmcode (Hexcode) ist nun bereits auf 1KB angewachsen, belegt also die Hälfte vom verfügbaren Flash-Speicher!
Das zeigt die Ausgabe von avra usart-cli.asm:

Segment usage:
   Code      :       536 words (1072 bytes)
   Data      :        60 bytes
   EEPROM    :         0 bytes
Hier der Output von einem Test der CLI mit picocom, das über /dev/ttyUSB0 (USB/Serial-Adapter) mit dem Atmel-Board kommuniziert:
akagisan@kobe:~/uC/atmel/projects/usart-cli$ picocom /dev/ttyUSB0
        picocom v1.4

port is        : /dev/ttyUSB0
flowcontrol    : none
baudrate is    : 9600
parity is      : none
databits are   : 8
escape is      : C-a
noinit is      : no
noreset is     : no
nolock is      : no
send_cmd is    : ascii_xfr -s -v -l10
receive_cmd is : rz -vv

Terminal ready
Atmel Evaluations-Board by Pollin, Version 2.0
Atmel AVR ATtiny2313 console, version 0.1
written by Thomas Hoehn, Erding, 10/2007

cli >
cli > foobar
Error: unknown command. Type 'help' for available commands.
cli > help
Available commands: sysinfo, help, led1
cli > sysinfo
System info:
Device: ATtiny2313
Flash: 2048 Byte
SRAM: 128 Byte
EEPROM: 128 Byte
f_osc: 8Mhz
cli >
Thanks for using picocom
akagisan@kobe:~/uC/atmel/projects/usart-cli$

Erweiterung der CLI:

Weitere Befehle für die Command-Line lassen sich mit wenig Aufwand hinzufügen. Man muß dafür nur folgendes in usart-cli.asm ändern/hinzufügen:
  • Anzahl der Befehle in CMD_COUNT setzen
  • Index-Nummer n des Befehls definieren mit .set LED_CMD_IDX = n
    (siehe Definition von SYSINFO_CMD_IDX, HELP_CMD_IDX, LED_CMD_IDX)
  • in der Routine EVAL_CMD Code für den neuen Befehl:
    ;
    ; new command
    ;
    NEW_CMD:
    cpi     r16,NEXT_CMD_IDX
    brne    NEXT_CMD
    rcall   EXEC_NEW_CMD
    rjmp    FINISH_CMD
    
    wobei beim vorigen Befehlscode brne NEW_CMD eingetragen werden muss und NEXT_CMD auf das Sprung-Label vom nachfolgenden Befehl zeigt. In der Routine mit dem Label EXEC_NEW_CMD führt definiert man dann, was der neue Befehl machen soll.
  • ganz am Schluß bei den String-Definitionen (; string constants) den neuen Befehls-String definieren:
    NEW_CMD_STR: .DB "new_cmd", 0x0
    (0x0 terminiert den String) und außerdem in der help-Message hinzufügen:
    HELP_MESSAGE: .DB
               "Available commands: sysinfo, help, led1, new_cmd", 0x0
  • in der Routine REGISTER_CMDS am Ende vor ret 2 neue Zeilen hinzufügen (die Reihenfolge der Einträge muß mit den Index-Nummern der Befehle übereinstimmen):
    ldi     r16,low(NEW_CMD_STR)
    st      Z+,r16
    ldi     r16,high(NEW_CMD_STR)
    st      Z+,r16
    


Next: Nachdem die Vorarbeit getan war, und nun eine Eingabe/Ausgabe- Schnittstelle für Steuerbefehle bereit stand, konnte ich zum nächsten Schritt übergehen und eine Motorsteuerung über die PWM-Schnittstelle bauen.

zurück zur Übersicht