Hallo liebe Mitstreiter,
habe vor ein paar Wochen mit diesem kommodigem AVR-Assembler begonnen
und erwecke nun einige Hardwarekomponenten (die üblichen Verdächtigen)
zum Leben.
Aus der Hochsprachenprogrammierung habe ich noch die dumme Angewohnheit
meinen Code auf Wiederverwendbarkeit und schlanke Schnittstellen zu
trimmen, sodass ich keine Monolithen bekomme und nie mit Codekopieren
arbeiten muss.
Das ist in Assembler gar nicht so leicht, insbesondere wenn kein
Betriebsystem und damit keine Speicherverwaltung vorhanden ist wie auf
meinen ATmega-Systemen.
Wenn ich nun Assembler-Dateien, z.B. für UART-Zugriff und
Display-Ansteuerung für Wiederverwendbarkeit gestalte, habe ich
Schwierigkeiten und erhoffe hier ein paar Hinweise von Euch zur
Gestaltung von Projekten mit mehreren Dateien.
Beispiel: ich habe Basisroutinen zur Ringpufferverwaltung auf die die
UART-Routinen aufbauen (ich arbeite mit blockierendem Senden und
gepuffertem Empfang). Die UART-Routinen sollen wiederum im Hauptprogramm
verwendet werden, welches aber möglicherweise selber auch
Ringpufferroutinen verwendet. In C und C++ und ähnlichen bindet jedes
"Modul" das ein, was es benötigt und Präprozessor oder ein anderer
Mechanismus verhindert doppeltes Kompilieren.
Im Moment muss ich immer auf die Reihenfolge der includes von
asm-Dateien achten und teilweise deren defs in eigene .inc-Dateien
auslagern, damit ich diese .inc-Dateien oben im Hauptprogramm und die
asm-Dateien weiter unten einbinden kann.
Gibt es dazu Hinweise und Verfahren, die sich bewährt haben, vielleicht
ähnliche Krücken wie in C mit #ifndef MEINEDATEI_H usw.?
Grüße, Dank und viel Spaß
Nunja, wenn ich bisher ein Projekt in Assembler aufgesetzt habe, dann
meistens immer aus dem Grund, das letzte Bisschen Geschwindigkeit und
Rechenzeit herauszunehmen.
Allein daher wars dann eben schon unpraktisch, 'generische' Module zu
schreiben und wieder Tempo zu verschenken.
Kurzun: Meistens immer ein langes ASM-Listing :->
Jan Schirrmacher schrieb:
> Wenn ich nun Assembler-Dateien, z.B. für UART-Zugriff und> Display-Ansteuerung für Wiederverwendbarkeit gestalte, habe ich> Schwierigkeiten und erhoffe hier ein paar Hinweise von Euch zur> Gestaltung von Projekten mit mehreren Dateien.
Schau Dir mal amforth (amforth.sf.net) an, dort werden einige viele
Assemblersourcen nach Bedarf zusammengestellt. Die Schnittstelle
zwischen
den Modulen ist relativ einfach als Stack ausgebildet. Wiederverwendung
und austauschbare Module sind ebenfalls an Bord.
Warum wird immer wie selbstverständlich die Annahme gemacht, daß man
seine Code-Module von vornherein möglichst generisch gestalten und auf
Wiederverwendbarkeit trimmen sollte? Steht das in irgendwelchen
Lehrbüchern, die nicht hinterfragt werden?
Ich halte das für einen großen Irrtum und einen Hauptgrund für
aufgeblasene, fehlerträchtige und ineffiziente Software.
Insofern schließe ich mich meinen Vorrednern an, gerade wenn es um
Assembler-Programmierung geht. ASM-Codeschnipsel sind per definitionem
weder portabel noch generisch. Was nicht heißt, daß man nicht aus einem
alten Projekt bewährte Codesequenzen in ein neues kopieren und dort
anpassen sollte.
Hi
Hier mal mein Ansatz am Beispiel Dog-Grafik-LCDs.
Für die ASM-LIB existiert eine Konfigurations-Datei in der alle
notwendigen Konstanten festgelegt und die verwendeten Unterprogramme
freigeschaltet werden. Diese wird in das Projektverzeichnis kopiert,
konfiguriert und am Anfang des Hauptprogramms mit include eingebunden.
Die ASM-Datei befindet sich in einen anderen Verzeichnis, das mit
'Additonal include path' für den Assembler sichtbar ist.
MfG Spess
Rolf Magnus schrieb:
> Wenn du in C genau das kannst, was du willst, warum schreibst du deine> Programme dann stattdessen in Assembler?
ooch, ich finde maschinennahe Sachen sehen in Assembler besser aus.
Beispielsweise sind die verschiedenen Bitroll-Befehle kaum unterstützt.
Außerddem kann man in Assembler sehr wohl strukturiert programmieren.
Das hängt aber ein wenig an einer eventuell vorhandenen
Betriebsystemstruktur ab. Habe solche Systeme in jungen Jahren
entwickelt.
Aber hauptsächlich finde ich AVR so schön Back-To-The-Roots. Da passt
eben Assembler. Aber dein Einwand ist natürlich prinzipiell berechtigt.
Oliver Döring schrieb:
> Warum wird immer wie selbstverständlich die Annahme gemacht, daß man> seine Code-Module von vornherein möglichst generisch gestalten und auf> Wiederverwendbarkeit trimmen sollte? Steht das in irgendwelchen> Lehrbüchern, die nicht hinterfragt werden?>> Ich halte das für einen großen Irrtum und einen Hauptgrund für> aufgeblasene, fehlerträchtige und ineffiziente Software.
Das steht sehr wohl in Lehrbüchern drin. Und Assembler ist nicht
gleichbedeutend mit Unstrukturiert. Die Strukturen sind eben bloß nicht
portierbar.
Dein Problem rührt einfach daher, dass der AVR Assembler nur ein recht
einfacher Assembler ist. Mit dem AVR-GCC kann man sehr wohl mehrere
Module getrennt assemblieren und dann zu einem einzigem ELF oder HEX
zusammen linken.
Symbole kann man dann in einem Modul (glaub per 'public') auch nach
aussen sichtbar machen und von wo anders her aufrufen. Schau dir am
besten mal die Man-Pages zum AVR-GCC an.
Einziger Stolperstein ist hier, dass der Linker immer noch den
Einsprungpunkt in der C-Startup Funktion vermutet. Aber das liess sich
auch deaktivieren, müsste nochmal nachschauen wo das war.
Wenn man meist auf dem selben AVR arbeitet, kann es schon Sinn machen
die gut optimierten Assembler Routinen in ein Modul zu schieben und dann
per #define abhängig vom konkret verwendeten AVR festzulegen welcher
Code erzeugt wird. Mit jedem neuem AVR wächst dann das Modul-Listing
eben etwas.
Gruß,
Ronny
> Warum wird immer wie selbstverständlich die Annahme gemacht, daß man> seine Code-Module von vornherein möglichst generisch gestalten und auf> Wiederverwendbarkeit trimmen sollte? Steht das in irgendwelchen> Lehrbüchern, die nicht hinterfragt werden?
Na endlich spricht das mal jemand an! Die meisten Projekte haben nämlich
immer wieder etwas andere Anforderungen an das konkrete Modul. Da fährt
man einfach dreimal besser, wenn man den Code kopiert und nicht bloss
einbindet. Insbesondere treten so in alten Projekten nicht plötzlich
Bugs aufgrund von Änderungen in den Bibliotheken auf.
Natürlich, wenn man am Anfang die Module perfekt durchplant und sehr
flexibel gestaltet, dann sollten weder Änderungen an den Bibliotheken
noch Kompatibilitätsprobleme mit älteren Projekten auftreten. Aber jeder
hier kennt die Grenzen von Vorausplanung (und -sehung) sowie
Bugfreiheit.
Und Assemblerprojekte zu modularisieren ist sowieso ein Widerspruch in
sich - wer heute auf Mikrocontrollern in Assembler programmiert, der
will ja gerade maximale Effizienz und/oder exakt vorhersehbares
Zeitverhalten. Das beisst sich aber mit generischen Schnittstellen ganz
gewaltig.
Hi,
kann auch nur WinAVR empfehlen, arbeitet mit AVR Studio zusammen (wenn
man das denn will) und man kann ASM und C kombinieren.
In meinem aktuellen Projekt (12x12 LED-Matrix mit Software PWM und
diversen auf dem AVR selbst berechneten Effekten) habe ich im C-Programm
mehr die Steuerfunktionen und Berechnungen.
Im ASM Teil habe ich die Sachen, die oft aufgerufen werden und Schleifen
beinhalten (PWM, kopieren von Speicherbereichen, Linien zeichnen etc.).
Zwangsweise ergeben sich zum C-Programm entsprechende Schnittstellen die
auch ein paar Zyklen kosten (Register auf Stack sichern etc.), auch habe
ich einige Programmteile ziemlich allgemein gehalten bzw. paramtrierbar
gemacht.
Aber ich schätze alles in allem dürfte der Vorteil durch direkten
ASM-Code noch min. Faktor 2 sein was auf einem µC schon einiges
ausmacht.
Ciao...
Markus
Markus M. schrieb:
> kann auch nur WinAVR empfehlen, arbeitet mit AVR Studio zusammen (wenn> man das denn will) und man kann ASM und C kombinieren.
Sehr gut, so langsam wird mir der Hintergrund klarer. Der AVR-Assembler
des AVR-Studio arbeitet ohne Linker (da er eh keinen Code zu relozieren
hat) und daher funktioniert Modularisierung auf Linker-Ebene sowieso
nicht.
habe mich auch schon gefragt, wie man dann C und Assembler mischen kann
(ausser inlne Assembler) ohne Linker. Aber der Hinweis mit WinAVR ist
interessant in diesem Zusammenhang, da ich über kurz oder lang die
Gurken auch mal in C programmieren werde.
Dann auch für die Hinweise der anderen Poster. Die Diskussion über den
Sinn des Assemblerprogrammierens ist berechtigt und ich sehe auch das
Problem der Codeanpassung, z.B. an andere Port-Konfigurationen, das
strukturiert nur aufwändig zu realisieren ist.
Wahrscheinlich denke ich zu softwarelastig. Mir ist gerade eingefallen,
dass die AVRs recht billig sind und daher eine Modularisierung auf
Hardwareebene ebenfalls in Frage kommt. So könnte ich mein LCD-Modul
einfach mit einem ATTiny ausstatten und ein simples SPI-Interface
implementieren mit einem eigenen Softwareprotokoll und schon wäre die
Sache einfacher in andere Projekte zu integrieren.
Haach. Das bringt schon Spaß mit diesen kleinen Chips.
Ich finde modulare Programmierung schon sehr sinnvoll.
Z.B. beim LCD möchte ich mir nicht vorschreiben lassen, welche Pins ich
nehmen soll, da man dann oft die Spezialfunktionen (UART, PWM, ADC, ...)
verliert.
Da nehme ich einfach nen Universalcode für beliebige Pins:
http://www.mikrocontroller.net/attachment/30300/lcd_drv.zip
Auch wird durch die Modularisierung die Programmierung deutlich leichter
und fehlersicherer.
Ein einmal entwickelter Modul wird benutzt und gut is.
Programme über 2kB möchte ich aber nicht mehr in Assembler proggen
müssen, das ist mir deutlich zu aufwendig.
C-Compiler sind garnicht so schlecht, etwa 5..20% Overhead bezüglich
Größe und Geschwindigkeit ist doch kein großer Nachteil.
Die 5..20% gelten aber nur für erfahrene Assemblerprogramierer.
Assembleranfänger erreichen oft das Gegenteil, d.h. größeren und
langsameren Code als der C-Compiler.
Was nützt es, einige Befehle einzusparen um damit bezogen auf das
Gesamtprogramm 0,1% CPU-Last weniger zu haben. Es ist einfach nur
vergeudete Arbeitszeit.
Peter
@oliver
>Ich halte das für einen großen Irrtum und einen Hauptgrund für>aufgeblasene, fehlerträchtige und ineffiziente Software.
Das Stimmt so nicht ganz. Aufgeblasene Software hat man doch ohnehin
meist "nur" auf dem PC, wo es eben nicht auf ein Megabyte genau ankommt.
Beim AVR hat man eben aber kein Megabyte. Und ich behaupte mal das wenn
man sich die Schnittstellen seiner Module vernünftig zurechtlegt dann
spart man doppelt und dreifache Arbeit, selbst wenn es etwas mehr Code
kostet.
Und wie Peter schon erwähnte, bei großen Projekten ist Assembler einfach
zu aufwendig, selbst wenn man dann vllt ein paar Prozent eingespart hat.
Zumal hat man bei Assembler eben nicht die Möglichkeit den Code für
einen anderen Prozessor (bzw andere Prozessorfamilie) einzusetzen und
muß dann wirklich alles neu schreiben.
Assembler ist dann eine Alternative, wenns auf jeden Taktzyklus oder
jedes Byte ankommt.
Oliver Döring schrieb:
> Warum wird immer wie selbstverständlich die Annahme gemacht, daß man> seine Code-Module von vornherein möglichst generisch gestalten und auf> Wiederverwendbarkeit trimmen sollte? Steht das in irgendwelchen> Lehrbüchern, die nicht hinterfragt werden?
Das zeichnet einfach die Entwickler aus, die etwas weiter als bis zum
aktuellen Projekt denken. ;)
Wiederverwendbarer Code spart nicht nur Arbeitszeit, er sorgt auch für
bessere getestete Software und - was immer wieder vergessen wird - er
ist oft deutlich besser strukturiert und damit besser wartbarer.
Außerdem macht sich der Entwickler vorher eher Gedanken über einen
sinnvollen Aufbau.
> Ich halte das für einen großen Irrtum und einen Hauptgrund für> aufgeblasene, fehlerträchtige und ineffiziente Software.
Nö. Aufgeblasen und fehlerträchig wird es erst, wenn der Entwickler
nicht verstanden hat, wie weit die Wiederverwendbarkeit sinnvoll
getrieben werden kann. Und auch dann, wenn sich der Entwickler keinerlei
Gedanken um Wiederverwendbarkeit macht und einfach drauflos
programmiert.
Ich versuche meinen Code auch immer so zu strukturieren und die
Schnittstellen von Funktionen so zu gestalten, dass ich die auf
möglichst vielseitige Art in verschiedenen Projekten nutzen kann.
Der Code ist deswegen kein bisschen aufgeblasen.
Hi
>C-Compiler sind garnicht so schlecht, etwa 5..20% Overhead bezüglich>Größe und Geschwindigkeit ist doch kein großer Nachteil
Ich habe hier noch keinen disassemblierten C-Code gesehen der diese
Bedingung erfüllt hätte.
>Programme über 2kB möchte ich aber nicht mehr in Assembler proggen>müssen, das ist mir deutlich zu aufwendig.
Wenn du davon 1,5k aus der Schublade holen kannst, kein Problem.
>Zumal hat man bei Assembler eben nicht die Möglichkeit den Code für>einen anderen Prozessor (bzw andere Prozessorfamilie) einzusetzen und>muß dann wirklich alles neu schreiben.
Das hat sich auch erledigt, wenn du in C auf irgendwelche IO-Register
zugreifst.
MfG Spess
>Das hat sich auch erledigt, wenn du in C auf irgendwelche IO-Register>zugreifst.
Wenn der Code wiederverwendbar werden soll benutzt man i.d.R. Makros die
man bei Wiederverwendung innerhalb der selben Prozessorfamilie einfach
nur anpasst.
Beispiel 7-Segmentanzeige :
#define LED7_DATA_PORT PORTA
#define LED7_DATA_DDR DDRA
#define LED7_STRB_PORT PORTD
#define LED7_STRB_DDR DDRD
#define LED7_STRB_PIN 4
#define LED7_SIZE 3
#define LED7_STRB_MASK 0x70
#define LED7_DATA_INIT LED7_DATA_DDR = 0xff;
#define LED7_STRB_INIT LED7_STRB_DDR |= LED7_STRB_MASK;
#define LED7_STRB(x) LED7_STRB_PORT = \
(LED7_STRB_PORT & ~LED7_STRB_MASK) | \
(1 << (LED7_STRB_PIN + (x)))
#define LED7_DATA(x) LED7_DATA_PORT = (x)
void v7SegInit (void);
void v7SegDoTimer (void);
void v7SegSet (char cSeg, unsigend char cDigits);
So ähnlich könnte ein 7-Segment-Modul-Header aussehen.
Der eigentliche Quellcode lässt sich für alle Prozessoren der selben
Familie verwenden. Außerdem kann man durch Anpassung der Makros
LED7_DATA und LED7_STRB die Anzeige auch über einen I2C Bus, ein
Schieberegister oder dergleichen für den Anschluß realisieren.
Wenn das ganze z.b. für einen ARM7 portiert werden soll, passt man
einfach nur die Makros LED7_DATA, LED7_DATA_INIT, LED7_STRB und
LED7_STRB_INIT an.
Bei komplexeren Sachen die die direkte Hardware des Prozessors betreffen
wie z.b. UART, I2C, SPI muß das Modul neugeschrieben werden.
Aber bei Verwendung der gleichen Software-Schnittstellen kann darauf
basierende Hardware (z.b. LCD via SPI oder I2C EEProm) bei
entsprechender Kapselung (meist) ohne weiteres angesprochen werden.
Alle weiteren Software-Module die nichts mit der Hardware zu tun haben,
Middleware, Tools und Ablaufsteuerungen lassen sich fast gänzlich ohne
Anpassungen portieren, dadurch das da eben keine Registerzugriffe
gemacht werden.
Hi
>Wenn der Code wiederverwendbar werden soll benutzt man i.d.R. Makros die>man bei Wiederverwendung innerhalb der selben Prozessorfamilie einfach>nur anpasst.
Geht in Assembler ebenso. Sieh dir mal meinen ersten Anhang vom 17.1.
an. Das Problem ist, das viele die Möglichkeiten des
Assemblers/Preprozessors nicht nutzen oder gar nicht kennen.
MfG Spess
spess53 schrieb:
> Ich habe hier noch keinen disassemblierten C-Code gesehen der diese> Bedingung erfüllt hätte.
Nimm einfach mal das oben erwähnte LCD-Beispiel und versuch es
einzudampfen.
Ich kann Dir auch das Assemblerlisting erzeugen (mit eingeschalteter
Optimierung).
Es gibt natürlich viele C-Programme, die aufgebläht sind.
Sehr oft sieht man z.B. daß gedankenlos int genommen wird, obwohl
uint8_t ausreicht. Klar, daß sich dann der Code verdoppeln muß, wenn man
16Bit auf nem 8Bitter macht.
Peter
P.S.:
Hast Du ein schönes Assemblermacro, was es auch gestattet, Portpins
bequem als Bitvariablen zu definieren. D.h. Bitnummmer und Adresse in
einem Namen abzulegen?
Das gefällt mir am GCC sehr, hat mir auch am Keil C51 schon gut
gefallen.