Bisakah saya membuat delayMicroseconds lebih akurat?


Saya mencoba menggigit data DMX dan itu membutuhkan pulsa 4us. Tidak memiliki banyak keberuntungan dengan hasil yang saya periksa untuk melihat seberapa baik Arduino menunda ... Tampaknya sangat mengerikan.

Inilah sedikit tes cepat yang saya lakukan:

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
}

Dan hasilnya:

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

Saya agak terkejut betapa buruk akurasinya. Ini dua kali lipat waktu yang saya ingin tunda, tetapi itu bahkan tidak konsisten di mana saya bisa membagi dengan 2!

Adakah yang bisa saya lakukan untuk mendapatkan hasil yang benar dan konsisten?


Mengapa Anda menggedor bukannya mengakses UART secara langsung?
Ignacio Vazquez-Abrams

Jadi saya dapat memiliki lebih dari satu output.
bwoogie

Mega memiliki empat UART. Bahkan jika Anda memiliki satu secara permanen untuk pemrograman yang masih memberi Anda tiga semesta.
Ignacio Vazquez-Abrams

Saya hanya menguji dengan mega karena itulah yang saat ini saya miliki, tugas akhir akan memiliki ATMEGA328
bwoogie

1
Masalahnya di sini adalah bagaimana Anda mengukur.
Gerben

Jawaban:


Seperti yang dijelaskan dalam jawaban sebelumnya, masalah Anda yang sebenarnya bukan keakuratannya delayMicroseconds(), melainkan resolusi micros().

Namun, untuk menjawab Anda yang sebenarnya pertanyaan , ada alternatif yang lebih akurat untuk delayMicroseconds(): fungsi _delay_us()dari AVR-libc adalah siklus-akurat dan, misalnya

_delay_us(1.125);

melakukan persis apa yang dikatakannya. Peringatan utama adalah bahwa argumen harus berupa konstanta waktu kompilasi. Anda harus #include <util/delay.h>memiliki akses ke fungsi ini.

Perhatikan juga bahwa Anda harus memblokir interupsi jika Anda ingin jenis penundaan yang akurat.

Sunting : Sebagai contoh, jika saya menghasilkan pulsa 4 μs pada PD2 (pin 19 pada Mega), saya akan melanjutkan sebagai berikut. Pertama, perhatikan kode berikut ini

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

membuat pulsa panjang 0,125 μs (2 siklus CPU), karena itulah waktu yang diperlukan untuk menjalankan instruksi yang mengatur port LOW. Kemudian, tambahkan saja waktu yang hilang dalam penundaan:

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

dan Anda memiliki lebar pulsa yang akurat-siklus. Perlu dicatat bahwa ini tidak dapat dicapai dengan digitalWrite(), karena panggilan ke fungsi ini membutuhkan waktu sekitar 5 μs.


Hasil tes Anda menyesatkan. delayMicroseconds()sebenarnya keterlambatan cukup tepat (untuk keterlambatan lebih dari 2 atau 3 mikrodetik). Anda dapat memeriksa kode sumbernya di file /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (di sistem Linux; atau di jalur serupa di sistem lain).

Namun, resolusi micros()adalah empat mikrodetik. (Lihat, misalnya, halaman garretlab tentangmicros() .) Oleh karena itu, Anda tidak akan pernah melihat pembacaan antara 4 mikrodetik dan 8 mikrodetik. Penundaan yang sebenarnya mungkin hanya beberapa siklus selama 4 mikrodetik, tetapi kode Anda akan melaporkannya sebagai 8.

Coba lakukan 10 atau 20 delayMicroseconds(4);panggilan secara berturut-turut (dengan menduplikasi kode, bukan dengan menggunakan loop) lalu laporkan hasilnya micros().


Dengan 10 keterlambatan 4 saya mendapatkan campuran 32 dan 28 ... tapi 4 * 10 = 40.
bwoogie

Apa yang Anda dapatkan dengan, katakanlah, 10 keterlambatan 5? :) Juga perhatikan, untuk bitbanging Anda mungkin perlu menggunakan akses port yang cukup langsung, yaitu, tidak digitalWrite(), yang membutuhkan beberapa mikrodetik untuk dijalankan.
James Waldby - jwpat7

40 & 44 ... Tapi bukankah harus mengumpulkan? Apa yang kulewatkan di sini?
bwoogie

Maksud Anda “mengumpulkan” maksud Anda delayMicroseconds()? Saya tidak melihat itu lebih baik daripada membulatkan tekad. ¶ Mengenai sumber ketidaktepatan, jika rutin mendapat inline maka waktunya tergantung pada kode di sekitarnya. Anda dapat membaca daftar perakitan atau pembongkaran untuk melihat. (Lihat bagian “Membuat daftar perakitan” dalam jawaban saya untuk pertanyaan Setara untuk PORTB di Arduino Mega 2560 , yang mungkin juga relevan dengan proyek bitbanging Anda
James Waldby - jwpat7

Saya sedang memeriksa untuk melihat seberapa baik Arduino menunda ... Tampaknya sangat mengerikan.

micros()memiliki resolusi yang terdokumentasi dengan baik dari 4 μs.

Anda dapat meningkatkan resolusi dengan mengubah prescaler untuk Timer 0 (tentu saja yang membuang angka-angka, tetapi Anda dapat mengimbanginya).

Atau gunakan Timer 1 atau Timer 2 dengan prescaler 1, yang memberi Anda resolusi 62,5 ns.


 Serial.begin(9600);

Lagipula itu akan lambat.


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

Output Anda persis konsisten dengan resolusi 4 μs micros()ditambah dengan fakta bahwa kadang-kadang Anda akan mendapatkan dua "kutu" dan kadang-kadang satu, tergantung pada kapan Anda memulai pengaturan waktu.


Kode Anda adalah contoh kesalahan pengukuran yang menarik. delayMicroseconds(4);akan menunda hampir 4 μs. Namun upaya Anda untuk mengukurnya salah.

Juga, jika terjadi interupsi maka itu akan meregangkan interval sedikit. Anda perlu mematikan interupsi jika Anda ingin penundaan yang tepat.


Ketika diukur dengan osiloskop, saya menemukan bahwa:

delayMicroseconds(0)= delayMicroseconds(1)= 4 μs keterlambatan nyata.

Jadi, jika Anda menginginkan penundaan 35 μs, Anda perlu:

delayMicroseconds(31);

Adakah yang bisa saya lakukan untuk mendapatkan hasil yang benar dan konsisten?

Implementasi Arduino cukup umum sehingga mungkin tidak seefektif pada beberapa aplikasi. Ada beberapa cara untuk penundaan pendek, masing-masing dengan kekurangannya sendiri.

  1. Gunakan nop. Masing-masing adalah satu instruksi jadi ke-16 kita.

  2. Gunakan tcnt0 secara langsung. Masing-masing adalah 4us saat prescaler diatur ke 64. Anda dapat mengubah prescaker untuk mencapai resolusi ke-16 kita.

  3. Gunakan tanda centang, Anda dapat menerapkan klon systick dan menggunakannya sebagai dasar penundaan. Ini menawarkan resolusi yang lebih baik plus akurasi.

edit:

Saya menggunakan blok berikut untuk mengatur waktu berbagai pendekatan:

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

sebelum itu, saya telah mengatur ulang timer0 prescaler ke 1: 1 sehingga setiap centang TCNT0 adalah 1/16 mikrodetik.

  1. delay4us () dibangun dari NOP (); itu menghasilkan penundaan 65 ticks, atau hanya lebih dari 4us;
  2. t0delayus () dibangun dari penundaan timer0. itu menghasilkan penundaan 77 kutu; sedikit lebih buruk daripada delay4us ()
  3. _delay_us () adalah fungsi gcc-avr. kinerja setara dengan delay4us ();
  4. delayMicroseconds () menghasilkan penundaan 45 ticks. cara arduino menerapkan fungsi-fungsi pengaturan waktunya, ia cenderung tidak menghitung, kecuali dalam lingkungan dengan gangguan lain.

semoga membantu.


Perhatikan bahwa hasil yang diharapkan adalah 65 ticks, not 64, karena membaca TCNT0membutuhkan 1 siklus CPU.
Edgar Bonet
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.