waktu penundaan); vs if (millis () - sebelumnya> waktu); dan melayang

8

Melalui proyek lama, saya memiliki kode pada dua Arduino Due yang terlihat seperti ini

void loop()
{
  foo();
  delay(time);
}

mengambil hati sebagian dari literatur menggunakan delay();saya recoded ini sebagai

void loop()
{
  static unsigned long PrevTime;
  if(millis()-PrevTime>time)
  {
    foo();
    PrevTime=millis();
  }
}

Namun, ini tampaknya telah menciptakan situasi di mana kedua perangkat melayang selama periode waktu yang sebelumnya tidak

Pertanyaan saya ada dua:

  1. Mengapa if(millis()-PrevTime>time)menyebabkan lebih banyak penyimpangan daripada delay(time)?
  2. Apakah ada cara untuk mencegah penyimpangan ini tanpa kembali ke delay(time)?
ATE-ENGE
sumber
1
Apa urutan besarnya "periode waktu" di mana Anda melihat penyimpangan? Apakah kedua perangkat berada di lokasi yang sama, sehingga pada suhu yang sama? Apakah mereka berjalan pada osilator kristal atau resonator keramik?
jose can uc
Secara pribadi saya lebih suka solusi Majenko, dan selalu menggunakannya (saya meletakkan kenaikan sebelum instruksi lain, tapi ini hanya pilihan). Perhatikan, bagaimanapun, bahwa waktu ini PRECISELY 100ms, sedangkan kode lainnya ( foo; delay;) memiliki periode lebih lama dari 100ms (100ms + waktu foo). Jadi Anda akan mengalami drift (tetapi delaySW yang diimplementasikan adalah drifting). Dalam kasus apa pun, perlu diingat bahwa implementasi yang sama sempurna sekalipun "melayang", karena jam tidak sama; jika Anda membutuhkan sinkronisasi lengkap gunakan sinyal untuk, yah, sinkronkan kedua program.
frarugi87
Kedua perangkat bersebelahan, setelah berjalan dari Jumat pukul 17.00 hingga Senin pukul 09.00 terjadi penyimpangan 4 menit. Saya memutuskan bahwa saya akan menggunakan pin digital untuk menyinkronkan input sesuai saran Anda
ATE-ENGE
"Kedua perangkat itu bersebelahan, ..." itu tidak berarti mekanisme pengaturan waktunya tidak akurat Anda berbicara tentang tingkat kesalahan ~ 800ppm, tinggi untuk dua osilator kristal tetapi masuk akal untuk satu resonator keramik sekalipun. Anda harus membandingkannya dengan standar waktu yang cukup akurat untuk memastikan: kristal biasanya dalam 20ppm, dan tcxo dapat melakukan sub 1ppm. itu akan menjadi cara saya melakukannya.
dannyf

Jawaban:

10

Ada satu hal penting yang perlu Anda ingat ketika bekerja dengan waktu pada Arudino dalam bentuk apa pun:

  • Setiap operasi membutuhkan waktu.

Fungsi foo () Anda akan membutuhkan waktu. Apa waktu itu, kita tidak bisa mengatakan.

Cara yang paling dapat diandalkan untuk menangani waktu adalah dengan hanya mengandalkan waktu untuk memicu, bukan untuk berolahraga ketika pemicu berikutnya seharusnya.

Misalnya, ambil yang berikut ini:

if (millis() - last > interval) {
    doSomething();
    last = millis();
}

Variabelnya lastadalah waktu yang dipicu oleh rutin * ditambah waktu yang doSomethingdiperlukan untuk menjalankan. Jadi katakanlah interval100, dan doSomethingbutuh 10ms untuk dijalankan, Anda akan mendapatkan pemicu pada 101ms, 212ms, 323ms, dll. Bukan 100ms yang Anda harapkan.

Jadi satu hal yang dapat Anda lakukan adalah selalu menggunakan waktu yang sama tanpa menghiraukannya pada saat yang sama (seperti yang Juraj sarankan):

uint32_t time = millis();

if (time - last > interval) {
    doSomething();
    last = time;
}

Sekarang waktu yang doSomething()diperlukan tidak akan berpengaruh apa pun. Jadi Anda akan mendapatkan pemicu pada 101ms, 202ms, 303ms, dll. Masih tidak cukup 100ms yang Anda inginkan - karena Anda mencari lebih dari 100ms yang telah berlalu - dan itu berarti 101ms atau lebih. Sebaliknya, Anda harus menggunakan >=:

uint32_t time = millis();

if (time - last >= interval) {
    doSomething();
    last = time;
}

Sekarang, dengan asumsi bahwa tidak ada hal lain yang terjadi di loop Anda, Anda mendapatkan pemicu pada 100ms, 200ms, 300ms, dll. Tetapi perhatikan bahwa bit: "selama tidak ada yang lain terjadi di loop Anda" ...

Apa yang terjadi jika operasi yang membutuhkan waktu 5 ms terjadi pada 99ms ...? Pemicuan Anda berikutnya akan ditunda hingga 104 ms. Itu sebuah penyimpangan. Tapi mudah untuk bertarung. Alih-alih mengatakan "Waktu yang direkam sekarang" Anda mengatakan "Waktu yang direkam 100ms lebih lambat dari itu". Itu berarti bahwa tidak peduli apa pun keterlambatan yang Anda dapatkan dalam kode Anda, pemicu Anda akan selalu berada pada interval 100 ms, atau melayang dalam centang 100 ms.

if (millis() - last >= interval) {
    doSomething();
    last += interval;
}

Sekarang Anda akan mendapatkan pemicu pada 100ms, 200ms, 300ms, dll. Atau jika ada keterlambatan dalam bit kode lain Anda mungkin mendapatkan 100ms, 204ms, 300ms, 408ms, 503ms, 600ms, dll. Selalu mencoba untuk menjalankannya sedekat mungkin dengan Interval mungkin terlepas dari penundaan. Dan jika Anda memiliki penundaan yang lebih besar dari interval itu, maka secara otomatis akan menjalankan rutinitas Anda cukup untuk mengejar waktu saat ini.

Sebelum Anda tertidur . Sekarang Anda memiliki jitter .

Majenko
sumber
1

Karena Anda mengatur ulang timer setelah operasi.

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}
Juraj
sumber
tidak. perhatikan bahwa PrevTime bersifat statis.
dannyf
4
@dannyf, ya, dan?
Juraj
jika Anda tahu apa artinya "statis", Anda akan tahu mengapa jawaban Anda tidak benar.
dannyf
Saya tahu apa yang statis lakukan dengan variabel lokal .. Saya tidak mengerti mengapa Anda berpikir jawaban saya ada hubungannya dengan statis. Saya baru saja memindahkan pembacaan milis saat ini sebelum foo () dipanggil.
Juraj
-1

Untuk apa yang Anda coba lakukan, delay () adalah cara yang tepat untuk mengimplementasikan kode. Alasan Anda ingin menggunakan if (millis ()) adalah jika Anda ingin mengizinkan loop utama untuk terus loop sehingga kode Anda atau kode lain di luar loop itu dapat melakukan pemrosesan lainnya.

Sebagai contoh:

long next_trigger_time = 0L;
long interval = DESIRED_INTERVAL;

void loop() {
   do_something(); // check a sensor or value set by an interrupt
   long m = millis();
   if (m >= next_trigger_time) {
       next_trigger_time = m + interval;
       foo();
   }
}

Ini akan menjalankan foo () pada interval yang ditentukan sambil membiarkan loop terus berjalan di antaranya. Saya meletakkan perhitungan next_trigger_time sebelum panggilan ke foo () untuk membantu meminimalkan penyimpangan, tetapi itu tidak bisa dihindari. Jika penyimpangan merupakan masalah yang signifikan, gunakan penghenti waktu atau semacam sinkronisasi jam / penghitung waktu. Juga ingat bahwa millis () akan membungkus setelah beberapa periode waktu dan saya tidak memperhitungkan itu untuk menjaga contoh kode sederhana.

Pekerjaan Itu
sumber
Benci untuk menyebutkan ini: masalah rollover dalam 52 hari.
Saya sudah menyebutkan masalah rollover di akhir jawaban saya.
ThatAintWorking
Baiklah, selesaikan itu.
Biaya konsultasi standar saya $ 100 / jam jika Anda ingin saya menulis kode untuk Anda. Saya pikir apa yang saya tulis cukup relevan.
ThatAintWorking
1
Apakah Anda sadar bahwa Majenko memposting jawaban yang lebih lengkap dan lebih baik dari milik Anda? Apakah Anda sadar bahwa kode Anda tidak dikompilasi? Itu long m - millis()tidak melakukan apa yang ingin Anda lakukan? Itu di rumah.
-4

kode Anda benar.

masalah yang Anda hadapi adalah dengan millis (): itu akan sedikit di bawah hitungan (under-count maksimum hanya sedikit 1ms, per doa).

solusinya adalah dengan kutu yang lebih baik, seperti micros () - tetapi itu juga akan sedikit di bawah hitungan.

dannyf
sumber
2
Tolong, berikan beberapa bukti atau referensi untuk " itu akan kurang dihitung sedikit ".
Edgar Bonet