AVRの効率的な逆(1 / x)


12

AVRで逆数を計算する(または近似する)効率的な方法を見つけようとしています。

速度を直線的に変化させることができるように、ステッピングモーターのパルス周期を計算しようとしています。周期は速度の逆数に比例します(p = K/v)が、これをオンザフライで計算する良い方法は考えられません。

私の式は

p = 202/v + 298; // p in us; v varies from 1->100

Arduinoの上のテスト、除算は離脱完全に無視しているように見えるpに固定298(おそらくこれはAVR-GCCで異なるだろうが)。また、をv超えるまでループで合計し、ループ202をカウントしようとしましたが、これは非常に遅いです。

ルックアップテーブルを生成してフラッシュに保存することはできましたが、別の方法があるかどうか疑問に思っていました。

編集:たぶん、タイトルは「効率的な分割」でなければなりません...

更新:pingsweptが指摘しているように、速度を速度にマッピングするための私の式は間違っています。しかし、主な問題は除算操作です。

編集2:さらなる調査で、除算はarduinoで機能しています。問題は、上記の誤った式と他の場所でのintオーバーフローの両方が原因でした。


2
vは整数または浮動小数点ですか?
mjh2007

整数ですが、それは私たちにピリオドを与えるので、整数除算はここで十分正確です。
ピーターギブソン

100個の整数の値を事前に計算し、速度に本当に関心がある場合は、乗算用のプリスケーラーのルックアップテーブルを作成できます。もちろん、メモリのトレードオフがあります。
RYS

回答:


7

除算の良い点の1つは、だいたい誰もがそれをやっているということです。これはC言語の非常に重要な機能であり、AVR-GCC(Arduino IDEによって呼び出されます)などのコンパイラーは、マイクロコントローラーにハードウェア除算命令がない場合でも、利用可能な最適な除算アルゴリズムを選択します。

つまり、非常に奇妙な特殊なケースがない限り、除算の実装方法を心配する必要はありません。


心配するなら、Atmelの公式に提案された除算アルゴリズム(コードサイズに最適化されたものと、実行速度に最適化されたもので、どちらもデータメモリを使用しない)をお楽しみください。彼らは次のとおりです。

http://www.atmel.com/dyn/resources/prod_documents/doc0936.pdf

これは、標準Arduinosで使用されているAtmega 168やAtmega 328などの(かなり大きい)AtmegaプロセッサーのAtmelページにリストされているアプリケーションノート「AVR200:Multiply and Divide Routines」です。データシートとアプリケーションノートのリストは次のとおりです。

http://www.atmel.com/dyn/products/product_card.asp?part_id=4720


4

必要なのは100エントリのルックアップテーブルだけであるように思えます。それよりずっと速くはなりません。

#define VALUE_FOR_V_EQUALS_ZERO 0
uint16_t formula_lookup[100] = {VALUE_FOR_V_EQUALS_ZERO, 500, 399, 365, 348, ..., 300};

...

//"calculate" formula
p = formula_lookup[v > 67 ? 67 : v];

67を超えるvの値は常に300と評価されるため、実際には68の値ルックアップテーブルのみを編集します。


質問で言ったように、別の方法があるかどうか疑問に思っていました
ピーターギブソン

3

いくつかの非常に良い技術は本の中で言及さがある「ハッカーディライトヘンリー・ウォーレンによって、彼のウェブサイト上でhackersdelight.org。定数で割ったときに小さいマイクロコントローラとよく働く技術について見ていてこのファイルを


あなたが言うように、これらは定数で割ると良いように見えますが、私の問題には本当に当てはまりません。彼は、逆数を事前に計算するなどの手法を使用します。逆数を掛けてからシフトします。
ピーターギブソン

それは素晴らしい本です!
ウィンデルオスケイ

3

あなたの関数はあなたが望む結果を与えるとは思えません。たとえば、値50は約302を返し、100は約300を返します。これらの2つの結果は、モーターの速度をほとんど変化させません。

私があなたを正しく理解していれば、あなたは本当に1から100を300から500までの範囲でマッピングするための高速な方法を探しています。

おそらく試してください:p = 500-(2 * v)

しかし、私は誤解しているかもしれません-あなたは一定周波数の方形波のオンタイムを計算しようとしていますか?298とは何ですか?


はい、式は間違っています。ポイントは、時間間隔ごとにターゲット速度を一定に変化させることで、ステッパーの出力から線形加速度を取得することです(speed ++など)。これは、+ veエッジがステッピングモーターコントローラーに送信される周期(周波数)にマッピングする必要があります。したがって、逆の関係(p = 1 / v)です。
ピーターギブソン

一定の加速度、つまり直線的に増加する速度を意味しますか?
pingswept

ええ、はい、一定の加速、私はもともと質問を書いたときにそれをあざけり、そこでも修正したことを覚えています
ピーターギブソン

3

除算を近似する効率的な方法は、シフトによるものです。例:x = y / 103; 103で除算することは0.0097087で乗算することと同じであるため、これを近似するには、最初に「適切な」シフト番号を選択します(ベース2の数値、2,4,8,16,32など)。

この例では、1024は、10/1024 = 0.009765と言うことができるため、適切です。その場合、コーディングが可能です。

x =(y * 10)>> 10;

もちろん、変数yが乗算されたときにその型がオーバーフローしないことを確認してください。正確ではありませんが、迅速です。


これは、timrorrが提供するリンクの手法に似ており、定数による除算には適していますが、コンパイル時に不明な値で除算する場合には適していません。
ピーターギブソン

3

別の注意点として、除算をサポートしていないCPUで除算を行おうとしている場合、このWiki記事でそれを行うための本当にクールな方法があります。

http://en.wikipedia.org/wiki/Multiplicative_inverse

乗算と減算のみを使用してxの逆数を近似するには、数yを推測し、yを2y − xy2で繰り返し置換します。yの変化が十分に小さくなる(とどまる)と、yはxの逆数の近似値になります。


面白い、私は、これは他の方法が記載さと比較する方法だろう
ピーター・ギブソン

1

ここでのこのプロセスはmcuフレンドリーに見えますが、少し移植する必要があるかもしれません。

LUTの方が簡単だと思われますが。必要なのは100バイトのみで、補間を使用した場合は少なくなります。また、LUTには定数が格納されているため、コンパイラはデータ領域ではなくコード領域に配置することさえあります。


除数が配当と等しくなるかそれを超えるまで除数を合計するのに似たようなことを試みましたが、かなり遅いことがわかりました。LUTを使用する方法になると思われます-avr-gccを使用すると、<avr / progmem.h>に特別なマクロが必要です。
ピーターギブソン

1

除算が浮動小数点として実行されていることを確認してください。AVRではなくMicrochipを使用していますが、C18を使用する場合は、リテラルを強制的に浮動小数点として処理する必要があります。例えば。数式を次のように変更してみてください。

p = 202.0/v + 298.0;


1

速くしたいので、ここに行きます。AVRは正規化を効率的に行うことができないため(シフトができなくなるまで左にシフトします)、擬似浮動小数点アルゴリズムを無視します。AVRで非常に正確で最速の整数除算を行う最も簡単な方法は、相互参照テーブルを使用することです。テーブルには、大きい数(2 ^ 32など)でスケーリングされた逆数が格納されます。次に、アセンブラーでunsigned32 x unsigned32 = unsigned 64乗算
を実装します。したがって、answer =(numerator * inverseQ32 [denominator])>> 32 です。インラインアセンブラーを使用して乗算関数を実装しました(ac関数にラップ)。GCCは64ビットの「ロングロング」をサポートしますが、結果を得るには、8ビットアーキテクチャのC言語の制限により、32ビット32 = 64ではなく64ビットを64ビットで乗算する必要があります......

この方法の欠点は、1〜4096の整数で除算する場合、4K x 4 = 16Kのフラッシュを使用することです......

Cで約300サイクルで非常に正確な符号なし除算が達成されました。

24ビットまたは16ビットのスケーリングされた整数を使用して、速度を上げて精度を下げることを検討できます。


1
p = 202/v + 298; // p in us; v varies from 1->100

p=298コンパイラーは最初に除算してから加算するため、方程式の戻り値は既にあります。整数muldivの解像度は次のとおりです。

p = ((202*100)/v + (298*100))/100 

これを使用するのはa*f、a = integer f = fraction を使用した同じMultiply です。

これは結果として得られますr=a*ff=b/cr=a*b/c演算子の位置が最終r=(a*b)/c関数またはmuldiv関数(整数のみを使用して小数を計算する方法)を生成するため、まだ機能しません。

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