Forum: Compiler & IDEs Fehler bei einfacher Integer-Division?


von Ralph K. (rosti_mcn)


Lesenswert?

Hallo,

hab ein Problem: in folgendem Code-Schnipsel wird current_time in der
ersten Zuweisung offenbar falsch berechnet, denn mit der zweiten
(hartkodierten) Zuweisung funktioniert alles wunderbar. Wer denkt hier
falsch: ich, der cpp oder (avrgc)c?
1
#define F_CPU 16000000UL
2
3
#if   ( F_CPU >= 1000000UL ) && ( F_CPU <= 10900000UL )
4
#  define PRESCALER_VALUE    1
5
#  define PRESCALER_BITMASK  (0<<CS12) | (0<<CS11) | (1<<CS10);
6
#elif ( F_CPU >= 8000000UL ) && ( F_CPU <= 87200000UL )
7
#  define PRESCALER_VALUE    8
8
#  define PRESCALER_BITMASK  (0<<CS12) | (1<<CS11) | (0<<CS10);
9
#else
10
#  error F_CPU not supported
11
#endif
12
13
uint16_t current_time;  // time of last signal edge (in microseconds)
14
15
current_time = 1000L * ICR1  / ( F_CPU / (PRESCALER_VALUE) / 1000 );  // 
16
§§§ -> error
17
current_time = ICR1 / 2;                      // §§§ -> okay

Übrigens liefert derselbe Code, wenn ich ihn einfach mal ins
Hauptprogramm reinschreibe und 'ne Debugausgabe mache, das richtige
Ergebnis.

Gruß, Mr Ratlos

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Ralph K. schrieb:
  #if   ( F_CPU >= 1.000.000 ) && ( F_CPU <= 10.900.000 )
  #elif ( F_CPU >= 8.000.000 ) && ( F_CPU <= 87.200.000 )

Irgendwie leuchtet mir dieser Code nicht recht ein.
Also die Überlappung.

: Bearbeitet durch User
von Ralph K. (rosti_mcn)


Lesenswert?

Gerade getestet: auch die folgende Variante schlägt fehl:

uint16_t tempICR1; tempICR1 = ICR1;
current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error

Also wie (1000 * x / 2000) bei ausreichender Bitbreite ungleich (x / 2) 
sein kann, ist mir schleierhaft.

Mr. Ratlos^2

von Ralph K. (rosti_mcn)


Lesenswert?

> Irgendwie leuchtet mir dieser Code nicht recht ein.
> Also die Überlappung.

Verstehe ich, aber die ist hier ja nicht von Belang.
Bei gegebenem F_CPU ist der Prescaler halt 8.

von (prx) A. K. (prx)


Lesenswert?

Probleme, die sich besser mit einem übersetzbaren Beispiel 
nachvollziehen lassen, lassen sich ohne übersetzbares Beispiel 
schlechter nachvollziehen.

> ist hier ja nicht von Belang.

Korrekt. Darum ging es auch nicht. War nur drüber gestolpert.

von lalala (Gast)


Lesenswert?

Welchen Wert hat denn ICR1?

von Roland .. (rowland)


Lesenswert?

Ralph K. schrieb:
> Also wie (1000 * x / 2000) bei ausreichender Bitbreite ungleich (x / 2)

Und was kommt bei beispielsweise x=2000 raus?

von Ralph K. (rosti_mcn)


Angehängte Dateien:

Lesenswert?

Das ist das 16bit-Register ICR1 auf einem ATMega168. Also irgendwas 
zwischen 0 und 2^16-1 :)

Hier kommt der Code (anbei), falls jemand willens ist, da reinzuschauen. 
Es geht um Zeile 229 ff. in DHT11+22.c

Ähm, Tabweite im Editor auf 4 Zeichen setzen, dann sieht's auch gut aus 
;)

: Bearbeitet durch User
von lalala (Gast)


Lesenswert?

Ralph K. schrieb:
> Gerade getestet: auch die folgende Variante schlägt fehl:
>
> uint16_t tempICR1; tempICR1 = ICR1;
> current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error
>
> Also wie (1000 * x / 2000) bei ausreichender Bitbreite ungleich (x / 2)
> sein kann, ist mir schleierhaft.
>
> Mr. Ratlos^2

Ich kann hier leider nix nachschauen aber schreib mal
uint32-t statt uint16-t
Und klammer mal ((uint32-t) 1000 * tempicr1)/2000

von Al (Gast)


Lesenswert?

Sobald ICR1 größer 65 wird reicht der 16 Bit Raum nicht mehr aus um die 
Multiplikation durchzuführen. Du musst also sowohl die Operanden und 
anschließend das Ergebnis nach 32 Bit casten.

Ebenso musst du mal eine Worst-Case Rechnung durchzuführen um zu wissen, 
ob current_time das Ergebnis der Division überhaupt aufnehmen kann.

Auf einem ATmega sind das aber teure Rechnungen.

von (prx) A. K. (prx)


Lesenswert?

Al schrieb:
> Sobald ICR1 größer 65 wird reicht der 16 Bit Raum nicht mehr aus um die
> Multiplikation durchzuführen.

1000L * ICR1 wird in 32 Bits berechnet.

> Du musst also sowohl die Operanden und
> anschließend das Ergebnis nach 32 Bit casten.

Ein Operand allein reicht.

von Walter (Gast)


Lesenswert?

Ralph K. schrieb:
> current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error

dann schreib mal
tempICR1 = 65;
current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error
printf("%d", current_time);

ich bin sicher dass das Ergebnis richtig rauskommt.
Allerdings stelle ich mir die Frage warum du nicht einfach durch 2 
dividierst?

von Walter (Gast)


Lesenswert?

Ralph K. schrieb:
> current_time = 1000L * ICR1  / ( F_CPU / (PRESCALER_VALUE) / 1000 );  //
> §§§ -> error
> current_time = ICR1 / 2;                      // §§§ -> okay

die beiden Zeilen sind aber auch definitiv nicht das gleiche, weder für 
PRESCALER_VALUE 1, noch für 8

von Roland .. (rowland)


Lesenswert?

Walter schrieb:
> die beiden Zeilen sind aber auch definitiv nicht das gleiche, weder für
> PRESCALER_VALUE 1, noch für 8

Und warum?

current_time = 1000L * ICR1  / ( F_CPU / (PRESCALER_VALUE) / 1000 );

Assoziativität von l9nks nach rechts bei / und *. Zudem bindet die 
Klammer stärker, also:
1
current_time = 1000L * ICR1  / ( 16000000 / 8 / 1000 );
2
current_time = 1000L * ICR1  / ( 2000000 / 1000 );
3
current_time = 1000L * ICR1  / 2000;
4
current_time = 1 * ICR1  / 2;

exakt das Selbe.
---

Ralph K. schrieb:
> current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error

Versuch mal anstatt des Zahlenwertes die Variable in 32Bit zu casten:

current_time = 1000 * (uint32_t)tempICR1 / 2000 ;  // geht vielleicht 
;-)

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Roland ... schrieb:
> Versuch mal anstatt des Zahlenwertes die Variable in 32Bit zu casten:

Sollte das gleiche Ergebnis haben. Bei Zweifel am Code könnte ebendieser 
helfen (.lss beispielsweise).

von Ralph K. (rosti_mcn)


Lesenswert?

So, zunächst erstmal ein Dankeschön für alle Beiträge.

Hier sind ja einige Dinge angesprochen worden, die ich kurz 
zusammenfassen möchte (alles überprüft und getestet):

a) 1000L -> Operation wird mit 32 ausgeführt
b) der typecast eines Operanden genügt
c) eine Klammer um den Ausdruck vor der Division ändert nichts am 
Ergebnis (und sollte es wegen der Operator-Assozitivität auch nicht)
d) die beiden Ausdrücke (also der gewünschte und die einfache Division 
SIND identisch)

Das Rätsel scheint aber auf einer ganz anderen Ebene zu liegen. Ich habe 
jetzt fast 2 Stunden im Nebel herumgestochert und dabei auch mal 
Codezeilen verschoben (wegen temporären Variablen und Zuweisungen an 
Debug-Variablen). Dabei hat sich gezeigt, dass die Reihenfolge der 
Anweisungen eine Rolle spielt. Ein Beispiel:
1
current_time = (uint16_t) ( ( (uint32_t)1000 * ICR1 )  / ( F_CPU / PRESCALER_VALUE / 1000 ) );    // §§§ error
2
#ifdef DEBUG_UART
3
  dbg_last_time[iEdge]    = ICR1;
4
  dbg_current_time[iEdge] = current_time;
5
#endif
6
current_time = 1000L * ICR1 / 2000 ;

liefert einen Fehler in der weiteren Verwertung des Ergebnisses, 
dbg_current_time[i] ist dabei konstant Null.
1
current_time = (uint16_t) ( ( (uint32_t)1000 * ICR1 )  / ( F_CPU / PRESCALER_VALUE / 1000 ) );    // §§§ error
2
current_time = 1000L * ICR1 / 2000 ;
3
#ifdef DEBUG_UART
4
  dbg_last_time[iEdge]    = ICR1;
5
  dbg_current_time[iEdge] = current_time;
6
#endif

funktioniert fehlerfrei. Ebenso folgende Variante, wobei hier natürlich 
die Debug-Variablen nichts Sinnvolles enthalten:
1
current_time = (uint16_t) ( ( (uint32_t)1000 * ICR1 )  / ( F_CPU / PRESCALER_VALUE / 1000 ) );    // §§§ error
2
// #ifdef DEBUG_UART
3
  // dbg_last_time[iEdge]    = ICR1;
4
  // dbg_current_time[iEdge] = current_time;
5
// #endif
6
current_time = 1000L * ICR1 / 2000 ;

Also das ist eindeutig ein Fall einer Verschwörung der Technik gegen 
ihren Herrn und Meister :D.
Den Prozessor habe ich inzwischen übrigens auch schon mal getauscht, 
hätte ja ein Bitfehler sein können - ist aber leider nicht der Fall.

Hat jemand von Euch schon mal ähnliche Erfahrungen der dritten Art 
gemacht? Wäre dankbar für jeden Hinweis der zur Ergreifung des Fehlers 
führt ;)

Mr. Ratlos^3

von Walter (Gast)


Lesenswert?

Roland ... schrieb:
> exakt das Selbe.

stimmt, habe ich mich verguckt
aber es gilt immer noch, ich bin zuversichtlich dass der Fehler 
außerhalb dieser Formel liegt:

Walter schrieb:
> dann schreib mal
> tempICR1 = 65;
> current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error
> printf("%d", current_time);

von lalala (Gast)


Lesenswert?

Erfahrungen der dritten Art sind eigentlich die bei denen Speicher 
falsch adressiert wird. Wird iEdge auch nicht zu groß? Oder alle anderem 
indices?

von Ralph K. (rosti_mcn)


Angehängte Dateien:

Lesenswert?

Das war ja auch zunächst mein Verdacht. Aber meine vorsichtige Antwort 
lautet nein. Wenn die #DEBUG-Defines nicht gesetzt sind, gibt es nur ein 
Array in dem betreffendem Modul und dessen Indizes sind dem gründlichen 
Augenschein nach fehlerfrei. iEdge kann auch nicht größer als 83 werden, 
denn das wird getestet und die Schleife beendet, in der die Codestelle 
steht, die mir Kopfzerbrechen macht.

Vielleicht schaut hier ja auch ein Assembler-Lesen Könnender rein? (Man 
soll die Hoffnung ja nie aufgeben :). Falls ich richtig liege, ist die 
entscheidende Stelle die folgende:

funktioniert (Division durch Konstante 2000):
1
  ldi r26,lo8(0)   ;  D.1712,
2
  ldi r27,hi8(0)   ;  D.1712,
3
  asr r27   ;  D.1712
4
  ror r26   ;  D.1712
5
  ror r25   ;  D.1712
6
  ror r24   ;  D.1712
7
  movw r12,r24   ;  current_time, D.1712

funktioniert nicht (Division durch eine Variable, der ich den Wert 2000 
zugewiesen habe):
1
  ldi r24,lo8(0)   ;  D.1712,
2
  ldi r25,hi8(0)   ;  D.1712,
3
  ldi r18,lo8(1000)   ; ,
4
  ldi r19,hi8(1000)   ; ,
5
  ldi r20,hlo8(1000)   ; ,
6
  ldi r21,hhi8(1000)   ; ,
7
  call __mulsi3
8
  ldi r18,lo8(2000)   ; ,
9
  ldi r19,hi8(2000)   ; ,
10
  ldi r20,hlo8(2000)   ; ,
11
  ldi r21,hhi8(2000)   ; ,
12
  call __divmodsi4
13
  movw r12,r18   ;  current_time, tmp113

Erkennt hier jemand Fehlerpotenzial?

[Ergänzung: Letzter Akt für heute - habe die disassemblierte Objektdatei 
hier mal noch drangehängt.]

: Bearbeitet durch User
von Walter (Gast)


Lesenswert?

Walter schrieb:
> aber es gilt immer noch, ich bin zuversichtlich dass der Fehler
> außerhalb dieser Formel liegt:
>
> Walter schrieb:
>> dann schreib mal
>> tempICR1 = 65;
>> current_time = (uint32_t)1000 * tempICR1 / 2000 ;  // §§§ error
>> printf("%d", current_time);

noch Mal, was kommt bei dieser Ausgabe raus!
Versteif dich doch nicht auf die blöde Formel wenn du sie noch nicht 
wirklich als Fehler isoliert hast.
Die "schwierigen" Fehler sind immer die bei denen man betriebsblind auf 
die faslsche Stelle starrt

von Ralph K. (rosti_mcn)


Lesenswert?

Hallo Walter, wenn ich diesen Ausdruck berechne, kommt halt 32 raus. Das 
geht soweit okay. Ich hatte gehofft, das in 
Beitrag "Re: Fehler bei einfacher Integer-Division?" schon klar genug 
beschrieben zu haben.

Inzwischen habe ich eine praktikable Variante gefunden, um das Problem 
zu umgehen: den Divisor
    ( F_CPU / (PRESCALER_VALUE) / 1000 ) in eine C-Konstante
zu packen.

Der Compiler erzeugt dann Code ohne call von __mulsi3, so wie er es auch 
macht, wenn da eine 2000 als Literal steht. Es läuft dann alles wie man 
es halt erwartet und auch diese mysteriöse Abhängigkeit von der 
Code-Reihenfolge löst sich in Luft auf.

Bleibt trotzdem der bittere Nachgeschmack, was da eigentlich passiert 
ist. An einer Aufklärung der Sache bin ich von daher immer noch sehr 
interessiert ;) Wenn jemand den Code in bestimmter Weise 
zurechtgestutzt/aufbereitet haben möchte, um damit dann bei der 
Fehlersuche zu helfen, mache ich das gerne.

Grüße, Mr. Ratlos^0

von Karl H. (kbuchegg)


Lesenswert?

Zeig halt mal den kompletten C-Source Code.

Auch wäre es hilfreich, mal ein paar Werte für ICR1 zu kennen. Dann 
könnte man den Code auch mal auf potentielle arithmetische Overflows 
abklappern.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Karl Heinz schrieb:
> Zeig halt mal den kompletten C-Source Code.
Beitrag "Re: Fehler bei einfacher Integer-Division?"

von Karl H. (kbuchegg)


Lesenswert?

Ach da oben ist er.
Ich hab nur das untere Zip-File gesehen, in dem ein Dissasembler Listing 
drinnen ist.

von Ralph K. (rosti_mcn)


Lesenswert?

Ich mach den Code mal hübsch (werfe das ganze Debugging-Gedöns raus) und 
stell ihn nochmal hier ein. Die hier als erstes gepostete Variante 
glänzt halt nicht gerade mit Übersichtlichkeit. So in einer Stunde 
etwa...

von (prx) A. K. (prx)


Lesenswert?

Ralph K. schrieb:
> Ich mach den Code mal hübsch (werfe das ganze Debugging-Gedöns raus) und
> stell ihn nochmal hier ein.

So weit reduzieren, dass das Problem gerade noch auftritt, aber nichts 
Unnötiges mehr drin ist.

von Ralph K. (rosti_mcn)


Lesenswert?

Ich geb mir Mühe - könnte dann aber etwas länger dauern.

von lalala (Gast)


Lesenswert?

Ralph K. schrieb:
> Ich mach den Code mal hübsch (werfe das ganze Debugging-Gedöns
> raus) und
> stell ihn nochmal hier ein. Die hier als erstes gepostete Variante
> glänzt halt nicht gerade mit Übersichtlichkeit. So in einer Stunde
> etwa...

Schön. Bis jetzt nur 1 Sache (aber bei mir ist die Formatierung auch 
nicht gut, d.h. bin nicht sicher):

- iByte = (iEdge-5)  2  8; Ist getestet dass IEdge>=5 und <=84?

Ersetz auch mal
if (iEdge == 83)
durch
if (iEdge >= 83)

von lalala (Gast)


Lesenswert?

und:
int8_t get_humidity( void)
  {
    if ( dataIsValid )
      return data[0];
    else
      return -128;
         }

ist eine Konversion von data[0] nach int8_t. Ist es klar und getestet, 
dass data[0] nur von 0 bis 127 geht?

von Ralph K. (rosti_mcn)


Lesenswert?

Danke für das Drüberschauen und die Tipps.

lalala schrieb:
> iByte = (iEdge-5)  2  8; Ist getestet dass IEdge>=5 und <=84?

Ja, das ist sicher.

> Ersetz auch mal
> if (iEdge == 83)
> durch
> if (iEdge >= 83)

Definitiv unnötig.

> ist eine Konversion von data[0] nach int8_t, Ist es klar und getestet,
> dass data[0] nur von 0 bis 127 geht?

Auch das ist vom Wertebereich her korrekt, wobei es im Falle von 
get_humidity() ein klarer Schönheitsfehler ist, schließlich ist die 
Luftfeuchtigkeit positiv definit.

Bin dann auch gleich fertig mit dem Code säubern...
Bis gleich

von Ralph K. (rosti_mcn)



Lesenswert?

Hier kommt Code - ohne Helm und ohne Mode :)

Ich habe mich entschieden, funktionell alles unverändert zu lassen. So 
lang ist der Quelltext ja nicht.

Für die Beschreibung des Problems bitte ich Euch, den Thread nochmal zu 
überfliegen. Fakt ist:
   Zeile 169 im Code aktiv -> der Wert von current_time ist nach allen 
Regeln des heuristischen Suchens = 0
   Zeile 169 auskommentiert (168 übernimmt) -> alles bestens.

Wenn noch was benötigt wird, nur zu, ich stehe zu Euren Diensten :D

//---------------------------------------------------------------------

Ich habe gerade noch den Quelltext von 4er-Tabs befreit, somit sollte 
ihn jeder angenehm lesen können. Siehe zweite Datei im Anhang.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wird die Division vom Compiler alss Division übersetzt oder als 
Multiplikation (i.W. mit dem Kehrwert) ?

von Ralph K. (rosti_mcn)


Angehängte Dateien:

Lesenswert?

Assembler ist nicht gerade meine Spezialität ;)

Anbei die disassemblierte ELF-Datei in der Variante mit der 
fehlerverursachenden Code-Zeile. Im Listing wird es in Zeile 346 
interessant.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Soweit ich das sehe wird folgender Code erzeugt:

1000 * x / 2000, wobei zunächst die Multiplikation als 16u*16u -> 32u 
ausgeführt wird und danach eine 32-Bit unsigned Division.

Das ist doch das, was du willst?

Wie ich am Disassembly sehe verwendest du eine recht neue Version von 
avr-gcc.  Welche genau?

Ändert sich das Verhalten mit -fwrapv ? mit -fno-strict-aliasing?

Die Positionierung von memset() im Disassembly von acquire_DHT() ist 
unklar, entspricht das er erwarteten Stelle? Hier scheint der Compiler 
den Code rumzuschieben, was zusammen mit dem weggecasteten volatile 
evtl. zu Problemen führt (evtl. auch nur dann, wenn sich an ganz 
anderer Stelle im Code was ändert).

von Ralph K. (rosti_mcn)


Lesenswert?

Johann L. schrieb:
> 1000 * x / 2000, wobei zunächst die Multiplikation als 16u*16u -> 32u
> ausgeführt wird und danach eine 32-Bit unsigned Division.
>
> Das ist doch das, was du willst?

Ja, genau das sollte passieren. Nur sollte nach der Zuweisung an die 
Variable current_time ein Zugriff darauf auch diesen Wert liefern.

> Wie ich am Disassembly sehe verwendest du eine recht neue Version von
> avr-gcc.  Welche genau?

avr-gcc (AVR_8_bit_GNU_Toolchain_3.4.3_1072) 4.8.1

Hatte bis vor kurzem noch die WinAVR2010-Installation, aber um zu 
prüfen, ob da möglicherweise die Probleme liegen, habe ich auf die 
gcc/libc aus der aktuellen ATMEL Toolchain gewechselt.

> Ändert sich das Verhalten mit -fwrapv ? mit -fno-strict-aliasing?

Sorry, aber heute kann ich das nicht mehr überprüfen, klappt vielleicht 
morgen. Ich melde mich wieder, sobald ich was habe.

Johann L. schrieb:
> Die Positionierung von memset() im Disassembly von acquire_DHT() ist
> unklar, entspricht das er erwarteten Stelle? Hier scheint der Compiler
> den Code rumzuschieben, was zusammen mit dem weggecasteten volatile
> evtl. zu Problemen führt (evtl. auch nur dann, wenn sich an ganz
> anderer Stelle im Code was ändert).

Ich glaube nicht, dass hier das Problem liegt. Zum einen scheint mir das 
Assemblerlisting da gut auszusehen. Zum anderen hatte ich schon mal 
testweise das memset() durch explizite Zuweisungen ersetzt ohne dass 
sich dadurch am Fehlerverhalten etwas geändert hatte. Zum anderen: dass 
data[] als volatile deklariert ist, ist ein historisches Überbleibsel 
und insofern ohne Belang, als das es keine Zugriffe auf das array aus 
einer ISR mehr gibt. Ich werde die Deklaration und das memset trotzdem 
noch mal ersetzen, bei so einer Suche im Dunkeln ist jede noch so kleine 
Unklarheit möglicherweise eine zuviel.

von Ralph K. (rosti_mcn)


Lesenswert?

Johann L. schrieb:
> Ändert sich das Verhalten mit -fwrapv ? mit -fno-strict-aliasing?

Nein, es ändert sich nicht.

Ich kann's kaum glauben, dass das Problem so ungewöhnlich ist, dass 
niemand eine Lösung/Antwort parat hat. Nun gut, wenn hier keine Ideen 
mehr kommen, lege ich das Problem vorerst zu den Akten - bis ich 
fließend Assembler lesen kann :)

von lalala (Gast)


Lesenswert?

Ralph K. schrieb:
> Johann L. schrieb:
>> Ändert sich das Verhalten mit -fwrapv ? mit -fno-strict-aliasing?
>
> Nein, es ändert sich nicht.
>
> Ich kann's kaum glauben, dass das Problem so ungewöhnlich ist, dass
> niemand eine Lösung/Antwort parat hat. Nun gut, wenn hier keine Ideen
> mehr kommen, lege ich das Problem vorerst zu den Akten - bis ich
> fließend Assembler lesen kann :)

Wie überprüfst Du genau, dass der Wert verkehrt ist? Der Fehler liegt 
vermutlich nicht an der von Dir vermuteten Stelle.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wenn du sicher bist, daß du die böse Zeile gefunden hast, kannst du ja 
für beide Varianten Code erzeugen und miteinander vergleichen.

von Ralph K. (rosti_mcn)


Lesenswert?

lalala schrieb:
> Wie überprüfst Du genau, dass der Wert verkehrt ist? Der Fehler liegt
> vermutlich nicht an der von Dir vermuteten Stelle.

Zum einen (schnelle Prüfung):
Durch Prüfen von data[] nach Ende der Schleife. Stimmt der Wert des in 
Frage stehenden Teilers (bzw. das Ergebnis der Division) nicht, dann 
stimmt das dadurch bestimmte Timing des Sensorsignal-Lesens nicht, die 
Schleife bricht ab und liefert (quasi als Error-Flag) ein ungültiges 
Ergebnis.

Zum anderen (DEBUG-Variante):
Durch eine Speicherung von current_time zu jedem Schleifendurchlauf in 
einem Array, welches dann (wegen des möglicherweise zeitkritischen 
Verhaltens der Schleife) nach Schleifenende per UART ausgegeben wird.

Johann L. schrieb:
> Wenn du sicher bist, daß du die böse Zeile gefunden hast, kannst du ja
> für beide Varianten Code erzeugen und miteinander vergleichen.

Mache ich heute abend und stelle es hier mit rein.

Noch eine Frage dazu: wie sollte ich idealerweise das Assemblerlisting 
erzeugen, damit beide Varianten gut diffbar sind?

: Bearbeitet durch User
von lalala (Gast)


Lesenswert?

Schmeiss doch mal alles Schreibzugriffe auf Array raus. I.e. memset, und 
data[..] mal auskommentieren.

von Ralph K. (rosti_mcn)


Angehängte Dateien:

Lesenswert?

Johann L. schrieb:
> Wenn du sicher bist, daß du die böse Zeile gefunden hast, kannst du ja
> für beide Varianten Code erzeugen und miteinander vergleichen.
lalala schrieb:
> Schmeiss doch mal alles Schreibzugriffe auf Array raus. I.e. memset, und
> data[..] mal auskommentieren.

Anbei die Listings. Das Array ist raus.

Hier schon mal ein Auszug.

<-------------------- okay -------------------->
current_time = 1000L * ICR1 / CONST_DIVISOR;
 1fe:  80 91 86 00   lds  r24, 0x0086
 202:  90 91 87 00   lds  r25, 0x0087
 206:  a0 e0         ldi  r26, 0x00  ; 0
 208:  b0 e0         ldi  r27, 0x00  ; 0
 20a:  b5 95         asr  r27
 20c:  a7 95         ror  r26
 20e:  97 95         ror  r25
 210:  87 95         ror  r24

<-------------------- BÖSER FEHLER -------------------->
current_time = 1000L * ICR1 / var_divisor;
 202:  20 91 86 00   lds  r18, 0x0086
 206:  30 91 87 00   lds  r19, 0x0087
 20a:  49 81         ldd  r20, Y+1  ; 0x01
 20c:  5a 81         ldd  r21, Y+2  ; 0x02
 20e:  a8 ee         ldi  r26, 0xE8  ; 232
 210:  b3 e0         ldi  r27, 0x03  ; 3
 212:  0e 94 13 06   call  0xc26  ; 0xc26 <__umulhisi3>
 216:  9a 01         movw  r18, r20
 218:  40 e0         ldi  r20, 0x00  ; 0
 21a:  50 e0         ldi  r21, 0x00  ; 0
 21c:  0e 94 f4 05   call  0xbe8  ; 0xbe8 <__divmodsi4>

Hab aber keine Ahnung, ob genau hier das Problem liegt oder nicht.
Seht ihr irgendwas Interessantes?

von lalala (Gast)


Lesenswert?

Und der Fehler tritt noch auf? Welcher Wert kommt denn heraus?

von lalala (Gast)


Lesenswert?

Nächster Schuss ins blaue: vielleicht verbraucht die Fehlerhafte Version 
zu viel Zeit. Test: nimm die ok Fassung rechne noch eine andere Variable 
aus (schau dass das nicht wegoptimiert wirs)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Das ist einfach zu viel Code, 1000 Zeilen Disassembly, sorry, das wird 
sich hier keiner anschauen.

Und oben war der Code noch ganz anders, dort wurde durch 2000 geteilt 
und jetzt durch eine Variable, die 2000(?) enthält.  Was denn nun?

Schreib mal ein kleines Testprogramm, einmal mit Version A und einmal 
mit Version B, z.b.
1
volatile unsigned X;
2
3
// Test A
4
unsigned __attribute__((noinline,noclone)) test_A (void)
5
{
6
    return 1000L * X / 2000;
7
}
8
9
// Test B
10
...

Und lasse das für verschiedene X durchlaufen und vergleiche die 
Ergebnisse. Kommt das gleiche raus?

Den Fehler innerhalt 1000 anderer Zeilen zu suchen ist nicht so 
sinnvoll, zumindest wenn du dir sicher bist, daß die Division der 
Übeltäter ist.

Und wie zeitkritisch ist all das?  Stört es denn garnicht, wenn die 
Division ewig Zeit verbraucht? test_A oben brauch rund 20 Ticks, test_B 
locker mehr als 500.

Du kannst auch die test_A und test_B in dein Programm einbauen, kommt 
der Fehler dann immer nocht? Dann ist es ein Timing-Problem. Kommt er 
nicht, steht der Aufruf von test_B wohl an anderer Stelle als die 
Division, nämlich dort, wo auch test_A aufgerufen wird.

Und wie ich bereits oben schrieb, befindet sich die Division im asm an 
anderer Stelle, als der C-Code vermuten lässt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

...und falls es dich beruhigt, ich habe folgendes Programm
1
volatile unsigned X;
2
3
// Test A
4
unsigned __attribute__((noinline,noclone))
5
test_A (void)
6
{
7
    return 1000L * X / 2000;
8
}
9
10
// Test B
11
unsigned __attribute__((noinline,noclone))
12
test_B (void)
13
{
14
    volatile int d = 2000;
15
    return 1000L * X / d;
16
}
17
18
#include <stdio.h>
19
20
int main (void)
21
{
22
    printf ("Start\n");
23
    do
24
    {
25
        if (test_A() != test_B())
26
        {
27
            printf ("X = %u\n", X);
28
        }
29
    } while (++X);
30
31
    printf ("Ready\n");
32
    return 0;
33
}

mit avrtest durchnudeln lassen für

* -O0
* -O1
* -O2
* -Os

für folgende Compiler

* avr-gcc 4.7.2
* avr-gcc 4.8.2
* WinAVR-20100110 (avr-gcc 4.3.3)

Nun rate mal, was das Ergebnis war! Genau: Die Ausgabe war immer

Start
Ready

[Geklimper von avrtest]

von Ralph K. (rosti_mcn)


Lesenswert?

Vielen Dank Johann.

Johann L. schrieb:
> Und wie zeitkritisch ist all das?  Stört es denn garnicht, wenn die
> Division ewig Zeit verbraucht? test_A oben brauch rund 20 Ticks, test_B
> locker mehr als 500.

500 :-O - das ist zu viel! Die Division+drumrum hat gesichert 
16MHz*20us=320 Takte Zeit, alles drüber kann (und ab 400 wird) zu einem 
Fehler führen.

Das einzige was mich dann noch wundert ist, dass meine Debugausgabe im 
Fehlerfall - zumindest immer wenn ich das überprüft hatte - 'ne saubere 
Null für das Ergebnis der Division geliefert hatte. Aber das schaue ich 
mir noch mal im Detail an.

Dann werde ich mich also mal an meine Hausaufgaben machen...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Mal nachgemessen für X=1000:

Selbst bei optimiertem Code (Verwendung von __umulhisi3) braucht 
Variante test_B 1333 Ticks mehr als Variante test_A!  Die Division 
wird ja mit 32 Bits durchgeführt.  Aber selbst mit 16 Bits wäre es immer 
noch ziemlich langsam!

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.