(→PS:) |
(→Autor) |
||
Zeile 380: | Zeile 380: | ||
Damit kann man sein know-how vor neugierigen Blicken verstecken. Um das zu verwenden, muß man im Hauptprogramm aber dann schreiben: | Damit kann man sein know-how vor neugierigen Blicken verstecken. Um das zu verwenden, muß man im Hauptprogramm aber dann schreiben: | ||
$LIB "RN_LIBRARY.LBX" | $LIB "RN_LIBRARY.LBX" | ||
+ | |||
+ | ==Anmerkung== | ||
+ | Library-Funktionen können auch ohne den Call-Standard verwendet werden. D.h. die Sache mit der Argumenten-Übergabe über den Softstack ist nicht zwingend vorgeschrieben. in unserem Beispiel lief ja im Kern der Copy über die Pointer-register Z u. X mit dem Zähler r24 | ||
+ | |||
+ | Man könnte das auch folgendermaßen machen und einigen Code sparen, den das Aufbauen des SoftStacks und das Lesen der Argumente in der Funktion selbst sind recht aufwendig. | ||
+ | |||
+ | '''Hauptprogramm''' | ||
+ | <pre> | ||
+ | $LIB = "library.lib" | ||
+ | $external move_z_to_x | ||
+ | Declare Sub move_z_to_x() | ||
+ | |||
+ | $asm | ||
+ | LDI r24, 5 | ||
+ | $end asm | ||
+ | LOADADR array(1), X ' das werden nur 2 Maschinenbefehle ! | ||
+ | LOADADR source_string, Z ' ebenso | ||
+ | GOSUB move_z_to_x ' das ist einer | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | |||
+ | '''library.lib''' | ||
+ | <pre> | ||
+ | ... | ||
+ | [move_z_to_x] | ||
+ | move_z_to_x: | ||
+ | _mov_lop: | ||
+ | ld r22, Z+ 'read source | ||
+ | st x+, r22 'write target | ||
+ | dec r24 'length-- | ||
+ | brne _mov_lop '<> 0 ? -->loop | ||
+ | ret | ||
+ | [end] | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | |||
+ | |||
==Autor== | ==Autor== |
Version vom 4. Juni 2006, 16:48 Uhr
Inhaltsverzeichnis
Bascom Libraries
Im Installationsbereich von Bascom befinden sich eine Reihe Libraries mit den Endungen ".LBX" und ".LIB". Erstere sind gleichnamige, aber vorkompilierte Versionen von den ".LIB" Files. Dieses Vorkompilieren geschieht im LIB-Manager (Menu->Tools).
Aber auch die ".LBX" Files sind keine Object-Libraries, wie man sie von anderen Sprachen her kennt und die durch einen "Linker" zusammengefügt werden. Beides sind normale Textfiles, die erst beim Kompilieren des Basic-Hauptprogrammes tatächlich in Maschinencode übersetzt werden.
Deshalb ist es Bascom auch ziemlich egal, ob man bei der $lib Anweisung "lib.lib" oder "lib.lbx" angibt, es ist im Grunde die selbe Arbeit
Warum nun überhaupt ".LBX" ?
Durch das Vorkompilieren werden die Libraries doch einigermassen unleserlich, und wenn von einer Library die ".LIB" Version nicht da ist, will man den Code wohl nicht preisgeben.
Beide Versionen sind Sammlungen von "Bausteinen", die Bascom nur in das Benutzer-Programm einbindet, wenn sie tatsächlich gebraucht werden. Diese Bausteine sehen folgendermassen aus:
[Baustein_Name] Baustein_Name: .... Assembler-Answeisungen... RET [end]
Beispiel
Man erzeugt mit Notepad oder einem anderen Editor eine Datei "Testlib.LIB" im Library-Verzeichnis von Bascom (muß man im Installationsverzeichnis nachsehen, wo das ist)
[Meine_Funktion] Meine_Funktion: RET [end]
(und speichern, natürlich)
Im Hauptprogramm (in BasCom) schreibt man nun
$regfile = "m32def.dat" $crystal = ..... $lib "testlib.lib" $external Meine_Funktion Declare Sub Meine_Funktion() '.... irgendwo... CALL Meine_Funktion
Dadurch wird die Funktion an die nächste freie Stelle in den Maschinen-Code eingefügt und bei "Call" aufgerufen.
Man braucht jetzt nur noch irgendetwas Sinnvolles in die Funktion reinzuschreiben.
Ersetzen von Bascom-Library-Functions
Es ist sehr einfach, Funktionen, die sich in irgendeiner mitgelieferten Bascom-Library befinden, durch eigenen Code zu ersetzen. Bascom geht nämlich beim Suchen nach einer gerade benötigten Funktion im in der Reihenfolge der "$LIB" Anweisung vor. Die Standardlibrary "MCS.LBX" wird immer erst als Letztes durchsucht. Schreibt man also
$LIB "testlib.lib"
sucht er erst in dieser Library, und erst wenn da nix drinsteht, nimmt er das, was sich in MCS.LIB befindet.
Leider sind nicht alle Bascom-Funktionen in Libraries vorhanden, vieles ist im BasCom-Programm fest eingebaut. Und manches findet man erst nach ein wenig Detektivarbeit ("PULSEIN" heißt zum Beispiel "_PULSE_IN").
Tips
Vorgangsweise
Wenn man eine Library-Funktion entwickeln will, ist es günstig, sie erstmal als normale Sub-Routine mit inline-Assembler zu schreiben, und erst, wenn das halbwegs klappt, sie in eine Library zu übertragen. Die Syntax ist zwar etwas verschieden, aber das kriegt man hin. Der Vorteil ist aber, daß man seinen Assemblercode im Simulator normal debuggen kann, und das ist was wert.
Argumente
Wenn man der Funktion Argumente oder eine Result-Adresse übergeben will, muß man natürlich den Bascom Call-Standard beachten. Der ist aber recht aufwendig. Alle (globalen) Variablen, die man mit DIM angelegt hat, kann man auch mit "LOADADR" adressieren, das spart Einiges.
Sichern
Zwischen "$ASM" und "$END ASM" ist man Herrscher über den kompletten Controller. Man kann alle Register hemmungslos einsetzen. Es ist also nicht so wie in einer ISR, daß man alles sichern und wieder herstellen muß
Nur die Register
YL:YH R4:R5 R6
sollte man sichern und am Ende wieder herstellen, wenn man sie verändern will.
Tutorial:Eine Library-Funktion erstellen
Es soll der ganze Vorgang der Entwicklung einmal durchgezogen werden.
Mem_move
Diese Beispiel-Funktion soll einfach ein SRAM-Bereich in ein anderes kopieren, das Ziel ist immer ein Byte-Array, die Quelle hingegen sollen Daten mit beliebigen Datentypen sein. Die Übertragungslänge muss man daher natürlich auch angeben. Als Ergebnis wiederum die Anzahl der übertragenen Bytes. Das ist in diesem Beispiel natürlich sinnlos, aber wir wollen ja auch sehen, wie man einen Wert zurückgeben kann.
Declare Function Mem_move(target As Byte , Srcaddr As Word , Byval Length As Byte) As Byte
Soweit alles klar, "Target" ist das Zielarray, "Length" die Übertragungslänge, nur was hat es mit "Srcaddr As Word" auf sich ? Es sollen ja beliebige Daten und nicht nur ein WORD übertragen werden.
Das ist ja ein Haken bei Bascom, das geht eben nicht so direkt.
Wir müssen die Funktion-Deklaration ja mit einem konkreten Datentyp befüllen, und den Typ "irgendwas" gibt's eben nicht.
Also macht man sowas mit "die Adresse von der Adresse" (C-Programmierer kennen das). D.h. wir definieren ein WORD, schreiben die aktuelle Datenquell-Adresse rein, und unsere Funktion kriegt immer die Adresse von diesem Word.
Dim Source As Word Source = Varptr(source_string) ' Die SRAM-Adresse von "Source_String"
Beispiel Hauptprogramm
(für einen Atmeg32, das ist aber völlig egal). Ein paar Dinge sind noch dabei, damit man mit "print" auch im Simulator-Terminal die Sache kontrollieren kann.
$regfile = "m32def.dat" $crystal = 8000000 $baud = 9600 $hwstack = 64 $swstack = 256 $framesize = 64 Declare Function Mem_move(target As Byte , Srcaddr As Word , Byval Length As Byte) As Byte Dim Ziel_len As Byte ' da soll das Ergebnis (Länge) rein Dim Ziel_arr(24) As Byte ' hier soll hinkopiert werden. Dim Print_string As String * 24 At Ziel_arr Overlay ' das ist nur für den "PRINT", um das ' Zielarray auf einfach herzuzeigen Dim Source_string As String * 24 ' das werden die Ziel-daten Source_string = "Hello, world" ' damit auch was drinsteht Dim Source As Word ' siehe oben Source = Varptr(source_string) Ziel_len = Mem_move(ziel_arr(1) , Source , 5) ' der funktionsaufruf: ' 5 Byte von Source kopieren Print "From " ; Source_string ' so sieht der Source-String aus Print "To (" ; Str(ziel_len) ; ") " ; Print_string ' und so die kopierten Daten End ' keine Schleife, wozu auch ?
Step 1: Inline Assembler
Die Funktions Argumente:
Nochmals der Verweis auf den Bascom Call-Standard. Am Anfang der Funktion findet diese ihre Argumente im "SoftStack", das Pointer-Registerpaar Y (YL:YH) zeigt auf den Beginn.
Bascom legt die Addressen der Argumente absteigend in folgender Reihenfolge auf den Soft-Stack:
Function Mem_move(target As Byte , Srcaddr As Word , Byval Length As Byte) As Byte
Erst die Adresse vom Funktionsergebnis, dann von links beginnend die Argument-Adressen in der Klammer, also insgesamt:
- High(Result)
- Low(Result)
- High(Target)
- Low(Target)
- High(Srcaddr)
- Low(Srcaddr)
- High(Length)
- Low(Length)
Und auf diesen letzten Wert zeigt YL:YH beim Aufruf der Funktion. Gelesen wird das mit
LD register, Y + Offset
Ein Beispiel:
ldi r24, 65 ldd xl, y + 6 ldd xh, y + 7 st x, r24
Entspricht dem Bascom-Statement
Mem_move = 65
d.h. die Funktion würde das Ergebnis 65 liefern
Und nun die Funktion "inline"
Function Mem_move(target As Byte , Srcaddr As Word , Byval Length As Byte) As Byte $asm ldd xl, y + 0 'length addr lo ldd xh, y + 1 'length addr high ld r24, x 'Length --> R24 clr r25 'Löschen Zählregister !and r24, r24 'length = 0 ? breq Mem_mov_xit 'wenn die Länge = 0 , ist garnix zu tun
Erst wir Xl:XH mit der adresse des Längenarguments geladen, dann Register 24 mit dem eigentlichen Wert. Der Zähler für das Funktionsergebnis wird auf Null gesetzt, dann wird die angegebene Länge geprüft, ob überhaupt was zu tun ist.
(Anm: das Rufzeichen beim "and" zeigt dem Bascom, daß das ein "Assembler-And" ist und ihn nix angeht)
ldd xl, y + 2 'source PARAM ldd xh, y + 3 'source PARAM ld zl, x+ 'source ld zh, x 'source
Für "Source" ist eine Ecke mehr: Auf dem Stack befindete sich die Adresse von einem WORD. Und erst da drin steht die eigentliche Adresse der Source-Daten.
ldd xl, y + 4 'target ldd xh, y + 5 'target
Das ist der "Normalfall". Auf dem Stack steht direkt die Zieladresse ( Ziel_arr(1) )
Mem_mov_lop: ld r22, Z+ 'read source st x+, r22 'write target inc r25 'counter++ dec r24 'length-- brne Mem_mov_lop '<> 0 ? -->loop
Die Copy-Schleife: von Z mit autoinkrement ins Register 22, von da nach X, auch mit Inkrement. Jedesmal wir der Ergebniszähler erhöht und der Kontrollzähler vermindert.
Mem_mov_xit: ldd xl, y + 6 ' result ldd xh, y + 7 ' result st x, r25 $end Asm End Function
X wird mit der Adresse des Funktionsergebnisses geladen (s.o) und R25 wird dorthingeschrieben.
Testen im Simulator Man sollte sich die tatsächlichen Variablen-Adressen aus der ".RPT" File anschauen. Damit und mit dem Register- und Memory-Fenster des Simulators kann man recht gut die einzelnen Steps beobachten. Für die Printout's beim "Teminal" ein Häkchen machen !
Step 2: Library Function
Angenommen, wir sind zufrieden, dann machen wir nun eine Library. Ich mach das mit Word- oder Notepad, ist egal, ein Text-Editor halt. Geht auch mit Bascom selbst, ist aber Geschmackssache
Eine leere Library
copyright = Anybody www = http://www.roboternetz.de email = picnick@nowhere.at comment = BASCOM AVR compiler library for Demo libversion = 0.1 date = 04 jun 2006 history = Beta
Und nun mit Cut & Paste aus dem Bascom Editor die Funktion einfügern
copyright = Anybody www = http://www.roboternetz.de email = picnick@nowhere.at comment = BASCOM AVR compiler library for Demo libversion = 0.1 date = 04 jun 2006 history = Beta ; Declare Function Mem_move(target As Byte , Srcaddr As Word , Byval Length As Byte) As Byte [Mem_move] Mem_move: ldd xl, y + 0 'length ldd xh, y + 1 'length ld r24, x clr r25 ' clear counter and r24, r24 'length = 0 ? breq Mem_mov_xit ' no action ldd xl, y + 2 'source PARAM ldd xh, y + 3 'source PARAM ld zl, x+ 'source ld zh, x 'source ldd xl, y + 4 'target ldd xh, y + 5 'target Mem_mov_lop: ld r22, Z+ 'read source st x+, r22 'write target inc r25 'counter++ dec r24 'length-- brne Mem_mov_lop '<> 0 ? -->loop Mem_mov_xit: ldd xl, y + 6 ' result ldd xh, y + 7 ' result st x, r25 ret ; ready [end]
Das Rufzeichen bei "AND" lassen wir weg, dafür kommt ist das dazugekommen. Ich rate, die Funktions-Deklaration als Kommentar dazuzuschreiben.
[Mem_move] Mem_move: ..... ret ; ready [end]
Das "RET" hat bei der Inline Version der Bascom gemacht ("END FUNCTION"), das darf man nun nicht vergessen.
Und nun "speichern als" RN_LIBRARY.LIB bei den anderen Bascom Libraries. Bei mir ist das z.B.
E:\Programme\MCS Electronics\BASCOM-AVR\LIB\RN_LIBRARY.LIB Aufpassen mit dem File-Typ !
Wir können das nun schon kontrollieren, im Bascom bei "Tool" ist ein Lib-Manager, das muß diese Library nun zu sehen sein.
Hauptprogramm umbauen
Das sieht nun so aus
$regfile = "m32def.dat" $crystal = 8000000 $baud = 9600 $hwstack = 64 $swstack = 256 $framesize = 64 $lib "RN_Library.LIB" ' das ist neu $external Mem_move ' das ist neu Declare Function Mem_move(target As Byte , Srcaddr As Word , Byval Length As Byte) As Byte Dim Ziel_len As Byte Dim Ziel_arr(24) As Byte Dim Print_string As String * 24 At Ziel_arr Overlay Dim Source_string As String * 24 Dim Source As Word Source_string = "Hello, world" Source = Varptr(source_string) Ziel_len = Mem_move(ziel_arr(1) , Source , 5) Print "From " ; Source_string Print "To (" ; Str(ziel_len) ; ") " ; Print_string End
Wenn wir nun "F7" drücken, wird sowohl das Basic-Programm als auch die Library übersetzt.
Das war's eigentlich
LBX ?
Von wegen ".LBX": Wenn wir mit dem Lib-Manager die Library kompilieren lassen, sieht das Ergebnis so aus:
Comment = Compiled LIB file, no comment included copyright = Anybody www = http://www.roboternetz.de email = robert.toegel@wienerborse.at comment = BASCOM AVR compiler library for Demo libversion = 0.1 date = 04 jun 2006 history = Beta [Mem_move] Mem_move: .OBJ 81A8 .OBJ 81B9 .OBJ 918C .OBJ 2799 .OBJ 2388 breq Mem_mov_xit ' no action .OBJ 81AA .OBJ 81BB .OBJ 91ED .OBJ 91FC .OBJ 81AC .OBJ 81BD Mem_mov_lop: .OBJ 9161 .OBJ 936D .OBJ 9593 .OBJ 958A brne Mem_mov_lop '<> 0 ? -->loop Mem_mov_xit: .OBJ 81AE .OBJ 81BF .OBJ 939C .OBJ 9508 [end]
Damit kann man sein know-how vor neugierigen Blicken verstecken. Um das zu verwenden, muß man im Hauptprogramm aber dann schreiben:
$LIB "RN_LIBRARY.LBX"
Anmerkung
Library-Funktionen können auch ohne den Call-Standard verwendet werden. D.h. die Sache mit der Argumenten-Übergabe über den Softstack ist nicht zwingend vorgeschrieben. in unserem Beispiel lief ja im Kern der Copy über die Pointer-register Z u. X mit dem Zähler r24
Man könnte das auch folgendermaßen machen und einigen Code sparen, den das Aufbauen des SoftStacks und das Lesen der Argumente in der Funktion selbst sind recht aufwendig.
Hauptprogramm
$LIB = "library.lib" $external move_z_to_x Declare Sub move_z_to_x() $asm LDI r24, 5 $end asm LOADADR array(1), X ' das werden nur 2 Maschinenbefehle ! LOADADR source_string, Z ' ebenso GOSUB move_z_to_x ' das ist einer
library.lib
... [move_z_to_x] move_z_to_x: _mov_lop: ld r22, Z+ 'read source st x+, r22 'write target dec r24 'length-- brne _mov_lop '<> 0 ? -->loop ret [end]