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: -
-
Schreiben Sie fein gestalteten Assembler-Code, der jede Anweisung so effizient wie möglich verwendet (anderer unkritischer Code kann weiterhin in C geschrieben werden).
-
Verwenden Sie Peripheriegeräte wie Timer / Zähler oder SPI.
-
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.
ol>