Forum: Mikrocontroller und Digitale Elektronik Pullup aktivieren in AVR-Assembler


von Lutz S. (lutzs)


Lesenswert?

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
von Peter D. (peda)


Lesenswert?

sbi bzw. cbi

von Falk B. (falk)


Lesenswert?

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" . . .

von Karl M. (Gast)


Lesenswert?

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

von c-hater (Gast)


Lesenswert?

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...

von Lutz S. (lutzs)


Lesenswert?

>> (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.

von leo (Gast)


Lesenswert?

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

von c-hater (Gast)


Lesenswert?

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...

von leo (Gast)


Lesenswert?

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

von Karl M. (Gast)


Lesenswert?

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

von S. R. (svenska)


Lesenswert?

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.

von Lutz S. (lutzs)


Lesenswert?

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
von Karl B. (gustav)


Lesenswert?

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
von Lutz S. (lutzs)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von c-hater (Gast)


Lesenswert?

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.

von Karl B. (gustav)


Angehängte Dateien:

Lesenswert?

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
Noch kein Account? Hier anmelden.