Forum: Mikrocontroller und Digitale Elektronik Problem mit for schleife


von Nico Droste (Gast)


Lesenswert?

Hallo, ich habe ein kleines Programm für einen Atmega8 mit einem 2x16
LCD display von displaytech.
Da der Speicher des uC bereits voll ist, möchte ich platz sparen. zum
schreiben auf das Display benutze ich bisher diese Funktionen:

void LCDEnable()
{
  PORTD |= _BV(5); // sbi PORTD, 5
  asm volatile("nop\n\tnop\n\tnop\n\t"::);
  asm volatile("nop\n\tnop\n\tnop\n\t"::);
  asm volatile("nop\n\tnop\n\tnop\n\t"::);
  asm volatile("nop\n\tnop\n\tnop\n\t"::);
  PORTD &= 0xff-_BV(5); // cbi PORTD, 5
}

void LCDData(unsigned char data)
{
  unsigned char data2 = data;

  asm volatile("swap %0\n\t" : "=r" (data) :);
  data &= 0x0F;
  data |= _BV(4);
  PORTD=data;
  LCDEnable();

  data2 &= 0x0F;
  data2 |= _BV(4);
  PORTD=data2;
  LCDEnable();
  Delay50us();
}

dann gibt es noch eine funktion, welche die arrays line1 und line2 auf
das diplay schreibt:
void LCDPrint()
{
  LCDGotoXY(1,1);
  LCDData(line1[0]); LCDData(line1[1]); LCDData(line1[2]);
LCDData(line1[3]); LCDData(line1[4]); LCDData(line1[5]);
LCDData(line1[6]); LCDData(line1[7]); LCDData(line1[8]);
LCDData(line1[9]);
  LCDData(line1[10]); LCDData(line1[11]); LCDData(line1[12]);
LCDData(line1[13]); LCDData(line1[14]); LCDData(line1[15]);
LCDData(line1[16]); LCDData(line1[17]); LCDData(line1[18]);
LCDData(line1[19]);
  LCDGotoXY(1,2);
  LCDData(line2[0]); LCDData(line2[1]); LCDData(line2[2]);
LCDData(line2[3]); LCDData(line2[4]); LCDData(line2[5]);
LCDData(line2[6]); LCDData(line2[7]); LCDData(line2[8]);
LCDData(line2[9]);
  LCDData(line2[10]); LCDData(line2[11]); LCDData(line2[12]);
LCDData(line2[13]); LCDData(line2[14]); LCDData(line2[15]);
LCDData(line2[16]); LCDData(line2[17]); LCDData(line2[18]);
LCDData(line2[19]);
}

LCDPrint ruft also für jedes Zeichen auf dem Display einmal LCDData auf
und übergibt den char von der entsprechenden stelle im array.
Da dies sehr umständlich und speicherraubend ist, wollte ich die ganzen
lcddata dadurch ersetzen:
for ( int i = 1 ; i < 17 ; i++ ) LCDData(line1[i]);
komischer weise funktioniert das nicht. das erste zeichen wird noch
richtig angezeigt, danach kommt immer das selbe. mit den einzelnen
aufrufen von lcddata funktionierts tadellos! ich habe keine idee warum
das nicht funktioniert mit der for schleife.. ich hoffe ihr könnt mir
helfen!

gruß
nico

von Benedikt (Gast)


Lesenswert?

probiers mal so:

unsigned char i;
for (i = 0 ; i < 17 ; i++ )
LCDData(line1[i]);

von Philipp S. (philipp)


Lesenswert?

Tja, Code sieht richtig aus (bis auf die unterschiedlichen Grenzen des
Arrays -- der "Einzelcode" ist wohl von einem 2x20 übernommen? Und
laß die for-Schleife von einem unsigned char durchzählen; das erkennt
der Compiler vielleicht nicht alleine, daß keine int gebraucht wird).

Bist Du Dir also sicher, daß Du den richtigen Inhalt in Deinem Array
hast? "Funktioniert tadellos" ist ja schon erstaunlich, wenn Du
offenbar 20 Zeichen in eine Zeile schreiben willst.

Als unwahrscheinliche Möglichkeit gäbe es da noch ein Compilerproblem
(avr-gcc?), z.B. beim Zusammenspiel von C und Assembler. Laß mal
Spielchen wie das SWAP weg (nutzt das der Compiler nicht automatisch
bei >>4 ?), um das auszuschließen. So etwas passiert gerne mit
irgendwelchen fremdgestrickten delay50µs-Routinen, die dann gnadenlos
ein Register belegen, das gar nicht frei ist.

Und dann kannst Du den Spaß immer noch simulieren, um das Problem zu
finden.

von Nico Droste (Gast)


Lesenswert?

Hallo Benedikt,
mit deinem Code klappts!
Danke und Gruß
Nico

von Benedikt (Gast)


Lesenswert?

Das Problem liegt warscheinlich daran, dass du i erst in der Schleife
deklarierst. Sowas funktioniert nicht bei jedem Compiler.

von Unbekannter (Gast)


Lesenswert?

Alles (fast) falsch, was hier geschrieben wurde.

Der Fehler liegt im asm-swap-Code. Richtig muss der heissen:


   asm volatile("swap %0\n\t" : "=r" (data) : "0" (data));


Der Constraint "=r" ist ein Write-Only-Output-Operand. Du musst dem
Compiler durch den "0"-Input-Operand noch sagen, dass der Input im
gleichen Register sein soll wie der erste Output-Operand.

Die Schleife machst Du so, wie Benedikt geschrieben hat.

von Nico Droste (Gast)


Lesenswert?

@unbekannter:
von dem assembler verstehe ich nunmal garnichts, ich habe jetzt einfach
nur die for schleife von benedikt übernommen und es klappt super! was
würde es ändern, wenn ich die eine zeile bei LCDData durch deine Zeile
ersetze?
gruß
nico

von Unbekannter (Gast)


Lesenswert?

Naja, dass es nun funktioniert, ist reiner Zufall, weil der
Compiler/Optimierer jetzt eben eine andere Registerbelegung verwendet.

Sobald Du an anderer Stelle etwas veränderst, kann es sein dass es dann
wieder nicht geht.

Das Problem an der Fehlerhaften Zeile ist, dass der Compiler nur
annimmt, dass diese Assembler-Zeile nur ein Ergebnis in einem Register
liefert und der Compiler dieses Ergebnis in diesem Register im weiteren
Code als 'data' verwenden soll.

Der Compiler weiß überhaupt nicht, dass diese Assembler-Zeile schon
vorher den Wert von 'data' im selben Register benötigt. D.h. der
Compiler geht davon aus, dass in dem Register drinn stehen kann, was
will weil die Assembler-Zeile darin eh etwas anderes reinschreibt.

Dass es mit der geänderterten Schleife ('unsigned char' anstatt
'int') funktioniert, liegt also nur daran, dass das verwendete
Register für 'unsigned char' der Schleife ein anderes ist, als die
Regsiter für ein 'int' in der Schleife.

Das Problem an diesen Assemblergeschichten ist immer, der Compiler
"versteht" den Assembler-Code ja nicht. Der Compiler weiß nicht, was
die Assembler-Zeile macht.
Der Compiler muss aber wissen, was für ein Register die Assembler-Zeile
benötigt (Typ 'r' ist jedes beliebige Register), und wo nach der
Ausführung der Assembler-Zeile das Ergebnis steht. Und der Compiler
muss auch wissen, in welches Register er den Eingabe-Parameter für die
Assembler-Zeile stecken soll. Und das muss für den swap-Befehl eben
genau im gleichen Register sein, wie für die Ausgabe verwendet werden
soll.

Du siehst also schon, diese Constraints sind die "Schnittstelle"
zwischen Assembler-Code und C-Code. Und wenn die Schnittstelle falsch
definiert ist, kannst Du Dir das so vorstellen, als ob Du eine
C-Funktion mit falschen Parameter, z.B. ein Pointer anstatt ein Float,
aufrufst. Die Funktion macht dann nur noch Mist.

Genau so ist es bei der Assembler-Geschichte. Nur eben etwas gemeiner,
weil der Compiler die Assembler-Zeile nicht überprüfen kann und sich zu
100% darauf verlassen muss, dass der Programmierer weiß, was er da tut.

Und weiß der Programmierer eben nicht, was er da tut, kommen sehr fiese
Fehler raus, weil die wirklich nicht eindeutig sind und von völlig
anderen Sachen abhängen können. Also völlig unreproduzierbare Fehler.

Du hast es ja selbst bemerkt: Nach Deiner Logik und auch nach dem
Verständnis der anderen, hätte Dein Code funktionieren müssen. Es sind
ja nur Vermutungen nach dem Motto "Versuch mal dieses, versuch mal
jenes" gekommen, weil keiner eine Idee hatte, wo der Fehler liegt und
jeder der festen Übezeugung war, dass Dein Code hätte eigentlich
funktionieren müssen.

Also, wenn Du keine unerklärlichen Fehler bei weiteren
Programmänderungen haben willst, musst Du diese Zeile dringst ändern.
Sonst taucht der Fehler irgendwann wieder plötzlich aus dem Nichts auf.

von Philipp S. (philipp)


Lesenswert?

@ Nico: wenn Du gar nichts von Assembler verstehst, solltest Du die
Finger davon lassen. (-;

An dieser Stelle ist es -- wie schon von mir vermutet -- nicht nur
gefährlich, sondern überflüssig, denn ich habe eben nachgeschaut:
zumindest der avr-gcc benutzt ganz von alleine ein SWAP bei >>4.

von Benedikt (Gast)


Lesenswert?

>zumindest der avr-gcc benutzt ganz von alleine ein SWAP bei >>4.

Das stimmt nur teilweise:
Wenn man auf Codegröße optimiert wann wird eine Schleife mit mehreren
lsr/lsl/rol/ror daraus !

von Philipp S. (philipp)


Lesenswert?

Mein Code ist auf Codegröße optimiert und ein SWAP mit ANDI ist ja auch
kleiner als jede denkbare Schleife. Möglich, daß es Fälle gibt, in
denen er für >>4 kein SWAP benutzt, aber mir fallen keine Fälle ein, wo
es sinnvoll wäre. (-:

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.