Arduinoで正弦(および余弦)を計算するさまざまな方法(および最速)


9

私のシステム(ロボットアーム)の角度を計算するために、Arduino Unoボードを使用しています。角度は、ADCの全範囲を使用した、ADCからの実際の10ビット値(0〜1023)です。ここでは、正弦と余弦の両方が正である第1象限(0〜90度)でのみ動作するので、負の数の問題はありません。私の疑問は3つの質問で表すことができます。

  1. Arduinoでこれらの三角関数を計算する別の方法は何ですか?

  2. 同じことをする最も速い方法は何ですか?

  3. Arduino IDEにはsin()関数とcos()関数がありますが、Arduinoは実際にそれらをどのように計算しますか(ルックアップテーブルや近似などを使用する場合と同様)。それらは明白な解決策のように見えますが、実際に試す前に、実際の実装を知りたいと思います。

PS:私は、Arduino IDEの標準コーディングとアセンブリコーディングのほか、言及されていないその他のオプションにも対応しています。また、デジタルシステムでは避けられないエラーや近似の問題もありません。ただし、可能であれば、起こり得るエラーの範囲について言及しておくとよい


おおよその値でよろしいですか?
sa_leinad 2017

はい、実際にはありますが、さまざまな方法のエラーの程度を知りたいのですが。これは精密な製品ではなく、私の副プロジェクトです。実際に近似は数学関数を実装し、ほぼすべての(ないもしあれば)デジタルシステムのために避けられない
トランジスタ覇王

私はあなたが学位で働きたいと思っていると思います。角度に整数または10進数を入力しますか?
sa_leinad

度はい。整数を使用すると、コードを記述してテストする方が簡単になると思うので、それを使います。編集についてより明確な情報を掲載します
トランジスタオーバーロード2017

1
ちょうど90(整数)度の場合、90エントリのルックアップテーブルが最も速く、最も効率的です。実際、360度全体では、90エントリのルックアップテーブルを使用できます。90-179の場合は逆に読み、180-269の場合は逆にします。270-359の場合は両方を実行します。
マジェンコ

回答:


11

2つの基本的な方法は、数学計算(多項式を使用)とルックアップテーブルです。

Arduinoの数学ライブラリ(libm、avr-libcの一部)は前者を使用します。100%アセンブリ言語で記述されているため、AVR用に最適化されているため、その動作を追跡することはほとんど不可能です(コメントもゼロです)。それが私たちのものよりはるかに優れた最も最適化された純粋なフロート実装の頭脳になるだろうが、安心してください。

ただし鍵は浮きがあります。浮動小数点を含むArduinoのすべては、純粋な整数と比較すると重量が大きくなります。0〜90度の整数のみを要求しているため、単純なルックアップテーブルは、最も単純で最も効率的な方法です。

91の値の表は、0から90までのすべてを示します。ただし、浮動小数点値のテーブルを0.0と1.0の間で作成すると、浮動小数点の操作が非効率になるため(浮動小数点を使用した計算ほど非効率的ではないsinため)、代わりに固定小数点値を保存する方がはるかに効率的です。

これは、値に1000を掛けた値を格納するのと同じくらい簡単なので、0.0から1.0の間ではなく0から1000の間になります(たとえば、sin(30)は0.5ではなく500として格納されます)。たとえば、各値(ビット)が1.0の1/65536を表すQ16値として値を格納する方が効率的です。これらのQ16値(および関連するQ15、Q1.15など)は、コンピューターが操作するのが苦手な10の累乗ではなく、コンピューターが操作する2の累乗を持っているため、より効率的に作業できます。

sin()関数がラジアンを期待することも忘れないでください。最初に整数度を浮動小数点ラジアン値に変換する必要がsin()あります。これにより、整数度の値を直接処理できるルックアップテーブルと比較して、さらに非効率になります。

ただし、2つのシナリオの組み合わせが可能です。線形補間により、2つの整数間の浮動小数点角度の概算を取得できます。ルックアップテーブル内の2つのポイント間の距離を計算し、2つの値の距離に基づいて加重平均を作成するのと同じくらい簡単です。たとえば、あなたが23.6度にいる場合、あなたは取る(sintable[23] * (1-0.6)) + (sintable[24] * 0.6)。基本的に、正弦波は、直線で結合された一連の個別の点になります。正確さと速度のトレードオフです。


私はしばらく前に、ライブラリよりも高速なsin / cosのテイラー多項式を使用したライブラリを作成しました。前提として、両方の入力として浮動小数点ラジアンを使用していました。
tuskiomi

8

そこにいくつか良い答えはここにあるが、私はまだ言及されていないメソッドを追加したい、非常によく組み込みシステム上三角関数の計算に適した1、それはCORDIC技術だ 。ここのWikiエントリそれが唯一のシフトを使用してtrigの関数を計算することができますし、追加し、小さなルックアップテーブル。

これは、Cの大まかな例です。実際には、CORDICを使用してCライブラリのatan2()関数を実装します(つまり、2つの直交成分を指定して角度を見つけます)。浮動小数点を使用しますが、固定小数点演算での使用に適合させることができます。

/*
 * 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;
}

しかし、最初にネイティブのArduinoトリガー関数を試してください-とにかく十分高速かもしれません。


1
過去にもstm8で同様のアプローチをとっています。2つのステップが必要です。1)sin(2x)からsin(x)とcos(x)を計算し、2)sin(x)、sin(x / 2)からsin(x +/- x / 2)を計算します。 、cos(x)、およびcos(x / 2)->反復により、ターゲットにアプローチできます。私の場合、私は45度(0.707)から始めて、目標に向かって進みました。標準のIAR sin()関数よりもかなり低速です。
dannyf

7

私は、固定小数点多項式近似を使用してArduinoで正弦と余弦を計算することを少し遊んでいます。標準cos()およびsin()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

これは、4回の乗算のみで計算された6次多項式に基づいています。乗算自体は、gccが効率的に実装していないことがわかったので、アセンブリで行われます。角度はuint16_t1回転の1/65536の単位で表されます。これにより、角度の計算は1回転を法として自然に機能します。

これがあなたの請求書に合うと思うなら、ここにコードがあります:固定小数点三角法。申し訳ありませんが、このページはまだ翻訳されていません。フランス語ですが、方程式は理解でき、コード(変数名、コメント...)は英語です。


編集:サーバーが消えたようですので、私が見つけた近似に関する情報をいくつか示します。

角度を2進固定小数点で、象限の単位で(または同等に)書きたかったのです。また、任意の多項式よりも計算が効率的であるため、偶数の多項式も使用したいと思いました。つまり、次のような多項式P()が必要でした。

cos(π/ 2 x)≈P(x 2)for x∈[0,1]

また、cos(0)= 1およびcos(π/ 2)= 0となるように、区間の両端で正確に近似する必要もありました。これらの制約により、

P(u)=(1 − u)(1 + uQ(u))

ここで、Q()は任意の多項式です。

次に、Q()の次数の関数として最適なソリューションを検索し、これを見つけました。

        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

上記のソリューションの中から選択するのは、速度と精度のトレードオフです。3番目のソリューションは、16ビットで実現可能な精度よりも高い精度を提供します。これは、16ビットの実装に選択したものです。


2
@Edgarさん、すごいですね。
SDsolar 2017

多項式を見つけるために何をしましたか?
TLW 2017

@TLW:Q(u)が任意の形式(1−x²)(1 +x²Q(x²))に制約されたいくつかの「素敵な」プロパティ(たとえば、cos(0)= 1)が必要でした。多項式(ページで説明されています)。私は1次Q(2つの係数のみ)を取り、近似によって近似係数を見つけ、試行錯誤によって最適化を手動で調整しました。
Edgar Bonet

@EdgarBonet-興味深い。キャッシュは機能しますが、そのページはロードされません。この回答に使用されている多項式を追加していただけませんか?
TLW 2017

@TLW:それを答えに追加しました。
エドガーボネット2017年

4

線形近似を使用して特定の角度のsin()とcos()を決定するいくつかの関数を作成できます。

私はこのようなことを考えています: それぞれについて、sin()とcos()のグラフィック表現を3つのセクションに分割し、そのセクションの線形近似を行いました。
線形近似

関数は理想的には最初に天使の範囲が0から90の間であることを確認します。
次に、ifelseステートメントを使用して、3つのセクションのどれが属するかを判断し、対応する線形計算を実行します(つまりoutput = mX + c


これには浮動小数点乗算が含まれませんか?
トランジスタオーバーロード2017

1
必ずしも。出力が0-1ではなく0-100の間でスケーリングされるようにすることもできます。この方法では、浮動小数点ではなく整数を扱います。注:100は任意です。0-128または0-512または0-1000または0-1024の間で出力をスケーリングできなかった理由はありません。2の倍数を使用すると、右シフトを実行するだけで結果を縮小できます。
sa_leinad

かなり賢い、@ sa_leinad。賛成票。トランジスタのバイアスを操作するときにこれを行ったことを覚えています。
SDsolar 2017

4

cos()とsin()に近似している他の人を探したところ、この答えに遭遇しました。

「事前計算された変換配列を使用した高速Sin / Cos」に対するdtbの回答

基本的に彼は、数学ライブラリのmath.sin()関数が値のルックアップテーブルを使用するよりも高速であると計算しました。しかし、私が知ることができることから、これはPCで計算されました。

Arduinoには、sin()およびcos()を計算できる数学ライブラリが含まれています。


1
PCには、高速化するFPUが組み込まれています。Arduinoはそうではありません、そしてそれはそれを遅くします。
マジェンコ

答えは、配列の境界チェックなどを行うC#にも当てはまります。
マイケル

3

ルックアップテーブルは、正弦を見つける最も速い方法です。また、固定小数点数(2進小数点がビット0の右側以外の整数である整数)での計算に慣れている場合は、サインを使用したその後の計算もはるかに高速になります。その場合、そのテーブルは単語のテーブルにすることができ、RAMスペースを節約するためにフラッシュ内に置くことができます。計算では、大きな中間結果を得るにはlongsを使用する必要がある場合があります。


1

通常、ルックアップテーブル>近似->計算。ram> flash。整数>固定小数点>浮動小数点。事前計算>リアルタイム計算。ミラーリング(サインからコサインまたはコサインからサイン)対実際の計算/ルックアップ...

それぞれにプラスとマイナスがあります。

あらゆる種類の組み合わせを作成して、アプリケーションに最適な組み合わせを確認できます。

編集:私は簡単なチェックをしました。8ビット整数出力を使用して、ルックアップテーブルで1024の正弦値を計算すると、0.6ミリ秒かかり、フローターでは133ミリ秒かかるか、200倍遅くなります。


1

OPに同じような質問がありました。正弦関数の最初の象限を0x8000から0xffffで始まる符号なし16ビット整数として計算するためのLUTテーブルを作成したいと思いました。そして私は楽しさと利益のためにこれを書いてしまいました。注:「if」ステートメントを使用すると、これはより効率的に機能します。また、あまり正確ではありませんが、サウンドシンセサイザーの正弦波には十分正確です。

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)
}

値を取得するには、この関数を使用します。0x0000から0x0800までの値を受け入れ、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

これがこのタスクへの最も効率的なアプローチではないことを思い出してください。適切な範囲で結果を出すためにテイラーシリーズを作成する方法を理解できなかっただけです。


コードがコンパイルされない:Imm_UI_Aが2回宣言されて;おり、a および一部の変数宣言が欠落しており、uLut_0グローバルである必要があります。必要な修正により、lu_sin()高速ですが(27から42 CPUサイクルの間)、非常に不正確です(最大エラー≈5.04e-2)。これらの「アルナダシアン多項式」の要点を理解することはできません。非常に重い計算のようですが、結果は単純な2次近似とほとんど同じくらい良くありません。また、この方法には莫大なメモリコストがかかります。PCでテーブルを計算し、それをPROGMEM配列としてソースコードに配置する方が良いでしょう。
Edgar Bonet、2017年

1

面白さのために、そしてそれが実行できることを証明するために、sin(x)の結果を計算して1ビットのエラーで24ビット(3バイト)になるAVRアセンブリルーチンを終了しました。入力角度は、小数点以下1桁の度数で、最初の象限のみ000から900(0〜90.0)までです。それは210未満のAVR命令を使用し、平均211マイクロ秒で実行され、211us(角度= 001)から213us(角度= 899)まで変化します。

AVRマイクロコントローラーを考慮して、計算に最適なアルゴリズムを検討するだけで10日(自由時間)以上かかり、浮動小数点はなく、すべての可能な除算が排除されました。さらに時間がかかったのは、整数に適切なステップアップ値を作成することでした。精度を高めるには、1e-8の値を2の整数2 ^ 28以上にステップアップする必要があります。精度と切り上げのすべてのエラーの原因が見つかったら、計算の分解能を2 ^ 8または2 ^ 16増やして、最良の結果が得られました。最初に、すべての値をInt(x)またはRound(x、0)としてAVRコア処理を正確に表すようにして、Excelですべての計算をシミュレートしました。

たとえば、アルゴリズムでは、角度はラジアンでなければならず、ユーザーが入力しやすいように入力は度単位です。度をラジアンに変換するための自明な式はrad = degrees * PI / 180ですが、見栄えがよく簡単ですが、そうではありません。PIは無限数です。使用する桁数が少ない場合、出力でエラーが発生し、180で割ると、 AVRビット操作には除算命令がないため、整数1をはるかに下回る数値が含まれるため、結果は浮動小数点数を必要とします。たとえば、ラジアン1°(度)は0.017453293です。PIと180は定数なので、単純な乗算のためにこれを逆にしないのはなぜですか?PI / 180 = 0.017453293、2 ^ 32を掛けると、結果は定数74961320(0x0477D1A8)になり、この数値に角度(度単位)を掛けます。90°に対して900と言い、4ビット右(÷16)にシフトして4216574250(0xFB53D12A)を取得します。これは、2 ^ 28の拡張による90°のラジアンであり、単一の除算なしで4バイトに収まります(4ビットシフト右)。ある意味では、そのようなトリックに含まれるエラーは2 ^ -27よりも小さいです。

したがって、以降のすべての計算では2 ^ 28高いことを覚えておく必要があります。外出先での結果を16、256、または65536で割り、解決に役立たない不要な増大するハンガーバイトを使用しないようにする必要があります。それは骨の折れる仕事であり、各計算結果のビットの最小量を見つけ、結果の精度を24ビット前後に保ちました。Excelフローで上位または下位のビット数を使用してtry / errorで実行されるいくつかの計算のそれぞれ、結果のエラービットの全体的な量を、コードを900回実行するマクロで0-90°を示すグラフで監視します。 10分の1度に1回。この「ビジュアル」Excelアプローチは私が作成したツールであり、コードのすべての部分に最適なソリューションを見つけるのに大いに役立ちました。

たとえば、この特定の計算結果13248737.51を13248738に切り上げるか、「0.51」の小数を失うだけで、900のすべての入力角度(00.1〜90.0)テストの最終結果の精度にどのくらい影響しますか?

動物をすべての計算で32ビット(4バイト)以内に収めることができ、結果として23ビット以内の精度を得るという魔法に終わりました。結果の3バイト全体をチェックする場合、エラーは±1 LSBであり、未解決です。

ユーザーは、独自の精度要件のために、結果から1バイト、2バイト、または3バイトを取得できます。もちろん、1バイトで十分な場合は、単一の256バイトのsinテーブルを使用し、それを取得するためにAVR 'LPM'命令を使用することをお勧めします。

Excelシーケンスをスムーズかつきちんと実行すると、ExcelからAVRアセンブリへの最終的な変換に2時間もかかりませんでした。

その時、私はさらに圧迫し、レジスターの使用を減らすことができました。実際の(最終ではない)コードは約205命令(約410バイト)を使用し、sin(x)計算を平均212us、16MHzのクロックで実行します。その速度では、1秒あたり4700以上のsin(x)を計算できます。重要ではありませんが、ルックアップテーブルを使用しなくても、23ビットの精度と解像度で最大4700Hzの正確な正弦波を実行できます。

基本アルゴリズムはsin(x)のテイラー級数に基づいていますが、AVRマイクロコントローラーと精度を念頭に置いて私の意図に合うように大幅に変更されました。

2700バイトのテーブル(900エントリ* 3バイト)を使用すると速度が魅力的になりますが、その面白さや学習経験は何ですか?もちろん、CORDICアプローチも検討されましたが、おそらく後で、ここでのポイントは、テイラーをAVRコアに押し込み、乾いた岩から水を取り出すことです。

Arduinoの「sin(78.9°)」は、212us未満で23ビットの精度で処理(C ++)を実行でき、必要なコードは205命令未満で実行できるのでしょうか。C ++がCORDICを使用している可能性があります。Arduinoスケッチはアセンブリコードをインポートできます。

ここにコードを投稿しても意味がありません。後でこの投稿を編集して、おそらくこのURLの私のブログにWebリンクを含めます。ブログはほとんどがポルトガル語です。

この趣味のお金なしのベンチャーは興味深いもので、除算命令なしで、8x8ビットでの乗算のみで、16MHzでほぼ16MIPSのAVRエンジンの限界を押し上げました。sin(x)、cos(x)[= sin(900-x)]およびtan(x)[= sin(x)/ sin(900-x)]を計算できます。

とりわけ、これは私の63歳の脳を磨き、油を塗っておくのに役立ちました。10代の若者が「古い人々」はテクノロジーについて何も知らないと言ったとき、私は「もう一度考えてみてください。今日あなたが楽しむすべての基礎を作ったのは誰だと思いますか?」と答えます。

乾杯


いいね!いくつかの注意:1 .標準sin()関数はあなたのそれとほぼ同じ精度を持ち、2倍の速度です。また、多項式に基づいています。2.任意の角度を0.1°の最も近い倍数に丸める必要がある場合、これは8.7e-4程度の丸め誤差につながる可能性があり、23ビット精度の利点をある程度無効にします。3.多項式を共有していただけませんか?
Edgar Bonet

1

他の人が言及しているように、速度が欲しい場合はルックアップテーブルが適しています。私は最近、ATtiny85でのトリガー関数の計算を高速ベクトル平均(私の場合は風)の使用について調査しています。常にトレードオフがあります...私にとっては1度の角度分解能しか必要なかったので、360 intのルックアップテーブル(-32767から32767へのスケーリング、intでのみ機能)が最善の方法でした。サインを取得することは、インデックス0-359を指定するだけの問題です...非常に高速です!私のテストからのいくつかの数字:

フラッシュルックアップ時間(US):0.99(PROGMEMを使用して保存されたテーブル)

RAMルックアップ時間(us):0.69(RAM内のテーブル)

Lib time(us):122.31(Arduino Libを使用)

これらは、それぞれの360ポイントのサンプル全体の平均であることに注意してください。テストはnanoで行われました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.