Hallo zusammen,
ich wäre sehr daran interessiert, die folgenden Codefragmente etwas
performanter zu lösen. Die beiden Berechnungen brauchen in der Form wie
ich sie zur Zeit programmiert habe fast 500 us auf einem ATMega8 mit
3,686 MHz Quartz. Kann ich hier evtl. mit geschickten Shift-Operationen
die Sache etwas schneller berechnen? Mir fehlt auf diesem Gebiet
sicherlich einiges an Erfahrung.
...
#define COUNTER_INCS_PER_MILLISECOND_T1 461 // (3.686.400 / 8) / 1000
volatile unsigned long ulAktuelleUmlaufzeitG=0;
volatile unsigned long ulAktuelleDrehzahlG=0;
ISR(INT0_vect)
{
...
ulAktuelleDrehzahlG = 600000UL / (ulAktuelleUmlaufzeitG*10 /
COUNTER_INCS_PER_MILLISECOND_T1);
...
unsigned int uiTableIndex = ulAktuelleDrehzahlG / 50;
...
}
Gruß
Werner
Solltest du noch nicht haben, versuche mal in den Projektoptionen die
Compileroptimierung aufs Höchste zu stellen - was optimierbar ist
(Shifts z.B.) macht der Compiler dann automatisch für dich.
Ansonsten wüsste ich nicht, was man optimieren kann (das macht alles
schon der Compiler). Ich weiß nicht ob es möglich ist, aber kannst du
vielleicht aus den Longs Integers machen? Das würde einiges bringen
(fast die Hälfte, geschätzt). Aber das wird sicherlich nicht gehen,
sonst wären es ja schon Ints :-)
Danke Randy für den Hinweis. Im Makefile benutze ich z.Zt. die folgenden
Einstellungen:
CC = avr-gcc
CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS)
LDFLAGS = -Wl,-Map,$(PRG).map
Ich bin mir aber nicht sicher, ob das schon das Maximum an
Codeoptimierung ist.
Gruß
Werner
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Optimierungsgrad
Ich weiß nicht in welcher Form das jetzt schon in dem Makefile steckt,
aber sowas wie -Os müsste da z.B. noch rein.
Einfach mal die Zeiten für die verschiedenen Optimierungsgrade testen,
wobei der kompakteste Code meistens nicht der Schnellste ist. Kommt
drauf an.
Schreib das ganze doch mal wie folgt, dann sparst du eine Division und
eine Multiplikation:
#define COUNTER_INCS_PER_MILLISECOND_T1 461
#define TIME_TO_RPM_CONST (60000L *
COUNTER_INCS_PER_MILLISECOND_T1) // (600000 *
COUNTER_INCS_PER_MILLISECOND_T1) / 10
ISR(INT0_vect)
{
...
ulAktuelleDrehzahlG = TIME_TO_RPM_CONST / ulAktuelleUmlaufzeitG;
...
unsigned int uiTableIndex = ulAktuelleDrehzahlG / 50;
...
}
Wenn der Wert für ulAktuelleDrehzahlG etwas ungenauer sein darf, kann
man noch eine Division sparen und schreiben:
#define COUNTER_INCS_PER_MILLISECOND_T1 461
#define TIME_TO_RPM_CONST (1200 *
COUNTER_INCS_PER_MILLISECOND_T1) // (600000 *
COUNTER_INCS_PER_MILLISECOND_T1) / ( 10 * 50)
ISR(INT0_vect)
{
...
unsigned int uiTableIndex = TIME_TO_RPM_CONST /
ulAktuelleUmlaufzeitG;
...
ulAktuelleDrehzahlG = 50 * uiTableIndex; // da zuerst die
Division ausgeführt wird, treten hier Rundungsfehler auf!!!!!
...
}
Wenn Dein Compiler nicht expilizit eine Division kann, dann ist die
Division sehr sehr sehr teuer. Darin denke ich, liegt der Grund für
Deine langen Zeiten.
Also: Nicht durch 50 teilen, sondern die Division durch eine
Multiplikation und anschließende Teilung durch eine Zweierpotenz
(shiften) ersetzen. Wieso multiplizierst Du mit 60000 und teilst dann
durch 50 (also nur mit 1200 multiplizieren)? Macht der Compiler wirklich
eine Division zur Laufzeit daraus (schau mal ins LST-File)?
An der Division würde ich den Optimierungshebel ansetzen.
Werner wrote:
> Die beiden Berechnungen brauchen in der Form wie> ich sie zur Zeit programmiert habe fast 500 us auf einem ATMega8 mit> 3,686 MHz Quartz.
Erklär dochmal, was Dich an den 500µs stört.
Warum brauchst Du die Ergebnisse schneller als 500µs?
Wie groß ist die Tabelle, die Du damit adressierst?
Eventuell reicht ja eine Berechnung mit 8Bit aus.
Du machst insgesamt 3 Divisionen mit 3 Konstanten. Fasse mal alle 3
Konstanten zusammen und mache nur eine Division.
Bzw. wenn Du nur ne Tabelle zugreifen willst, sollte es auch ganz ohne
Division gehen.
Peter
Wie wäre es mit einer intelligenteren Optimierung?
Wenn ich das richtig sehe möchtest du die Drehzahl ermitteln,
wahrscheinlich
per Interrupt indem du die Ticks pro Zeiteinheit zählst. Die Berechnung
ist also NUR NÖTIG wenn du die Drehzahl wirklich abfragst. Zähle in der
Interruptroutine nur die Ticks und dort wo du die Drehzahl brauchst(im
Hauptprogramm) berechnest du sie. Damit dürfte die Interruptzeit von
500us (wirklich so viel?) auf 2us fallen und du sparst dir zusätzlich
noch eine ganze Menge unnötige Berechnungen.
Hallo zusammen,
vielen Dank für zahlreichen hervorragenden Tipps, die ich gleich heute
Abend umsetzen werde. Bin schon wirklich gespannt, um wieviel sich die
ursprüngliche Zeit zusammenkürzen läßt.
@I_H.:
>Warum hast du die beiden globalen Variablen denn als volatile>deklariert?
Die beiden globalen Variablen benutze ich sowohl in Interruptroutinen
als auch im Hauptprogramm. Gesetzt werden sie allerdings nur in der
Interruptroutine und bei der globalen Initialisierung. Ich war mir
ehrlich gesagt nicht ganz sicher, ob ich hierfür das "volatile" nun
zwingend angeben muß oder nicht.
@Peter Danneger:
>Erklär dochmal, was Dich an den 500µs stört.>Warum brauchst Du die Ergebnisse schneller als 500µs?>>Wie groß ist die Tabelle, die Du damit adressierst?>Eventuell reicht ja eine Berechnung mit 8Bit aus.
Na ja, das Ganze ist eine variable Zündsteuerung für einen
Zweitakt-Motor. Der erste Geber-Impuls (PickUp) wird 72° Grad vor OT
ausgelöst und aktiviert den Interrupt, der die aktuelle Drehzahl
berechnet und dann davon abhängig einen Timer-Wert (Delay) aus einer
Tabelle ausliest. Nach Ablauf des Timers wird dann der eigentliche
Zündimpuls ausgelöst. Bei Drehzahlen > 10.000 U/Min wird bei 500us
Berechnungszeit für den Timer die Luft für den Einstellungsspielraum
schon ziemlich dünn.
Die Tabelle enthält 256 Werte und ich werde Deinen Tipp beherzigen und
auf eine 8Bit-Berechnung umstellen.
Ich denke, dass sich alle Tipps gut umsetzen lassen. Nochmals Danke an
Alle.
Gruß
Werner