Hallo, habe neulich ein recht komplexes Programm in Assembler für einen AVR gesehen, das mir dennoch sehr elegant erschien. Das hat mir einfach gefallen. Nachdem ich sonst immer in C programmierte wollte ich mich deshalb mal näher mit Assembler beschäftigen. Meine Frage und bestimmt ein einfaches Problem: ich möchte an einem Pin den Pullup aktivieren. Der Code funktioniert, aber ginge das mit Abfrage des bisherigen Status auch kürzer? Oder auch: kann man einzelne Bits da draussen ausserhalb von R16-R31 auch direkt setzen? lds r16,PORTB_PIN2CTRL sbr r16,8 ;Bit 3 für Pullup enable setzen sts PORTB_PIN2CTRL,r16 ;Pullup aktivieren PB2 Und weiter - Statusabfrage am Pin PB2 und dann abhängig davon einen Ausgang setzen oder rücksetzen( als Beispiel, oder eben irgend was anderes veranlassen): lds a, VPORTB_IN ;Eingänge PortB in a laden, a ist hier wieder R16 sbrs a,2 ;skip next, if high LED_green_on sbrc a,2 ;skip next, if low LED_green_off Geht das besser (und gibt es so was wie 'else')? Entschuldigt die aus eurer Sicht vielleicht dummen Fragen, Assembler ist für mich bisher weitgehend Neuland. Danke für euren Input.
:
Bearbeitet durch User
Lutz S. schrieb: > Entschuldigt die aus eurer Sicht vielleicht dummen Fragen, Assembler ist > für mich bisher weitgehend Neuland. Ohje, klingt wie "Das Internet ist für uns alle Neuland" . . .
Noch ein Tipp zur Schreibweise, einiges kann eindeutiger schreiben:
1 | ; alt |
2 | sbr r16,8 ;Bit 3 für Pullup enable setzen |
3 | |
4 | ; besser |
5 | sbr r16,(1<<3) ;Bit 3 für Pullup enable setzen |
Auch verwende ich sehr viele Macros, die die Arbeit im Code erledigen. Auch kann man über je ein Macro, die Auswertung der Register, ob IN/ OUT oder LDS/ STS verwendet werden muss, vereinfachen. Hier sind einige Beispiele aus der LunaAVR Implementierung:
1 | .macro xIn |
2 | .if @1<64 |
3 | in @0,@1 |
4 | .else |
5 | lds @0,@1 |
6 | .endif |
7 | .endmacro |
8 | |
9 | .macro xOut |
10 | .if @0<64 |
11 | out @0,@1 |
12 | .else |
13 | sts @0,@1 |
14 | .endif |
15 | .endmacro |
16 | |
17 | .macro gCbi |
18 | .if @0<32 |
19 | cbi @0,@1 |
20 | .else |
21 | xIn WL,@0 |
22 | cbr WL,(1<<@1) |
23 | xOut @0,WL |
24 | .endif |
25 | .endmacro |
26 | |
27 | .macro gSbi |
28 | .if @0<32 |
29 | sbi @0,@1 |
30 | .else |
31 | xIn WL,@0 |
32 | sbr WL,(1<<@1) |
33 | xOut @0,WL |
34 | .endif |
35 | .endmacro |
36 | |
37 | .macro xCbi |
38 | .if @0<32 |
39 | cbi @0,@1 |
40 | .else |
41 | push WL |
42 | xIn WL,@0 |
43 | cbr WL,(1<<@1) |
44 | xOut @0,WL |
45 | pop WL |
46 | .endif |
47 | .endmacro |
48 | |
49 | .macro xSbi |
50 | .if @0<32 |
51 | sbi @0,@1 |
52 | .else |
53 | push WL |
54 | xIn WL,@0 |
55 | sbr WL,(1<<@1) |
56 | xOut @0,WL |
57 | pop WL |
58 | .endif |
59 | .endmacro |
Wir verwenden diese List an internen Registernamen
1 | ;standard register names |
2 | ;low block A |
3 | .def _LA0 = R0 |
4 | .def _LA1 = R1 |
5 | .def _LA2 = R2 |
6 | .def _LA3 = R3 |
7 | ;low block B |
8 | .def _LB0 = R4 |
9 | .def _LB1 = R5 |
10 | .def _LB2 = R6 |
11 | .def _LB3 = R7 |
12 | ;temp block A |
13 | .def _TMP0 = R8 |
14 | .def _TMP1 = R9 |
15 | .def _TMP2 = R10 |
16 | .def _TMP3 = R11 |
17 | ;temp block B |
18 | .def _TMPA = R12 |
19 | .def _TMPB = R13 |
20 | .def _TMPC = R14 |
21 | .def _TMPD = R15 |
22 | ;high block A |
23 | .def _HA0 = R16 |
24 | .def _HA1 = R17 |
25 | .def _HA2 = R18 |
26 | .def _HA3 = R19 |
27 | ;high block B |
28 | .def _HB0 = R20 |
29 | .def _HB1 = R21 |
30 | .def _HB2 = R22 |
31 | .def _HB3 = R23 |
32 | ;pointers |
33 | .def WL = R24 |
34 | .def WH = R25 |
35 | .def XL = R26 |
36 | .def XH = R27 |
37 | .def YL = R28 |
38 | .def YH = R29 |
39 | .def ZL = R30 |
40 | .def ZH = R31 |
Lutz S. schrieb: > Nachdem ich sonst immer in C programmierte wollte ich mich deshalb mal > näher mit Assembler beschäftigen. Tja, das fängt damit an, sich den Sprachumfang reinzuziehen. Wie in jeder anderen Sprache auch. Damit solltest du also beginnen. Das Lustige an Assembler ist: Der Sprachumfang ist i.d.R. recht klein, also sehr schnell vollumfänglich erlernbar. Schwierig ist nur, ihn sinnvoll zu benutzen. Aber so weit bist du längst noch nicht. > auch kürzer? Oder auch: kann man einzelne Bits da draussen ausserhalb > von R16-R31 auch direkt setzen? Diese Frage könntest du dir dann schon selbst beantworten. Naja, vielleicht nicht vollständig, aber immerhin könntest du eine sinnvollere Frage stellen, einer der man anmerken kann, dass du dich zumindest andeutungsweise tatsächlich mit der Materie beschäftigt hast. > Und weiter - Statusabfrage am Pin PB2 und dann abhängig davon einen > Ausgang setzen oder rücksetzen( als Beispiel, oder eben irgend was > anderes veranlassen): > > lds a, VPORTB_IN ;Eingänge PortB in a laden > sbrs a,2 ;skip next, if high > LED_green_on > sbrc a,2 ;skip next, if low > LED_green_off > > Geht das besser Sicher. Und wieder: diese Frage hättest du dir selber beantworten können, wenn du wenigstens die Worte der Sprache gelernt hättest... > (und gibt es so was wie 'else')? Ja, immer. Da das 'if' im Prinzip immer ein bedingtes 'goto' ist, ist das 'else' prinzipbedingt natürlich immer die Stelle, wo nicht hingesprungen wird, also die nächste Anweisung...
>> (und gibt es so was wie 'else')? > > Ja, immer. Da das 'if' im Prinzip immer ein bedingtes 'goto' ist, ist > das 'else' prinzipbedingt natürlich immer die Stelle, wo nicht > hingesprungen wird, also die nächste Anweisung... Ich dachte bei 'else' an etwas was er dann alternativ ausführt. Also entweder Zweig A oder Zweig B ausführen, und dann geht es weiter.
Karl M. schrieb: > ; besser > sbr r16,(1<<3) ;Bit 3 für Pullup enable setzen Und diese unsaeglichen Klammern sind genau wofuer? Seit irgendwann ein unbedarfter Programmierer die so hingemalt hat, wird das ewig kopiert, in ASM und C. leo
leo schrieb: > Und diese unsaeglichen Klammern sind genau wofuer? Sie bewirken natürlich garnix, hier hätte ich sie sicher auch nicht gesetzt. Trotzdem ist meine Meinung: Immer lieber ein Klammernpaar zu viel als eins zu wenig. Macht die Sache oft einfacher lesbar. Man muss nicht auch noch beim Lesen des Codes über Operator-Präzedenzen nachdenken. Die Klammer stellt absolut klar, was intendiert war. Sie kostet auch nix zur Runtime, und sie macht sogar oft das Compilieren/Assemblieren schneller, weil der Tokenizer früher entscheiden kann. Sprich: nur Volldeppen regen sich wegen eines überflüssigen Klammerpaares auf. Typisch sind das dann genau die C-ler, die glauben, das Programm mit den wenigstens Tastenanschlägen und Zeilen wäre auch das, was am effizientesten liefe... Nö, es ist nur sicher das, was sich am schlechtesten lesen läßt...
c-hater schrieb: > ... Immer lieber ein Klammernpaar zu viel als > eins zu wenig. Macht die Sache oft einfacher lesbar. Mehr Klammerung dient nicht unbedingt der Lesbarkeit, zumal sie so klar ist wie hier. > Sie kostet auch nix zur Runtime, und sie macht sogar oft das > Compilieren/Assemblieren schneller, weil der Tokenizer früher > entscheiden kann. Nein. Der Tokenizer muss wieder eine Ebene entfernen. Die Entscheidung faellt auf Grund der Prioritaet schon frueher. > Sprich: nur Volldeppen regen sich wegen eines überflüssigen > Klammerpaares auf. Keiner hat sich aufgeregt, der V.. gehoert dir. > Typisch sind das dann genau die C-ler, Ich schrieb explizit ASM und C ... leo
Hallo Lutz S., spannend wäre jetzt zu erfahren, ob die Anregungen Dir weiter helfen. Zu Klammern, ich schreibe sie gerne und häufig! Peter Dannegger (peda) verwendete mal folgendes für das Zusammensetzen von Bitmasken, was in C. Ist auch ok!
1 | ; peda |
2 | ldi r16,1<<0^1<<2^1<<4 |
3 | ; oder |
4 | ldi r16,1<<0|1<<2|1<<4 |
Lutz S. schrieb: > Geht das besser (und gibt es so was wie 'else')? Ist nicht AVR-Assembler, aber das Prinzip ist überall gleich.
1 | CP A, 4 ; vergleiche A und 4 |
2 | JP Z, YEAH ; wenn gleich (Zeroflag), springe nach YEAH |
3 | |
4 | ; hier ist der else-Teil |
5 | ; denn A war nicht gleich 4 |
6 | ; also wurde nicht gesprungen |
7 | |
8 | JP FERTIG |
9 | YEAH: |
10 | |
11 | ; hier ist der if-Teil |
12 | ; denn A war gleich 4 |
13 | ; also wurde gesprungen |
14 | |
15 | FERTIG: |
16 | |
17 | ; hier ist nach dem if |
18 | ; denn das wird nach beiden |
19 | ; Teilen ausgeführt |
Ist also recht logisch.
Danke für eure Tips. Lese weiter mit und im Instruction Set Manual. Im Moment sieht das nun so aus: .DEF a = r16 #define LED_green_off sbi VPORTB_OUT,7 #define LED_green_on cbi VPORTB_OUT,7 #define LED_red_off sbi VPORTB_OUT,6 #define LED_red_on cbi VPORTB_OUT,6 sbi VPORTB_DIR,6 ;PB6 ist Ausgang sbi VPORTB_DIR,7 ;PB7 ist Ausgang ldi a,1<<3|1<<7 sts PORTB_PIN2CTRL,a ;PB2 Pullup und invers ldi a,1<<3 sts PORTB_PIN3CTRL,a ;PB3 Pullup start: sbic VPORTB_IN,2 rjmp green_off LED_green_on rjmp green_ende green_off: LED_green_off green_ende: sbis VPORTB_IN,3 rjmp red_off LED_red_on rjmp red_ende red_off: LED_red_off red_ende: rjmp start
:
Bearbeitet durch User
Hi, bei den Atmel AVRs lohnte sich einmal ein Blick auf die targetspezifischen Include-Files. Da wird genau auf Dein Problem hingewiesen. ;* DESCRIPTION ;* File Name : "2323def.inc" ;* Title : Register/Bit Definitions for the AT90S2323 ;* Please observe the difference in using the bit names with instructions ;* such as "sbr"/"cbr" (set/clear bit in register) and "sbrs"/"sbrc" ;* (skip if bit in register set/cleared). The following example illustrates ;* this: ;* ;* in r16,PORTB ;read PORTB latch ;* sbr r16,(1<<PB6)+(1<<PB5) ;set PB6 and PB5 (use masks, not bit#) ;* out PORTB,r16 ;output to PORTB ;* ;* in r16,TIFR ;read the Timer Interrupt Flag Register ;* sbrc r16,TOV0 ;test the overflow flag (use bit#) ;* rjmp TOV0_is_set ;jump if set ;* ... ;otherwise do something else Und "Memory Mapped" Register können nur über "Umwege" beschrieben werden. Welche das sind, ist auch targetspezifisch. Man sieht sofort, dass Portierbarkeit der Progs. u. U. eingeschränkt ist. Dann Zählweise nicht 1...8 sondern 0...7 bei 8-Bit Breite Also sbi r16, 0 ; setzt Bit 0 in Register r16 Du kannst auch für das direkte Bit-Setzen folgendes machen. ldi r16, 0b00000001 macht man aber nicht, weil r16 für arithmetische Operationen bereits reserviert sein könnte und Rechenergebnise der letzten Operation enthält, die im Verlauf des Programms später wieder gebraucht werden, so dass man dies Register auf den Stapel "rettet". Und hinterher wieder zurückholt. So lautete der Workaround oben besser: push r16 ldi r16, 0b00000001 pop r16 Für die Dauer des Befehlsausführung wird dann Bit 0 von r16 auf 1 gesetzt. Das direkte Bit-Setzen ist da codesparender. Praktisches Beispiel, wie man einen definierten Impuls generiert: Enable: nop sbi impuls, 0 ;Enable auf "high" setzen nop ;Impulsdauer mindestens nop ;0,5 Mikrosekunden laut Datenblatt nop ;lieber ein paar "nops" mehr nop ;spendieren nop nop nop cbi impuls, 0 ;Enable auf "low" zurücksetzen nop ret Das ist zum Beispiel der Enable-Impuls für ein LCD nach Hitachi-Spec. ciao gustav
:
Bearbeitet durch User
Hallo Karl, habe hier die tn3217def.inc in Verwendung, da wird das leider nicht mehr so ausführlich erläutert. Danke auch an dich für deine Tips.
Außerdem lernt man viel, indem man sich guten Code von Anderen anschaut. Gerade bei Assembler ist das sehr nützlich. Es gibt z.B. ein paar Codeschnipsel, mit denen ein Videosignal erzeugt wird. Da ist viel Arbeit reingeflossen, der Code ist entsprechend stark optimiert. Du kannst auch einfach ein paar C-Schnipsel durch den Compiler schieben und dir die Ergebnisse anschauen. Auch das ist sehr lehrreich. Natürlich immer die Instruction Reference neben der Tastatur, damit sich das nachvollziehen lässt. Es bringt nur wenig, sich an schlechtem Code zu orientieren. Um mit Assembler überhaupt einen Vorteil zu C zu haben, muss man schon sehr guten Code schreiben können.
Karl B. schrieb: > Und "Memory Mapped" Register können nur über "Umwege" beschrieben > werden. Welche das sind, ist auch targetspezifisch. Bei AVRs ist das allerdings letztendlich einfach, denn da sind grundsätzlich alle Register Memory-mapped. Den Inhalt dieses Dokuments muß man einfach kennen, wenn man AVRs in Assembler programmieren will: http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf Oliver
Oliver S. schrieb: > Bei AVRs ist das allerdings letztendlich einfach, denn da sind > grundsätzlich alle Register Memory-mapped. Nein. Die MCU-Register sind bei den ATXMega nicht memory-mapped zu erreichen. Und damit auch nicht bei den ganz neuen Tinys und Mega0, denn die haben den XMega-Core. Und bezüglich der SFIORs ist es natürlich ziemlich kontraproduktiv, sie langsam über den SRAM-Space anzusprechen, wenn man sie auch schnell über den SFIOR-Space ansprechen kann. Für In/Out ist das aber überhaupt kein Problem, mit zwei kleinen Makros kann man den Assembler die Arbeit erledigen lassen, nachzuschlagen, ob das Register nur im SFIOR-Space erreichbar ist oder nicht. Solche Makros wurden hier im Forum schon mehrfach veröffentlicht. Man sollte allerdings in diese Makros gleich Warnungen einbauen. Auch wenn es funktional keinen Unterschied macht, wie genau auf das Register zugegriffen wird, in zeitkritischen Routinen kann natürlich der eine Takt Unterschied sehr wohl den Unterschied zwischen geht und geht nicht ausmachen... Schwieriger ist das allerdings für sbic/sbis/sbi/cbi. Da gibt es leider keine ähnlich einfache Makro-Lösung, weil für die Ausweichvariante immer ein MCU-Register als Hilfe benötigt wird.
Oliver S. schrieb: > Bei AVRs ist das allerdings letztendlich einfach, denn da sind > grundsätzlich alle Register Memory-mapped. Hi, wurde schon gesagt, nicht ganz richtig. Noch ein praktisches Beispiel, was ich meinte. Der Timer-Init wird bei verschiedenen Targets anders ausgeführt. Prog. ist nicht 1:1 portierbar. ciao gustav
:
Bearbeitet durch User
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.