En nuestro post anterior, usamos instrucciones NOP insertadas entre instrucciones de acceso directo a registros GPIO para crear anchos de pulso variables con resoluciones obtenibles de 62.5ns.
Ahora se podría usar un bucle for para crear múltiples instrucciones NOP:
for (int i = 0; i < 3; i++)
{
_NOP();
}De hecho, esto funciona exactamente igual que usar tres NOPs manualmente (312.5ns de ancho de pulso) porque es inlineado por el compilador, es decir, el compilador simplemente genera tres NOPs separados ya que el número de NOPs es conocido en tiempo de compilación.
Si en cambio usáramos una variable numNOPs y usáramos volatile para que se le indique al compilador que no asuma que es constante:
volatile int numNOPs = 3;
for (int i = 0; i < numNOPs; i++)
{
_NOP();
}terminaremos con un ancho de pulso no de 312.5ns sino de 3250ns:
Medí el ancho de pulso para diferentes configuraciones de numNOPs:
numNOPs = 1:1.50usde ancho de pulsonumNOPs = 2:2.25usde ancho de pulsonumNOPs = 3:3.00usde ancho de pulsonumNOPs = 4:3.75usde ancho de pulsonumNOPs = 5:4.5usde ancho de pulsonumNOPs = 6:5.25usde ancho de pulsonumNOPs = 7:6.00usde ancho de pulsonumNOPs = 8:6.75usde ancho de pulsonumNOPs = 9:7.5usde ancho de pulsonumNOPs = 10:8.25usde ancho de pulso
De esta tabla es obvio que la fórmula para el ancho de pulso es
$$(\text{numNOPs} + 1) \cdot 0.75us$$¿Por qué es mucho más lento que pegar NOPs manualmente? Porque el bucle for introduce muchas instrucciones adicionales incluyendo carga de memoria, comparación y salto (condicional) en las instrucciones de máquina para tu programa compilado. Es por eso que en lugar de 1 instrucción de longitud 62.5ns en realidad toma 12 instrucciones de longitud 750ns para completar una iteración del bucle.
Ejemplo completo
#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);
}Aquí un ejemplo corto usando bucles NOP en el AVR para generar pulsos cortos:
void loop() {
// Conmutar pin
PORTB |= (1 << 5);
// Pequeño retraso usando NOPs
for (volatile uint8_t i=0;i<10;i++) __asm__ __volatile__("nop\n");
PORTB &= ~(1 << 5);
delay(1000);
}Ejemplo de NOP en assembly:
.nop_loop:
nop
subi r24, 1
brne .nop_loop