Forum: Mikrocontroller und Digitale Elektronik Inline Assembler Abbruchbedingung


von Martin G. (hb9tzw)


Lesenswert?

Hallo

Ich habe diese Routine mit Inline Assmebler:
1
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {
2
3
  asm volatile(  "eor r18, r18         ;r18<-0"                    "\n\t"
4
                 "eor r19, r19         ;r19<-0"                    "\n\t"
5
                 "1:"                                              "\n\t"
6
                 "add r18, %0          ;1 cycle"                   "\n\t"
7
                 "adc r19, %1          ;1 cycle"                   "\n\t"
8
                 "adc %A3, %2          ;1 cycle"                   "\n\t"
9
                 "lpm                  ;3 cycles"                  "\n\t"
10
                 "out %4, __tmp_reg__  ;1 cycle"                   "\n\t"
11
                 "rjmp 1b              ;2 cycles. Total 9 cycles"  "\n\t"
12
                 :
13
                 :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD))
14
                 :"r18", "r19"
15
  );
16
}

Die Routine (nicht selbst geschrieben) generiert an einem Port (hier D) 
ein Signal mit einem R/2R Netzwerk als D/A-Wandler, *Signal ist ein 
Pointer auf die Tabelle (z.B. Sinustabelle), ad0-2 ist die gewünschte 
Frequenz in 3 Bytes.

So wie es ist bricht der Loop da drin nie ab, ich brauche es aber so, 
dass die Schleife abbricht, wenn sich der Status an einem 
Eingangs-Portbit von 0 auf 1 ändert (signalOUT wird aufgerufen, wenn der 
Eingang auf 0 geht, und soll wieder abbrechen, wenn er wieder hoch 
geht).

Da ich mit Assembler sehr ungeübt bin möchte ich euch bitten mir zu 
helfen, dies sauber einzubauen. Tut sowas das Gewünschte:
1
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {
2
3
  asm volatile(  "eor r18, r18         ;r18<-0"                    "\n\t"
4
                 "eor r19, r19         ;r19<-0"                    "\n\t"
5
                 "1:"                                              "\n\t"
6
                 "add r18, %0          ;1 cycle"                   "\n\t"
7
                 "adc r19, %1          ;1 cycle"                   "\n\t"
8
                 "adc %A3, %2          ;1 cycle"                   "\n\t"
9
                 "lpm                  ;3 cycles"                  "\n\t"
10
                 "out %4, __tmp_reg__  ;1 cycle"                   "\n\t"
11
                 "cmp pina1, 0         ;??? cycles"                "\n\t"
12
                 "jne 2f               ;??? cycles"                "\n\t"
13
                 "rjmp 1b              ;2 cycles. Total ? cycles"  "\n\t"
14
                 "2:"                                              "\n\t"
15
                 :
16
                 :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD))
17
                 :"r18", "r19"
18
  );
19
}

oder sollte/muss ein anderer Befehl verwendet werden? Die Anzahl Zyklen 
sollte minimiert werden. Woher weiss man überhaupt, welcher Befehl 
wieviele Zyklen braucht?

Vielen Dank

Martin

von Martin G. (hb9tzw)


Lesenswert?

Mittlerweile habe ich selbst etwas anderes ausgearbeitet:
1
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {
2
3
   asm volatile(   "eor r18, r18                   ;r18<-0"                           "\n\t"
4
                   "eor r19, r19                   ;r19<-0"                           "\n\t"
5
                   "1:"                                                               "\n\t"
6
                   "add r18, %0                    ;1 cycle"                          "\n\t"
7
                   "adc r19, %1                    ;1 cycle"                          "\n\t"
8
                   "adc %A3, %2                    ;1 cycle"                          "\n\t"
9
                   "lpm                            ;3 cycles"                         "\n\t"
10
                   "out %4, __tmp_reg__            ;1 cycle"                          "\n\t"
11
                   "sbic 0x19,1                    ;2 cycles if skipping takes place" "\n\t"
12
                   "ret                            ;0 cycles if skipped"              "\n\t"
13
                   "rjmp 1b                        ;2 cycles. Total 11 cycles"        "\n\t"
14
                   :
15
                   :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTE))
16
                   :"r18", "r19"
17
   );
18
}

0x19 ist das PINA-Register des Mega128.

der Abbruch funktioniert jedoch nicht. Sieht jemand warum? Wird 
vielleicht nicht dorthin zurückgesprungen, von wo signalOUT aufgerufen 
wurde? Denke da komme ich allein nicht dahinter, wär wirklich gut wenn 
mir da jemand den Tip geben könnte.

Gruss
Martin

von Karl H. (kbuchegg)


Lesenswert?

Martin Geissmann schrieb:
>                    "sbic 0x19,1                    ;2 cycles if skipping takes 
place" "\n\t"
>                    "ret                            ;0 cycles if skipped" "\n\t"
>

Autsch.
Mach das nicht.
Nur weil nichts dort steht, bedeutet es nicht, dass nicht der Compiler 
zwischen Betreten der Funktion und deinem Code nicht noch Sachen 
erledigt. Dito für das Ende der Funktion. Stichwort: Register sichern 
bzw. nach Ablauf der Funktion die gesicherten Register wiederherstellen 
und aufräumen.

von Martin G. (hb9tzw)


Lesenswert?

Ok, ist denn sowas besser:
1
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {
2
3
   asm volatile(   "eor r18, r18                      ;r18<-0"                                  "\n\t"
4
                   "eor r19, r19                      ;r19<-0"                                  "\n\t"
5
                   "1:"                                                                         "\n\t"
6
                   "add r18, %0                       ;1 cycle"                                 "\n\t"
7
                   "adc r19, %1                       ;1 cycle"                                 "\n\t"
8
                   "adc %A3, %2                       ;1 cycle"                                 "\n\t"
9
                   "lpm                               ;3 cycles"                                "\n\t"
10
                   "out %4, __tmp_reg__               ;1 cycle"                                 "\n\t"
11
                   "in  r30, 0x19                     ;1 cycle (read porta)"                    "\n\t"
12
                   "and r30, 0b00000010               ;1 cycle (mask ptt pin)"                  "\n\t"
13
                   "cpi r30, 0b00000010               ;1 cycle (compare if 1)"                  "\n\t"
14
                   "breq exit                         ;1 cycle if input is 1 and loop goes on"  "\n\t"
15
                   "rjmp 1b                           ;2 cycles. Total 13 cycles"               "\n\t"
16
                   "exit:                             ;"                                        "\n\t"
17
                   "ret                               ;"                                        "\n\t"                
18
                   :
19
                   :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTE))
20
                   :"r18", "r19"
21
  );
22
}

Hier lese ich das PINA-Register, maskiere das PINA1-Bit, teste das 
Resultat Gleichheit mit der Maske und wenn es gleich ist springe ich zum 
ret. Funktioniert aber auch nicht. Das kann doch nicht so falsch sein! 
Wo ist der sprichwörtliche Hund begraben?

Gruss
Martin

von Karl H. (kbuchegg)


Lesenswert?

Martin Geissmann schrieb:
> Ok, ist denn sowas besser:

Nein.
Der 'ret' hat da drinn nichts verloren.
Lass du Kontrolle einfach unten aus deinem Assemblerabschnitt (von dem 
ich sowieso noch nicht überzeugt bin, dass der in Assembler sein muss) 
rausfallen.
Der Compiler weiß schon, was es noch alles zu tun gibt, ehe er den 
tatsächlichen Return auf Assemblerebene durchführen darf.

Sieh es so:
Der Compiler baut dir das Framework zusammen
1
    Funktion
2
    Alles was es beim Betreten der Funktion zu tun gibt
3
4
    Benutzercode
5
6
    Alles was es beim Verlassen der Funktion zu tun gibt
7
    ret

Du klemmst dich an die Stelle 'Benutzercode' rein. Aus allem anderen 
hältst du dich raus. Insbesondere hältst du dich aus allem was mit dem 
Stack zu tun hat (und dazu gehört auch der ret) raus. Dein Code, wenn er 
nichts mehr zu tun hat, fällt einfach durch sodass der vom Compiler 
generierte Aufräumcode weitermachen kann.

von Martin G. (hb9tzw)


Lesenswert?

Ok danke, das habe ich korrigiert, indem ich die Zeile mit dem ret 
rausgelöscht habe, läuft aber trotzdem noch nicht.

Scheinbar muss das in Assembler geschrieben sein, damit man die absolute 
Kontrolle über die benötigten Clockcycles hat - diese müssen bekannt 
sein für die Frequenzberechnung bzw. den Wert, durch den diese geteilt 
werden muss.

von Karl H. (kbuchegg)


Lesenswert?

Das hier
                   "in  r30, 0x19
schmeckt mir überhaupt nicht.
Hast du kontrolliert, ob das auch wirklich den richtigen Port abfrägt 
(das Pin-Register von diesem Port)? Der Port ist auf Eingang geschaltet?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dann lass den Inlineassembler weg und pack das Ding in eine eigene
Assembler-Quelldatei.  Eine komplette Funktion in inline asm zu
hacken ist unsinnig, du hast keinen Gewinn von der Integration in
das C-Umfeld, dafür musst du dich mit dem ganzen constraining
befassen, das eigentlich nur dafür da ist, die Integration mit dem
Compiler anpassbar zu halten.

Der Inline-Assembler-Code enthält übrigens keine initiale Zuweisung
an das Registerpaar r30/r31, das die Quelladresse für den LPM-Befehl
bildet.  Damit steht da irgendwas drin.  Zu allem Übel überbügelst
du dieses irgendwas hernach noch mit deiner Eingabe aus PIND.  Die
Auswertung der Eingabe ist auch reichlich umständlich: nach dem AND
ist bereits das Z-Flag gesetzt, du kannst also sofort ohne weiteren
Vergleich einen bedingten Sprung benutzen.

von Stephan M. (stephanm)


Lesenswert?

Martin Geissmann schrieb:
> Ok, ist denn sowas besser:
>
>
1
> ...
2
>
>
> Hier lese ich das PINA-Register, maskiere das PINA1-Bit, teste das
> Resultat Gleichheit mit der Maske und wenn es gleich ist springe ich zum
> ret.

Statt

in  r30, 0x19
and r30, 0b00000010
cpi r30, 0b00000010

wäre ein sbi{r,c} gefolgt von einem rjmp einfacher und schneller. Bei 
meinem digitalen Sinusgenerator habe ich folgenden Code verwendet:
1
    ; 16 cycles total at 16 MHz -> 1MS/s D/A conversion rate
2
1:  add     r6, r2        ; 1 cycle
3
    adc     r7, r3        ; 1 cycle
4
    adc     r8, r4        ; 1 cycle
5
    adc     r30, r5       ; 1 cycle
6
    brcc    2f            ; 1 cycle if branch not
7
                          ; taken, 2 cycles if branch taken
8
    eor     r31, r0       ; 1 cycle
9
2:  lpm     r16, Z        ; 3 cycles
10
    out     _SFR_IO_ADDR(PORTD), r16  ; 1 cycle
11
12
    nop                   ; 3 cycles
13
    nop
14
    nop
15
16
    sbic    _SFR_IO_ADDR(PINC), 3  ; 1 cycle (if rjmp is not skipped)
17
    rjmp    1b                     ; 2 cycles
18
19
    ; Hier landet man, wenn PC3 Low ist

Die beiden letzten Instruktionen sind genau dazu da, den Status eines 
I/O-Pins abzufragen und die Schleife zu beenden, wenn der Pin Low ist.

Ohne die drei NOPs wäre das ganze schneller, aber ich wollte eine 
Samplerate von 1M/sec.

Der Trick mit dem brcc und dem eor ermöglicht 512 Byte große LUTs. Das 
kann man weglassen und sich nochmal 2 Zyklen sparen.

Um aus der ganzen Sache eine Funktion zu machen, die man von C aus 
aufrufen kann ist nicht mehr allzu viel Arbeit nötig: Register sichern, 
benötigte Werte von Hand laden oder in die "richtigen" Register 
verschieben, Register wieder herstellen, fertich.

Stephan

von Stephan M. (stephanm)


Lesenswert?

Noch'n Nachtrag:

Stephan M. schrieb:
> Murks

Damit die Sache mit der 512 Byte-LUT funktioniert (brcc+eor), muss r0 = 
1 sein.

Stephan

von Martin G. (hb9tzw)


Lesenswert?

Vielen Dank für eure Antworten. So funktioniert's, analog dem Code von 
Stephan:
1
void static inline Signal_OUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {
2
3
   asm volatile(   "eor r18, r18          ;r18<-0"                      "\n\t"
4
                   "eor r19, r19          ;r19<-0"                      "\n\t"
5
                   "1:"                                                 "\n\t"
6
                   "add r18, %0           ;1 cycle"                     "\n\t"
7
                   "adc r19, %1           ;1 cycle"                     "\n\t"  
8
                   "adc %A3, %2           ;1 cycle"                     "\n\t"
9
                   "lpm                   ;3 cycles"                    "\n\t"
10
                   "out %4, __tmp_reg__   ;1 cycle"                     "\n\t"
11
                   "sbis %5, 1            ;1 cycle if no skip"          "\n\t"
12
                   "rjmp 1b               ;2 cycles. Total 10 cycles"   "\n\t"
13
                   :
14
                   :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTE)), "I" (_SFR_IO_ADDR(PINA))
15
                   :"r18", "r19" 
16
   );
17
}

Grüsse
Martin

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.