Saya menggunakan papan Arduino Uno untuk menghitung sudut sistem saya (lengan robot). Sudut sebenarnya nilai 10 bit (0 hingga 1023) dari ADC, menggunakan rentang penuh ADC. Saya hanya akan beroperasi di kuadran 1 (0 hingga 90 derajat), di mana baik sinus dan cosinus positif, sehingga tidak ada masalah dengan angka negatif. Keraguan saya dapat diungkapkan dalam 3 pertanyaan:
Apa cara berbeda untuk menghitung fungsi trigonometri ini di Arduino?
Apa cara tercepat untuk melakukan hal yang sama?
Ada fungsi sin () dan cos () di Arduino IDE, tetapi bagaimana Arduino sebenarnya menghitungnya (seperti apakah mereka menggunakan tabel pencarian, atau perkiraan dll)? Mereka tampak seperti solusi yang jelas, tetapi saya ingin tahu implementasi yang sebenarnya sebelum saya mencobanya.
PS: Saya terbuka untuk pengkodean standar pada Arduino IDE dan assembly coding, serta opsi lain yang tidak disebutkan. Saya juga tidak memiliki masalah dengan kesalahan dan perkiraan, yang tidak dapat dihindari untuk sistem digital; namun jika memungkinkan akan lebih baik untuk menyebutkan sejauh mana kemungkinan kesalahan
sumber
Jawaban:
Dua metode dasar adalah perhitungan matematis (dengan polinomial) dan tabel pencarian.
Perpustakaan matematika Arduino (libm, bagian dari avr-libc) menggunakan yang pertama. Ini dioptimalkan untuk AVR karena ditulis dengan bahasa assembly 100%, dan karena itu hampir tidak mungkin untuk mengikuti apa yang dilakukannya (tidak ada komentar juga). Yakinlah meskipun itu akan menjadi otak implementasi float murni yang paling dioptimalkan yang jauh lebih unggul dari yang dapat kami hasilkan.
Namun kuncinya ada float . Apa pun pada Arduino yang melibatkan floating point akan menjadi kelas berat dibandingkan dengan bilangan bulat murni, dan karena Anda hanya meminta bilangan bulat antara 0 dan 90 derajat, tabel pencarian sederhana sejauh ini merupakan metode paling sederhana dan paling efisien.
Tabel dengan 91 nilai akan memberi Anda segalanya dari 0 hingga 90 inklusif. Namun jika Anda membuat tabel nilai floating point antara 0,0 dan 1,0 Anda masih memiliki inefisiensi dalam bekerja dengan float (diberikan tidak
sin
seefisien perhitungan dengan float), jadi menyimpan nilai titik tetap malah akan jauh lebih efisien.Itu mungkin sesederhana menyimpan nilai dikalikan dengan 1000, jadi Anda memiliki antara 0 dan 1000 bukannya antara 0,0 dan 1,0 (misalnya sin (30) akan disimpan sebagai 500 bukan 0,5). Lebih efisien adalah menyimpan nilai sebagai, misalnya, nilai Q16 di mana setiap nilai (bit) mewakili 1/65536 dari 1,0. Nilai-nilai Q16 ini (dan Q15 terkait, Q1.15, dll.) Lebih efisien untuk digunakan karena Anda memiliki kekuatan dua komputer yang suka bekerja dengan bukan kekuatan-of-sepuluh yang mereka benci bekerja dengan.
Jangan lupa juga bahwa
sin()
fungsi mengharapkan radian, jadi pertama-tama Anda harus mengubah derajat bilangan bulat Anda menjadi nilai radian floating point, membuat penggunaansin()
bahkan lebih tidak efisien dibandingkan dengan tabel pencarian yang dapat bekerja langsung dengan nilai derajat bilangan bulat.Kombinasi dari dua skenario, adalah mungkin. Interpolasi linier akan memungkinkan Anda untuk mendapatkan perkiraan sudut titik mengambang antara dua bilangan bulat. Ini sesederhana menghitung seberapa jauh antara dua titik dalam tabel pencarian Anda dan membuat rata-rata tertimbang berdasarkan jarak kedua nilai tersebut. Misalnya jika Anda berada pada 23,6 derajat Anda ambil
(sintable[23] * (1-0.6)) + (sintable[24] * 0.6)
. Pada dasarnya gelombang sinus Anda menjadi serangkaian titik diskrit yang disatukan oleh garis lurus. Anda menukar akurasi untuk kecepatan.sumber
Ada beberapa jawaban yang baik di sini, tetapi saya ingin menambahkan metode yang belum disebutkan, salah satu yang sangat cocok untuk menghitung fungsi trigonometri pada sistem embedded, dan itulah teknik CORDIC Wiki Entry Here Ini dapat menghitung fungsi trig menggunakan hanya pergeseran dan menambah dan tabel pencarian kecil.
Berikut adalah contoh kasar dalam C. Akibatnya, ia mengimplementasikan fungsi atan2 () pustaka C menggunakan CORDIC (yaitu menemukan sudut yang diberi dua komponen ortogonal.) Menggunakan titik mengambang, tetapi dapat disesuaikan untuk digunakan dengan aritmatika titik tetap.
Tapi coba fungsi trigonometri Arduino asli terlebih dahulu - mereka mungkin cukup cepat pula.
sumber
Saya telah bermain sedikit dengan menghitung sinus dan cosinus di Arduino menggunakan pendekatan polinomial titik tetap. Berikut adalah pengukuran waktu eksekusi rata-rata dan kesalahan terburuk, dibandingkan dengan standar
cos()
dansin()
dari avr-libc:Ini didasarkan pada polinomial tingkat 6 yang dihitung hanya dengan 4 perkalian. Perkalian itu sendiri dilakukan dalam perakitan, karena saya menemukan bahwa gcc mengimplementasikannya secara tidak efisien. Sudut dinyatakan sebagai
uint16_t
dalam satuan 1/65536 revolusi, yang membuat aritmatika sudut secara alami bekerja modulo satu revolusi.Jika menurut Anda ini sesuai dengan tagihan Anda, berikut adalah kodenya: Fixed-point trigonometry . Maaf, saya masih belum menerjemahkan halaman ini, yang dalam bahasa Perancis, tetapi Anda dapat memahami persamaan, dan kode (nama variabel, komentar ...) dalam bahasa Inggris.
Sunting : Karena server tampaknya telah menghilang, berikut adalah beberapa info tentang perkiraan yang saya temukan.
Saya ingin menulis sudut dalam titik tetap biner, dalam satuan kuadran (atau, sebaliknya, bergantian). Dan saya juga ingin menggunakan polinomial yang genap, karena ini lebih efisien untuk dihitung daripada polinomial yang arbitrer. Dengan kata lain, saya ingin P () jumlahnya banyak
cos (π / 2 x) ≈ P (x 2 ) untuk x ∈ [0,1]
Saya juga meminta perkiraan tepat pada kedua ujung interval, untuk memastikan bahwa cos (0) = 1 dan cos (π / 2) = 0. Kendala ini mengarah pada bentuk
P (u) = (1 - u) (1 + uQ (u))
di mana Q () adalah polinomial yang arbitrer.
Selanjutnya, saya mencari solusi terbaik sebagai fungsi dari derajat Q () dan menemukan ini:
Pilihan di antara solusi di atas adalah trade-off kecepatan / akurasi. Solusi ketiga memberikan akurasi lebih dari yang bisa dicapai dengan 16-bit, dan itu yang saya pilih untuk implementasi 16-bit.
sumber
Anda bisa membuat beberapa fungsi yang menggunakan pendekatan linier untuk menentukan sin () dan cos () dari sudut tertentu.
Saya sedang memikirkan sesuatu seperti ini: Untuk masing-masing saya telah memecah representasi grafis dari sin () dan cos () menjadi 3 bagian dan telah melakukan pendekatan linier dari bagian itu.
Fungsi Anda idealnya pertama-tama akan memeriksa bahwa kisaran malaikat adalah antara 0 dan 90.
Kemudian akan menggunakan
ifelse
pernyataan untuk menentukan apa dari 3 bagian yang dimilikinya dan kemudian melakukan perhitungan linier yang sesuai (yaituoutput = mX + c
)sumber
Saya mencari orang lain yang memiliki perkiraan cos () dan sin () dan saya menemukan jawaban ini:
jawaban dtb untuk "Dosa Cepat / Cos menggunakan array terjemahan yang sudah dihitung sebelumnya"
Pada dasarnya dia menghitung bahwa fungsi math.sin () dari perpustakaan matematika lebih cepat daripada menggunakan tabel nilai-nilai pencarian. Tapi dari apa yang saya tahu, ini dihitung pada PC.
Arduino termasuk perpustakaan matematika yang dapat menghitung sin () dan cos ().
sumber
Tabel pencarian akan menjadi cara tercepat untuk menemukan sinus. Dan jika Anda nyaman komputasi dengan angka titik tetap (bilangan bulat yang titik binernya berada di tempat lain di sebelah kanan bit-0), perhitungan lebih lanjut dengan sinus akan jauh lebih cepat juga. Tabel itu kemudian bisa menjadi tabel kata, mungkin di Flash untuk menghemat ruang RAM. Perhatikan bahwa dalam matematika Anda, Anda mungkin perlu menggunakan rindu untuk hasil menengah yang besar.
sumber
secara umum, lihat tabel> perkiraan -> perhitungan. ram> flash. integer> fixed point> floating point. pra-perhitungan> perhitungan waktu nyata. mirroring (sinus ke cosinus atau cosinus ke sinus) vs. perhitungan aktual / pencarian ....
masing-masing memiliki kelebihan dan kekurangannya.
Anda dapat membuat segala macam kombinasi untuk melihat mana yang paling cocok untuk aplikasi Anda.
sunting: Saya melakukan pengecekan cepat. menggunakan output integer 8-bit, menghitung nilai 1024 sin dengan tabel pencarian membutuhkan 0,6 ms, dan 133ms dengan floaters, atau 200x lebih lambat.
sumber
Saya punya pertanyaan serupa dengan OP. Saya ingin membuat tabel LUT untuk menghitung kuadran pertama dari fungsi sinus sebagai bilangan bulat 16 bit bertanda mulai dari 0x8000 hingga 0xffff. Dan akhirnya saya menulis ini untuk kesenangan dan keuntungan. Catatan: Ini akan bekerja lebih efisien jika saya menggunakan pernyataan 'jika'. Juga tidak terlalu akurat, tetapi akan cukup akurat untuk gelombang sinus dalam synthesizer suara
Sekarang untuk mendapatkan kembali nilai, gunakan fungsi ini. Ini menerima nilai dari 0x0000 hingga 0x0800 dan mengembalikan nilai yang sesuai dari LUT
Ingat, ini bukan pendekatan yang paling efisien untuk tugas ini, saya hanya tidak tahu bagaimana membuat seri taylor untuk memberikan hasil dalam kisaran yang sesuai.
sumber
Imm_UI_A
dideklarasikan dua kali, a;
dan beberapa deklarasi variabel tidak ada, danuLut_0
harus bersifat global. Dengan perbaikan yang diperlukan,lu_sin()
cepat (antara 27 dan 42 siklus CPU) tetapi sangat tidak akurat (kesalahan maksimum ≈ 5.04e-2). Saya tidak dapat memahami maksud dari “polinomial Arnadathian” ini: tampaknya perhitungan yang cukup berat, namun hasilnya hampir sama buruknya dengan perkiraan kuadratik sederhana. Metode ini juga memiliki biaya memori yang sangat besar. Akan lebih baik untuk menghitung tabel pada PC Anda dan memasukkannya ke dalam kode sumber sebagaiPROGMEM
array.Hanya untuk bersenang-senang, dan untuk membuktikannya dapat dilakukan, saya menyelesaikan rutin perakitan AVR untuk menghitung sin (x) menghasilkan 24 bit (3 byte) dengan satu bit kesalahan. Sudut input dalam derajat dengan satu digit desimal, dari 000 hingga 900 (0 ~ 90,0) hanya untuk kuadran pertama. Ini menggunakan kurang dari 210 instruksi AVR dan berjalan rata-rata 212 mikrodetik, bervariasi dari 211 us (sudut = 001) hingga 213 us (sudut = 899).
Butuh beberapa hari untuk melakukan semuanya, lebih dari 10 hari (jam bebas) hanya memikirkan algoritma terbaik untuk perhitungan, mengingat mikrokontroler AVR, tanpa titik mengambang, menghilangkan semua divisi yang mungkin. Yang membutuhkan waktu lebih banyak adalah membuat nilai step-up yang tepat untuk bilangan bulat, untuk memiliki presisi yang baik, perlu meningkatkan nilai 1e-8 menjadi bilangan bulat biner 2 ^ 28 atau lebih. Setelah semua kesalahan penyebab presisi dan pembulatan ditemukan, meningkatkan resolusi perhitungan mereka dengan tambahan 2 ^ 8 atau 2 ^ 16, hasil terbaik terpenuhi. Saya pertama-tama mensimulasikan semua perhitungan di Excel dengan menjaga semua nilai sebagai Int (x) atau Putaran (x, 0) untuk mewakili secara tepat pemrosesan inti AVR.
Misalnya, dalam algoritme sudut harus dalam radian, inputnya dalam Derajat untuk memudahkan pengguna. Untuk mengonversi Derajat ke Radian, rumus sepele adalah rad = derajat * PI / 180, sepertinya bagus dan mudah, tetapi tidak, PI adalah angka yang tak terbatas - jika menggunakan beberapa digit itu akan membuat kesalahan pada output, pembagian dengan 180 membutuhkan Manipulasi bit AVR karena tidak memiliki instruksi pembagian, dan lebih dari itu, hasilnya akan memerlukan titik apung karena melibatkan angka jauh di bawah bilangan bulat 1. Misalnya, Radian 1 ° (derajat) adalah 0,017453293. Karena PI dan 180 adalah konstanta, mengapa tidak membalikkan hal ini untuk perkalian sederhana? PI / 180 = 0,017453293, kalikan dengan 2 ^ 32 dan hasilnya menjadi 74961320 konstan (0x0477D1A8), kalikan angka ini dengan sudut Anda dalam derajat, katakanlah 900 untuk 90 ° dan geser 4 bit ke kanan (÷ 16) untuk mendapatkan 4216574250 (0xFB53D12A), yaitu radian dari 90 ° dengan ekspansi 2 ^ 28, muat dalam 4 byte, tanpa satu divisi (kecuali 4 sedikit bergeser ke kanan). Di satu sisi, kesalahan yang termasuk dalam trik tersebut lebih kecil dari 2 ^ -27.
Jadi, semua perhitungan lebih lanjut perlu mengingatnya 2 ^ 28 lebih tinggi dan diurus. Anda perlu membagi hasil yang sedang berjalan dengan 16, 256 atau bahkan 65536 hanya untuk menghindarinya menggunakan byte kelaparan yang tidak perlu tumbuh yang tidak akan membantu resolusi. Itu adalah pekerjaan yang melelahkan, hanya menemukan jumlah bit minimum dalam setiap hasil perhitungan, menjaga hasil presisi sekitar 24 bit. Masing-masing dari beberapa perhitungan di mana dilakukan dalam try / error dengan bit yang lebih tinggi atau lebih rendah dihitung dalam aliran Excel, menonton jumlah keseluruhan bit kesalahan pada hasil dalam grafik yang menunjukkan 0-90 ° dengan makro menjalankan kode 900 kali, sekali per kesepuluh gelar. Pendekatan Excel "visual" itu adalah alat yang saya buat, banyak membantu untuk menemukan solusi terbaik untuk setiap bagian kode.
Misalnya, mengumpulkan hasil perhitungan khusus ini 13248737.51 hingga 13248738 atau hanya kehilangan desimal "0,51", seberapa besar itu akan mempengaruhi ketepatan hasil akhir untuk semua tes sudut 900 input (00.1 ~ 90.0)?
Saya dapat menjaga hewan yang terkandung dalam 32 bit (4 byte) pada setiap perhitungan, dan berakhir dengan keajaiban untuk mendapatkan ketepatan dalam 23 bit hasilnya. Saat memeriksa seluruh 3 byte hasil, kesalahannya adalah ± 1 LSB, luar biasa.
Pengguna dapat mengambil satu, dua atau tiga byte dari hasil untuk persyaratan presisi sendiri. Tentu saja, jika hanya satu byte sudah cukup, saya akan merekomendasikan untuk menggunakan tabel dosa 256 byte tunggal dan menggunakan instruksi AVR 'LPM' untuk mengambilnya.
Setelah urutan Excel berjalan mulus dan rapi, terjemahan terakhir dari Excel ke perakitan AVR membutuhkan waktu kurang dari 2 jam, seperti biasa Anda harus berpikir lebih dulu, bekerja lebih lambat.
Pada saat itu saya dapat menekan lebih banyak dan mengurangi penggunaan register. Kode aktual (bukan final) menggunakan sekitar 205 instruksi (~ 410 byte), menjalankan kalkulasi sin (x) rata-rata 212 us, clock pada 16MHz. Pada kecepatan itu dapat menghitung 4700+ sin (x) per detik. Tidak menjadi penting, tetapi dapat menjalankan gelombang sinus yang tepat hingga 4700Hz dengan 23 bit presisi dan resolusi, tanpa tabel pencarian.
Algoritma dasar didasarkan pada seri Taylor untuk sin (x), tetapi banyak dimodifikasi agar sesuai dengan niat saya dengan mikrokontroler AVR dan presisi dalam pikiran.
Bahkan jika menggunakan tabel 2.700 byte (900 entri * 3 byte) akan sangat menarik, apa yang menyenangkan atau pengalaman belajar tentang itu? Tentu saja, pendekatan CORDIC juga dipertimbangkan, mungkin nanti, intinya di sini adalah untuk memeras Taylor ke inti AVR dan mengambil air dari batu kering.
Saya ingin tahu apakah Arduino "sin (78.9 °)" dapat menjalankan Processing (C ++) dengan presisi 23 bit dalam waktu kurang dari 212 us dan kode yang diperlukan lebih kecil dari 205 instruksi. Mungkin jika C ++ menggunakan CORDIC. Sketsa Arduino dapat mengimpor kode perakitan.
Tidak masuk akal untuk mengirim kode di sini, nanti saya akan mengedit posting ini untuk menyertakan tautan web, mungkin di blog saya di url ini . Blog sebagian besar dalam bahasa Portugis.
Usaha hobi-tanpa-uang ini menarik, mendorong batas-batas mesin AVR hampir 16MIPS pada 16MHz, tanpa instruksi divisi, perkalian hanya dalam 8x8 bit. Ini memungkinkan untuk menghitung sin (x), cos (x) [= sin (900-x)] dan tan (x) [= sin (x) / sin (900-x)].
Di atas itu semua, ini membantu menjaga otak saya yang berusia 63 tahun dipoles dan diminyaki. Ketika remaja mengatakan 'orang tua' tidak tahu apa-apa tentang teknologi, saya menjawab "pikirkan lagi, siapa yang menurut Anda menciptakan basis untuk semua yang Anda nikmati hari ini?".
Bersulang
sumber
sin()
fungsi memiliki sekitar akurasi yang sama seperti milik Anda dan dua kali lebih cepat. Ini juga didasarkan pada polinomial. 2. Jika sudut sewenang-wenang harus dibulatkan ke kelipatan terdekat 0,1 °, ini dapat menyebabkan kesalahan pembulatan setinggi 8,7e-4, yang semacam meniadakan manfaat dari akurasi 23 bit. 3. Apakah Anda keberatan berbagi polinomial Anda?Seperti yang orang lain sebutkan tabel pencarian adalah cara untuk pergi jika Anda ingin kecepatan. Saya baru-baru ini menyelidiki perhitungan fungsi trig pada ATtiny85 untuk penggunaan rata-rata vektor cepat (angin dalam kasus saya). Selalu ada trade off ... bagi saya, saya hanya memerlukan resolusi 1 derajat sudut sehingga tabel pencarian 360 int (scaling -32767 ke 32767, hanya bekerja dengan int) adalah cara terbaik untuk pergi. Mengambil kembali sinus hanyalah masalah memasok indeks 0-359 ... sangat cepat! Beberapa angka dari tes saya:
Waktu pencarian FLASH (kami): 0,99 (tabel disimpan menggunakan PROGMEM)
Waktu pencarian RAM (kami): 0,69 (tabel dalam RAM)
Waktu Lib (kami): 122,31 (Menggunakan Arduino Lib)
Perhatikan ini adalah rata-rata di sampel 360 titik untuk masing-masing. Pengujian dilakukan pada nano.
sumber