Frage:
Wie kann ich die Geschwindigkeit dieses Codeabschnitts für diesen Mikrocontroller schätzen?
ty_1917
2020-01-09 17:00:38 UTC
view on stackexchange narkive permalink

Ich verwende einen ATmega328P, um den Status eines digitalen Eingangs mithilfe des folgenden in C geschriebenen Codeabschnitts zu lesen (es kann alternative Möglichkeiten geben, dies ist jedoch ein Beispiel). val ist ein Variablentyp uint8_t und speichert den Status des digitalen Eingangspins:

Hier ist der Teil des Codes:

  if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
    val = 1;
} else {
    val = 0;
}}
 

Ich stelle die Uhr wie folgt ein:

  #define F_CPU 16000000UL
 

Stellen Sie sich vor, der digitale Eingang ist eine EIN / AUS-Impulsfolge mit einem Tastverhältnis von 50%, und wir erhöhen die Frequenz schrittweise. Irgendwann bei einer bestimmten Frequenz sollte der obige Code den digitalen Eingangszustand nicht mehr korrekt erfassen können.

  1. Wie können wir ungefähr die maximale Pulsfrequenz schätzen, die der obige Code verarbeiten kann, um den Status korrekt zu lesen?
  2. Sollten wir herausfinden, wie viele Taktzyklen es verwendet, und es mit der Taktfrequenz multiplizieren?
  3. Und wenn ja, wie kann ich das in der Praxis tun?

      int main (void) {
    
        DDRD = B0100000;
        DDRD | = 1<<5;
    
        während (1) {
    
            lange Daten ohne Vorzeichen = 0;
            uint8_t val;
    
            für (int i = 0; i<25; i ++) {
                Daten << = 1;
                PORTD & = ~ (1 << 5);
                Verzögerung (2);
                PORTD | = (1 << 5);
                Verzögerung (2);
    
                if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
                    val = 1;
                }}
                sonst {
                    val = 0;
                }}
    
                Daten | = Wert;
            }}
    
            // Der Rest des Codes
    
        }}
    }}
     
  4. ol>
Ich gehe davon aus, dass dies den digitalen Eingangszustand jedes Mal, wenn er ausgeführt wird, bei jeder Frequenz korrekt erfasst.Die Frage ist, wie oft können Sie diesen Code ausführen?
Sie können Ihren Code nur als `val = ((PIND & (1 << PIND6)) == (1 << PIND6));` oder `val = ((PIND >> PIND6) & 1);` (und wahrscheinlich eine Reihe anderer Möglichkeiten).Sie sollten sich Ihre Compilerausgabe ansehen, um festzustellen, ob Sie jeweils einen anderen Assemblycode erhalten und welcher am schnellsten ist.
Haben Sie versucht, mit einem Hardware-Profiler zu messen, anstatt zu schätzen?
Ihr neuer Code hat das gleiche Problem wie der alte Code: - `val` und` data` werden nicht verwendet, daher werden sie weg optimiert.
Fünf antworten:
Swedgin
2020-01-09 17:08:36 UTC
view on stackexchange narkive permalink
  1. Da das Codefragment, an dem Sie interessiert sind, nicht groß ist, können Sie Ihren kompilierten Code zerlegen, alle Montageanweisungen lesen und zählen, wie viele Zyklen sie benötigen. Die Anzahl der Zyklen für jede Anweisung finden Sie im Datenblatt.

  2. Wenn Sie ein Oszilloskop haben, können Sie einen Pin vor der if -Anweisung einschalten und nach Ihrem Code-Snippet ausschalten. (Verwenden der direkten Portmanipulation PORTB , nicht der Arduino-Bibliotheksfunktion) Mit einem Bereich können Sie sehen, wie lange es dauert, den Code auszuführen.

  3. Verwenden Sie die Funktion micros () in der Arduino-Bibliothek. Platzieren Sie eine vor und nach dem Code-Snippet. Hier haben Sie jedoch einige Mikrosekunden Overhead, da auch 'micros ()' ausgeführt werden muss.

  4. Verwenden Sie einen Debugger oder Hardware-Simulator, der Zyklen zählen kann. Setzen Sie einen Haltepunkt auf die erste Anweisung des Code-Snippets und einen auf die Anweisung nach dem Snippet. delta_t = Zyklen / clock_freq (in Übereinstimmung mit Oldfarts Antwort)

  5. ol>
Es gibt auch Overhead beim Ein- und Ausschalten der Stifte.Der Overhead ist besonders schlecht, wenn Sie durch die Arduino-Bibliothek gehen.Ich denke, ich würde bei micros () bleiben.
@Dampmaskin seit OP `PIND6` verwendet Ich dachte, er wird die Arduino-Bibliothek nicht verwenden und nur` PORTB6` verwenden.Damit ist der Overhead viel kleiner als "micros ()"
Ich benutze Atmel Studio
@ty_1917 Es spielt keine Rolle, welchen Editor Sie verwenden, es war eine Antwort auf den anderen Kommentar.Die Funktionen der Arduino-Bibliothek haben viel Overhead.Wenn Sie also mit der Bit-Einstellung / dem Löschen von "PORTB" umschalten, ist dies viel schneller als mit der Arduino-Bibliotheksfunktion.Ich vertraue meinem Bereich mehr als der Verwendung der Funktionen `micros ()`.
Ich denke, der Punkt war, dass der Overhead nicht nur die Bibliothek ist, sondern die E / A im Allgemeinen (vielleicht ist der Atmel / Mikrochip-Teil im Besonderen schnell) einige Uhren benötigt, um etwas zu tun, da es keinen Grund gibt, warum sie an der laufenKern-CPU-Rate.Während Sie also die Anzahl der CPU-Anweisungen verkürzen, Verzweigungen usw. reduzieren können, sind Sie möglicherweise an die E / A gebunden.Dies kann jedoch experimentell bestimmt werden.Auch das Verstehen, dass wahrscheinlich nicht für diesen Kern, aber die Ausrichtung des Codes und anderer Faktoren (ohne Interrupts) dazu führen kann, dass derselbe Maschinencode nicht jedes Mal dieselbe Leistung auf derselben MCU erbringt.
@Dampmaskin, damit Sie die Zeit zwischen Pin-Änderungen ohne Testcode messen, um den Overhead zu erhalten, und ihn dann vom Ergebnis abziehen.
Eine Sache, auf die Sie achten sollten, ist der Compiler, der Ihren Code optimiert.In dem angegebenen Beispiel wird 'val' nicht verwendet, daher wird kein Code generiert, um es festzulegen!
Bitte sehen Sie meine Bearbeitung.Ich könnte jetzt den größeren Teil des Codes laden.Wo und wie würden Sie Wert und Daten für eine bessere Effizienz deklarieren?Außerhalb der while-Schleife und flüchtig?
@old_timer gibt es kein solches Problem bei AVR - Port-Schreibvorgängen dauern einen Zyklus (wenn Sie drei lesen, maskieren und schreiben müssen) und sofort.
yar
2020-01-10 06:16:27 UTC
view on stackexchange narkive permalink

Lass es uns tun!

Angenommen, wir haben den Code

  int main (void)
{
    flüchtig uint8_t val = 0;

    während (1)
    {
        if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
            val = 1;
        } else {
            val = 0;
        }}
    }}
}}
 

Angenommen, wir verwenden AVR GCC mit dem Optimierungsflag -O1, dann sieht die Demontage des relevanten Abschnitts folgendermaßen aus:

  val = 1;
00000046 LDI R24,0x01 Sofort laden, 1 Taktzyklus
    if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
00000047 SBIS 0x09,6 Überspringen, wenn das Bit im E / A-Register gesetzt ist, 1/2 Taktzyklus
00000048 RJMP PC + 0x0003 Relativer Sprung, 2-Takt-Zyklus
        val = 1;
00000049 STD Y + 1, R24 Indirekt mit Verschiebung speichern, 2-Takt-Zyklus
0000004A RJMP PC-0x0003 Relativer Sprung, 2-Takt-Zyklus
        val = 0;
0000004B STD Y + 1, R1 Indirekt mit Verschiebung speichern, 2-Takt-Zyklus
0000004C RJMP PC-0x0005 Relativer Sprung, 2-Takt-Zyklus
 

Ich habe die Kommentare mit der Anzahl der Taktzyklen basierend auf dem Datenblatt Seite 281ff hinzugefügt. Beachten Sie, dass SBIS 1 oder 2 Zyklen dauern kann, je nachdem, ob der nächste Befehl übersprungen wird oder nicht.

Wir sehen also, dass der if-Zweig (Zeilen 0x47, 0x49, 0x4A) 6 Taktzyklen benötigt und der else-Zweig (Zeilen 0x47, 0x48, 0x4B, 0x4C) 7 Taktzyklen benötigt.

Nehmen wir jetzt den längeren. Bei 16 MHz dauert es (7 / 16e6) Sekunden, d. H. Sie probieren mit einer Frequenz von 16e6 / 7 Hz. Da Sie immer mindestens einen Abtastpunkt bei niedrig / hoch haben möchten, müssen Sie mit> 2x Ihrer Signalfrequenz abtasten, dh Ihre Signalfrequenz muss <16e6 / (7 * 2) Hz sein, was ~ 1 MHz entspricht. P. >

Beachten Sie nun, dass dies ein rein virtuelles Beispiel ist, da val korrekt eingestellt ist, Sie es jedoch nicht testen können. Sie müssen irgendwie val ausgeben, wodurch zusätzliche Taktzyklen hinzugefügt werden.

Ist es besser oder vorteilhaft, außerhalb der while-Schleife als flüchtig uint8_t zu deklarieren, wie Sie es getan haben?In meinem Code wurde es als uint8_t val = 0 deklariert;innerhalb der while-Schleife.Soll ich also als flüchtig uint8_t und außerhalb der while-Schleife deklarieren, wie Sie es getan haben?
Bitte sehen Sie meine Bearbeitung.Ich habe einen größeren Teil des Codes hinzugefügt.Wo und wie würden Sie Wert und Daten für eine bessere Effizienz deklarieren?Außerhalb der while-Schleife und flüchtig?
@ty_1917: Sollte keinen Unterschied machen, haben Sie es nur als flüchtig deklariert, um zu verhindern, dass der Compiler es wegoptimiert (da die Variable in seinem minimalen Beispiel nicht gelesen wird).Sie können jederzeit den Assembly-Code einsehen (oder den Code vergleichen), um sicherzugehen.
@Michael Ich habe hier eine detailliertere Frage dazu gestellt: https://electronics.stackexchange.com/questions/475476/stuck-with-deciding-the-location-and-the-type-of-variable-declarations-for-this
Dies ist eine gute Übung und genau so, wie Sie es tun sollten - sehen Sie sich immer den generierten Maschinencode an.Ich denke jedoch, dass die Verwendung einer "flüchtigen" Ergebnisvariablen in diesem Beispiel die Optimierung etwas vermasseln könnte. Der einzige Teil, der flüchtig sein muss, ist das Register selbst.Schauen Sie sich dieses [Beispiel] an (https://godbolt.org/z/VhmVUh).Es sind effektiv 4 Anweisungen mit -O2.
@ty_1917 `volatile` dient in diesem Fall nur als künstliche Methode, um den Compiler dazu zu bringen, überhaupt Code mit aktivierter Optimierung zu generieren.Ohne "flüchtig" würde der gesamte Code vom Optimierer entfernt.Wie aus meinem obigen Kommentar hervorgeht, hat "volatile" jedoch auch einen leichten Einfluss auf die Leistung.Es ist besser, wie oben im Godbolt-Link beschrieben, eine isolierte Funktion zu zerlegen. Dann muss der Compiler den Code generieren, da er nicht weiß, wie / ob diese Funktion aufgerufen wird.
Beachten Sie auch, dass beim Schreiben in C und nicht in asm die Leistung eines Snippets vom * umgebenden * Code abhängen kann, je nachdem, wie der Compiler ihn in früheren / späteren Code optimiert.(Auch wenn Sie die Typen gleich halten).Und natürlich hängt es von der Optimierungsstufe ab, die in den meisten Antworten nicht einmal erwähnt wurde.
Beachten Sie, dass zusätzlich zu dem von Peter Cordes hervorgehobenen Problem, dass sich die generierte Baugruppe sehr wahrscheinlich ändert, sobald der Optimierer den Code in den Griff bekommt und die Dinge einfügt, diese Methode der Zykluszählung nur für Mikrocontroller funktioniert, die deterministische Zykluszählungen für bereitstellenAnleitung.Das mag beim AVR der Fall sein, aber es ist nicht für viele ARM-Kerne und sicherlich nicht für x86.Zusätzlich zu dem Offensichtlichen (z. B. Pipelining und Scheduling) müssen Sie auch Speicherlatenzen, das Vorabrufen von Befehlen und andere Faktoren berücksichtigen.Es wird Ihnen jedoch eine grobe Schätzung geben.
@Lundin Wenn Sie volatile verwenden, muss der Compiler es in SRAM schreiben, während in Ihrem Beispiel die Variable im Register verbleibt.In diesem künstlichen Beispiel macht es eigentlich keinen Unterschied, aber ich habe flüchtig verwendet, um der Realität ein bisschen näher zu sein.
Oldfart
2020-01-10 07:44:50 UTC
view on stackexchange narkive permalink

Normalerweise verwende ich den eingebauten Simulator von Atmel Studio, der einen Zykluszähler im Prozessorstatusfenster hat. Dies ist ein kombinierter Screenshot vom Durchlaufen des Codes:

Enter image description here]

Wie Sie sehen können, ist der Zykluszähler 18 vor und 22 nach dem Durchlaufen der beiden Anweisungen.Laut Simulator dauert es also 4 Zyklen.

Hiermit können Sie die gesamte Schleife durchlaufen.

Ist die Taktfrequenz 16MHz oder 1MHz?Ich habe 16 MHz eingestellt, aber wie in Ihrem Screenshot wird 1 MHz unter "Prozessorstatus" angezeigt.Es dauert 4 Zyklen, aber wie hoch ist die Frequenz pro Zyklus?
Die Anzahl der Zyklen zum Ausführen von Anweisungen ist unabhängig von der Frequenz.
Ja gut, aber ich frage mich, wie lange es pro Zyklus dauert.Denn das bestimmt die maximal zu lesende Eingangsfrequenz der Impulsfolge, die ich in meiner Frage stelle.
if ((PIND & (1 << PIND6)) == (1 << PIND6)) { val = 1; } else { val = 0; } Dies liest den Zustand und wenn die Eingangsimpulsfolge 1 GHz ist, kann sie den Zustand beispielsweise nicht korrekt lesen, da der mcu-Takt viel langsamer als 1 GHz ist.Ich versuche herauszufinden, wie hoch die maximale Frequenz ist, mit der der Pin-Status korrekt gelesen werden kann.
Yar hat bereits Beispielberechnungen gegeben, wie man von CPU-Zyklen und Taktfrequenz zur Abtastrate gelangt.Beachten Sie auch die Warnung bezüglich des Ausgangswerts.
Curd
2020-01-09 17:35:27 UTC
view on stackexchange narkive permalink

Wenn Geschwindigkeit für diesen Code wichtig ist, ist wahrscheinlich Folgendes bemerkenswert:

Sie könnten einfach schreiben

val = (PIND & (1 << PIND6))! = 0;

oder

val = 1 & (PIND >> PIND6);

Ich denke, der letzte ist kürzer / schneller.

Zur Geschwindigkeits- / Zeitschätzung: Entweder

  • Lassen Sie Ihren Compiler eine assembler-Liste der -Datei (* .lst) oder
  • erstellen
  • Schauen Sie sich den -zerlegten Code
  • an

und schauen Sie dann nach und fügen Sie die Ausführungszeiten (Taktzyklen) der Anweisungen hinzu.

Welche Frequenzen Ihr Code "verarbeiten" kann, hängt natürlich davon ab, wie oft er aufgerufen wird, dh von der Geschwindigkeit des umgebenden / aufrufenden Codes (dh wie oft das Code-Snippet besucht wird), nicht nur vom CodeSnippet selbst.

_ "Ich denke, die letzte ist kürzer / schneller." _ - Ich habe alle 3 Variationen in AVR GCC 5.4.0 mit Optimierungs-Os ausprobiert und sie haben identischen Code generiert (4 Anweisungen / Zyklen).
Das ist ein Zeichen dafür, dass der Compiler gute Arbeit leistet.
Schreiben Sie also den am besten lesbaren Code, der wahrscheinlich `bool val = PIND & (1u << PIND6);` ist.
@Lundin:, aber diese Zeile liefert unterschiedliche Ergebnisse: Wenn die Eingabe hoch ist, ist "val" "1 << PIND6" anstelle von "1".
@Curd Nr. Verwenden Sie das Standard-C `bool` von stdbool.h, das zu` _Bool` erweitert wird.
Ja, aber dazu muss der Typ von "val" geändert werden.Am Anfang hat OP keinen Code gepostet, der zeigt, wie "val" verwendet wird, daher war dies keine Option und jetzt möchte OP ihn als "int" verwenden.
val in meinem Code muss der Status eines einzelnen Bits sein.data ist die 32-Bit-Variable, die von jedem Val-Status in der for-Schleife ausgefüllt werden muss.
Das war am Anfang nicht klar, als Sie nur die ersten 3 Codezeilen gepostet haben, in denen `val` gesetzt, aber noch nicht verwendet wurde.Es könnte z.B.dass später auch höhere Bits in `val` verwendet wurden.
Bruce Abbott
2020-01-12 15:35:23 UTC
view on stackexchange narkive permalink

Wie können wir die maximale Pulsfrequenz des obigen Codes ungefähr schätzen? kann damit umgehen, den Zustand richtig zu lesen?

Der -Status wird immer korrekt gelesen. Die Frage, die Sie meiner Meinung nach stellen möchten, ist die maximale Frequenz, die gemessen werden kann, ohne Höhen oder Tiefen zu verpassen.

Sollten wir herausfinden, wie viele Taktzyklen es verwendet, und es mit dem multiplizieren Taktfrequenz?

Grundsätzlich ja. Der wichtige Faktor ist die Zeit zwischen jedem Lesen des Ports. Beachten Sie, dass dies je nach Funktionsweise des Maschinencodes möglicherweise nicht immer gleich ist. Verwenden Sie daher die maximale Zeit zwischen den Lesevorgängen.

Und wenn ja, wie kann ich das in der Praxis tun?

Sie können den Code zerlegen und herausfinden, wie viel Zeit jeder Befehl benötigt, oder ihn im Simulator durchlaufen oder den Code in einem tatsächlichen ATmega328p ausführen und die physische Ausgabe überwachen (z. B. umschalten eines Ausgangsstifts oder Anzeige der Frequenz auf einem LCD-Bildschirm).

Beachten Sie, dass die Ergebnisse entscheidend davon abhängen, welchen Maschinencode der Compiler generiert. Durch Optimierungen von Variablen, die nicht zur Ausgabe beitragen, können diese möglicherweise wegoptimiert werden, und andere scheinbar triviale Änderungen können einen großen Einfluss auf die Menge des generierten Codes haben. Daher ist die einzige garantierte genaue Möglichkeit, Ihren Code zu testen, die vollständige. Das Ausführen kleiner Ausschnitte aus isoliertem Code kann eine sehr irreführende Vorstellung von der Leistung in der fertigen Anwendung vermitteln.

Hier ist zum Beispiel die Auflistung des Codes in Ihrer Frage: -

  int main (void) {
  86: 89 e1 ldi r24, 0x19; 25
  88: 90 e0 ldi r25, 0x00; 0

        uint8_t val;

        für (int i = 0; i<25; i ++) {
            Daten << = 1;
            PORTD & = ~ (1 << 5);
  8a: 5d 98 cbi 0x0b, 5; 11
 // _delay_us (2);
            PORTD | = (1 << 5);
8c: 5d 9a sbi 0x0b, 5; 11
 // _delay_us (2);

            if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
  8e: 29 b1 in r18, 0x09; 9
  90: 01 97 sbiw r24, 0x01; 1

    während (1) {

        uint8_t val;

        für (int i = 0; i<25; i ++) {
  92: d9 f7 brne.-10; 0x8a <main + 0xa>
  94: f8, vgl. Rjmp.-16; 0x86 <main + 0x6>
 

Für val und data ​​code> wird kein Code generiert, und die innere Schleife enthält nur 5 Anweisungen, die 9 Zyklen dauern. Bei einem 16-MHz-Takt beträgt die innere Schleifenzeit 62,5 ns * 9 = 562,5 ns, was mit einer Eingangsfrequenz von ~ 888 kHz mithalten sollte.

Als nächstes gebe ich Daten an PORTD aus, wodurch der Compiler gezwungen wird, Code dafür zu generieren: -

  while (1) {

        uint8_t val;

        für (int i = 0; i<25; i ++) {
            Daten << = 1;
  90: 88 0f füge r24, r24 hinzu
  92: 99 1f adc r25, r25
  94: aa 1f adc r26, r26
  96: bb 1f adc r27, r27
            PORTD & = ~ (1 << 5);
  98: 5d 98 cbi 0x0b, 5; 11
 // _delay_us (2);
            PORTD | = (1 << 5);
  9a: 5d 9a sbi 0x0b, 5; 11
 // _delay_us (2);

            if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
  9c: 49 b1 in r20, 0x09; 9
            }}
            sonst {
                val = 0;
            }}

            Daten | = Wert;
  9e: 46 fb bst r20, 6
  a0: 44 27 eor r20, r20
  a2: 40 f9 bld r20, 0
  a4: 84 2b oder r24, r20
  a6: 21 50 subi r18, 0x01; 1
  a8: 31 09 sbc r19, r1

    während (1) {

        uint8_t val;

        für (int i = 0; i<25; i ++) {
aa: 91 f7 brne.-28; 0x90 <main + 0x10>
            }}

            Daten | = Wert;
        }}

    PORTD = (uint8_t) Daten;
  ac: 8b b9 aus 0x0b, r24; 11

        // Der Rest des Codes

    }}
  ae: ee cf rjmp.-36; 0x8c <main + 0xc>
 

Die innere Schleife hat jetzt 14 Anweisungen, die 17 Zyklen dauern, und die maximale Frequenz, der sie genau folgen kann, ist fast halbiert.

Schließlich mache ich data ​​code> statisch, um den Compiler zu zwingen, sie im Speicher zu speichern (was für ein komplexeres Programm möglicherweise erforderlich ist): -

  while (1) {

        uint8_t val;

        für (int i = 0; i<25; i ++) {
            Daten << = 1;
  9a: 40 91 00 01 lds r20, 0x0100; 0x800100 <_edata>
  9e: 50 91 01 01 lds r21, 0x0101; 0x800101 <_edata + 0x1>
  a2: 60 91 02 01 lds r22, 0x0102; 0x800102 <_edata + 0x2>
  a6: 70 91 03 01 lds r23, 0x0103; 0x800103 <_edata + 0x3>
  aa: 44 0f addiere r20, r20
  ac: 55 1f adc r21, r21
  ae: 66 1f adc r22, r22
  b0: 77 1f adc r23, r23
  b2: 40 93 00 01 M. 0x0100, r20; 0x800100 <_edata>
  b6: 50 93 01 01 M. 0x0101, r21; 0x800101 <_edata + 0x1>
  ba: 60 93 02 01 M. 0x0102, r22; 0x800102 <_edata + 0x2>
  sein: 70 93 03 01 M. 0x0103, r23; 0x800103 <_edata + 0x3>
            PORTD & = ~ (1 << 5);
  c2: 5d 98 cbi 0x0b, 5; 11
 // _delay_us (2);
            PORTD | = (1 << 5);
  c4: 5d 9a sbi 0x0b, 5; 11
 // _delay_us (2);

            if ((PIND & (1 << PIND6)) == (1 << PIND6)) {
  c6: 29 b1 in r18, 0x09; 9
            }}
sonst {
                val = 0;
            }}

            Daten | = Wert;
  c8: 26 fb bst r18, 6
  ca: 22 27 eor r18, r18
  cc: 20 f9 bld r18, 0
  ce: 40 91 00 01 lds r20, 0x0100; 0x800100 <_edata>
  d2: 50 91 01 01 lds r21, 0x0101; 0x800101 <_edata + 0x1>
  d6: 60 91 02 01 lds r22, 0x0102; 0x800102 <_edata + 0x2>
  da: 70 91 03 01 lds r23, 0x0103; 0x800103 <_edata + 0x3>
  de: 42 2b oder r20, r18
  e0: 40 93 00 01 M. 0x0100, r20; 0x800100 <_edata>
  e4: 50 93 01 01 M. 0x0101, r21; 0x800101 <_edata + 0x1>
  e8: 60 93 02 01 M. 0x0102, r22; 0x800102 <_edata + 0x2>
  ec: 70 93 03 01 M. 0x0103, r23; 0x800103 <_edata + 0x3>
  f0: 01 97 sbiw r24, 0x01; 1

    während (1) {

        uint8_t val;

        für (int i = 0; i<25; i ++) {
  f2: 99 f6 brne.-90; 0x9a <main + 0xa>
  f4: d0 vgl. rjmp.-96; 0x96 <main + 0x6>
 

Der Code der inneren Schleife ist jetzt auf 29 Befehle angewachsen, die 49 Zyklen benötigen, wodurch die maximal messbare Frequenz auf ~ 163 kHz reduziert wird. Diese einfache Hinzufügung des Schlüsselworts static reichte aus, um es mehr als fünfmal langsamer zu machen. Dies ist jedoch die realistische Geschwindigkeit, die Sie erwarten können, wenn der Code in einer größeren Anwendung verwendet wird.

Wenn Sie unabhängig von Compiler-Macken die schnellstmögliche Geschwindigkeit benötigen, haben Sie drei Möglichkeiten: -

  1. Schreiben Sie fein gestalteten Assembler-Code, der jede Anweisung so effizient wie möglich verwendet (anderer unkritischer Code kann weiterhin in C geschrieben werden).

  2. Verwenden Sie Peripheriegeräte wie Timer / Zähler oder SPI.

  3. Fügen Sie einen externen Chip wie einen Vorteiler zum Teilen der Frequenz oder ein Schieberegister (z. B. CD4031) hinzu, um die Wellenform zu erfassen.

  4. ol>


Diese Fragen und Antworten wurden automatisch aus der englischen Sprache übersetzt.Der ursprüngliche Inhalt ist auf stackexchange verfügbar. Wir danken ihm für die cc by-sa 4.0-Lizenz, unter der er vertrieben wird.
Loading...