DelayMicroseconds komutunu daha doğru hale getirebilir miyim?

DMX verilerini çarpmaya çalışıyorum ve bu 4us darbesi gerektiriyor. Arduino'nun gecikmekte ne kadar iyi olduğunu görmek için kontrol ettiğim sonuçlara pek şans vermemek ... Bu konuda oldukça korkunç görünüyor.

İşte yaptığım küçük bir test:

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
}

Ve sonuçlar:

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

Kesinliğin ne kadar kötü olduğu konusunda şok oldum. Geciktirmek istediğim sürenin iki katı, ancak 2'ye bölebildiğim yerle bile tutarlı değil!

Doğru ve tutarlı sonuçlar elde etmek için yapabileceğim bir şey var mı?

8
micros() dört mikrosaniyelik bir çözünürlüğe sahiptir.
katma yazar Hugo, kaynak
Doğrudan UART'a erişmek yerine neden ıskalıyorsun?
katma yazar Ignacio Vazquez-Abrams, kaynak
Mega dört UART'a sahip. Programlama için kalıcı olarak bir tane tutsanız bile, size hala üç evren verir.
katma yazar Ignacio Vazquez-Abrams, kaynak
ATmega328PB'nin iki UART'ı vardır.
katma yazar Ignacio Vazquez-Abrams, kaynak
Ayrıca, özellikle 250kbps'de birden fazla seri portun bitmesi, tüm AVR'lerden daha fazla homurdanmaya ihtiyaç duyar.
katma yazar Ignacio Vazquez-Abrams, kaynak
Buradaki sorun nasıl ölçtüğünüzdür.
katma yazar Al., kaynak
Böylece birden fazla çıktı alabilirim.
katma yazar user26642, kaynak
Sadece mega ile test ediyorum çünkü şu anda elimde olan bu, final projesinde ATMEGA328 olacak.
katma yazar user26642, kaynak

5 cevap

Önceki cevaplarda açıklandığı gibi, gerçek sorununuz delayMicroseconds() doğruluğu, ancak bunun çözünürlüğü mikrosaniye() .

Ancak, gerçek sorunuzu yanıtlamak için daha doğru bir yol var delayMicroseconds() seçeneğine alternatif: işlevinden _delay_us() işlevi AVR-libc döngüsel olarak doğrudur ve örneğin

_delay_us(1.125);

does exactly what it says. The main caveat is that the argument has to be a compile-time constant. You have to #include in order to have access to this function.

Ayrıca, istediğiniz herhangi bir kesintiyi engellemeniz gerektiğini unutmayın. doğru gecikme.

Edit: As an example, if I were to generate a 4 µs pulse on PD2 (pin 19 on the Mega), I would proceed as follows. First, notice that the following code

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

makes a 0.125 µs long pulse (2 CPU cycles), because that's the time it takes to execute the instruction that sets the port LOW. Then, just add the missing time in a delay:

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

and you have a cycle-accurate pulse width. It is worth noting that this cannot be achieved with digitalWrite(), as a call to this function takes about 5 µs.

6
katma

Test sonuçlarınız yanıltıcıdır. delayMicroseconds() aslında tam olarak oldukça geciktirir (2 veya 3 mikrosaniyeden fazla gecikmeler için). Kaynak kodunu /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c dosyasında inceleyebilirsiniz (bir Linux sisteminde veya diğer sistemlerdeki benzer bir yolda).

Ancak, micros() çözünürlüğü dört mikrosaniyedir. (Bkz. Örneğin, hakkında> code> micros() .) Dolayısıyla, 4 mikrosaniye ve 8 mikrosaniye arasında bir okuma görmeyeceksiniz. Gerçek gecikme, 4 mikrosaniyeden yalnızca birkaç devir olabilir, ancak kodunuz bunu 8 olarak bildirir.

10 veya 20 delayMicroseconds (4) yapmayı deneyin; bir satırda çağırır (bir döngü kullanarak değil, kodu kopyalayarak) ve sonra micros() sonucunu bildirir.

3
katma
Diyelim ki, 5 gecikmenin 10 gecikmesi? :) Ayrıca, bitbanging için, yürütmek için birkaç mikrosaniye alan digitalWrite() değil oldukça doğrudan bağlantı noktası erişimi kullanmanız gerekebileceğini unutmayın.
katma yazar Martin C. Martin, kaynak
"Yuvarlama" derken, delayMicroseconds() 'ı mı kastediyorsunuz? Bunu yuvarlamaktan daha iyi görmüyorum. ¶ Hataların kaynağı ile ilgili olarak, rutinin satır içi olması durumunda zamanlama çevre koduna bağlıdır. Görmek için montaj veya sökme listelerini okuyabilirsiniz. (Bkz. Arduino Mega 2560'daki PORTB için Eşdeğer Değerli , bu, yine de bitbanging projeniz için geçerli olabilir
katma yazar Martin C. Martin, kaynak
10'luk 4 gecikmeyle, 32'li ve 28'li bir karışımı alıyorum ... ama 4 * 10 = 40.
katma yazar user26642, kaynak
40 & 44 ... Ama yuvarlanması gerekmiyor mu? Burada neyi özlüyorum?
katma yazar user26642, kaynak

Arduino'nun gecikmekte ne kadar iyi olduğunu kontrol ediyorum ... Bu konuda oldukça korkunç görünüyor.

micros() has a well-documented resolution of 4 µs.

Zamanlayıcı 0 için önceden ayarlayıcıyı değiştirerek çözünürlüğü artırabilirsiniz (elbette rakamları atan ama bunu telafi edebilirsiniz).

Alternatif olarak, Zamanlayıcı 1 veya Zamanlayıcı 2'yi kullanarak, 62,5 ns'lik bir çözünürlük veren 1 ön ölçekleyiciyi kullanın.


  Serial.begin (9600);
 

Zaten yavaş olacak.


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

Çıktınız, zamanlamaya tam olarak ne zaman başladığınıza bağlı olarak bazen iki "keneler" ve bazen bir tane alacağınız gerçeğiyle birlikte, 4 µs micros() çözünürlüğü ile tamamen tutarlıdır.


Kodunuz, ilginç bir ölçüm hatası örneğidir. delayMicroseconds (4); 4 µs'ye kadar gecikecektir. Ancak bunu ölçmeye çalıştığınız girişimler hatalı.

Ayrıca, bir kesinti olursa, o zaman aralığı biraz uzatacaktır. Kesin bir gecikme istiyorsanız kesmeleri kapatmanız gerekir.

2
katma

Bir osiloskopla ölçüldüğünde şunu buldum:

delayMicroseconds(0) = delayMicroseconds(1) = 4 μs real delay.

So, if you want a 35 μs delay you need:

delayMicroseconds(31);
1
katma

Doğru ve tutarlı sonuçlar almak için yapabileceğim bir şey var mı?

Arduino uygulaması oldukça geneldir, bu nedenle bazı uygulamalarda etkili olmayabilir. Kısa gecikmelerin, her birinin kendi eksiklikleri olan birkaç yolu vardır.

  1. Nop kullanın. Her biri, bize göre 16’nın bir talimatı.

  2. Doğrudan tcnt0 kullanın. Her biri, ön ölçekleyici 64 olarak ayarlandığı için 4us'tur. Önceden ayarlayıcıyı, 16ncı çözüme ulaşmak için değiştirebilirsiniz.

  3. Keneleri kullanın, bir systick klonu uygulayabilir ve gecikmenin temeli olarak kullanabilirsiniz. Daha iyi çözünürlük ve doğruluk sunar.

Düzenle:

Çeşitli yaklaşımları zamanlamak için aşağıdaki bloğu kullandım:

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

ondan önce, zamanlayıcı0 ön ayarlayıcısını 1: 1 olarak ayarlamıştım, böylece her bir TCNT0 kene mikrosaniyenin 1/16'sı kadardı.

  1. delay4us (), NOP'dan() oluşturulmuştur; 65 kene gecikmesi veya 4usun üzerinde bir gecikme üretti;
  2. t0delayus (), zamanlayıcı0 gecikmelerinden oluşur. 77 kene gecikme üretti; delay4us()
  3. değerinden biraz daha kötü
  4. _delay_us() bir gcc-avr işlevidir. delay4us() ile eşit performans;
  5. delayMicroseconds (), 45 tene kadar gecikme üretti. arduino'nun zamanlama işlevlerini yerine getirme şekli, diğer kesintilerin olduğu bir ortamda olmadığı sürece eksik sayılma eğilimindedir.

Umarım yardımcı olur.

0
katma
Beklenen sonucun 64 değil, 64 işareti olduğunu unutmayın; çünkü TCNT0 okumak 1 CPU döngüsü alır.
katma yazar Sprogz, kaynak