Generación de pulsos cortos con Arduino Uno — Parte 4: bucles NOP

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:

nop_for_loop_snippet.cpp
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:

short_pulse_example.cpp
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:

Traza de osciloscopio mostrando pulso de 3250ns generado por Arduino Uno bucle NOP con variable volatile numNOPs

Medí el ancho de pulso para diferentes configuraciones de numNOPs:

  • numNOPs = 1: 1.50us de ancho de pulso
  • numNOPs = 2: 2.25us de ancho de pulso
  • numNOPs = 3: 3.00us de ancho de pulso
  • numNOPs = 4: 3.75us de ancho de pulso
  • numNOPs = 5: 4.5us de ancho de pulso
  • numNOPs = 6: 5.25us de ancho de pulso
  • numNOPs = 7: 6.00us de ancho de pulso
  • numNOPs = 8: 6.75us de ancho de pulso
  • numNOPs = 9: 7.5us de ancho de pulso
  • numNOPs = 10: 8.25us de 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

nop_loop_full_example.cpp
#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:

nop_loop_example.ino
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_asm.s
.nop_loop:
	nop
	subi r24, 1
	brne .nop_loop

Echa un vistazo a artículos similares por categoría: Arduino