AVR Assembler, Objektfeldadresse in X Register laden?

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1432
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von fliegermichl »

Hallo,

ich habe ein objekt TNeoPixel, welches ein Datenfeld fPixels vom Typ PByte definiert.
in einer Assemblerroutine möchte ich die Adresse des ersten Bytes auf das fPixels zeigt in das X Register laden.
Ich hab es so versucht, geht aber nicht

Code: Alles auswählen

procedure Show; assembler; nostackframe;
asm
    lds  r26,low(fPixels)
    lds  r27,high(fPixels)
    ...
end;
Da bekomme ich die Fehlermeldung "Error: Wrong symbol type"?

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1432
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von fliegermichl »

Ich hab's herausgefunden
SELF wird 2 Byte oberhalb des Stacks gespeichert.

Jetzt hab ich allerdings herausgefunden, daß dies nur bei -O1 der Fall ist. Bei -O2 wird self in den Registerpaaren r24 und r25 übergeben.
Wenn ich aber

Code: Alles auswählen

procedure TNeoPixel.show; assembler; nostackframe; register;
asm
 ...
end;
schreibe, dann bekomme ich eine Warnung "Calling convention directive ignored "Register".
Woher soll ich in meiner Routine nun wissen, ob Register oder Stack verwendet wurde?

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von Timm Thaler »

Ich hab als ich vor ein paar Jahren mit Assembler für AVR in Pascal angefangen habe ganz schnell aufgehört mit Objects zu arbeiten. Es hat einen erheblichen, unnötigen Overhead bei Prozeduraufrufen erzeugt. Weiß nicht, ob das noch immer so ist.

Wenn ich eine reine Assembler-Prozedur aufrufe, wird bei

procedure test(val : int16); der Wert in r24,r25 übergeben, call-by-value
procedure test(var val : int16); die Adresse des Wertes in r24,r25 übergeben, call-by-reference
procedure test(paddr : pbyte); die Adresse des Wertes oder Arrays in r24,r25 übergeben, Aufruf mit test(@myarray);

Achso ja, und mit nostackframe wird natürlich das Erzeugen eines Stackframes wie der Name schon sagt unterdrückt. Ist auch besser so, denn Stackhandling ist in dem Fall sehr fehleranfällig - von Programmiererseite.

Und ich optimiere immer mit -O3 bei AVR, sonst bekommt man erheblich unnötigen Quatsch reingeschrieben.

Wenn Du überprüfen willst, welche Register in welcher Reihenfolge verwendet werden, setze bei Compilereinstellungen "-al". Dann bekommst Du zu jeder Unit ein Assemblerlisting (die *.s-Dateien) welches Du mit einem Texteditor ansehen kannst.

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von PascalDragon »

fliegermichl hat geschrieben:
So 7. Feb 2021, 13:04
schreibe, dann bekomme ich eine Warnung "Calling convention directive ignored "Register".
Das register hat nichts damit zu tun wie das Self übergeben wird. Es gibt stattdessen die Calling Convention an, ist dabei aber nur für Platformen relevant, wo es tatsächlich mehrere Aufrufkonventionen gibt. Hierbei ist mit register vor allem die Delphi-kompatible Aufrufkonvention für i386 gemeint, welche die ersten drei Parameter in ECX, EDX und EAX übergibt und dann den Stack nutzt, während die anderen Konventionen nur den Stack nutzen. Eine ähnliche Konvention gibt es auch für m68k zum Beispiel. Bei den meisten anderen Platformen (und hier zählt AVR mit rein) gibt es jedoch nur eine einzige Aufrufkonvention und dadurch ist der Modifizierer für die Aufrufkonvention nutzlos.

Warum jedoch die Optimierungseinstellung die Art der Parameterübergabe ändert, kann ich dir gerade nicht sagen. Möglicherweise handelt es sich um einen Bug oder du hast etwas bei der Übergabe falsch verstanden (zumindest sehe ich gerade in dem Code, der für die Parameterübergabe zuständig ist, auf die Schnelle nichts was hier zu 'nem Unterschied führen sollte).
FPC Compiler Entwickler

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1432
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von fliegermichl »

Danke erst mal für die Infos.
Ich habe mit -al die Assemblerausgabe aktiviert und da schreibt der Compiler netterweise einen Kommentar in jeden Routine, wo drinsteht welcher Parameter wo zu finden ist.

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von Timm Thaler »

PascalDragon hat geschrieben:
Mo 8. Feb 2021, 09:41
Warum jedoch die Optimierungseinstellung die Art der Parameterübergabe ändert, kann ich dir gerade nicht sagen.
Es wird bei AVR embedded entweder direkt über Register oder über den Stack übergeben.

Das hängt nicht nur von der Anzahl der übergebenen Werte ab (irgendwann reichen halt die Register nicht mehr), sondern auch von der Prozedur. Es kann sein, dass in einer Prozedur mit vielen lokalen Variablen viele Register belegt werden und die Übergabe über den Stack erfolgt.

Ändert man dann etwas in der Prozedur, werden Register frei und dann wird über Register übergeben.

Es kann sogar sein, dass beim Umstellen der Optimierung von -O2 auf -O3 eine For-Schleife nicht mit 16 Bit, sondern nur mit den nötigen 8 Bit gebaut wird, es wird ein Register frei und plötzlich wird statt Stack über Register übergeben.

Bei reinen ASM-Prozeduren mit nostackframe sollte aber immer über Register übergeben werden, entweder der Wert oder bei call-by-reference, Arrays oder Strings die Adresse.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1432
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von fliegermichl »

Die Procedure ist ansonsten Parameterlos (ausser natürlich der implizite self Parameter) und hat auch keine lokalen Variablen.
Die Register reichen mir um alles lokale zu verwalten.

Gibt es eine Möglichkeit Objektfelder über Namen zu referenzieren?
Ich lade die Adresse von self in das Z Register.
um dann einzelne Felder anzusprechen muss ich immer erst die Byteposition zählen und wehe ich füge versehentlich ein Feld ein oder lösche eines.

Code: Alles auswählen

ldd r18,Z+5	// Laden von fnumBytes
ldd r18,Z+fnumBytes // schmeisst Fehlermeldungen, wäre aber wesentlich angenehmer
ldd r18,Z+@fnumBytes - @fBegun // Das könnte der Compiler ausrechnen - macht er aber nicht sondern meckert "Assembler syntax error in operand"

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von Timm Thaler »

Gib mal bißchen mehr Code. Ich mach sowas über records und das funktioniert bestens, aber vielleicht verstehe ich ja falsch was Du machen willst.

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von PascalDragon »

Timm Thaler hat geschrieben:
Mo 8. Feb 2021, 15:51
PascalDragon hat geschrieben:
Mo 8. Feb 2021, 09:41
Warum jedoch die Optimierungseinstellung die Art der Parameterübergabe ändert, kann ich dir gerade nicht sagen.
Es wird bei AVR embedded entweder direkt über Register oder über den Stack übergeben.
Die Parameterübergabe selbst ist aber nicht abhängig von der Optimierungsstufe, da die ABI ja keine Ahnung von der Optimierung eines Compilers hat und Routinen ja unterschiedlich optimiert kompiliert werden können, der Aufrufer davon aber keine Ahnung hat.

Was ich mir jedoch vorstellen kann, ist dass die Optimierung die Größe des übergebenen Self ändert und dadurch sich der Übergabeort ändert, denn größenabhängige Prüfungen bzgl. Stack vs. Register sind im Code, der für die Parameterübergabe zuständig ist, enthalten.

Wir brauchen hier aber definitiv ein volles Beispiel von fliegermichl, um zu sehen warum sich hier was ändert.
FPC Compiler Entwickler

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1432
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von fliegermichl »

Ich wollte das eben nochmal nachvollziehen.
Hat aber nicht geklappt. Bei der reinen Assemblerprozedur wird - unabhängig von der Optimierung - self immer in r24,r25 übertragen.

Zu der Frage der Adressierung von Objektfeldern.

Code: Alles auswählen

  TNeoPixel = object     // Offset 
  private
    fBegun : boolean;    // 0     ///< true if _begin() previously called
    fnumBytes : word;    // 2     ///< Size of 'pixels' buffer below
    fnumLEDs : word;     // 4     ///< Number of RGB(W) LEDs in strip
    fPin : int16;        // 6     ///< Output pin number (-1 if not yet set)
    fbrightness : byte;  // 8     ///< Strip brightness 0-255 (stored as +1)
    fEndTime : dword;    // 10     ///< Latch timing reference
    fPixels : Pbyte;     // 14    ///< Holds LED color values (3 or 4 bytes each)
    ...
  public
    procedure show;
    ...
  end;
  
procedure TNeoPixel.show; assembler; nostackframe;
label wait, NextBit, NextByte;
asm
   push r29      // verwendete Register sichern
   push r28      // r28/r29 fnumBytes
   push r18      // hi PinMask
   push r19      // lo PinMask
   push r20      // Bit Zähler
   push r21      // aktuell ausgegebenes Byte
   push r22      // next
   push r30      // Z zeigt auf fPixels
   push r31      //
   push r24      // Das Ergebnis von canShow wird in r24 zurückgegeben
wait:
   call canShow
   tst  r24
   breq wait

   pop  r24
   mov  r30,r24   // load self in Z register
   mov  r31,r25
   ldd  r28,Z+2   // Load fnumBytes in r28/r29
   ldd  r29,Z+3
   ldd  r24,Z+6   // Pin auslesen
   call digitalPinToBitmask
   in   r18,PIND  // aktuellen Portzustand auslesen
   mov  r19,r18   // und diesen auch nach lo schreiben
   or   r18,r24   // Pin Bit setzen (hi)
   and  r19,r24   // alles ausser Pin Bit löschen (lo)
   ldi  r20,8     // Bit Zähler
   ld   r21,X+    // erstes Byte laden und X Register erhöhen

   nop            // Zum auffinden dieser Stelle im disassembler
   nop
   nop
   nop
   nop

   ldd  r26,Z+13    // fPixels in X Register laden
   ldd  r27,Z+14
   cli             // Interrupts sperren
NextBit:
   out  PORTD,r18  // PORT := hi
   sbrc r21,7      // if (Byte and 128) then
   mov  r22,r18    //   next := hi
   dec  r20        // Bit := Bit - 1
   out  PORTD,r22  // Port := next
   mov  r22,r19    // next := lo
   breq NextByte   // if Bit = 0 then NextByte
   rol  r21        // Rotate Left on Byte
   rjmp +0         // dirty trick, macht das gleiche wie zweimal nop, belegt aber ein Byte weniger Speicher
   nop             //
   out  PORTD,r19  // Port := lo
   nop             // 3 Takte Pause machen
   rjmp +0         //
   rjmp NextBit    // Nächstes Bit ausgeben
NextByte:
   ldi  r20,8      // Bit := 8
   ld   r21,X+     // Nächstes Byte von fPixels laden
   out  PORTD,r19  // Port := lo
   nop             // 1 Takt warten
   sbiw r28,1      // Bytezähler decrementieren
   brne NextBit    // Nicht Null? nächstes Bit ausgeben
   sei             // Interrupts wieder zulassen
   pop  r31
   pop  r30
   pop  r22
   pop  r21
   pop  r20
   pop  r19
   pop  r18
   pop  r28
   pop  r29
end;
Um in TNeoPixel.show an die Anzahl auszugebender Bytes zum kommen, muß ich auf self.fNumBytes zugreifen.
Das geschieht in der Zeile ldd r28,z+2 bzw ldd r29,z+2
Da fnumBytes nach fbegun steht (Offset 2)

Gibt es keine syntaktische Möglichkeit statt der 2 eine Referenz auf das Objektfeld per Name zu machen?
Zumal sich das Offset ja auch ändert wenn {$packrecords on} definiert wird.

Edit: Mir ist eben aufgefallen, daß ich fPixels nicht in das X Register sondern in das Z Register lade. Das ist natürlich falsch.

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von PascalDragon »

fliegermichl hat geschrieben:
Di 9. Feb 2021, 10:35
Gibt es keine syntaktische Möglichkeit statt der 2 eine Referenz auf das Objektfeld per Name zu machen?
Zumal sich das Offset ja auch ändert wenn {$packrecords on} definiert wird.
Du kannst folgendes nutzen, wobei du jedoch noch immer manuell zwischen dem Lo und dem Hi Teil unterscheiden musst (funktioniert sowohl mit record als auch mit object):

Code: Alles auswählen

program tavrofs;

type
  TMyRecord = record
    Field1: Word;
    Field2: Word;
  end;

procedure DoTest(constref aRec: TMyRecord); assembler; nostackframe;
asm
  ldd r28, Z+TMyRecord.Field2
  ldd r29, Z+TMyRecord.Field2+1
end;

var
  a: TMyRecord;
begin
  DoTest(a);
end.
FPC Compiler Entwickler

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1432
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von fliegermichl »

Super! Danke das war's wonach ich gesucht hab.

siro
Beiträge: 730
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von siro »

Da ich mich auch SEHR intensiv mit den RGB Leds/Stripes beschäftigt habe, hier eine kleine Info:
Die High Impulse sollte man recht genau einhalten, aber die Low Impulse dürfen "wesentlich" länger sein.

getestet mit WS2812 und SK2812

So konnte ich mit einem kleinen PIC Controller den Code sogar in "C" implementieren.
Vermutlich geht das mit Pascal dann auch.....

Folgendes läuft ohne Probleme:

Low Phasen 4,2us zwischen den Bits
Low Phase zwischen den Bytes sogar 7,2us

Code: Alles auswählen


/* Jede LED hat 3 Bytes insgesamt also 24 Bits */
typedef
{
  U8 green; /* 8 Bit fuer die Helligkeit */
  U8 red;   /* 8 Bit fuer die Helligkeit */
  U8 blue;  /* 8 Bit fuer die Helligkeit */
} TLed;     /* Type Bezeichner ist TLed */

TLed LedArray[LED_COUNT];

void LedShiftOut(void)
{ U8* leds = (U8*)LedArray;
  U8 count = LED_COUNT;
  U8 one_byte;
  S8 bit_count;

  // damit keine Multiplikation verwendet wird:
  count = (count << 1) + count;  // 3 Bytes pro Led RGB
  DISABLE; // alle Interrupts sperren
  while (count) {
    CLRWDT(); // den Watchdog bedienen, falls erforderlich
    one_byte = *leds++; // aktuelles Datenbyte laden
    // 8 Bits durchlaufen:
    for (bit_count = 0; bit_count < 8; bit_count++) {
      if (one_byte & 0x80) // wenn das oberste Bit 7 gesetzt ist dann
      {
        LATA5 = 1; // lange High Phase einleiten
        NOP();
        NOP();
        NOP();
        LATA5 = 0; // High Phase beenden
        NOP(); // testweise länger auf low 
        NOP();
        NOP();
/*        NOP();    zum testen diverse NOPs bis es nicht mehr funktionierte
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
 */
      } else // Kurze High Phase weil das Datenbit Low ist
      {
        LATA5 = 1; // kurze High Phase einleiten
        NOP();
        LATA5 = 0; // High Phase beenden
        NOP(); // testweise länger auf low 
        NOP();
/*        NOP();    zum testen diverse NOPs bis es nicht mehr funktionierte
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
        NOP();
*/
      }
      one_byte <<= 1; // Das Datenbyte 1 mal links verschieben
    }
    count--; // Anzahl auszugebener Datenbytes -1
  }
  ENABLE; // Interrupt wieder einschalten
  __delay_us(80); // Das Ende der Datenübertragung erreicht wenn die Leitung länger als 80us Low bleibt.
}
in Pascal könnte es evtl. so aussehen ?

Code: Alles auswählen

Type TLED = record
  green : Byte;
  red   : Byte;
  blue  : Byte;
end;

const LED_COUNT = 24;

var LedArray : Array [0..LED_COUNT-1] of TLED;

procedure FillLeds(R,G,B:Byte);
var i:Integer;
begin
  for i:=0 to LED_COUNT-1 do begin
    LedArray[i].red  :=R;
    LedArray[i].green:=G;
    LedArray[i].blue :=B;
  end;
end;

Procedure ShiftOut_V1;
var pLed      : ^Byte;
var b         : Byte;
var bitCount  : Byte;
var byteCount : Byte;
Begin
  pLed := @LedArray;
  byteCount:=SizeOf(LedArray);
  while byteCount > 0 do begin
    b:=pLed^;            // hole ein yte aus dem Array
    inc(pLed);           // Zeiger aufs nächste Byte
    for bitCount:=0 to 7 do begin
      if (b and $80) > 0 then begin  // High Bit
                                     // code für High Bit
      end else begin                 // Low Bit
                                     // code für Low Bit
      end;
      b:=b SHL 1;           // nächstes Bit
    end; // for bitcount;
    dec(byteCount);
  end;
end;


Siro
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von Timm Thaler »

Wieso gehst Du davon aus, dass boolean 16bit sind?

Code: Alles auswählen

    fBegun : boolean;    // 0     ///< true if _begin() previously called
    fnumBytes : word;    // 2     ///< Size of 'pixels' buffer below
Das sind normal 16 Bit, das sind mit höherer Optimierung auch gern 8 Bit.

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: AVR Assembler, Objektfeldadresse in X Register laden?

Beitrag von PascalDragon »

Timm Thaler hat geschrieben:
Di 9. Feb 2021, 20:40
Wieso gehst Du davon aus, dass boolean 16bit sind?

Code: Alles auswählen

    fBegun : boolean;    // 0     ///< true if _begin() previously called
    fnumBytes : word;    // 2     ///< Size of 'pixels' buffer below
Das sind normal 16 Bit, das sind mit höherer Optimierung auch gern 8 Bit.
Das fBegun Feld mag zwar unterschiedliche Größe haben, aber der Offset von fnumBytes wird sich abhängig von der Optimierungsstufe nicht ändern, weil sich eben Code auf den Offset verlassen könnte. Die einzige Optimierung, die am Offset was ändert, ist OrderFields und die ist nur für Delphi-Klassen aktiviert, da hier normalerweise die Reihenfolge der Felder nicht relevant ist (im Gegensatz zu record und object). Ansonsten ändert sich am Offset nur was, wenn an den Packing Einstellungen gedreht wird (z.B. packed object).
FPC Compiler Entwickler

Antworten