使用 Arduino Uno 生成短脉冲 — 第 4 部分:NOP 循环

在我们之前的文章中,我们在直接 GPIO 寄存器访问指令之间插入 NOP 指令来创建可变脉冲宽度,可获得 62.5ns 的分辨率。

现在可以使用 for 循环创建多个 NOP 指令:

nop_for_loop_snippet.cpp
for (int i = 0; i < 3; i++)
{
    _NOP();
}

事实上,这与手动使用三个 NOP 完全相同312.5ns 脉冲宽度),因为它被编译器内联,即编译器只生成三个单独的 NOP,因为 NOP 的数量在编译时已知。

如果我们使用变量 numNOPs 并使用 volatile,这样编译器被指示不要假设它是常量:

short_pulse_example.cpp
volatile int numNOPs = 3;
for (int i = 0; i < numNOPs; i++)
{
  _NOP();
}

我们最终得到的脉冲宽度不是 312.5ns 而是 3250ns 示波器轨迹显示 Arduino Uno 使用 volatile numNOPs 变量的 NOP for 循环生成的 3250ns 脉冲

我测量了不同 numNOPs 设置的脉冲宽度:

从这个表格很明显,脉冲宽度的公式是

$$(\text{numNOPs} + 1) \cdot 0.75us$$

为什么它比手动粘贴 NOP 慢这么多?因为 for 循环在编译程序的机器指令中引入了许多额外指令,包括内存加载、比较和(条件)跳转。这就是为什么不是 1 条长度为 62.5ns 的指令,而是实际需要 12 条长度为 750ns 的指令来完成一次循环迭代。

完整示例

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);
}

这是一个在 AVR 上使用 NOP 循环生成短脉冲的简短示例:

nop_loop_example.ino
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);
}

汇编 NOP 示例:

nop_asm.s
.nop_loop:
	nop
	subi r24, 1
	brne .nop_loop

Check out similar posts by category: Arduino