[C++] Code-Optimierung auf AVR

OP Persönliche Seite #3940344
Lesenswert?

Hallo Forum,

ich habe hier diese "kleine" Interrupt-Routine.
Der Code läuft auf einem ATmega2560. Compiler ist der avr-g++ (also C++ 
code) mit -Os.
1
#define US_TIMER_REGISTER GPIOR0 //general purpose register
2
volatile uint16_t usVal[7];
3

4
ISR(PCINT1_vect) {
5
  static bool update[7];
6
  static uint16_t timer[7];
7
  for (uint8_t i = 0; i < 7; i++) {
8
    if (PINJ & (1 << i)) {
9
      if (update[i] == false)
10
        timer[i] = US_TIMER_REGISTER;
11
      update[i] = true;
12
    } else if (!(PINJ & (1 << i))) {
13
      if (update[i] == true)
14
        usVal[i] = US_TIMER_REGISTER - timer[i];
15
      update[i] = false;
16
    }
17
  }
18
}
leider wird dieser Code vom Compiler aufgebläht zu einem Code von fast 
200 Takten. Ich denke das das etwas zu lang für eine ISR ist.
Anbei findet ihr das Assembler-Resultat.

Falls Infos fehlen bitte fragen, ich habe garantiert etwas vergessen ;)

Nun zu meiner Frage:
Hat jemand Ideen für Optimierung(auf C++ und Assembler-Ebene)?

vielen Dank schon mal
N.G.
Angehängte Dateien:
Gast #3940356
Lesenswert?

ich versteht nicht was der code machen soll, aber selbst in C kann man 
noch einiges optimieren.

> if (PINJ & (1 << i)) {
das ist schon mal sehr langsam auf einen Atmel. Vermeide das schieben 
mit einer Variable.

> if (!(PINJ & (1 << i)))
wozu die 2.abfrage? das bit kann doch nur 0 oder 1 sein, damit reicht 
das else

1
ISR(PCINT1_vect) {
2
  static bool update[7];
3
  static uint16_t timer[7];
4

5
  uint8_t tmp = PINJ;
6
  for (uint8_t i = 0; i < 7; i++) {
7
    if (tmp & 1) {
8
      if (update[i] == false)
9
        timer[i] = US_TIMER_REGISTER;
10
      update[i] = true;
11
    } else {
12
      if (update[i] == true)
13
        usVal[i] = US_TIMER_REGISTER - timer[i];
14
      update[i] = false;
15
    }
16
    tmp = tmp >> 1;
17
  }
18
}
OP Persönliche Seite #3940370
Lesenswert?

A. K. schrieb:
> N. G. schrieb:
>>     if (PINJ & (1 << i)) {
>
> Shifts mit variabler Anzahl sind höchst ungünstig. Eine sukzessiv
> verschobene Maske mitzuführen ist billiger.

Hallo A.K.
ja, stimmt, das wusste ich (eigentlich). Habe aber gehofft, dass der AVR 
mir das wegoptimiert. Naja, ist halt doch kien Wunderding der GCC ;)

Peter II schrieb:
>> if (!(PINJ & (1 << i)))
> wozu die 2.abfrage? das bit kann doch nur 0 oder 1 sein, damit reicht
> das else

Hallo Peter II,
ja, da hast du vollkommen Recht, ist sinnlos.
Danke für deinen Code, das spart schon etwa 40 Takte.
Aber leider immer noch zu wenig :(

noch jemand Ideen?
Gast #3940381
Lesenswert?

N. G. schrieb:
> noch jemand Ideen?

ja. Das sieht mir doch stark nach ein soft-PWM aus.

Kannst du die Werte nicht schon vorher berechnen? Warum müssen sie 
ständig neu berechnet werden?

Damit musst du nur eine Zuweisung in der ISR machen.

Kannst du das Problem etwas genauer beschreiben, was der code macht?
OP Persönliche Seite #3940389
Lesenswert?

Peter II schrieb:
> ja. Das sieht mir doch stark nach ein soft-PWM aus.
>
> Kannst du die Werte nicht schon vorher berechnen? Warum müssen sie
> ständig neu berechnet werden?
>
> Damit musst du nur eine Zuweisung in der ISR machen.
>
> Kannst du das Problem etwas genauer beschreiben, was der code macht?

Nein, leider nicht.

Zum Code:
also es geht um 7 Sensoren des Typs SRF05. Diese werden mit einer 
Leitung ausgelesen.
Also so grob:
eine andere Timer ISR schickt zyklisch den Auslese-Befehl.
Die Sensoren hängen alle an dem selben Pin-Change-Interrupt.
also muss man folgendes tun:
wenn die ISR aufgerufen wird muss geprüft werden welcher Pin gewechselt 
hat und welchen Zustand er vorher hatte. Wenn er auf high gewechselt ist 
dann speichert man den Timerstand eines Timers der mit 58us läuft 
(dieses US_TIMER_REGISTER).
Wenn es aber auf low gewechselt ist, dann ist die Messung fertig und das 
Ergebnis muss nur in dem usVal-Array gespeichert werden.

Soweit der Code.
OP Persönliche Seite #3940405
Lesenswert?

A. K. schrieb:
> Und weshalb darf das keine 10µs dauern?

Berechtigte Frage.
Leider müssen "gleichzeitig" noch X andere Dinge erledigt werden: eine 
Soft-PWM für 4 Motoren (das Layout war falsch; es wurden nicht die Pins 
fürs Hardware-PWM verwendet), ein bisschen I2C hier, ein bisschen UART 
da, dann noch eine Software-Lösung für einen Parallel-Bus, SPI, ...
Es summiert sich auf.
Alles was geht wird interruptgesteuert erledigt, aber in der 
main-Schleife wird immer noch viel gerechnet.
Der AVR langweilt sich auf jeden Fall nicht ;)
#3940417
Lesenswert?

@ N. G. (newgeneration)

>wenn die ISR aufgerufen wird muss geprüft werden welcher Pin gewechselt
>hat und welchen Zustand er vorher hatte. Wenn er auf high gewechselt ist
>dann speichert man den Timerstand eines Timers der mit 58us läuft
>(dieses US_TIMER_REGISTER).

Das mit der Maske wurde ja schon gesagt.

https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Schiebeoperationen

Was aber auch recht gefährlich ist, sind deine if() else Konstruktionen 
mit nhur einer Anweisung ohne Klammern. Jaja, das ist gültiges C, aber 
man schießt sich dabei gern mal ins Knie. Man sollte IMO IMMER Klammern 
setzen.
Ausserdem sollte man nur EINMAL auf PINJ zugreifen. Nicht nur wegen der 
Geschwindigkeit, sondern auch wegen der Möglichkeit, dass sich während 
des Schleifendurchlaufs einzelne Pins ändern.
1
#define US_TIMER_REGISTER GPIOR0 //general purpose register
2
volatile uint16_t usVal[7];
3

4
ISR(PCINT1_vect) {
5
  static bool update[7];
6
  static uint16_t timer[7];
7
  uint8_t mask, i, j;
8
  
9
  j = PINJ;
10

11
  for (i = 0, mask=1; i < 7; i++, mask<<=1;) {
12
    if (j & mask) {
13
      if (update[i] == false) {
14
        timer[i] = US_TIMER_REGISTER;
15
      }
16
      update[i] = true;
17
    } else {
18
      if (update[i] == true) {
19
        usVal[i] = US_TIMER_REGISTER - timer[i];
20
      }
21
      update[i] = false;
22
    }
23
  }
24
}
OP Persönliche Seite #3940418
Lesenswert?

A. K. schrieb:
> N. G. schrieb:
>> Soft-PWM für 4 Motoren
>
> Und zwar mit Mikrosekundenauflösung, nehme ich an. ;-)

Haha, nicht ganz. Aber 10kHz ist Maximum.

>> Der AVR langweilt sich auf jeden Fall nicht ;)
>
> Es gibt Dinge, für die ein AVR etwas zu knapp ist. Andererseits gibt es
> AVRs mit mehr Hardware-PWM als die 08/15 Typen bieten.

Hinterher ist man immer schlauer. Aber leider steht die Hardware schon.
Jetzt muss es halt irgendwie gehen...
#3940420
Lesenswert?

@ N. G. (newgeneration)

>>> Soft-PWM für 4 Motoren

>Haha, nicht ganz. Aber 10kHz ist Maximum.

Dream on. Man kann sicher 10 KHz PWM-Takt (=Interruptfrequenz) 
erreichen, aber nie und nimmer 10 kHz PWM-Frequenz.

Siehe Soft-PWM.

>Hinterher ist man immer schlauer. Aber leider steht die Hardware schon.
>Jetzt muss es halt irgendwie gehen...

Ja und? Nimm einen möglichst pinkompatiblen AVR und zieh ein paar 
Drähte.
OP Persönliche Seite #3940422
Lesenswert?

Falk Brunner schrieb:
> Was aber auch recht gefährlich ist, sind deine if() else Konstruktionen
> mit nhur einer Anweisung ohne Klammern. Jaja, das ist gültiges C, aber
> man schießt sich dabei gern mal ins Knie. Man sollte IMO IMMER Klammern
> setzen.
> Ausserdem sollte man nur EINMAL auf PINJ zugreifen. Nicht nur wegen der
> Geschwindigkeit, sondern auch wegen der Möglichkeit, dass sich während
> des Schleifendurchlaufs einzelne Pins ändern.

Hallo Falk,
okay, da hast du natürlich recht. Beim lesen erkennt man, gerade bei 
schlechter Einrückung, nicht immer direkt was wozu gehört. Werde 
dahingehen meinen Stil ändern.
Das mit dem Portzugriff ist auch wichtig, ich hatte das vollkommen 
übersehen, Danke.
#3940423
Lesenswert?

N. G. schrieb:
> Hinterher ist man immer schlauer. Aber leider steht die Hardware schon.
> Jetzt muss es halt irgendwie gehen...

Tja, jetzt gibts eben einen Wettbewerb. Einer von euch baut neue 
Hardware, der andere lernt Assembler. Wer schneller fertig ist gewinnt. 
Wird die Hardware sein, nehme ich an. ;-)

In gut überlegtem Assembler kann man jeweils ein paar Register für die 
ISRs reservieren, was sowohl hier wie auch bei der PWM deutlich Zeit 
einspart. Aber das geht praktisch nur bei 100% Assembler.
OP Persönliche Seite #3940427
Lesenswert?

Falk Brunner schrieb:
> Ja und? Nimm einen möglichst pinkompatiblen AVR und zieh ein paar
> Drähte.

Gibt leider IMHO keinen. Der ATmega2560 und der ATmega128 sind soweit 
ich weiß der einzige 100pinner die sind Pinkompatibel sind. Aber 
gewonnen hat man dadurch nichts, nur weniger Speicher :(

Den Artikel hab ich schon mal Überflogen, muss ihn jetzt aber mal 
durcharbeiten
OP Persönliche Seite #3940432
Lesenswert?

A. K. schrieb:
> Wird die Hardware sein, nehme ich an. ;-)

Haha, leider ja, aber da fehlt leider das nötige Kleingeld, und auch die 
liebe Zeit. Aber beim Assembler auch :(

Bastler schrieb:
> Mal PeDas Entprellcode (Entprellung) anschauen und abschauen, wie
> mal bis zu 8 Eingansleitungen eines Port gleichzeitig untersuchen kann.
> Deine Anforderung scheint zumindest nicht was völlig Anderes zu sein.

Hallo Bastler,
ja,  das mach ich.
Danke
Gast #3940437
Lesenswert?

ich denke man kann das update Array noch wegoptimieren.

Es wird ja nur gebraucht um ein bit zu speichern. Das sollte sich aber 
alles über bitoperationen mit einem Byte zu schaffen sein.

[c]
ISR(PCINT1_vect) {
  static uint8_t changed;
  static uint16_t timer[7];

  uint8_t value = PINJ;
  changed = changed ^ value;

  uint8_t tmp = 1;
  for (uint8_t i = 0; i < 7; i++) {
    if ( changed & tmp ) {
       if (value & tmp ) {
          timer[i] = US_TIMER_REGISTER;
       } else {
          usVal[i] = US_TIMER_REGISTER - timer[i];
       }
    }
    tmp = tmp << 1;
  }

  changed = value;
}
[c]

so in der art.
#3940458
Lesenswert?

Lässt sich die Aufgabenstellung so umdefinieren, dass nur bei geändertem 
Pinzustand etwas für diesen Sensor getan werden muss? Also für die 
übrigens Pins nichts getan werden muss. Dann gäbe es eine mögliche 
Lösung, als Grundidee:

Per XOR gegenüber Vorzustand die Maske des geänderten Bits ermitteln und 
per 256 Byte Tabelle in eine Bitnummer umsetzen (ein "find first set 
bit" Befehl wär da hilfreich gewesen).

Eine while Schleife verbleibt zwar noch, aber die schlägt nur dann 
mehrfach zu, wenn mehrere Bits gleichzeitig kippten, was die Tabelle 
ebenfalls anzeigen wird.
OP Persönliche Seite #3940461
Lesenswert?

A. K. schrieb:
> Lässt sich die Aufgabenstellung so umdefinieren, dass nur bei geändertem
> Pinzustand etwas für diesen Sensor getan werden muss?

Wenn ich dich richtig verstehe, ja.
Sind 7 unabhängige Signale. Und es kommt immer nur auf die 
Flankenwechsel an.

PS: ich hönge einfach nochmal das Datasheet an, der Modus mit nur einer 
Leitung(Modus 2) wird genutzt.
Angehängte Dateien:
Gast #3940466
Lesenswert?

Oder konkreter:
1
#define US_TIMER_REGISTER GPIOR0 //general purpose register
2
volatile uint16_t usVal[7];
3

4
ISR(PCINT1_vect) {
5
  static uint8_t prev;
6

7
  static uint16_t timer[7];
8
  uint8_t mask, i, j;
9
  
10
  i = PINJ;
11
  j = i^prev;  // j enthält die geänderten Bits
12
  prev = i;    // aktuellen Zustand merken
13

14
  // j    : geänderte Bits
15
  // prev : aktueller Zustand 
16

17
  // und nun (schnell auf'm iPad getippt und sicher nicht fehlerfrei!
18
  for (i = 0, mask=1; i < 7; i++, mask<<=1;) {
19
    if (j & mask) {
20
      if (!(prev & mask)) {
21
        timer[i] = US_TIMER_REGISTER;
22
      } else {
23
        usVal[i] = US_TIMER_REGISTER - timer[i];
24
      }
25
    }
26
  }
27
}
OP Persönliche Seite #3940468
Lesenswert?

Peter II schrieb:
> N. G. schrieb:
>> Danke Peter,
>>
>> werde ich auch noch testen.
>> Leider habe ich die Hardware aktuell nicht bei mir, muss also
>> ausweichen...
>
> Spannend ist erst mal ob der ASM-code kürzer geworden ist.

gegenüber der Methode von Falk: 2 Instruktionen. Ich muss noch 
zusammenrechnen, ob es bei den Takten einen größeren Unterschied macht
#3940474
Lesenswert?

N. G. schrieb:
> PS: ich hönge einfach nochmal das Datasheet an, der Modus mit nur einer
> Leitung(Modus 2) wird genutzt.

So tief gehe ich da nicht rein. Aber wenn im Regelfall nur ein Pin sich 
ändert, dann käme beispielsweise sowas raus:
   static uint8_t vorher;
   uint8_t zustand = PINJ;
   uint8_t maske = zustand ^ vorher;
   vorher = zustand;
   uint8_t nummer = maske2bit[maske];
   if (nummer < 7) {
      // Nur Pin "nummer" hat sich geändert
      ...
   } else {
      // mehrere Pins haben sich geändert, Schleife nötig
      ...
   }
was durchaus geeignet sein könnte, statistisch Zeit einzusparen.
(Firma: CIA) #3940942
Lesenswert?

So seltsamer Code kommt heraus, wenn jemand der in C++ denkt versucht 
einen µC zu programmieren. Auf Multicore Systemen sind solche 
verkorksten Ansätze in der Regel egal. Auf einem µC denket man in 
Assembler und programmiert in C. Wenn man dann vernünftigen C Code hat 
und will diesen Weitergeben oder häufiger wiederverwenden kann man 
diesen in C++ Kapseln.
Gast #3943143
Lesenswert?

eventuell noch die 16bit-rechnung rausziehen aus der ISR
zum kürzen der maximalen Takte innerhalb der ISR.
Ungprüft:
1
ISR(..)
2
{
3
  
4
  static  uint8_t prev =0;
5
  uint8_t pin    ;
6
  uint8_t i,mask,changed ; 
7

8
  Pin      = PINJ;
9
  changed  = pin ^ prev;     
10
  
11
  for(i=0 , mask=1 ; i<7 ; i++, mask<<=1 )
12
  {
13
    if( mask & changed )
14
      if( mask & pin ) //steigende Flanke
15
        tStart[i] = US_TIMER_REGISTER;
16
      else             //fallende Flanke
17
        tStop [i] = US_TIMER_REGISTER;
18
  }
19
  prev = pin;
20
}
21

22
...
23
sonstwo 
24
  dtVal[i] = tStop[i] - tStart[i]
ich gehe mal davon aus, ab und an Murks wegen fehlender
Synchronisation war Dir auch vorher nicht so wichtig..

Benötigst Du denn alle Sensorwerte gleichzeitig?
Wenn nicht, dann trigger sie nacheinander und Du kannst Dir die Schleife 
hier sparen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren