Forum: Mikrocontroller und Digitale Elektronik Zeilenweise keine Optimierung AVr Studio


von Nils (Gast)


Lesenswert?

Hallo,

es gibt doch sicher die Möglichkeit im Avr Studio zu sagen, dass 
bestimmte Zeiöen nicht wegoptimiert werden sollen. Z.b. möchte ich das 
folgende Zeilen tatsächlch so ausgeführt wird, wie ich es geschrieben 
habe:

x = y;
z = x;

mfg

von Floh (Gast)


Lesenswert?

Warum sollte der C-Compiler da was wegoptimieren?
x und z erhalten beide den Wert von y.

von (prx) A. K. (prx)


Lesenswert?

Es gibt Wege und Tricks, wie man bestimmte Optimierungen gezielt 
verhindet. Beispielsweise indem die Variablen volatile sind. Andere 
Tipps setzen einen grösseren Kontext voraus.

Ausserdem wäre noch offen, ob du grad die richtige Frage stellst, oder 
schon ein Weilchen auf dem Holzweg unterwegs bist und an der falschen 
Stelle fragst.

von Nils (Gast)


Lesenswert?

Floh schrieb:
> Warum sollte der C-Compiler da was wegoptimieren?
> x und z erhalten beide den Wert von y.

Wahrscheinlöich, weil ich x nur an dieser Stelle verwende und sonst 
nirgends im Code.

von Nils (Gast)


Lesenswert?

A. K. schrieb:
> Ausserdem wäre noch offen, ob du grad die richtige Frage stellst, oder
> schon ein Weilchen auf dem Holzweg unterwegs bist und an der falschen
> Stelle fragst.

Es handelt sich hier um eine Zeitkritische verarbeitung. Die wirklichen 
Zeilen in meinem Code lauten:
1
value = TCNT1;
2
capture._int[x*2] = value;  //Dieser Code verbraucht ca. 30 byte mehr als der obere, deutlich länger als der obere Code.

Ich möchte erst den zeitkritischen Transfer in value machen, dann den 
Zeitaufwendigerendarunter.

von (prx) A. K. (prx)


Lesenswert?

So wird das nix. Aber vielleicht meist du sowas:
Beitrag "GCC Optimierer zerlegt Block (compound-statement)"

von Peter D. (peda)


Lesenswert?

Schreib mal die Variablendefinitionen dazu, daß man das übersetzen und 
angucken kann.


Peter

von Nils (Gast)


Lesenswert?

1
[...]
2
  unsigned int value;
3
  
4
  union{
5
    unsigned char _char[NULL_N*4];
6
    unsigned int _int[NULL_N*2];
7
    unsigned long _long[NULL_N];
8
  }capture;
9
[...]
10
  capture._int[x*2] = TCNT1;
11
  caapture._int[x*2+1] = (int)overflow;
12
[...]


capture._int[x*2] = TCNT1; Diese Zeile dauert viel zu lange. 30 Bytes 
(warum soviel?).

von Peter D. (peda)


Lesenswert?

1
void test( uint8_t x )
2
{
3
  capture._int[x*2] = TCNT1;
4
  82:   2c b5           in      r18, 0x2c       ; 44
5
  84:   3d b5           in      r19, 0x2d       ; 45
6
  86:   e8 2f           mov     r30, r24
7
  88:   f0 e0           ldi     r31, 0x00       ; 0
8
  8a:   ee 0f           add     r30, r30
9
  8c:   ff 1f           adc     r31, r31
10
  8e:   ee 0f           add     r30, r30
11
  90:   ff 1f           adc     r31, r31
12
  92:   e0 5a           subi    r30, 0xA0       ; 160
13
  94:   ff 4f           sbci    r31, 0xFF       ; 255
14
  96:   31 83           std     Z+1, r19        ; 0x01
15
  98:   20 83           st      Z, r18
16
}
17
  9a:   08 95           ret

TCNT1 wird doch zuerst eingelesen.
Und der Rest ist eben den Index aufdröseln. Das geht nicht besser.

30 Byte = 15 Befehle ist fürn nen RISC garnichts.
Besser wirds nur mit nem CISC, der entsprechende Adressierungsbefehle 
hat.


Peter

von (prx) A. K. (prx)


Lesenswert?

Es kann passieren, dass eine vorherige Berechnung von x erst an dieser 
Stelle durchgeführt wird. Das entspricht dann exakt dem Problem im oben 
angeführten Thread.

Abhilfe wäre dann wie ebendort:
  #define barrier(var) asm volatile ("" : "+r"(var))
  ...
  barrier(x);
  capture._int[x*2] = TCNT1;

Effektiver wäre aber wohl sowas wie
  unsigned *p = &capture._int[x*2];
  barrier(p);
  p[0] = TCNT1;
  p[1] = (int)overflow;
weil dann die Adressrechnung ebenfalls vorgezogen wird.

von Nils (Gast)


Lesenswert?

ALso es geht um Frequenzmessung. Wenn ich 55 Hz erzeuge messe ich ca. 
55,01.. Hz und bei 4000HZ messe ich 4100.

Der Timerwert spiegelt die Frequenz wieder. Es scheint ein konstanter 
Verarbeitungswert des Timerwertes um 30-40 Taktzyklen vorzuliegen, 
welcher den Timerwert verfälscht. Bei tiefen Frequenzen sind diese +30 
kaum erwähnenswert, da dem gegenüber ein Timerwert von 100.000+ steht. 
Bei sehr hohen Frequenzen liegt allerdings ein Timerwert von 4000- vor, 
da sind dann diese 30 Taktzyklen verarbeitungszeit katastrophal.

Also vom feststellen, dass ein Nulldurchgang passiert ist, bis zu dem 
Zeitpunkt, wo der Timerwert abgeseichert ist vergeht zuviel Zeit. Die 
Schleife, ob ein Nulldurchgang passiert braucht ca. 14 Byte. Das kann 
natürlich auch die Fehlerquelle sein.

Ich hab mal einen schnelleren Quarz verwendet und schon ist der Fehler 
deutlich geringer. Bin aber schon bei 16Mhz.

von Ingo (Gast)


Lesenswert?

Dann nimm den ICP, dafür ist der da...


Ingo

von Nils (Gast)


Lesenswert?

Ich verwende jetzt mal den ICP, kriege aber identische Werte heraus.
Der Entsprechende Code sieht ungefähr so aus:
1
for(x = 0; x < NULL_N ; x++){    //N Nulldurchgänge messen  
2
  if(TIFR & (1<<ICF1)) TIFR |= (1<<ICF1); 
3
  TCNT1 = 0;        
4
  overflow = 0;
5
  if(TIFR & (1<<TOV1)) TIFR |= (1<<TOV1);
6
7
  do{    
8
    if(TIFR & (1<<TOV1)){    
9
      overflow++;      
10
      TIFR |= (1<<TOV1);
11
      if(overflow > 4) break;
12
    }
13
  }while(!(TIFR & (1<<ICF1)));
14
15
  capture._int[x*2] = ICR1;
16
  capture._int[x*2+1] = (int)overflow;  
17
18
  if(overflow > 4) break;
19
}

Kann vielleicht die Software die ich verwende doch ungenaue Frequenzen 
erzeugen? Oder habe ich bei der Konfiguration des ICP etwas verkehr 
tgemacht? Ich benutze den Analog Komparator als Trigger.

von Nils (Gast)


Lesenswert?

Pardon. Ich messe bei 4000Hz nun 4030Hz, vorher waren es 4100Hz.

Noch mehr Tipps`?

von Peter D. (peda)


Lesenswert?

Nils schrieb:
> if(TIFR & (1<<ICF1)) TIFR |= (1<<ICF1);
>   TCNT1 = 0;

Damit holst Du doch wieder nen Jitter rein.
Du mußt den Timer durchlaufen lassen und die Differenz von einem zum 
nächsten ICP nehmen.


Peter

von Nils (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Damit holst Du doch wieder nen Jitter rein.
> Du mußt den Timer durchlaufen lassen und die Differenz von einem zum
> nächsten ICP nehmen.

Natürlich kostet das Zeit, aber diese Befehle werden bei einer maximal 
zu messenden Frequenz von ca. 4400Hz doch locker innherlab einer halben 
Periode durchgeführt.

Also erst die neue Messung initialisieren, dann warten, bis das Flag vom 
ICP gesetzt wird und darauf reagieren. Bis das Warten auf das Flag 
beginnt ist doch bei weitem noch keine halbe Periode vergangen. Nach dem 
Warten wird ebenfalls wieder keine halbe Periode vergehen, bis wann auf 
den nächsten Nulldurchgang wartet.

Bzw. da der ICP auf steigende oder fallende Flanke reagiert und nicht 
auf einen Flankenwechsel, kann man nur ganze Perioden messen oder? So 
hab ich es eben gemacht.

von (prx) A. K. (prx)


Lesenswert?

Nils schrieb:

> Natürlich kostet das Zeit, aber diese Befehle werden bei einer maximal
> zu messenden Frequenz von ca. 4400Hz doch locker innherlab einer halben
> Periode durchgeführt.

Es bringt nichts, den Timer am Ende der Messzeit per Capture taktgenau 
einzufangen, aber dessen Anfangswert per Software mit Jitter zu setzen. 
Das kann sogar weniger genau sein als zweimal per Software.

Da du den Timer nicht per Capture-Event auf 0 setzen kannst, musst du 
statt dessen die Differenz zwischen zwei Captures verwenden. Nur so wird 
das taktgenau. Der Anfang der Messung ist genauso wichtig wie das Ende.

von Karl H. (kbuchegg)


Lesenswert?

Nils schrieb:
> Ich verwende jetzt mal den ICP, kriege aber identische Werte heraus.
> Der Entsprechende Code sieht ungefähr so aus:
>
> [c]for(x = 0; x < NULL_N ; x++){    //N Nulldurchgänge messen
>   if(TIFR & (1<<ICF1)) TIFR |= (1<<ICF1);
>   TCNT1 = 0;
>   overflow = 0;
>   if(TIFR & (1<<TOV1)) TIFR |= (1<<TOV1);


Hä?

Gemeint war: verwende den Input Capture Interrupt in Form einer ISR! 
Hier hast du doch schon wieder Latenzen.

es gibt doch wirklich genügend Frequenzzähler-Beiträge hier im Forum und 
in der Codesammlung. Mal ansehen wie andere das machen?

von Nils (Gast)


Lesenswert?

A. K. schrieb:
> Da du den Timer nicht per Capture-Event auf 0 setzen kannst, musst du
> statt dessen die Differenz zwischen zwei Captures verwenden. Nur so wird
> das taktgenau. Der Anfang der Messung ist genauso wichtig wie das Ende.

Danke, das habe ich verstanden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Nils schrieb:
> Danke, das habe ich verstanden.

Jetzt musst du nur noch verstehen, dass man zwei uint16_t-Werte
(aus dem ICRx) unabhängig von ihrer aktuellen Größe einfach und
bedenkenlos voneinander subtrahieren kann, solange man sich
sicher ist, dass zwischen beiden Werte höchstens ein timer overflow
dazwischen liegt.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:

> bedenkenlos voneinander subtrahieren kann, solange man sich
> sicher ist, dass zwischen beiden Werte höchstens ein timer overflow
> dazwischen liegt.

Und genau da liegt bei ihm der Hase im Pfeffer, denn der Code zählt auch 
die Overflows mit, weil er sonst offenbar mit dem Frequenzbereich nicht 
klar kommt. Das geht zwar, aber weil da hässliche Gleichzeitigkeiten 
auftreten können muss man deutlich Hirnschmalz in eine Korrektur 
investieren, um nicht ab und zu Fehlmessungen zu bekommen.

Weniger anstrengend für die grauen Zellen: den Prescaler adaptiv 
einstellen. Wenn mehr als ein Overflow reinrutscht, dann war der 
Prescaler zu klein, also vergrössern und nochmal messen. Wenn die 
gemessene Differenz zu klein ist, dann war der Prescaler zu gross.

von Peter D. (peda)


Lesenswert?

Beitrag "AVR Timer mit 32 Bit"

Das gleiche Prinzip geht natürlich auch für 16Bit-Timer und ICP.


Peter

von Nils (Gast)


Lesenswert?

A. K. schrieb:
> Und genau da liegt bei ihm der Hase im Pfeffer, denn der Code zählt auch
> die Overflows mit, weil er sonst offenbar mit dem Frequenzbereich nicht
> klar kommt. Das geht zwar, aber weil da hässliche Gleichzeitigkeiten
> auftreten können muss man deutlich Hirnschmalz in eine Korrektur
> investieren, um nicht ab und zu Fehlmessungen zu bekommen.
>
> Weniger anstrengend für die grauen Zellen: den Prescaler adaptiv
> einstellen. Wenn mehr als ein Overflow reinrutscht, dann war der
> Prescaler zu klein, also vergrössern und nochmal messen. Wenn die
> gemessene Differenz zu klein ist, dann war der Prescaler zu gross.

So ist es. Das mit dem Prescaler wechseln, auf die IDee bin ich bis 
jetzt noch nicht gekommen. Ich habe den Code jetzt soweit verbessert, 
ohne ICP, dass ich bei 4000Hz ein Pendeln zwischen 4011-4018 angezeigt 
bekomme.

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.