www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Inline Assembler Abbruchbedingung


Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo

Ich habe diese Routine mit Inline Assmebler:
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {

  asm volatile(  "eor r18, r18         ;r18<-0"                    "\n\t"
                 "eor r19, r19         ;r19<-0"                    "\n\t"
                 "1:"                                              "\n\t"
                 "add r18, %0          ;1 cycle"                   "\n\t"
                 "adc r19, %1          ;1 cycle"                   "\n\t"
                 "adc %A3, %2          ;1 cycle"                   "\n\t"
                 "lpm                  ;3 cycles"                  "\n\t"
                 "out %4, __tmp_reg__  ;1 cycle"                   "\n\t"
                 "rjmp 1b              ;2 cycles. Total 9 cycles"  "\n\t"
                 :
                 :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD))
                 :"r18", "r19"
  );
}

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:
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {

  asm volatile(  "eor r18, r18         ;r18<-0"                    "\n\t"
                 "eor r19, r19         ;r19<-0"                    "\n\t"
                 "1:"                                              "\n\t"
                 "add r18, %0          ;1 cycle"                   "\n\t"
                 "adc r19, %1          ;1 cycle"                   "\n\t"
                 "adc %A3, %2          ;1 cycle"                   "\n\t"
                 "lpm                  ;3 cycles"                  "\n\t"
                 "out %4, __tmp_reg__  ;1 cycle"                   "\n\t"
                 "cmp pina1, 0         ;??? cycles"                "\n\t"
                 "jne 2f               ;??? cycles"                "\n\t"
                 "rjmp 1b              ;2 cycles. Total ? cycles"  "\n\t"
                 "2:"                                              "\n\t"
                 :
                 :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTD))
                 :"r18", "r19"
  );
}

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

Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mittlerweile habe ich selbst etwas anderes ausgearbeitet:
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {

   asm volatile(   "eor r18, r18                   ;r18<-0"                           "\n\t"
                   "eor r19, r19                   ;r19<-0"                           "\n\t"
                   "1:"                                                               "\n\t"
                   "add r18, %0                    ;1 cycle"                          "\n\t"
                   "adc r19, %1                    ;1 cycle"                          "\n\t"
                   "adc %A3, %2                    ;1 cycle"                          "\n\t"
                   "lpm                            ;3 cycles"                         "\n\t"
                   "out %4, __tmp_reg__            ;1 cycle"                          "\n\t"
                   "sbic 0x19,1                    ;2 cycles if skipping takes place" "\n\t"
                   "ret                            ;0 cycles if skipped"              "\n\t"
                   "rjmp 1b                        ;2 cycles. Total 11 cycles"        "\n\t"
                   :
                   :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTE))
                   :"r18", "r19"
   );
}

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok, ist denn sowas besser:
void static inline signalOUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {

   asm volatile(   "eor r18, r18                      ;r18<-0"                                  "\n\t"
                   "eor r19, r19                      ;r19<-0"                                  "\n\t"
                   "1:"                                                                         "\n\t"
                   "add r18, %0                       ;1 cycle"                                 "\n\t"
                   "adc r19, %1                       ;1 cycle"                                 "\n\t"
                   "adc %A3, %2                       ;1 cycle"                                 "\n\t"
                   "lpm                               ;3 cycles"                                "\n\t"
                   "out %4, __tmp_reg__               ;1 cycle"                                 "\n\t"
                   "in  r30, 0x19                     ;1 cycle (read porta)"                    "\n\t"
                   "and r30, 0b00000010               ;1 cycle (mask ptt pin)"                  "\n\t"
                   "cpi r30, 0b00000010               ;1 cycle (compare if 1)"                  "\n\t"
                   "breq exit                         ;1 cycle if input is 1 and loop goes on"  "\n\t"
                   "rjmp 1b                           ;2 cycles. Total 13 cycles"               "\n\t"
                   "exit:                             ;"                                        "\n\t"
                   "ret                               ;"                                        "\n\t"                
                   :
                   :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTE))
                   :"r18", "r19"
  );
}

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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
    Funktion
    Alles was es beim Betreten der Funktion zu tun gibt

    Benutzercode

    Alles was es beim Verlassen der Funktion zu tun gibt
    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.

Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stephan M. (stephanm)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Martin Geissmann schrieb:
> Ok, ist denn sowas besser:
>
>
> ...
> 
>
> 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:
    ; 16 cycles total at 16 MHz -> 1MS/s D/A conversion rate
1:  add     r6, r2        ; 1 cycle
    adc     r7, r3        ; 1 cycle
    adc     r8, r4        ; 1 cycle
    adc     r30, r5       ; 1 cycle
    brcc    2f            ; 1 cycle if branch not
                          ; taken, 2 cycles if branch taken
    eor     r31, r0       ; 1 cycle
2:  lpm     r16, Z        ; 3 cycles
    out     _SFR_IO_ADDR(PORTD), r16  ; 1 cycle

    nop                   ; 3 cycles
    nop
    nop

    sbic    _SFR_IO_ADDR(PINC), 3  ; 1 cycle (if rjmp is not skipped)
    rjmp    1b                     ; 2 cycles

    ; 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

Autor: Stephan M. (stephanm)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Noch'n Nachtrag:

Stephan M. schrieb:
> Murks

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

Stephan

Autor: Martin Geissmann (hb9tzw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank für eure Antworten. So funktioniert's, analog dem Code von 
Stephan:
void static inline Signal_OUT(const uint8_t *signal, uint8_t ad2, uint8_t ad1, uint8_t ad0) {

   asm volatile(   "eor r18, r18          ;r18<-0"                      "\n\t"
                   "eor r19, r19          ;r19<-0"                      "\n\t"
                   "1:"                                                 "\n\t"
                   "add r18, %0           ;1 cycle"                     "\n\t"
                   "adc r19, %1           ;1 cycle"                     "\n\t"  
                   "adc %A3, %2           ;1 cycle"                     "\n\t"
                   "lpm                   ;3 cycles"                    "\n\t"
                   "out %4, __tmp_reg__   ;1 cycle"                     "\n\t"
                   "sbis %5, 1            ;1 cycle if no skip"          "\n\t"
                   "rjmp 1b               ;2 cycles. Total 10 cycles"   "\n\t"
                   :
                   :"r" (ad0),"r" (ad1),"r" (ad2),"e" (signal),"I" (_SFR_IO_ADDR(PORTE)), "I" (_SFR_IO_ADDR(PINA))
                   :"r18", "r19" 
   );
}

Grüsse
Martin

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.