Inline-Assembler ändert Wert nur mit Serial.print

Hi

Nebenschauplatz - ich bin noch an meinem Dot-Display am Schrauben.

Das Display besteht aus 4 Stk 8x8 DOT-Matrix-LED-Elementen (fertig verlötet vom freundlichen China-Mann, bin damit auch zufrieden).

Aktuell möchte ich die Bits meines 5x5-Zeichensatz 'umdrehen', da ich die Zeichen 'falsch herum' rein schieben muß, damit ich die nicht sichtbaren LED-Elemente 'rechts' vom Feld habe.

In C scheint es dafür keine Funktion zu geben - in Assembler ist's dagegen ein Klacks. (die Bits aus dem einen Register nach links raus- und in eine anderes Register von links rein-schieben)
11000000 -> 00000011

Etwas eingelesen und ein Beispiel zu Inline-Assembler gefunden:
Thread im Arduinoforum.de

Weiter brauchte ich noch Pointer (da die Variablen für Funktionen kopiert werden ... ich aber die Variable selber ändern will).
Dafür fand ich dieses PDF, recht lehrreich:
Pointers_in_Arduino

Beides zusammen geworfen, etwas Abgekupfert, heraus kam:

//...
void serialbin(unsigned int var, byte stellen);                           //gibt Zahl in BIN aus
void mirrorbits(byte &zeichenbyte);                                       //spiegelt die Bits eines Byte-Wert, der Pointer auf die Variable wird übergeben (das & muß beim Aufruf mit übergeben werden)
byte mirrorwert;                                                          //global, da örtlich MIT Assembler nicht klappt
//...
void setup() {
  Serial.begin(9600);

  //***********************************
  //Testen der Assembler-Mirror-Funktion
  byte x = 0;
  byte save = 0;
  while (1) {
    Serial.print("X=");
    serialbin(x,8);
    //Serial.print(x, BIN);
    save = x;
    mirrorbits(&save);
    Serial.print("<->");
    //Serial.println(save, BIN);
    serialbin(save,8);
    Serial.println();
    x++;
  }
}

void loop(){
}

void mirrorbits(byte *zeichenbyte) {  //spiegelt die Bits eines Byte-Wert
  //  byte wert;    //erstellen die Variable global, da sonst das Rückschreiben nicht funktioniert (die Variable gibt es nach dem STS nicht mehr ?!?)
  mirrorwert = *zeichenbyte;
  //hier Inline-ASM zum Byte spiegeln
//Serial.print (" ");
//Serial.print (mirrorwert);
  asm volatile ("push r30 \n\t"
                "push r31 \n\t"
                "lds r30,(mirrorwert) \n\t"

                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
                "rol r30 \n\t"
                "ror r31 \n\t"
//"clr r30 \n\t"
                "sts (mirrorwert),r31 \n\t"
                "pop r31 \n\t"
                "pop r30 \n\t"
               );
  Serial.print ("->");
  //Serial.print (mirrorwert);
//  Serial.print (" ");
  *zeichenbyte = mirrorwert;
}

void serialbin(unsigned int var, byte stellen) {
  int vartest = pow(2, stellen - 1) + 1; //2 ^ (stellen - 1); //128;
  /*
    Serial.print("Stellen ");
    Serial.print(stellen);
    Serial.print("Grenze:");
    Serial.print(vartest);
    Serial.print(" Wert:");
    Serial.print(var);
    Serial.println();
  */
  while (vartest > var and vartest > 1) {
    Serial.print ("0");
    vartest = vartest >> 1;
  }
  //  Serial.println();
  Serial.print (var, BIN);
}

Die Funktion mirrorbits nimmt einen Pointer entgegen, liest den Inhalt aus, übergibt Diesen an die Assembler-Routiene (Diese liest den Wert aus dem SRam), Diese schiebt die Bits nach links raus (ins Carry-Flag) und schiebt Dieses (Carry-Flag) in das 2.te Register von Links wieder rein.
Die Register werden dabei zerstört (da die Bits vom Ziel-Register ebenso in das Quell-Register geschoben werden), was aber Nichts macht, da das Quell-Register nicht weiter benötigt wird.
Das Ziel-Register wird dann zurück ins SRam kopiert.
Vor der ganzen Arbeit werden die Register 30 und 31 gesichert, danach wieder zurück geholt.

Eigentlich müsste nun Alles top sein, ohne die Serial.print-Zeile bleibt aber mein geändertes Byte im Original-Zustand.

Zur Not kann ich damit leben - ist aber unschön und ich hätte gerne gewusst, was mir hier 'in die Suppe spuckt'.

Das 'clr r30' kam rein, da ich Mal ein umgedrehtes Byte bekam und Mal nicht - also noch während der Debug-Phase.
Die Register 30 und 31 nutze ich, da Diese als Z-Pointer vom µC benutzt werden und es sehr unwahrscheinlich ist, daß genau dort meine Variable liegt - wie ich heraus bekomme, welche Register 'frei' sind, fand ich noch nicht heraus.
Mit r24/r25 hatte ich teilweise seltsames Verhalten :confused:

Leider bekam ich auch keine Schleife hin - PC (program counter) kennt der Compiler nicht, wollte einen relativen Sprung 'brne PC-3' machen - wobei Labels habe ich gar nicht versucht, müsste schauen, wie ein Label beim Inline-Assembler auszusehen hat - gibt's dazu ein halbwegs bezahlbares Buch? (aus Papier, ein eBook wollte ich eigentlich nicht)

Die Funktion serialbin gibt mit ein Byte als 8-Zeichen BIN-Zahl aus, dort bekomme ich noch eine Warnung, daß ich unsigned und signed Variablen miteinander vergleiche - wird wohl dauern, bis ich Das, der Optik wegen, ausmerze.

Wer kann helfen?
Ich könnte ein Array mit einem Element erstellen, da Dieses selber den Pointer repräsentiert - muß ich Morgen Mal probieren, ob's damit besser geht - zumindest hätte ich dort nicht das Problem, daß ich erst umständlich den Pointer nutzen muß und sollte direkt auf den Inhalt zugreifen können.

MfG

Solltest etwas mehr C++ lernen, damit das Denken in Assembler in Vergessenheit gerät.

Es gibt z.B. einen Unterschied zwischen Pointer und Referenz
void mirrorbits(byte &zeichenbyte); // Der Parameter ist hier eine Referenz, kein Pointer.

Natürlich kann man in C als Hardware-unabhängiger Sprache nicht mit Registern und Carry-Bits arbeiten und Befehle wie "relativen Sprung 'brne PC-3' " machen natürlich keinen Sinn.
(Heisst das Register EAX oder R23 ?)

byte mirrorbits(byte x) {
 byte b = 0;
 byte set = 0x80;
 while (x!=0){
   b |= (x &1)? set:0;
   x >>= 1;
   set >>= 1;
 }
 return b;
}

Wenn du es für besser hältst, kannst du auch mit Zeiger oder Referenz arbeiten und das Ergebnis in die gleiche Speicherstelle zurückschreiben. Klarer geht das aber ausserhalb

Eine solche Klacks-Funktion sollte selbstverständlich ohne globale Variable auskommen.

Wenn du das Spiegeln im gleichen Byte machen willst, schreibst du einfach

save = mirrorbits(save);

Hi

Danke Dir.
Ja, die Funktion hätte ich auch als normale Funktion erstellen können - irgendwie gefiel mir der Gedanke, daß die Variable selber geändert wird.
Auch, weil das 'Umdrehen' in Assembler nur Bit-Schieberei ist.
Das Register heißt beim AVR R23 (R0 bis R31, wovon R16-R31 für Alles benutzt werden können, R0 und R1 teilweise intern Verwendung finden (meine, bei den 'Großen' für mul ... müsste nachschauen) so wie die oberen 6 Register für Pointer benutzt werden (Z R30 & R31, Y R28 & R29, X R26 & R27, wobei das tiefere Register das LOW-Byte ist - mit X, Y und teilweise Z kann man 'der Reihe nach' Daten auslesen/schreiben - R24 & R25 sind auch als Pointer nutzbar, haben aber keinen eigenen Assembler-Befehl).

Ein Umsortieren der Bits als Schleife kam mir auch in den Sinn, fand ich aber 'nicht so schön' :slight_smile:

Die Funktion habe ich ja, allerdings nur, wenn ich den Serial.print(); drin lasse - Ohne bleibt das Original-Byte erhalten, egal, was der Assembler-Quelltext zwischenzeitlich gesagt hat.

MfG

Es könnte sein, dass der Kompiler Deine Routine weg optimiert, wenn Du das Ergebnis nicht nutzt.

Gruß Tommy

Hi

Naja - ich schreibe das neue Byte irgend wo in den Speicher - zumindest sollte das Byte genau dahin kommen, wo zuvor das Quell-Byte gelesen wurde.
Nur wird Das NUR ausgeführt, wenn ich dieses Serial.print(); im Code drin habe - darin gebe ich die Variable NICHT aus bzw. muß Diese nicht ausgeben, damit Sie überlebt.
Sobald keine serielle Ausgabe in der Funktion steht, bekomme ich das Byte nicht umgedreht.
Da der Assembler-Quelltext als volatile definiert ist, sollte Dieser erhalten bleiben.
Da das ausgelesene Byte im Assembler-Quelltext verarbeitet wird, sollte auch Das bleiben.
Zum Schluß wird das Byte wieder im SRam abgelegt - kA, ob auch Teile des ASM-Code optimiert werden können - aber Da haben wir wohl verschiedene Ansichten vom Optimum - also der Compiler und ich.

Da ist Assembler 'gerechter' - da wird ALLES so gemacht, wie ICH Das geschrieben habe - dafür bin ich aber auch zu 100% immer selber schuld.

Vll. hat ja noch Wer eine Idee, wodurch diese Besonderheit ausgelöst wird, welche Interner Da rein spielen.

Die Funktion bekomme ich hin - so oder so (zur Not auch mit dem Serial.print() ... dem µC muß ja Keiner zuhören).

Äääh - vll. kommt mir gerade ein Gedanke:
Bei ISRs declariert man innerhalb manipulierte Variablen auch als volatile - vll. ist dem Compiler der ASM-Quellcode 'so was von egal', daß Er hinter dem ASM-Bereich nur sieht 'aha, jetzt soll das Byte wieder zurück' und entfernt sowohl das Lesen wie das Schreiben, da - für Ihn - sich Nichts ändert?
... dann muß ich den Arduino-Pointer direkt im Assembler bearbeiten :wink: ...
Erklärt zwar immer noch nicht, was Serial.print() dabei 'besser' macht, aber immerhin ein Lichtblick.

MfG

Wenn du schon Assembler benutzt, könntest du auch das Gesamt-Ergebnis im Disassembler ansehen.
(avr-objdump)

Da lernst du auch nebenbei, wie gut der Compiler Assembler programmieren kann.

Und "damit es funktioniert" ein Dummy Serial.print einbauen, ist sicher erheblich schlechter als das Dutzend Assemblerbefehle anstelle von 3 Zeilen c-Code. ( Serial.print wurde auch in c++ geschrieben )

Hi

Das Dutzend ist es nur, weil der Compiler mir keine Schleife erlaubt - naja, dafür sinkt die Ausführungszeit, da keine unnötigen Sprünge nötig sind.
Mir ist schon klar, daß der Compiler brauchbaren Assembler generiert - dafür ist Er ja da - nur sind wir uns noch nicht einig, Wer hier das Sagen hat :slight_smile:
... oder bestimmen darf, was gemacht wird (ok, Das macht der Compiler)

Mir geht es hier schlicht darum, warum dieses (egal womit programmierte) Serial.print das Verhalten des Programm so beeinflusst.

Habe Es noch nicht geschafft, der Variable ein volatile mitzugeben.

'avr-objdump' werde ich Mal Google nach befragen, wäre mir in den Menüs nicht aufgefallen, denke also, daß Das ein separates Programm o.Ä. ist.

Bei meinen Programmen kommt Es ja nicht auf die ultimative Geschwindigkeit an - obwohl es mich z.B. ärgert, wenn in fremdem Code eine identische Ausgabe in zwei IF-Bereichen steht ... Da kann man den gemeinsamen Teil auch VOR (und auch NACH) der IF-Abfrage ausgeben und nur den unterschiedlichen Kram eben situationsbedingt.
Da ticke ich aber eben 'eigen'.

Mir sind die Vorteile einer Hochsprache ja nicht ganz unbekannt - der normale Arduino-Code läuft auf so ziemlich jedem Arduino (wenn die Beinchen halt passen) - sobald Da Assembler im Spiel ist, haben vll. noch die ganzen AVR ein Fuß in der Tür, da dort die Assembler-Befehle wohl identisch sind (vll. abgesehen, daß ein 'mul' beim ATtiny zu Schluckauf führt).
Die anderen verbauten µC sind mir bis dato völlig unbekannt und werden wohl, wenn, kein Assembler von mir sehen :wink:

So, jetzt 'Mal schnell' der Variable ein 'volatile' anheften und schauen, was passiert ...

EDIT
Wenn die globale Variable zusätzlich als volatile declariert wird, funktioniert der Assembler-Schnipsel, wie gewünscht, auch ohne das Serial.print.

Denke mir, daß beim Abarbeiten des Serial.print nicht genug Ressourcen vorhanden sind und der eingelesene Quellwert temporär nicht mehr behalten werden kann - und danach, wo der Wert zurück geschrieben werden soll, wird Dieser erst erneut vom SRam ausgelesen und dann in die 'Variable' (aka Register) geschrieben, Die der Compiler gerade in diesem Moment für gut befindet.

MfG

Ich würde mir das so erklären, dass der Compiler den Assembler-Code nicht anguckt, und daher "weiss", dass die Zeile

  mirrorwert = *zeichenbyte;

ohne den Serial.println Befehl (oder ohne volatile) völlig sinnlos ist.

Übrigens:

 void mirrorbits(byte *zeichenbyte) { ... }

hat nicht viel zu tun mit deiner Deklaration
void mirrorbits(byte &zeichenbyte);
(die auch ignoriert wird, weil sie nicht verwendet wird)

In

   byte save = x;
   mirrorbits(&save);

arbeitest du mit Zeigern, nicht mit Referenzen.

Mir persönlich würde eine Funktion wie

   byte m = mirrorbyte(x);

übrigens besser gefallen. ( Einfacher, klarer )


avr-objdump ist in deiner Arduino "toolchain" mit enthalten. Viel Spass.