Menggunakan millis () dan micros () di dalam rutinitas interupsi

13

Dokumentasi untuk attachInterrupt()mengatakan:

... millis()bergantung pada interupsi untuk menghitung, sehingga tidak akan bertambah dalam ISR. Karena delay()membutuhkan interupsi untuk bekerja, itu tidak akan berfungsi jika dipanggil di dalam ISR. micros()bekerja pada awalnya, tetapi akan mulai berperilaku tidak menentu setelah 1-2 ms. ...

Bagaimana micros()perbedaannya millis()(kecuali tentu saja untuk ketepatannya)? Apakah peringatan di atas berarti bahwa menggunakan micros()rutinitas interupsi selalu merupakan ide yang buruk?

Konteks - Saya ingin mengukur hunian pulsa rendah , jadi saya perlu memicu rutinitas saya ketika sinyal input saya berubah dan mencatat waktu saat ini.

Petr Pudlák
sumber

Jawaban:

16

Jawaban lainnya sangat bagus, tetapi saya ingin menguraikan cara micros()kerjanya. Ini selalu membaca timer hardware saat ini (mungkin TCNT0) yang terus-menerus diperbarui oleh perangkat keras (pada kenyataannya, setiap 4 mikrodetik karena prescaler dari 64). Ini kemudian menambahkan penghitung limpahan Timer 0, yang diperbarui oleh penghentian overflow timer (dikalikan dengan 256).

Dengan demikian, bahkan di dalam ISR, Anda dapat mengandalkan micros()pembaruan. Namun jika Anda menunggu terlalu lama maka Anda melewatkan pembaruan melimpah, dan kemudian hasilnya dikembalikan akan turun (yaitu Anda akan mendapatkan 253, 254, 255, 0, 1, 2, 3 dll.)

Ini micros()- sedikit disederhanakan untuk menghapus definisi untuk prosesor lain:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

Kode di atas memungkinkan untuk overflow (memeriksa bit TOV0) sehingga dapat mengatasi overflow sementara interupsi mati tetapi hanya sekali - tidak ada ketentuan untuk menangani dua overflow.


TLDR;

  • Jangan lakukan penundaan di dalam ISR
  • Jika Anda harus melakukannya, Anda dapat menggunakan waktu micros()tetapi tidak millis(). Juga delayMicroseconds()kemungkinan.
  • Jangan tunda lebih dari 500 µs atau lebih, atau Anda akan melewatkan waktu yang berlebihan.
  • Bahkan penundaan singkat dapat menyebabkan Anda kehilangan data serial yang masuk (pada 115200 baud Anda akan mendapatkan karakter baru setiap 87 μs).
Nick Gammon
sumber
Tidak dapat memahami pernyataan yang diberikan di bawah ini yang digunakan dalam micros (). Bisakah Anda jelaskan? Sejak ISR ditulis, bendera TOV0 akan dihapus segera setelah ISR dimasukkan dan karenanya kondisi di bawah ini mungkin tidak menjadi kenyataan !. if ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Rajesh
micros()bukan ISR. Ini adalah fungsi normal. Bendera TOV0 memungkinkan Anda menguji perangkat keras untuk melihat apakah pelimpah waktu telah terjadi (tetapi belum diproses).
Nick Gammon
Karena tes dilakukan dengan interupsi, Anda tahu bahwa bendera tidak akan berubah selama tes.
Nick Gammon
Jadi Anda bermaksud mengatakan setelah fungsi cli () di dalam micros (), jika ada overflow terjadi, yang perlu diperiksa? Masuk akal. Kalau tidak, meskipun micros bukan fungsi, TIMER0_OVF_vect ISR akan menghapus TOV0 di TIFR0 adalah keraguan saya.
Rajesh
Juga mengapa TCNT0 dibandingkan dengan 255 untuk melihat apakah itu kurang dari 255? Setelah cli () jika TCNT0 mencapai 255 apa yang akan terjadi?
Rajesh
8

Tidak salah menggunakan millis()atau micros()dalam rutinitas interupsi.

Ini adalah salah untuk menggunakannya secara tidak benar.

Hal utama di sini adalah saat Anda berada dalam rutinitas interupsi "jam tidak berdetak". millis()dan micros()tidak akan berubah (well, micros()awalnya akan, tetapi begitu melewati titik milidetik ajaib di mana centang milidetik diperlukan, semuanya akan berantakan.)

Jadi Anda tentu dapat menelepon millis()atau micros()mencari tahu waktu saat ini dalam ISR Anda, tetapi jangan berharap waktu itu berubah.

Adalah kurangnya perubahan waktu yang diperingatkan dalam kutipan yang Anda berikan. delay()mengandalkan millis()perubahan untuk mengetahui berapa banyak waktu yang telah berlalu. Karena itu tidak berubah delay()tidak pernah bisa selesai.

Jadi intinya millis()dan micros()akan memberi tahu Anda saat ISR Anda dipanggil, tidak masalah ketika di ISR ​​Anda menggunakannya.

Majenko
sumber
3
Tidak, micros()pembaruan. Selalu membaca register pengatur waktu perangkat keras.
Nick Gammon
4

Ungkapan yang dikutip bukanlah peringatan, itu hanya pernyataan tentang bagaimana segala sesuatu bekerja.

Secara intrinsik tidak ada yang salah dengan menggunakan millis()atau micros()dalam rutinitas interupsi yang ditulis dengan benar.

Di sisi lain, melakukan apa saja dalam rutinitas interupsi yang ditulis dengan tidak benar secara definisi salah.

Rutin interupsi yang membutuhkan lebih dari beberapa mikrodetik untuk melakukan tugasnya, kemungkinan besar, ditulis secara tidak benar.

Singkatnya: Rutin interupsi yang ditulis dengan benar tidak akan menyebabkan atau menghadapi masalah dengan millis()atau micros().

Sunting: Mengenai "mengapa micros ()" mulai berperilaku tidak menentu "", seperti yang dijelaskan dalam laman " pemeriksaan fungsi mikro Arduino ", micros()kode pada Uno biasa secara fungsional setara dengan

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

Ini mengembalikan empat byte unsigned long yang terdiri dari tiga byte terendah dari timer0_overflow_countdan satu byte dari register penghitung timer-0.

Ini timer0_overflow_countbertambah sekitar satu kali per milidetik oleh TIMER0_OVF_vectinterrupt handler, seperti yang dijelaskan dalam pemeriksaan halaman web fungsi arduino millis .

Sebelum pengendali interupsi dimulai, perangkat keras AVR menonaktifkan interupsi. Jika (misalnya) interrupt handler dijalankan selama lima milidetik dengan interupsi masih dinonaktifkan, setidaknya empat timer 0 overflow akan terlewatkan. [Interupsi yang ditulis dalam kode C dalam sistem Arduino bukan reentrant (mampu menangani dengan benar beberapa eksekusi yang tumpang tindih dalam handler yang sama) tetapi orang dapat menulis handler bahasa assembly reentrant yang mengaktifkan kembali interupsi sebelum memulai proses yang memakan waktu.]

Dengan kata lain, kelebihan waktu tidak "menumpuk"; setiap kali overflow terjadi sebelum interupsi dari overflow sebelumnya telah ditangani, millis()penghitung kehilangan milidetik, dan perbedaan timer0_overflow_countpada gilirannya membuat micros()kesalahan dengan milidetik juga.

Mengenai "lebih pendek dari 500 μs" sebagai batas waktu atas untuk pemrosesan interupsi, "untuk mencegah pemblokiran timer terlalu lama", Anda bisa naik hingga di bawah 1024 μs (misalnya 1020 μs) dan millis()masih akan berfungsi, sebagian besar waktu. Namun, saya menganggap interrupt handler yang mengambil lebih dari 5 μs sebagai sluggard, lebih dari 10 μs sebagai slothful, lebih dari 20 μs sebagai seperti siput.

James Waldby - jwpat7
sumber
Bisakah Anda menjelaskan lebih lanjut tentang "cara kerja", khususnya mengapa micros()"mulai berperilaku tidak menentu"? Dan apa yang Anda maksud dengan "rutin interupsi yang ditulis dengan benar"? Saya berasumsi itu berarti "lebih pendek dari 500us" (untuk mencegah pemblokiran penghitung waktu terlalu lama), "menggunakan variabel volatil untuk komunikasi" dan "tidak memanggil kode perpustakaan" sebanyak mungkin, apakah ada hal lain?
Petr Pudlák
@ PetrPudlák, lihat sunting
James Waldby - jwpat7