前回、参照テーブルを使わずに減衰曲線を生成する方法を考察しましたが、その方法をマイクロプロセッサで試してみました。やはり一筋縄ではいかないようです。
実際のプログラムは記事に載せるには長すぎますが、関係のある部分を抜粋して、こんな感じのプログラムを組みました。一見動きそうだし、これを普通に PC で実行すれば動作するはずですが、AVR ではうまく動作しません。
volatile uint32_t g_value; // value holder, Q10.22 fixed point number volatile uint32_t g_decay_factor; // amount of decay, Q0.32 fixed point volatile uint8_t g_update_ready; ISR(Time1_OVF_vect) { OCR1A = g_value >> 22; // reflect the value to PWM、Q10.22 to integer g_update_ready = 1; } void UpdateValue() { if (!update_ready) { return; } // reduce the value by multiplying the decay factor uint64_t temp = g_value; temp *= g_decay_factor; g_value = temp >> 32; g_update_ready = false; } int main() { g_value = 1023 << 22; // integer to Q10.22 g_decay_factor = 0.99309249543703590153321021688807 * 0x100000000; g_update_ready = 0; while (true) { UpdateValue(); } }
どうも AVR 用のコンパイラ avr-gcc は、C 言語標準に厳密には従っていないようです。どこかにドキュメントがあるかもしれませんが見つけていません。レジスタを細かく制御したいための仕様なのかもしれません。レジスタを意識して、データ領域からレジスタにどんな風にデータをロードしてレジスタ上でどんな演算をするのか意識して書くと動くようです。上記のプログラムは以下のように書き換えれば動くはずです。
volatile uint32_t g_value; // value holder, Q10.22 fixed point number volatile uint32_t g_decay_factor; // amount of decay, Q0.32 fixed point volatile uint8_t g_update_ready; ISR(Time1_OVF_vect) { register uint32_t temp = g_value; // copy the Q10.22 value to a variable temp >>= 22; // Q10.22 to integer OCR1A = temp; // reflect the value to PWM g_update_ready = 1; } void UpdateValue() { if (!update_ready) { return; } register uint64_t temp = g_value; // copy the Q10.22 value to a 64-bit int temp *= g_decay_factor; // multiply by the Q0.32 decay factor temp >>= 32; // shift 32 bit to adjust scale, now it's Q10.22 again g_value = temp; // copy back to the value holder g_update_ready = false; } int main() { register uint32_t temp = 1023 << 22; // this is calculated by the compiler g_value = temp; g_decay_factor = 0.99309249543703590153321021688807 * 0x100000000; g_update_ready = 0; while (true) { UpdateValue(); } }
temp は register 変数として宣言しなくても動くようです。コンパイラが良きに計らってくれるようです。
要点は、ビットを伸ばしたり縮めたりする演算をするときには、以下の点を気を付けると動くようです
- データをどこにロードさせるかはっきりさせる (一時変数を宣言する)
- 演算がどこで行われるかはっきりさせる
- 二項演算子 +, -, *, >>, << のかわりに +=, -=, *=, >>=, <<= を使う。
- 一行一演算、一行に二つの演算を入れない
「ようです」が多いのが恐縮です。このことが書かれているドキュメントを読んだわけではないので、コンパイラの挙動から推測しています。