我可以使delayMicroseconds更准确吗?


8

我正在尝试爆炸DMX数据,这需要4us脉冲。我正在检查结果是否运气不佳,以查看Arduino在延迟方面的表现如何……似乎非常糟糕。

我做了一个快速的小测试:

unsigned long ptime;

void setup() {
  Serial.begin(9600);
}

void loop() {
  ptime = micros();
  delayMicroseconds(4);
  Serial.println(micros() - ptime);

  delay(1000); //just to keep the serial monitor from going nuts
}

结果:

8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 4 8 8 8 4 8 4

我对它的准确性有多差感到震惊。这是我想延迟的时间的两倍,但是甚至与我只能除以2的位置也不相符!

我能做些什么以获得正确,一致的结果?


为什么为什么要位冲击而不是直接访问UART?
伊格纳西奥·巴斯克斯

所以我可以有多个输出。
bwoogie

Mega具有四个UART。即使您永久持有一个用于编程的程序,仍然可以得到三个Universe。
伊格纳西奥·巴斯克斯

我正在测试大型设备,因为这是我目前可用的设备,最终项目将具有ATMEGA328
bwoogie 17-4-22

1
这里的问题是如何衡量。
Gerben

Answers:


7

如前面的答案所述,您的实际问题不是的准确性delayMicroseconds(),而是的分辨率 micros()

但是,要回答您的实际问题,还有一种更准确的替代方法delayMicroseconds()_delay_us()AVR-libc中的函数具有周期精确性,例如

_delay_us(1.125);

完全按照说的去做。主要警告是该参数必须是编译时常量。您必须#include <util/delay.h>具有访问此功能的权限。

还请注意,如果您需要任何类型的准确延迟,则必须阻止中断。

编辑:作为示例,如果我要在PD2上生成4 µs脉冲(Mega上的引脚19),我将按照以下步骤进行操作。首先,请注意以下代码

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

产生一个0.125 µs的长脉冲(2个CPU周期),因为这是执行设置端口指令的时间LOW。然后,只需延迟增加丢失的时间即可:

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
_delay_us(3.875);
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

并且您具有周期精确的脉冲宽度。值得注意的是,用不能实现这一点digitalWrite(),因为对该函数的调用大约需要5 µs。


3

您的测试结果具有误导性。 delayMicroseconds()实际上延迟相当精确(延迟超过2或3微秒)。您可以在文件/usr/share/arduino/hardware/arduino/cores/arduino/wiring.c中检查其源代码(在Linux系统上;或在其他系统上的类似路径上)。

但是,分辨率micros()为4微秒。(例如,请参阅有关的garretlab页面micros()。)因此,您将永远不会看到4微秒至8微秒之间的读数。实际的延迟可能只是4微秒内的几个周期,但是您的代码会将其报告为8。

尝试delayMicroseconds(4);连续执行10或20次调用(通过复制代码,而不是使用循环),然后报告的结果 micros()


随着4 10个延迟我得到的32年代和28级的...但4 * 10 = 40的混合
bwoogie

您说10次延迟5次会得到什么?:)还请注意,对于位banging,您可能需要使用相当直接的端口访问,即not digitalWrite(),这需要花费几微秒的时间才能执行。
詹姆斯·沃尔德比-jwpat7

40&44 ...但不应该舍入吗?我在这里想念什么?
bwoogie

“围捕”是什么意思delayMicroseconds()?我认为这比四舍五入更好。¶关于错误的根源,如果例程被内联,则时间取决于周围的代码。您可以阅读汇编或反汇编清单进行查看。(请参阅我对Arduino Mega 2560中PORTB的等效问题的回答中的 “生成装配体列表”部分,无论如何这可能与您的敲打项目有关
James Waldby-jwpat7 '17

2

我正在检查Arduino在延迟方面的表现如何……似乎非常糟糕。

micros()据可查的4 µs分辨率。

您可以通过更改定时器0的预分频器来提高分辨率(当然这会抛出数字,但是您可以对此进行补偿)。

或者,使用定时器1或定时器2的预分频比为1,则分辨率为62.5 ns。


 Serial.begin(9600);

无论如何这都会很慢。


8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 4 8 8 8 4 8 4

您的输出与4 µs分辨率完全一致,micros()并且有时您会得到两次“滴答声”,有时还会收到“滴答声”,具体取决于您何时开始计时。


您的代码是一个有趣的测量误差示例。delayMicroseconds(4);将延迟接近4 µs。但是,您尝试测量它的方法是错误的。

另外,如果发生中断,则间隔会延长一点。如果需要精确的延迟,则需要关闭中断。


1

用示波器测量时,我发现:

delayMicroseconds(0)= delayMicroseconds(1)= 4μs实际延迟。

因此,如果要延迟35μs,则需要:

delayMicroseconds(31);

0

我能做些什么以获得正确,一致的结果?

Arduino实现非常通用,因此在某些应用程序中可能效果不佳。有几种方法可以缩短延迟时间,每种方法都有其不足之处。

  1. 使用nop。每个指令只有一个指令的十六分之一。

  2. 直接使用tcnt0。当预分频器设置为64时,每个为4us。您可以更改预分频器以达到16 us分辨率。

  3. 使用刻度线,您可以实现systick的克隆并将其用作延迟的基础。它提供了更高的分辨率和精度。

编辑:

我使用以下代码块来计时各种方法:

time0=TCNT0;
delay4us();             //65
//t0delayus(4*16);          //77
//_delay_us(4);             //65
//delayMicroseconds(4);     //45
time1=TCNT0 - time0;        //64 expected

在此之前,我已将timer0预分频器重置为1:1,因此每个TCNT0滴答是1/16微秒。

  1. delay4us()由NOP()建立;它产生了65个滴答声的延迟,或刚好超过4us;
  2. t0delayus()是由timer0延迟建立的。它产生了77个滴答声的延迟;比delay4us()稍差
  3. _delay_us()是gcc-avr函数。性能与delay4us()相当;
  4. delayMicroseconds()产生了45个滴答声的延迟。除非在有其他中断的环境中使用,否则arduino实现其计时功能的方式往往会计数不足。

希望能帮助到你。


请注意,预期结果是65个滴答声,而不是64个滴答声,因为读取TCNT0需要1个CPU周期。
Edgar Bonet
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.