Mengapa Arduino begitu lambat dan apa yang bisa dilakukan untuk itu

LOGO


Suatu ketika saya menemukan artikel yang sangat bagus ( tyk ) - di dalamnya penulis cukup jelas menunjukkan perbedaan antara menggunakan fungsi Arduino dan bekerja dengan register. Banyak artikel telah ditulis, baik memuji Arduino dan mengklaim bahwa ini semua sembrono dan umumnya untuk anak-anak, jadi kami tidak akan mengulangi diri kami sendiri, tetapi cobalah untuk mencari tahu apa yang menyebabkan hasil yang diperoleh oleh penulis artikel itu. Dan, sama pentingnya, kita akan memikirkan apa yang bisa kita lakukan. Siapa pun yang tertarik, saya bertanya di bawah kucing.


Bagian 1 "Pertanyaan"


Mengutip penulis artikel ini:


Ternyata kehilangan kinerja dalam kasus ini - 28 kali. Tentu saja, ini tidak berarti arduino bekerja 28 kali lebih lambat, tetapi saya pikir untuk kejelasan, ini adalah contoh terbaik dari apa yang tidak disukai Arduino.

Karena artikel baru saja dimulai, kita belum akan mengerti, tetapi akan mengabaikan kalimat kedua dan menganggap bahwa kecepatan pengontrol kira-kira setara dengan frekuensi switching pin. Yaitu kita dihadapkan pada tugas membuat generator dengan frekuensi tertinggi dari apa yang kita miliki. Pertama, mari kita lihat seberapa buruk semuanya.


Kami akan menulis program sederhana untuk Arduino (pada kenyataannya, cukup salin blink).


void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1); // turn the LED on (HIGH is the voltage level) digitalWrite(13, 0); // turn the LED off by making the voltage LOW } 

Jahit pengontrol. Karena saya tidak memiliki osiloskop, tetapi hanya penganalisis logika Cina, itu perlu dikonfigurasi dengan benar. Frekuensi analisa maksimum adalah 24 MHz, oleh karena itu, frekuensi tersebut harus disamakan dengan frekuensi pengontrol - yang diatur ke 16 MHz. Kami melihat ...


Test_1


... untuk waktu yang lama. Kami mencoba mengingat kecepatan pengontrol tergantung - tepatnya, frekuensi. Kami mencari di arduino.cc . Clock Speed ​​adalah 16 MHz, dan di sini kami memiliki 145,5 kHz. Apa yang harus dilakukan Mari kita coba menyelesaikannya di dahi. Pada arduino.cc yang sama kita melihat bagian papan:


  • Leonardo - tidak cocok - ada juga 16 MHz
  • Mega - juga - 16 MHz
  • 101 - akan dilakukan - 32MHz
  • DUE - Even Better - 84 MHz

Dapat diasumsikan bahwa jika Anda meningkatkan frekuensi pengontrol sebanyak 2 kali, maka frekuensi kedip LED juga akan meningkat 2 kali, dan jika 5, kemudian 5 kali.


Test_2


Kami tidak mendapatkan hasil yang diinginkan. Dan generatornya semakin sedikit seperti berkelok-kelok. Kami berpikir lebih jauh - sekarang, mungkin bahasanya buruk. Sepertinya dengan c, c ++, tetapi sulit (sesuai dengan efek Dunning-Krueger , kita tidak dapat menyadari bahwa kita sudah menulis dalam c ++), oleh karena itu kami mencari alternatif. Pencarian singkat membawa kita ke BASCOM-AVR (tidak buruk diceritakan di sini ), tulis, tulis kode:


 $Regfile="m328pdef.dat" $Crystal=16000000 Config Portb.5 = Output Do Toggle Portb.5 Loop 

Kami mendapatkan:


Test_3


Hasilnya jauh lebih baik, selain itu, kami mendapat jalan berliku yang sempurna, tapi ... DASAR di 2018, serius? Mungkin kita akan meninggalkan ini di masa lalu.


Bagian 2 "Jawaban"


Sepertinya sudah waktunya untuk berhenti bermain bodoh dan mulai mengerti (dan juga ingat si dan assembler). Cukup salin kode "berguna" dari artikel yang disebutkan di awal hingga loop ().


Di sini, saya percaya, diperlukan penjelasan: semua kode akan ditulis dalam proyek Arduino, tetapi di lingkungan Atmel Studio 7.0 (ada disassembler yang nyaman di sana), tangkapan layar akan berasal dari sana.


 void setup() { DDRB |= (1 << 5); // PB5 } void loop() { PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON } 

hasil:


Test_4


Ini dia! Hampir apa yang Anda butuhkan. Hanya saja bentuknya tidak terlalu mirip dengan berliku-liku dan frekuensi, meskipun sudah lebih dekat, tetapi masih tidak sama. Kami juga mencoba memperbesar dan menemukan celah dalam sinyal setiap milidetik.


Test_5


Ini disebabkan oleh pemicu interupsi dari penghitung waktu yang bertanggung jawab atas millis (). Jadi yang akan kita lakukan hanyalah memutuskan koneksi. Kami mencari ISR ​​(fungsi interrupt handler). Kami menemukan:


 ISR(TIMER0_OVF_vect) { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; nsigned char f = timer0_fract; m += MILLIS_INC; f += FRACT_INC; if (f >= FRACT_MAX) { f -= FRACT_MAX; m += 1; } timer0_fract = f; timer0_millis = m; timer0_overflow_count++; } 

Banyak kode yang tidak berguna bagi kita. Anda dapat mengubah mode operasi penghitung waktu atau menonaktifkan interupsi, tetapi ini tidak perlu untuk tujuan kami, jadi cukup nonaktifkan semua interupsi dengan perintah cli (). Lihat saja kode kami:


 PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON 

terlalu banyak operator, kurangi menjadi satu tugas.


 PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON 

Ya, dan beralih ke loop () membutuhkan banyak perintah, karena ini adalah fungsi tambahan di loop utama.


 int main(void) { init(); // ... setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; } 

Jadi buat saja loop tanpa akhir di setup (). Kami mendapatkan yang berikut ini:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON } } 

Test_6


61 ns adalah maksimum yang sesuai dengan frekuensi controller. Apakah mungkin lebih cepat? Spoiler - tidak. Mari kita coba memahami mengapa - untuk ini kita membongkar kode kita:


Code_asm_1


Seperti yang dapat dilihat dari layar, untuk menulis ke port 1 atau 0, tepat 1 siklus clock dihabiskan, tetapi ada transisi yang tidak dapat diselesaikan dalam waktu kurang dari satu siklus clock (RJMP dilakukan dalam dua siklus clock, dan, misalnya, JMP, dalam tiga ) Dan kita hampir sampai - untuk mendapatkan liku-liku, Anda perlu menambah waktu ketika 0 diberikan oleh dua ukuran. Tambahkan untuk dua perintah assembler nop ini yang tidak melakukan apa pun selain mengambil 1 siklus clock:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF asm("nop"); asm("nop"); PORTB = 0b11111111; //ON } } 

Test_end


Bagian 3 "Kesimpulan"


Sayangnya, semua yang kami lakukan sama sekali tidak berguna dari sudut pandang praktis, karena kami tidak dapat lagi menjalankan kode apa pun. Juga, dalam 99,9% kasus, frekuensi port switching cukup untuk tujuan apa pun. Ya, dan jika kita benar-benar perlu menghasilkan kelokan yang berliku-liku, Anda dapat mengambil stm32 dengan dma atau chip timer eksternal seperti NE555. Artikel ini bermanfaat untuk memahami perangkat mega328p dan arduino secara umum.


Namun demikian, penulisan ke dalam register nilai 8-bit PORTB = 0b11111111; jauh lebih cepat daripada digitalWrite(13, 1); tetapi Anda harus membayar untuk ini dengan ketidakmampuan untuk mentransfer kode ke papan lain, karena nama-nama register mungkin berbeda.


Hanya ada satu pertanyaan lagi: mengapa penggunaan batu yang lebih cepat tidak memberikan hasil? Jawabannya sangat sederhana - dalam sistem yang kompleks, frekuensi gpio lebih rendah dari frekuensi inti. Tetapi seberapa jauh lebih rendah dan cara mengaturnya selalu dapat dilihat di lembar data pada pengontrol tertentu.


Publikasi mengutip artikel:



Source: https://habr.com/ru/post/id422177/


All Articles