Hallo Leute. Ich habe mal ein paar grundsätzliche Fragen wie man seine Progrämmchen in ASM am besten strukturiert. Ich komme von Hochsprachen wie C/C++, und habe es mir dort angewohnt alles schön Modular aufzubauen, in vielen Funktionen, Prozeduren, auf mehrere Dateien. Viele Probleme werden in ASM nun sehr schwierig für mich so modular zu lösen. Beispiele: Parameterübergabe: Wie macht man das am Elegantesten? Ich nehme dann einfach Register. Ist aber manchmal doch etwas unübersichtlich Interrupt-Handling: Wenn ein Interrupt auftritt möchte ich das mein Programm das weiß, und entsprechende Prozeduren abhandelt. Nun könnte ich alle Prozeduren in die Interrupt Routine stecken, dann kann ich aber den selben Interrupt nicht währenddessen auslösen. Wie kann ich Programmverzweigungen durch einen Iinterrupt bewirken nachdem ich die entsprechene Interrupt Routine bereits beendet habe. Eigene Status-Flags einbauen? Prozeduren auf mehrere Dateien verteilen: Wenn ich obiges tun will, wie mache ich das? Der Assembler scheint mir meine includierten Dateien an den Anfang stellen zu wollen, und ist so der Meinung die includierten Dateien seien der Anfang des Programms. Lösung wäre hier eine Klarstellung mit .org direktive. Das ist aber SEHR umständlich weil ich dann die Codelängen aller Programmteile kennen muss, und dann entsprechened mit .org die Teile direkt hintereinander setzen muss. Und sobald ich was ändere is alles wieder für die Katz. Wie regelt man Dateien inkludieren in ASM?? Was kommt in die Main-Routine?? Ein paradoxes Problem: Der Interruptcharacter der uC's bedingt, das ein Program ewig läuft, was tu ich ins Main, was tu ich als Interrupte. Eigentlich kann ich ja alles durch Interrupte realisieren. Aber ich hab Hemmungen meine Mainroutine aus NOP bestehen zu lassen :) Was gibts sonst allgemein für Tips zum Programmierstil? Es scheint mir der Character von ASM bewirkt das man oft sehr schluderich und unverständlich schreibt, vorallem wenn der Code schnell sein muss. Was gibts dennoch für Grundregeln? (ähnlich denen der Prozeduralen/Objektorientierten Programmierung, die aber auf ASM nur bedingt anwendbar sind). Besonders zwingt einen der ASM-Charakter geradezu die heiligen Grundsätze des "guten Programmierstils" zu verletzten, die mir bei Hochsprachen immer eingepredigt wurden. Gibts vielleicht Links oder gute Bücher die Profitricks mit ASM verraten? Danke für alle Antworten!! Moritz
Die Frage ist gut! Aber, warum soll man einen MC in Assembler programmieren, wenn man schon C beherrscht? Das Problem ist neben dem Programmierstil aber auch das Systemdesign. Die angesprochene Funktion main() wird eigentlich als Funktion des Betriebssystems aufgerufen. Wo aber ist beim MC das Betriebssystem? Parameterübergaben werden beim PC fast immer über den Stack gemacht, ebenso die lokalen Variablen, laufen alle über den Stack. Nur der ist nicht so üppig wie beim PC. Bin mittlerweile der Ansicht, das es bei Kleinserien eher auf die Entwicklungszeit als auf die optimale Ausnutzung der vorhanden Resourcen ankommt. Daher würde ich MCs nur noch in C Programmieren. Beim Interrupthandling würde ich die Behandlung des Interruptereignisses nur in der Interruptserviceroutine machen. Die Verarbeitung selbst kann in main() erfolgen. Was spricht dagegen, Main() ewig laufen zu lassen. In allen anderen Betriebssystemen dreht der Prozessor auch mit den Daumen, wenn es nichts zu tun gibt. Siegfried
Hallo Moritz, Beim 8051 nehme ich nur noch C, da das der Keil sehr effizient macht (nur 5..20% Overhead). Beim AVR gibt es dagegen gute Gründe noch Assembler zu nehmen, da die C-Compiler doch extrem verschwenderisch mit dem Programmspeicher umgehen. Ich will mal versuchen niederzuschreiben, was mir bei der AVR-Assemblerprogrammierung so aufgefallen ist: 1. Register Der AVR kann nur mit den Registern richtig arbeiten. Die richtige Registeraufteilung ist das A und O, wenn man nicht 50% und mehr nur mit PUSHen und POPen beschäftigt sein will. Deshalb teile ich die Register immer in 3 Teile: a) Main-Register: Damit arbeitet die Main-Loop b) Interrupt-Register: Damit arbeiten die Interrupts c) häufig benutzte Variablen Das ist der Rest, der nach a) und b) übrig ist Damit wird das PUSHen und POPen in Interrupts und in den Main-Funktionen kaum noch benötigt und die Kodeeffizienz ist fast so gut wie beim 8051. Viele meiner AVR-Programme benutzen PUSH und POP garnicht. 2. Interrupts Da gelten folgende 3 Hauptregeln: - schreibe die Interrupts kurz - schreibe sie kürzer - schreibe sie so kurz, wie nur irgend möglich Der Grund, beim AVR gibt es keine verschiedenen Interruptprioritäten, d.h. kein Interrupt kann einen anderen unterbrechen. Wenn also nur bei einem Interrupt geschludert wird und der ist dann sehr langsam, werden dadurch alle anderen genauso langsam. Also der Interupt darf nur das unbegingt nötigste machen und setzt dann ein Flag für die Main-Loop. Alles was Zeit hat, macht dann die Main-Loop 3. Parameter Keine Frage, nur die Übergabe in Registern ist effizient. Und da die Main-Loop alle Funktionen nacheinander abarbeitet, auch gefahrlos möglich (Funktionen müssen nicht reentrant sein). Bei langen Paramterlisten übergibt man nur den Pointer (X, Y oder Z-Register) darauf (wie in C). Zu Deiner Frage, die Main-Loop: Wie der Name schon sagt, ist es eine Endlos-Schleife. Es gibt ja kein Betriebssystem zu dem man zurückkehren kann. Diese Main-Loop ruft in der einfachsten Variante alle Funktionen der Reihe nach auf. Oder sie pollt die Flags, die in Interrupts gesetzt wurden, um die weniger zeitkritischen Sachen zuende zu führen. Oder sie ruft einen Scheduler auf, der sich darum kümmert, wann welche Funktion ausgeführt werden soll. Oder sie macht ein SLEEP und wartet somit auf den nächsten Interrupt. Peter
Nachtrag zu Deinem Include-Problem: Da hat der Assembler überhaupt keine Wahl. Er muß es genau an der Stelle einfügen, wo die .include "xxx" Zeile steht. Ich hänge Unterfunktionen immer hinten ran und schreibe die Funktionen, an denen ich gerade arbeite, möglichst weit vorne. Dann erscheinen im Listing auch die Fehlermeldungen vorne. Und wenn die Funktion dann läuft, kommt sie auch nach hinten und die nächste wird in Angriff genommen. Die Include-Directive ist auch die einzige Möglichkeit, um auf AVRs mit Hardwarestack modular zu programmieren. Da kann man ja Unterfunktionen nicht beliebig tief CALLen, sondern fügt sie quasi inline per .include an der Stelle ein, wo sie ausgeführt werden sollen. Peter
Da muss ich Peter recht geben. Den 8051 würde ich heute auch nur noch in C mit der Keil Entwicklungsumgebung programmieren. Wegen des Preises ist dies aber wohl nur für kommerzielle Projekte interessant. Was man sich bei Intel damals wohl bei der Syntax für den 8080/8085/8048/8051 gedacht hat, wird wohl immer ein Rätsel bleiben. Die AVR Familie wurde wohl auch für ein anderes Marktsegment entwickelt (kleiner kompakter Prozessorkern, beschränkter I/O-Bereich, hohe Geschwindigkeit bei niedriger Taktfrequenz). Für die Übergabe von Parametern ist ja die Prozessor- architektur von entscheidender Bedeutung. Da bei AVR-Familie der Stack-Pointer nicht direkt zügänglich ist, scheidet für mich dort die Übergabe von Parametern via Stack aus. Die Korrektur ist mir viel zu zeitaufwendig. Der Befehlssatz ist recht übersichtlich, logisch aufgebaut und schnell erlernbar. Auch ist das ganze Timing recht übersichtlich. Zeitkritische Anwendungen sind daher sehr leicht realisierbar. Für Steuerungsaufgaben wo sich der Rechenaufwand in Grenzen hält, programmiere ich immer noch vorzugsweise in Assembler, da es hier in vielen Fällen auf das exakte Timing ankommt. Mit der Zeit sammeln sich bei jedem Assemblerprogrammieren eine Menge Makros die einem die Arbeit erleichtern aber u.U. auch dazu führen, dass die Sourcen für andere teilweise schwer verständlich sind. An sich ist es völlig egal in welcher Sprache eine Anwendung kodiert ist wenn sie funktioniert und alle Anforderungen erfüllt. Und man sollte Wert auf eine vernünftige Dokumentation und aussagekräftige Kommentare legen, damit die Quelle auch nach 1-2 Jahren noch versteht und evtl. an andere Hardwaregegebenheiten anpassen kann.
Hi Moritz! Warum soll man in ASM nicht strukt. progr. können. Ich fasse vieles in Sub's zusammen bzw. behandle genau eine Aufgabe. Das schöne an ASM ist aber das man in einer momentanen Aufgabe schnell mal was ganz anderes machen kann. zB.: Bearbeitung der internen "Uhrzeit" in einer ISR da kann man ja schnell mal Nachfragen ob sie eventuell auch gestellt werden soll(Tastenabfrage)Das ist ja leider bei den höheren Sprachen verpönt, spart aber manchmal allerhand Speicher. Mit dem einfügen fertiger Programmteile habe ich eigentlich keine Probl. Du kannst mitten im Prog. .include "NAME" eintragen und es wird da eingefügt. Bei mir sind das lauter kleine ASM-Prog. Zum EE-lesen lautet der Eintrag einfach: .include "EEL12.asm" Als Assembler nutze ich auch nur den normalen WAVRASM von Atmel. Wenn dein Prog. nur aus ISR's besteht kann die Main durchaus so aussehen: Main: rjmp Main ;Warten auf neue Aufgaben Parameterübergabe sollte man bei Atmel über die Reg. machen, da ist der Zugriff am schnellsten. Gruss Uwe
Dein Problem mit dem Include kannst Du mit Makros ganz elegant lösen. Das geht so: Du schreibst die Standardroutinen alle in irgendwelche Dateien, als Macros. Also zwischen ein .MACRO name und ein .ENDMACRO. Das Include der Dateien kannst Du hinschreiben, wo Du willst, weil das Include von reinen Macros noch nicht zu Code führt. Wann immer in Deinem Hauptprogramm Du das Macro mit dem Namen name aufrufst, wird der Quellcode dieses Macros genau an diese Stelle gesetzt. Da brauchst Du auf irgendwelche Adressen nicht zu achten. Macht auch nichts, wenn Du jedes Macro nur einmal aufrufst (um es in den Code zu platzieren), und nicht, wie der Erfinder der Macros sich das ausgedacht hat, mehrmals. Ein Beispiel. In deine Include-Datei kommt: ; Nur ein Beispiel für ein Unterprogramm .macro mactest1 ldi zh,high(adr) ldi zl,low(adr) lpm ret .endmacro In Deinen Quellcode kommt dann folgende Konstruktion: test1: mactest1 [...] rcall test1 Mit dem Macro-Aufruf mactest1 wird der Macrocode in das Programm an dieser Stelle eingefügt. Mit rcall label kannst Du diese Subroutine dann aufrufen. Wenn Du mit dieser Methode Makros dabei hast, die Du für eine bestimmte Anwendung gar nicht brauchst, macht das auch nichts. Der Code wird ja nur dann erzeugt, wenn Du das Macro auch tatsächlich aufrufst. Im Prinzip kannst Du alle Unterroutinen, die Du je schreiben wirst, als Macros schreiben und alle mit Include bereitstellen (ich bin aber nicht sicher, ob handelsübliche Assembler da nicht ausflippen! Mein selber geschriebener kann das jedenfalls!). Es ist ein wenig mit den Header-Dateien in C vergleichbar: nur die tatsächlich aufgerufenen Funktionen werden in den compilierten Code aufgenommen, der Rest bleibt wirkungslos und außen vor. Ich schätze, diese Methode ist unter Assembler-Programmierern nicht sehr üblich, könnte Dir aber vielleicht den Übergang von C in ASM vielleicht etwas erleichtern. Deine Hemmung, dein "MAIN" nur aus NOPs bestehen zu lassen, kann ich nicht ganz nachvollziehen. Fast alle meine Hauptprogramme bestehen aus einem Loop mit ein paar Flagabfragen (wie beschrieben) und einem SLEEP, das den Prozessor in den stromsparenden Tiefschlaf versetzt, bis ihn ein INT wieder aufweckt. Also etwa so: loop: sleep nop sbrc flag,bit1 rcall sub1 sbrc flag,bit2 rcall sub2 [...] rjmp loop Der NOP hinter dem SLEEP ist übrigens keine Beschäftigungstherapie, sondern notwendig! Die Introutinen selbst sind dann extrem kurz, weil sie nur das entsprechende Bit setzen müssen, um eine Behandlung auszulösen, also z.B. irgendeinint: in R17,SREG sbr flag,02h out SREG,R17 reti Kürzer geht es, glaube ich, nicht. MfG Gerd
Als Assemblerprogrammierer finde es gemein, dass Tabulatorzeichen von diesem Forum nicht mal zu Leerzeichen umgewandelt werden, sondern einfach verschwinden. Im Editor hat alles richtig ausgesehen. Natürlich müssen da Tabs oder Leerzeichen im vorhergehenden Posting an alle Zeilenanfänge mit Code, die nicht mit einem Punkt anfangen. MfG Gerd
> Beim AVR gibt es dagegen gute Gründe noch Assembler zu nehmen, da die > C-Compiler doch extrem verschwenderisch mit dem Programmspeicher umgehen. Diese undifferenzierte Aussage kann man so aber nicht stehen lassen. Fakt ist, zumindest nach meiner Erfahrung, dass es hier grosse Unterschiede gibt und dass es sowohl Compiler gibt, die wirklich sehr kompakten Code produzieren, den man auch mit handcodiertem Assembler kaum noch zusammenquetschen könnte. Andererseits gibt es auch Compiler, bei denen die obige Aussage im wahrsten Sinne des Wortes zutrifft. Einen Compiler, den ich in diese Kategorie einordnen würde, wäre z.B. der CodeVision. Ich hatte mal verschiedene Sourcecodes im direkten Vergleich zum avrgcc untersucht und die Unterschiede in der Grösse des generierten Codes waren tatsächlich extrem. Ich wollte dies nur mal anmerken, damit hier kein generell schlechtes Bild über C-Compiler für den AVR entsteht. Dies wäre nicht der Realität entsprechend. Gruss, Peter
Hallo Peter, wäre echt super, wenn Du dann doch bitte sagen könntest, welches dieser gute Compiler ist. Nur den schlechten zu nennen, ist irgendwie nicht gerade hilfreich :-( Wenn Du dann noch ein Source-Beispiel mit dem Assembler-Listing posten könntest, damit man mal ein gutes Beispiel sieht. Irgendwas aus der Praxis mit I/O-Zugriffen, ifs, switches, interrupts usw.. Also nicht irgendwelche praxisfernen Werbebeispiele, wie Sieve of Erathostenes oder so. Meine Einschätzung ist nur deshalb so negativ, weil ich bisher eben nur schlechte Beispiele gesehen habe. Peter D.
Sorry, hatte das mit dem avrgcc überlesen. Ist der also einer dieser guten Compiler und gibt es noch andere ? Wie gesagt, ein Beispiel dafür wäre echt super. Peter
Hallo, also die Atmels sind ja nicht so meine Welt aber oben wurde ja auch kein Spezificher Controller genannt. Es gibt durchaus auch Controller, die sowas wie einen Software Interrupt haben. Einfach per INT Befehl. Parameter über den Stack zu übergeben ist zwar etwas aufwendiger als in einem Register, dafür kann man die werte aber lokal vermanschen und hat nicht immer nur Globale variablen. Achja und es gibt durchaus auf Assemblerebene noch sowas wie einen Linker, da kann man dann Funktionen aus anderen Quellen aufrufen. Eckhard
Hallo Eckhard, ob global oder lokal ist beim Mikrokontroller nur eine unterschiedliche Betrachtungsweise. Jedes Register und jede RAM-Adresse ist global von jeder Funktion aus zugreifbar. Man kann aber in einer Funktion für ein Register einen Namen vereinbaren und diesen Namen nur in dieser Funktion verwenden, dann ist diese Registervaiable lokal. Und in der nächsten Funktion gibt man dem Register wieder einen anderen Namen. Die Parameterübergabe im Stack scheint kein Mikrokontrollerentwickler vorgesehen zu haben, es gibt z.B. keinen Befehl, eine Konstante zu PUSHen. D.h. der Overhead ist daher beträchtlich gegenüber Registern. Das eine Funktion dann 100..200% mehr Kodespeicher benötigt, ist nicht ungewöhnlich. Außerdem wird mehr SRAM benötigt, die Register sind ja immer da und sie nicht zu verwenden ist Verschwendung. Noch was interessehalber, wozu kann ein Mikrokontroller einen Softwareinterrupt sinnvoll einsetzen ? Peter D.
Gerd, das Problem mit der Formatierung ist gelöst. Tabs werden jetzt in 2 Leerzeichen umgewandelt und Leerzeichen am Zeilenanfang verschwinden nicht mehr. Gruß Andreas
@Peter, kannst du nochmal genauer deine Meinung zu den Unterschieden zwischen CodeVision und AVRGCC erklären? Ich habe folgende Erfahrung gemacht: Es bestand ein Projekt mit einem Infineon C167-Prozessor(16biter). Das Programm wurde auf Keil µVision2 geschrieben und compiliert. Es entstand eine .h86-Datei mit 22kB. Der C167-Prozessor hat u.a. spezielle Befehle die vom Keil C-Compiler unterstützt werden. Da die Hardwarekosten mit dem C167er zu teuer wurde, ist ein neues Design mit dem ATmega128 entstanden. Das vorhandene Programm vom C167er wurde für mega128 mit CodeVision(aktuelle Version) modifiziert. Daraus entstand eine hex-Datei mit 17kB. Dem Keil-Compiler sagt man eine effiziente Umsetzng nach. Mein Beispiel zeigt, daß CodeVision mindestens genauso gut ist. Gruß Norbert
Hallo Peter, also ein Softwareinterrupt läßt sich für Systemroutinen einsetzen. Sowas wird manchmal auch Trap genannt. Fast ganz MS-Dos hat so funktioniert, ob das nun Sinnvoll ist sei mal dahingestellt. Ansonsten haben ja nicht alle Controller so massig Register wie der AVR, ich benutze nen HC08, da kann man nicht gerade sonderlich vieler Parameter in Registern übergeben. Dafür kann man den Stack mit Speziellen Adressierungsmodi Relativ zum SP sehr gut nutzen. Eckhard
@Andreas Das ging aber schnell. Ich schließe daraus, dass Du das ganze Forum selbst programmiert hast. Alle Achtung! MfG gerd
Hallo Norbert, ich habe bisher noch keinen AVR in C programmiert, ich habe aber eben nur Beispiele gesehen, deren Kodeeffizienz einen nicht gerade umhaut. Deshalb bin ich ja so schar darauf, daß mir endlich mal jemand zeigt, daß C beim AVR auch effizient sein kann. Wenn ich einfach nur so einen Compiler bestelle und dann nicht damit arbeite, weil ständig der Flash dicht ist, dann würde mein Chef doch nicht gerade erfreut sein, oder ? Mit dem AVRGCC will ich mal was machen, aber die UNIX-Syntax ist doch etwas abschreckend und mit Make-Files habe ich mich auch noch nicht angefreundet. Ich benutze nur den Keil-C51 und der braucht keine kryptischen Anweisungen und auch kein Make, das macht ein sogenanntes AMAKE vollautomatisch. Der C167 ist mit dem Mega128 überhaupt nicht vergleichbar, der spielt doch in einer ganz anderen Liga. Ein 16-Bitter kann nur da punkten, wo auch sehr oft 16-Bit-Operationen benutzt werden. Wenn aber die meisten Variablen nur 8-Bit sind, ist doch vollkommen klar, das der 8-Bitter die Nase vorn hat. Wenn also auf dem AVR der Kode kleiner wird, müßte er auf dem 8051 noch weiter schrumpfen. Hallo Eckhard, das war mir schon klar. Nur habe ich noch keinen 8051 mit MS-DOS drauf gesehen (wozu auch). Und ein Softwareinterrupt ist wieder auch nur reine Ansichtssache. Ob ich nun einen INT21 aufrufe oder einen CALL 1000h und die entsprechende Routine an 1000h linke, ist vollkommen gleich. Der einzige Unterschied, unter DOS kann ich einen Interrupt verbiegen, aber nur, weil der Kodespeicher im RAM ist. Aber bei Mikrokontrollern ist der Kodespeicher nur lesbar und deshalb gibt es auch keinen Softwareinterrupt. Peter
Die Software-Interrupts wurden ja damals überwiegend aus Platzgründen (ein INT Aufruf belegt 2 Byte ein Absolut Call 5 Byte Speicher) missbraucht, obwohl der Prozessorhersteller damit eigentlich anderes im Sinn hatte. Aber das soll ja hier nicht Gegenstand der Diskussion sein. Bei einem Mikrocontroller haben ich ja ein festes Programm, dass ich bei Bedarf durch Neuprogrammierung austauschen kann. Einen C167 mit einem MEGA128 zu vergleichen halte ich auch für einen Fehlgriff. Der Unterschied in der Codegrösse ist für mich teilweise auch nicht ganz nachvollziehbar wenn bei beiden Prozessoren gleiche Datentypen verwendet wurden. Allerdings wäre wohl beide Prozessoren für die Anwendung etwas überdimensioniert. Das kryptische MAKE beim AVRGCC stört mich auch enorm. Aber vielleicht ändert sich ja hier was in der Zukunft, da ja Atmel die Schnittstelle zum AVR Studio 4 offenlegen will. Dann ist ja komplette Integration in die Entwicklungsoberfläche möglich.
Hallo, auch wenn das vielleicht ein wenig über das ziel hinausschießt. Es gibt ja auch relativ fette Controller. Z.B 6833X von Motorola. Auf denen wird dann gerne ein RTOS gefahren. Diese Dinger kennen einen User und einen Supervisor Mode. Das OS läuft normalerweise im Supervisormode. Die restlichen Prozesse im Usermode. Die einzige möglichkeit um das OS-Funktionen aufzurufen, die den Supervisormode nutzen ist der Softwareinterrupt. Nur um nochmal ein Beispiel zu geben. In der 8-Bit wWelt ist das aber eher nicht so. Eckhard
@Norbert Die Grösse des fertig übersetzten Codes für verschiedene Prozessoren lassen sich wohl kaum miteinander vergleichen, insbesondere nicht zwischen solchen mit RISC und CISC-Architektur. Da vergleichst du Äpfel mit Birnen! @Peter Dannegger ich arbeite schon lange mit dem gcc. Den kannte ich schon lange, bevor ich mich überhaupt mit Mikrocontrollern beschäftigte. Den gcc gibt es für viele verschiedene Prozessoren und er ist sehr ausentwickelt, vor allem was die Effizienz des erzeugten Codes betrifft. Ich habe früher auf folgenden Maschinen/Systemen damit programmiert: Silicon Graphics Indy (Irix) Hewlett Packard C100 (HPUX, PA-RISC) Sun Sparcstation 10 (Solaris, SPARC) Siemens MX100 (SINIX) und Linux div. Systeme (i486, Pentium) Und auf allen Maschinen hat der gcc im Vergleich zu kommerziellen Compilern eine sehr gute Figur gemacht und diese oft sogar übertroffen. Und im Bereich Atmel bin ich auch recht überzeugt, dass dies hier genauso ist. Ich habe noch nicht alle AVR-Compiler getestet, aber im Vergleich zu denen, die ich testen konnte, lag auch der avrgcc mit vorn an der Spitze. Beste Grüsse, Peter
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.