Berbagai cara (dan tercepat) untuk menghitung sinus (dan cosinus) di Arduino

9

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:

  1. Apa cara berbeda untuk menghitung fungsi trigonometri ini di Arduino?

  2. Apa cara tercepat untuk melakukan hal yang sama?

  3. 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

Tuan Transistor
sumber
Apakah Anda baik-baik saja dengan nilai perkiraan?
sa_leinad
Ya sebenarnya, tapi saya ingin tahu sejauh mana kesalahan metode yang berbeda. Ini bukan produk presisi tetapi proyek sampingan saya. Sebenarnya perkiraan tidak dapat dihindari untuk hampir semua (jika tidak ada) sistem digital yang menerapkan fungsi matematika
Transistor Overlord
Saya berasumsi Anda ingin bekerja dalam derajat. Apakah Anda ingin memasukkan bilangan bulat atau angka desimal untuk sudut?
sa_leinad
Derajat ya. Saya pikir akan lebih mudah untuk menulis kode dan menguji jika kita menggunakan bilangan bulat, jadi saya akan melakukannya. Saya akan memberikan informasi yang lebih jelas tentang pengeditan
Transistor Overlord
1
Untuk derajat hanya 90 (integer) tabel pencarian 90 entri akan tercepat dan paling efisien. Bahkan untuk 360 derajat penuh Anda dapat menggunakan tabel pencarian 90 entri. Cukup baca mundur untuk 90-179 dan balikkan untuk 180-269. Lakukan keduanya untuk 270-359.
Majenko

Jawaban:

11

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 sinseefisien 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 penggunaan sin()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.

Majenko
sumber
Saya menulis sebuah perpustakaan beberapa waktu lalu yang menggunakan polinomial Taylor untuk sin / cos yang lebih cepat dari perpustakaan. Mengingat, saya menggunakan radian floating point sebagai input untuk keduanya.
tuskiomi
8

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.

/*
 * Simple example of using the CORDIC algorithm.
 */

#include <stdio.h>
#include <math.h>

#define CORDIC_TABLE_SIZE  16

double cordic_table[CORDIC_TABLE_SIZE];

void init_table(void);
double angle(double I, double Q);

/*
 * Given a sine and cosine component of an
 * angle, compute the angle using the CORIDC
 * algoritm.
 */
double angle(double I, double Q)
{
    int L;
    double K = 1;
    double angle_acc = 0;
    double tmp_I;

    if (I < 0) {
        /* rotate by an initial +/- 90 degrees */
        tmp_I = I;
        if (Q > 0.0) {
            I = Q;           /* subtract 90 degrees */
            Q = -tmp_I;
            angle_acc = -90;
        } else {
            I = -Q;          /* add 90 degrees */
            Q = tmp_I;
            angle_acc = 90;
        }
    } else {
        angle_acc = 0;
    }

    /* rotate using "1 + jK" factors */
    for (L = 0, K = 1; L <= CORDIC_TABLE_SIZE; L++) {
        tmp_I = I;
        if (Q >= 0.0) {
            /* angle is positive: do negative roation */
            I += Q * K;
            Q -= tmp_I * K;
            angle_acc -= cordic_table[L];
        } else {
            /* angle is negative: do positive rotation */
            I -= Q * K;
            Q += tmp_I * K;
            angle_acc += cordic_table[L];
        }
        K /= 2.0;
    }
    return -angle_acc;
}

void init_table(void)
{
    int i;
    double K = 1;

    for (i = 0; i < CORDIC_TABLE_SIZE; i++) {
        cordic_table[i] = 180 * atan(K) / M_PI;
        K /= 2.0;
    }
}
int main(int argc, char **argv)
{
    double I, Q, A, Ar, R, Ac;

    init_table();

    printf("# Angle,    CORDIC Angle,  Error\n");
    for (A = 0; A < 90.0; A += 0.5) {

        Ar = A * M_PI / 180; /* convert to radians for C's sin & cos fn's */

        R = 5;  // Arbitrary radius

        I = R * cos(Ar);
        Q = R * sin(Ar);

        Ac = angle(I, Q);
        printf("%9f, %9f,   %12.4e\n", A, Ac, Ac-A);
    }
    return 0;
}

Tapi coba fungsi trigonometri Arduino asli terlebih dahulu - mereka mungkin cukup cepat pula.

Halzephron
sumber
1
Saya telah mengambil pendekatan yang sama di masa lalu, pada STM8. dibutuhkan dua langkah: 1) menghitung dosa (x) dan cos (x) dari dosa (2x), dan kemudian 2) menghitung dosa (x +/- x / 2) dari dosa (x), dosa (x / 2) , cos (x), dan cos (x / 2) -> melalui iterasi Anda dapat mendekati target Anda. dalam kasus saya, saya mulai dengan 45 derajat (0,707), dan mencari jalan keluar menuju target. itu jauh lebih lambat daripada fungsi standar IAR sin ().
dannyf
7

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()dan sin()dari avr-libc:

function    max error   cycles   time
-----------------------------------------
cos_fix()   9.53e-5     108.25    6.77 µs
sin_fix()   9.53e-5     110.25    6.89 µs
cos()       2.98e-8     1720.8   107.5 µs
sin()       2.98e-8     1725.1   107.8 µs

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_tdalam 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:

        Q(u)              degree of P(x²)  max error
─────────────────────────┼─────────────────┼──────────
          0                       2         5.60e-2
       0.224                     4         9.20e-4
0.2335216 + 0.0190963 u          6         9.20e-6

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.

Edgar Bonet
sumber
2
Itu luar biasa, @Edgar.
SDsolar
Apa yang Anda lakukan untuk menemukan polinomial?
TLW
@ TTW: Saya mengharuskannya untuk memiliki beberapa properti “bagus” (mis. Cos (0) = 1), yang dibatasi pada formulir (1 − x²) (1 + x²Q (x²)), di mana Q (u) adalah arbitrer polinomial (dijelaskan di halaman). Saya mengambil Q tingkat pertama (hanya 2 koefisien), menemukan perkiraan koefisien berdasarkan kecocokan, kemudian menyesuaikan optimasi dengan coba-coba.
Edgar Bonet
@ EdgarBonet - menarik. Perhatikan bahwa halaman itu tidak memuat untuk saya, meskipun cached berfungsi. Bisakah Anda menambahkan polinomial yang digunakan untuk jawaban ini?
TLW
@ TTW: menambahkan itu ke jawabannya.
Edgar Bonet
4

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.
pendekatan linier

Fungsi Anda idealnya pertama-tama akan memeriksa bahwa kisaran malaikat adalah antara 0 dan 90.
Kemudian akan menggunakan ifelsepernyataan untuk menentukan apa dari 3 bagian yang dimilikinya dan kemudian melakukan perhitungan linier yang sesuai (yaitu output = mX + c)

sa_leinad
sumber
Apakah ini tidak melibatkan multiplikasi floating point?
Transistor Overlord
1
Belum tentu. Anda bisa memilikinya sehingga outputnya diskalakan antara 0-100 bukannya 0-1. Dengan cara ini Anda berurusan dengan bilangan bulat, bukan floating point. Catatan: 100 arbitrer. Tidak ada alasan bahwa Anda tidak dapat mengatur skala output antara 0-128 atau 0-512 atau 0-1000 atau 0-1024. Dengan menggunakan kelipatan 2, Anda hanya perlu melakukan shift kanan untuk mengurangi hasilnya.
sa_leinad
Cukup pintar, @sa_leinad. Suara positif. Saya ingat melakukan ini ketika bekerja dengan biasing dari transistor.
SDsolar
4

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 ().

sa_leinad
sumber
1
PC memiliki FPU di dalamnya yang membuatnya cepat. Arduino tidak, dan itu membuatnya lambat.
Majenko
Jawabannya juga untuk C # yang melakukan hal-hal seperti memeriksa batas array.
Michael
3

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.

JRobert
sumber
1

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.

dannyf
sumber
1

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

void sin_lut_ctor(){

//Make a Look Up Table for 511 terms of the sine function.
//Plugin in some polynomials to do some magic
//and you get an aproximation for sines up to π/2.
//

//All sines yonder π/2 can be derived with math

const uint16_t uLut_d = 0x0200; //maximum LUT depth for π/2 terms. 
uint16_t uLut_0[uLut_d];        //The LUT itself.
//Put the 2 above before your void setup() as global variables.
//This coefficients will only work for uLut_d = 511.

uint16_t arna_poly_0 = 0x000a; // 11
uint16_t arna_poly_1 = 0x0001; // 1
uint16_t arna_poly_2 = 0x0007; // 7
uint16_t arna_poly_3 = 0x0001; // 1   Precalculated Polynomials
uint16_t arna_poly_4 = 0x0001; // 1   
uint16_t arna_poly_5 = 0x0007; // 7
uint16_t arna_poly_6 = 0x0002; // 2
uint16_t arna_poly_7 = 0x0001; // 1

uint16_t Imm_UI_0 = 0x0001;              //  Itterator
uint16_t Imm_UI_1 = 0x007c;              //  An incrementor that decreases in time

uint16_t Imm_UI_2 = 0x0000;              //  
uint16_t Imm_UI_3 = 0x0000;              //              
uint16_t Imm_UI_4 = 0x0000;              //
uint16_t Imm_UI_5 = 0x0000;              //
uint16_t Imm_UI_6 = 0x0000;              //  Temporary variables
uint16_t Imm_UI_7 = 0x0000;              //
uint16_t Imm_UI_8 = 0x0000;              //
uint16_t Imm_UI_9 = 0x0000;              //
uint16_t Imm_UI_A = 0x0000;
uint16_t Imm_UI_B = 0x0000;

uint16_t Imm_UI_A = uLut_d - 0x0001;     //  510

uLut_0[0x0000] = 0x8000;        //Assume that the middle point is 32768 (0x8000 hex)
while (Imm_UI_0 < Imm_UI_A) //Construct a quarter of the sine table
  {
Imm_UI_2++;                                   //Increase temporary variable by 1

Imm_UI_B = Imm_UI_2 / arna_coeff_0;           //Divide it with the first coefficient (note: integer division)
Imm_UI_3 += Imm_UI_B;                         //Increase the next temporary value if the first one has increased up to the 1st coefficient
Imm_UI_1 -= Imm_UI_B;                         //Decrease the incrementor if this is the case
Imm_UI_2 *= 0x001 - Imm_UI_B;                 //Set the first temporary variable back to 0

Imm_UI_B = Imm_UI_3 / arna_poly_1;           //Do the same thing as before with the next set of temporary variables
Imm_UI_4 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_3 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_4 / arna_poly_2;           //And again... and again... you get the idea.
Imm_UI_5 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_4 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_5 / arna_poly_3;
Imm_UI_6 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_5 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_6 / arna_poly_4;
Imm_UI_7 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_6 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_7 / arna_poly_5;
Imm_UI_8 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_7 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_8 / arna_poly_6;
Imm_UI_9 += Imm_UI_B;
Imm_UI_1 -= Imm_UI_B;
Imm_UI_8 *= 0x0001 - Imm_UI_B;

Imm_UI_B = Imm_UI_9 / arna_poly_7          //the last set won't need to increment a next variable so skip the step where you would increase it.
Imm_UI_1 -= Imm_UI_B;
Imm_UI_9 *= 1 - Imm_UI_B;

uLut_0[Imm_UI_0] = (uLut_0[Imm_UI_0 - 0x0001] + Imm_UI_1); //Set the current value as the previous one increased by our incrementor
Imm_UI_0++;              //Increase the itterator
  }   
  uLut_0[Imm_UI_A] = 0xffff; //Lastly, set the last value to 0xffff

  //And there you have it. A sine table with only one if statement (a while loop)
}

Sekarang untuk mendapatkan kembali nilai, gunakan fungsi ini. Ini menerima nilai dari 0x0000 hingga 0x0800 dan mengembalikan nilai yang sesuai dari LUT

uint16_t lu_sin(uint16_t lu_val0)
{
  //Get a value from 0x0000 to 0x0800. Return an appropriate sin(value)
  Imm_UI_0 = lu_val0/0x0200; //determine quadrant
  Imm_UI_1 = lu_val0%0x0200; //Get which value
  if (Imm_UI_0 == 0x0000)
  {
    return uLut_0[Imm_UI_1];
  }
  if (Imm_UI_0 == 0x0001)
  {
    return uLut_0[0x01ff - Imm_UI_1];
  }
  if (Imm_UI_0 == 0x0002)
  {
    return 0xffff - uLut_0[Imm_UI_1];
  }
  if (Imm_UI_0 == 0x0003)
  {
    return 0xffff - uLut_0[0x01ff - Imm_UI_1];
  }
}// I'm using if statements here but similarly to the above code block, 
 //you can do without. just with integer divisions and modulos

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.

Arnadath
sumber
Kode Anda tidak dikompilasi: Imm_UI_Adideklarasikan dua kali, a ;dan beberapa deklarasi variabel tidak ada, dan uLut_0harus 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 sebagai PROGMEMarray.
Edgar Bonet
1

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

Wagner Lip
sumber
Bagus! Beberapa komentar: 1. Standar 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?
Edgar Bonet
1

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.

acicuc
sumber