Fungsi millis
akan berjalan dalam rentang 100+ mikrodetik atau kurang. Apakah ada cara yang dapat diandalkan untuk mengukur waktu yang diambil dengan satu panggilan millis?
Salah satu pendekatan yang muncul dalam pikiran adalah menggunakan micros
, bagaimanapun, panggilan untuk micros
akan mencakup waktu yang diambil oleh panggilan fungsi micros
itu sendiri juga, jadi tergantung pada berapa lama mik, pengukuran untuk millis
mungkin tidak aktif.
Saya perlu menemukan ini sebagai aplikasi yang saya kerjakan membutuhkan pengukuran waktu yang akurat untuk setiap langkah yang diambil dalam kode, termasuk millis
.
miilis
berlangsung.Jawaban:
Jika Anda ingin tahu persis berapa lama sesuatu akan terjadi, hanya ada satu solusi: Lihatlah pembongkaran!
Dimulai dengan kode minimal:
Kode ini dikompilasi dan kemudian dimasukkan ke dalam
avr-objdump -S
menghasilkan pembongkaran yang didokumentasikan. Berikut petikannya yang menarik:void loop()
menghasilkan:Yang merupakan panggilan fungsi (
call
), empat salinan (yang menyalin masing-masing byte dalam nilaiuint32_t
pengembalianmillis()
(perhatikan bahwa dokumen arduino menyebutnya sebagai along
, tetapi tidak benar untuk tidak secara eksplisit menentukan ukuran variabel)), dan akhirnya fungsi kembali.call
membutuhkan 4 siklus clock, dan masing-masingsts
membutuhkan 2 siklus clock, jadi kami memiliki minimal 12 siklus clock hanya untuk panggilan fungsi overhead.Sekarang, mari kita lihat pembongkaran
<millis>
fungsi, yang terletak di0x14e
:Seperti yang Anda lihat,
millis()
fungsinya cukup sederhana:in
menyimpan pengaturan register interupsi (1 siklus)cli
mematikan interupsi (1 siklus)lds
salin salah satu dari 4 byte dari nilai saat ini dari penghitung mili ke dalam register sementara (2 siklus clock)lds
Byte 2 (2 siklus jam)lds
Byte 3 (2 siklus jam)lds
Byte 4 (2 siklus jam)out
pulihkan pengaturan interupsi (1 siklus jam)movw
register acak sekitar (siklus 1 jam)movw
dan lagi (1 clock cycle)ret
kembali dari subrutin (4 siklus)Jadi, jika kita menambahkan semuanya, kita memiliki total 17 siklus clock dalam
millis()
fungsi itu sendiri, ditambah panggilan overhead 12, dengan total 29 siklus clock.Dengan asumsi clock rate 16 Mhz (kebanyakan arduinos), setiap siklus clock adalah
1 / 16e6
detik, atau 0,0000000625 detik, yaitu 62,5 nanoseconds. 62,5 ns * 29 = 1,812 mikrodetik.Oleh karena itu, total waktu eksekusi untuk satu
millis()
panggilan di sebagian besar Arduino adalah 1.812 mikrodetik .Referensi Majelis AVR
Sebagai catatan tambahan, ada ruang untuk optimasi di sini! Jika Anda memperbarui
unsigned long millis(){}
definisi fungsi menjadiinline unsigned long millis(){}
, Anda akan menghapus overhead panggilan (dengan biaya ukuran kode yang sedikit lebih besar). Lebih jauh lagi, sepertinya kompiler sedang melakukan dua gerakan yang tidak perlu (duamovw
panggilan, tapi saya belum melihatnya dari dekat).Sungguh, mengingat overhead panggilan fungsi adalah 5 instruksi, dan isi sebenarnya dari
millis()
fungsi hanya 6 instruksi, saya pikirmillis()
fungsi harus benar-benar secarainline
default, tetapi basis kode Arduino agak dioptimalkan dengan buruk.Inilah pembongkaran penuh bagi siapa pun yang tertarik:
sumber
sts
tidak boleh dihitung sebagai panggilan overhead: ini adalah biaya untuk menyimpan hasil dalam variabel volatil, yang biasanya tidak Anda lakukan. 2) Di sistem saya (Arduino 1.0.5, gcc 4.8.2), saya tidak punyamovw
. Maka biaya panggilanmillis()
adalah: 4 siklus overhead panggilan + 15 siklusmillis()
itu sendiri = 19 siklus total (≈ 1,188 μs @ 16 MHz).x
adalahuint16_t
. Seharusnya paling banyak 2 salinan jika itu penyebabnya. Bagaimanapun, pertanyaannya adalah berapa lamamillis()
waktu yang digunakan , bukan ketika dipanggil saat mengabaikan hasilnya. Karena setiap penggunaan praktis akan melibatkan melakukan sesuatu dengan hasilnya, saya memaksakan hasilnya disimpan melaluivolatile
. Biasanya, efek yang sama akan dicapai dengan penggunaan variabel di kemudian hari yang diatur ke nilai balik panggilan, tetapi saya tidak ingin panggilan ekstra itu mengambil ruang dalam jawabannya.uint16_t
dalam sumber tidak sesuai perakitan (4 byte disimpan ke dalam RAM). Anda mungkin memposting sumber dan membongkar dua versi berbeda.Tulis sketsa yang milis 1000 kali, bukan dengan membuat lingkaran, tetapi dengan menyalin dan menempel. Ukur itu dan bandingkan dengan waktu yang diharapkan sebenarnya. Pikiran Anda bahwa hasil dapat bervariasi dengan berbagai versi IDE (dan kompilernya pada khususnya).
Pilihan lain adalah untuk beralih pin IO sebelum dan sesudah panggilan millis, kemudian mengukur waktu untuk nilai yang sangat kecil dan nilai yang agak lebih besar. Bandingkan timing yang diukur dan hitung overhead.
Cara paling akurat adalah dengan melihat daftar pembongkaran, kode yang dihasilkan. Tetapi itu bukan untuk orang yang lemah hati. Anda harus mempelajari lembar data dengan cermat berapa lama setiap siklus instruksi berlangsung.
sumber
millis()
panggilan?delay
, Anda benar. Tapi idenya tetap sama, Anda dapat mengatur waktu sejumlah besar panggilan dan rata-rata. Mematikan interupsi secara global mungkin bukan ide yang sangat bagus; o)Saya kedua memanggil millis berulang kali dan kemudian membandingkan aktual vs yang diharapkan.
Akan ada beberapa overhead minimal, tetapi akan berkurang secara signifikan semakin sering Anda menelepon millis ().
Jika Anda melihat
Anda dapat melihat bahwa millis () sangat kecil dengan hanya 4 instruksi
(cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))
dan pengembalian.Saya akan menyebutnya sekitar 10 juta kali menggunakan loop FOR yang memiliki volatile sebagai kondisional. Kata kunci yang mudah menguap akan mencegah kompiler dari mencoba optimasi apa pun pada loop itu sendiri.
Saya tidak menjamin yang berikut ini sempurna secara sintaksis ..
Dugaan saya adalah yang membutuhkan ~ 900ms atau sekitar 56us per panggilan ke millis. (Saya tidak punya ATM berguna aruduino.
sumber
int temp1,temp2;
kevolatile int temp1,temp2;
untuk mencegah kompiler dari berpotensi mengoptimalkan mereka.