Warum ist Arduino so langsam und was kann man dagegen tun?

LOGO


Es war einmal ein ausgezeichneter Artikel ( tyk ) - darin zeigte der Autor ganz deutlich den Unterschied zwischen der Verwendung von Arduino-Funktionen und der Arbeit mit Registern. Es wurden viele Artikel geschrieben, in denen sowohl Arduino gelobt als auch argumentiert wird, dass dies alles leichtfertig und allgemein für Kinder ist. Wir werden es also nicht wiederholen, sondern versuchen herauszufinden, was die vom Autor dieses Artikels erzielten Ergebnisse verursacht hat. Und genauso wichtig ist, dass wir darüber nachdenken, was wir tun können. Wer interessiert ist, frage ich unter der Katze.


Teil 1 "Fragen"


Zitieren des Autors dieses Artikels:


In diesem Fall stellt sich ein Leistungsverlust heraus - 28 Mal. Natürlich bedeutet dies nicht, dass Arduino 28-mal langsamer arbeitet, aber ich denke, dass dies aus Gründen der Klarheit das beste Beispiel dafür ist, warum Arduino nicht gemocht wird.

Da der Artikel gerade erst begonnen hat, werden wir ihn noch nicht verstehen, aber den zweiten Satz ignorieren und davon ausgehen, dass die Reglergeschwindigkeit ungefähr der Pin-Schaltfrequenz entspricht. Das heißt, Wir stehen vor der Aufgabe, den Generator auf die höchste Frequenz dessen zu bringen, was wir haben. Mal sehen, wie schlimm alles ist.


Wir werden ein einfaches Programm für Arduino schreiben (in der Tat nur kopieren blinken).


void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1); // turn the LED on (HIGH is the voltage level) digitalWrite(13, 0); // turn the LED off by making the voltage LOW } 

Controller einnähen. Da ich kein Oszilloskop habe, sondern nur einen chinesischen Logikanalysator, muss es korrekt konfiguriert werden. Die maximale Frequenz des Analysators beträgt 24 MHz und muss daher mit der auf 16 MHz eingestellten Reglerfrequenz ausgeglichen werden. Wir schauen ...


Test_1


... für eine lange Zeit. Wir versuchen uns zu erinnern, wovon die Geschwindigkeit des Controllers abhängt - genau von der Frequenz. Wir schauen in arduino.cc . Die Taktrate beträgt 16 MHz, und hier haben wir 145,5 kHz. Was zu tun ist? Versuchen wir es in der Stirn zu lösen. Auf der gleichen arduino.cc schauen wir uns den Rest des Boards an:


  • Leonardo - nicht geeignet - gibt es auch 16 MHz
  • Mega - auch - 16 MHz
  • 101 - reicht - 32MHz
  • FÄLLIG - noch besser - 84 MHz

Es kann davon ausgegangen werden, dass sich die LED-Blinkfrequenz um das Zweifache und bei 5 um das Fünffache erhöht, wenn Sie die Reglerfrequenz um das Zweifache erhöhen.


Test_2


Wir haben nicht die gewünschten Ergebnisse erzielt. Und der Generator ist immer weniger wie ein Mäander. Wir denken weiter - jetzt ist die Sprache wahrscheinlich schlecht. Es scheint, als wäre es mit c, c ++, aber es ist schwierig (gemäß dem Dunning-Krueger- Effekt können wir nicht erkennen, dass wir bereits in c ++ schreiben), daher suchen wir nach Alternativen. Eine kurze Suche führt uns zu BASCOM-AVR (hier wird nicht schlecht darüber berichtet), setzen Sie es ein, schreiben Sie den Code:


 $Regfile="m328pdef.dat" $Crystal=16000000 Config Portb.5 = Output Do Toggle Portb.5 Loop 

Wir bekommen:


Test_3


Das Ergebnis ist viel besser, außerdem haben wir den perfekten Mäander, aber ... BASIC im Jahr 2018, im Ernst? Vielleicht lassen wir das in der Vergangenheit.


Teil 2 "Antworten"


Es scheint an der Zeit zu sein, nicht mehr den Narren zu spielen und zu verstehen (und sich auch an si und Assembler zu erinnern). Kopieren Sie einfach den „nützlichen“ Code aus dem am Anfang erwähnten Artikel in loop ().


Ich glaube, hier ist eine Erklärung erforderlich: Der gesamte Code wird im Arduino-Projekt geschrieben, aber in der Atmel Studio 7.0-Umgebung (dort gibt es einen praktischen Disassembler) werden die Screenshots davon stammen.


 void setup() { DDRB |= (1 << 5); // PB5 } void loop() { PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON } 

Ergebnis:


Test_4


Hier ist es! Fast was Sie brauchen. Nur die Form ist dem Mäander und der Frequenz nicht besonders ähnlich, obwohl sie bereits näher ist, aber immer noch nicht dieselbe. Wir versuchen auch, jede Millisekunde zu vergrößern und Lücken im Signal zu finden.


Test_5


Dies ist auf das Auslösen von Interrupts vom Timer zurückzuführen, der für millis () verantwortlich ist. Wir trennen also einfach die Verbindung. Wir suchen nach ISR (Interrupt Handler Funktion). Wir finden:


 ISR(TIMER0_OVF_vect) { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; nsigned char f = timer0_fract; m += MILLIS_INC; f += FRACT_INC; if (f >= FRACT_MAX) { f -= FRACT_MAX; m += 1; } timer0_fract = f; timer0_millis = m; timer0_overflow_count++; } 

Viel nutzloser Code für uns. Sie können den Timer-Betriebsmodus ändern oder den Interrupt deaktivieren, dies ist jedoch für unsere Zwecke nicht erforderlich. Deaktivieren Sie daher einfach alle Interrupts mit dem Befehl cli (). Schauen Sie sich einfach unseren Code an:


 PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON 

zu viele Operatoren auf eine Zuordnung reduzieren.


 PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON 

Ja, und das Umschalten auf loop () erfordert viele Befehle, da dies eine zusätzliche Funktion in der Hauptschleife ist.


 int main(void) { init(); // ... setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; } 

Machen Sie also einfach eine Endlosschleife in setup (). Wir bekommen folgendes:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON } } 

Test_6


61 ns ist das Maximum, das der Frequenz des Reglers entspricht. Ist es schneller möglich? Spoiler - nein. Versuchen wir zu verstehen, warum - dafür zerlegen wir unseren Code:


Code_asm_1


Wie aus dem Screenshot ersichtlich ist, wird zum Schreiben auf Port 1 oder 0 genau 1 Taktzyklus ausgegeben, es gibt jedoch einen Übergang, der nicht in weniger als einem Taktzyklus abgeschlossen werden kann (RJMP wird in zwei Taktzyklen und beispielsweise JMP in drei Taktzyklen ausgeführt ) Und wir sind fast da - um einen Mäander zu bekommen, müssen Sie die Zeit, in der 0 gegeben ist, durch zwei Maßnahmen verlängern. Fügen Sie für diesen Assembler zwei NOP-Befehle hinzu, die nur einen Taktzyklus ausführen:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF asm("nop"); asm("nop"); PORTB = 0b11111111; //ON } } 

Test_end


Teil 3 "Schlussfolgerungen"


Leider war alles, was wir getan haben, aus praktischer Sicht absolut nutzlos, da wir keinen Code mehr ausführen können. In 99,9% der Fälle reichen Port-Switching-Frequenzen für jeden Zweck aus. Ja, und wenn wir wirklich einen glatten Mäander erzeugen müssen, können Sie stm32 mit dma oder einem externen Timer-Chip wie NE555 nehmen. Dieser Artikel ist nützlich, um die Mega328p- und Arduino-Geräte im Allgemeinen zu verstehen.


Schreiben in die Register von 8-Bit-Werten PORTB = 0b11111111; viel schneller als digitalWrite(13, 1); Sie müssen dies jedoch bezahlen, da Sie den Code nicht auf andere Karten übertragen können, da die Namen der Register unterschiedlich sein können.


Es bleibt nur noch eine Frage: Warum hat die Verwendung schnellerer Steine ​​keine Ergebnisse gebracht? Die Antwort ist sehr einfach: In komplexen Systemen ist die GPIO-Frequenz niedriger als die Kernfrequenz. Wie viel niedriger und wie es eingestellt wird, können Sie immer im Datenblatt eines bestimmten Controllers sehen.


Die Veröffentlichung zitierte Artikel:



Source: https://habr.com/ru/post/de422177/


All Articles