Kurzimpuls-Erzeugung mit Arduino Uno — Teil 4: NOP-Schleifen
In unserem vorherigen Beitrag haben wir NOP-Befehle, die zwischen direkten GPIO-Register-Zugriffs-Befehlen eingefügt wurden, verwendet, um Impulsbreiten zu erzeugen, die mit erreichbaren Auflösungen von 62.5ns variabel sind.
Man könnte nun eine for-Schleife verwenden, um mehrere NOP-Befehle zu erzeugen:
for (int i = 0; i < 3; i++)
{
_NOP();
}Tatsächlich führt dies genauso aus wie die manuelle Verwendung von drei NOPs (312.5ns Impulsbreite), da es vom Compiler inline eingefügt wird, d.h. der Compiler generiert einfach drei separate NOPs, da die Anzahl der NOPs zur Kompilierzeit bekannt ist.
Wenn wir stattdessen eine Variable numNOPs verwenden und volatile verwenden, um den Compiler anzuweisen, nicht anzunehmen, dass sie konstant ist:
volatile int numNOPs = 3;
for (int i = 0; i < numNOPs; i++)
{
_NOP();
}enden wir mit einer Impulsbreite von nicht 312.5ns sondern 3250ns:

Ich habe die Impulsbreite für verschiedene numNOPs-Einstellungen gemessen:
numNOPs = 1:1.50usImpulsbreitenumNOPs = 2:2.25usImpulsbreitenumNOPs = 3:3.00usImpulsbreitenumNOPs = 4:3.75usImpulsbreitenumNOPs = 5:4.5usImpulsbreitenumNOPs = 6:5.25usImpulsbreitenumNOPs = 7:6.00usImpulsbreitenumNOPs = 8:6.75usImpulsbreitenumNOPs = 9:7.5usImpulsbreitenumNOPs = 10:8.25usImpulsbreite
Aus dieser Tabelle ist offensichtlich, dass die Formel für die Impulsbreite
$$(\text{numNOPs} + 1) \cdot 0.75us$$Warum ist es so viel langsamer als das manuelle Einfügen von NOPs? Weil die for-Schleife viele zusätzliche Befehle einschließlich Speicherladen, Vergleichen und (bedingtem) Sprung in die Maschinenbefehle für Ihr kompiliertes Programm einführt. Deshalb dauert es statt 1 Befehl der Länge 62.5ns tatsächlich 12 Befehle der Länge 750ns, um eine Iteration der Schleife abzuschließen.
Vollständiges Beispiel
#include <Arduino.h>
#include <avr/io.h>
#define PORT11 PORTB
#define PIN11 3
#define PIN11_MASK (1 << PIN11)
void setup() {
pinMode(11, OUTPUT);
}
volatile int numNOPs = 1;
void loop() {
cli();
PORT11 |= PIN11_MASK;
for (int i = 0; i < numNOPs; i++) {
_NOP();
}
PORT11 &= ~PIN11_MASK;
sei();
delay(10);
}Hier ist ein kurzes Beispiel mit NOP-Schleifen auf dem AVR, um kurze Impulse zu erzeugen:
void loop() {
// Toggle pin
PORTB |= (1 << 5);
// Small delay using NOPs
for (volatile uint8_t i=0;i<10;i++) __asm__ __volatile__("nop\n");
PORTB &= ~(1 << 5);
delay(1000);
}Assembler NOP-Beispiel:
.nop_loop:
nop
subi r24, 1
brne .nop_loop