Die Antwort von @ vicatcu ist ziemlich umfassend. Eine weitere zu beachtende Sache ist, dass die CPU beim Zugriff auf E / A, einschließlich Programm- und Datenspeicher, in Wartezustände (blockierte CPU-Zyklen) geraten kann.
Zum Beispiel verwenden wir einen TI F28335 DSP; Einige Bereiche des RAM sind 0-Wartezustand für Programm- und Datenspeicher. Wenn Sie also Code im RAM ausführen, wird dieser mit 1 Zyklus pro Befehl ausgeführt (mit Ausnahme der Befehle, die mehr als 1 Zyklus benötigen). Wenn Sie Code aus dem FLASH-Speicher ausführen (mehr oder weniger integriertes EEPROM), kann er jedoch nicht mit den vollen 150 MHz ausgeführt werden und ist um ein Vielfaches langsamer.
In Bezug auf Hochgeschwindigkeits-Interrupt-Code müssen Sie eine Reihe von Dingen lernen.
Machen Sie sich zunächst mit Ihrem Compiler vertraut. Wenn der Compiler gute Arbeit leistet, sollte er für die meisten Dinge nicht viel langsamer sein als die handcodierte Assembly. (wo "so viel langsamer": ein Faktor von 2 wäre für mich in Ordnung; ein Faktor von 10 wäre inakzeptabel) Sie müssen lernen, wie (und wann) Compiler-Optimierungsflags verwendet werden, und von Zeit zu Zeit sollten Sie nachsehen am Ausgang des Compilers, um zu sehen, wie es funktioniert.
Einige andere Dinge, die der Compiler tun kann, um den Code zu beschleunigen:
Verwenden Sie Inline-Funktionen (ich kann mich nicht erinnern, ob C dies unterstützt oder ob es sich nur um einen C ++ - Ismus handelt), sowohl für kleine Funktionen als auch für Funktionen, die nur ein- oder zweimal ausgeführt werden sollen. Der Nachteil ist, dass Inline-Funktionen schwer zu debuggen sind, insbesondere wenn die Compiler-Optimierung aktiviert ist. Sie ersparen Ihnen jedoch unnötige Aufruf- / Rückgabesequenzen, insbesondere wenn die "Funktions" -Abstraktion eher für konzeptionelle Entwurfszwecke als für die Codeimplementierung dient.
Sehen Sie im Handbuch Ihres Compilers nach, ob es über intrinsische Funktionen verfügt. Hierbei handelt es sich um compilerabhängige integrierte Funktionen, die direkt den Montageanweisungen des Prozessors zugeordnet sind. Einige Prozessoren verfügen über Montageanweisungen, die nützliche Funktionen wie Min / Max / Bit-Umkehrung ausführen. Auf diese Weise können Sie Zeit sparen.
Wenn Sie numerische Berechnungen durchführen, stellen Sie sicher, dass Sie die Funktionen der Mathematikbibliothek nicht unnötig aufrufen. Wir hatten einen Fall, in dem der Code so etwas wie y = (y+1) % 4
ein Zähler mit einer Periode von 4 war, und erwarteten, dass der Compiler das Modulo 4 als bitweises UND implementiert. Stattdessen wurde die Mathematikbibliothek aufgerufen. Also haben wir ersetzt, um y = (y+1) & 3
zu tun, was wir wollten.
Machen Sie sich mit der Seite der Bit-Twiddling-Hacks vertraut . Ich garantiere Ihnen, dass Sie mindestens eine davon häufig verwenden werden.
Sie sollten auch die Timer-Peripheriegeräte Ihrer CPU verwenden, um die Codeausführungszeit zu messen. Die meisten von ihnen verfügen über einen Timer / Zähler, der so eingestellt werden kann, dass er mit der CPU-Taktfrequenz ausgeführt wird. Erfassen Sie eine Kopie des Zählers am Anfang und Ende Ihres kritischen Codes, und Sie können sehen, wie lange es dauert. Wenn Sie dies nicht tun können, besteht eine andere Alternative darin, einen Ausgangspin am Anfang Ihres Codes abzusenken und am Ende anzuheben und diesen Ausgang auf einem Oszilloskop zu betrachten, um die Ausführung zeitlich zu steuern. Jeder Ansatz hat Kompromisse: Der interne Timer / Zähler ist flexibler (Sie können mehrere Dinge zeitlich festlegen), aber es ist schwieriger, die Informationen herauszuholen, während das Setzen / Löschen eines Ausgangsstifts auf einem Bereich sofort sichtbar ist und Sie Statistiken erfassen können Es ist schwierig, mehrere Ereignisse zu unterscheiden.
Schließlich gibt es eine sehr wichtige Fähigkeit, die mit Erfahrung verbunden ist - sowohl allgemein als auch mit bestimmten Prozessor / Compiler-Kombinationen: zu wissen, wann und wann nicht zu optimieren ist . Im Allgemeinen lautet die Antwort nicht optimieren. Das Donald Knuth-Zitat wird häufig auf StackOverflow veröffentlicht (normalerweise nur der letzte Teil):
Wir sollten kleine Wirkungsgrade vergessen, etwa in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels
Aber Sie befinden sich in einer Situation, in der Sie wissen, dass Sie eine Art Optimierung durchführen müssen. Es ist also an der Zeit, in die Kugel zu beißen und zu optimieren (oder einen schnelleren Prozessor oder beides zu bekommen). Schreiben Sie NICHT Ihren gesamten ISR in Assembly. Das ist fast eine garantierte Katastrophe - wenn Sie dies tun, werden Sie innerhalb von Monaten oder sogar Wochen Teile dessen vergessen, was Sie getan haben und warum, und der Code ist wahrscheinlich sehr spröde und schwer zu ändern. Es gibt jedoch wahrscheinlich Teile Ihres Codes, die sich gut für die Montage eignen .
Anzeichen dafür, dass Teile Ihres Codes für die Baugruppencodierung gut geeignet sind:
- Funktionen, die gut enthalten sind, gut definierte kleine Routinen, die sich wahrscheinlich nicht ändern
- Funktionen, die bestimmte Montageanweisungen verwenden können (Min / Max / Rechtsverschiebung / usw.)
- Funktionen, die viele Male aufgerufen werden (Sie erhalten einen Multiplikator: Wenn Sie bei jedem Aufruf 0,5 usec speichern und 10 Mal aufgerufen werden, sparen Sie 5 usec, was in Ihrem Fall von Bedeutung ist).
Lernen Sie die Funktionsaufrufkonventionen Ihres Compilers kennen (z. B. wo die Argumente in Registern abgelegt werden und welche Register gespeichert / wiederhergestellt werden), damit Sie C-aufrufbare Assembly-Routinen schreiben können.
In meinem aktuellen Projekt haben wir eine ziemlich große Codebasis mit kritischem Code, die in einem 10-kHz-Interrupt ausgeführt werden muss (100 usec - klingt vertraut?), Und es gibt nicht so viele Funktionen, die in Assembly geschrieben werden. Dies sind Dinge wie CRC-Berechnung, Software-Warteschlangen, ADC-Verstärkung / Offset-Kompensation.
Viel Glück!