マイクロプロセッサでの減衰曲線の実装

前回、参照テーブルを使わずに減衰曲線を生成する方法を考察しましたが、その方法をマイクロプロセッサで試してみました。やはり一筋縄ではいかないようです。

実際のプログラムは記事に載せるには長すぎますが、関係のある部分を抜粋して、こんな感じのプログラムを組みました。一見動きそうだし、これを普通に 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 変数として宣言しなくても動くようです。コンパイラが良きに計らってくれるようです。

要点は、ビットを伸ばしたり縮めたりする演算をするときには、以下の点を気を付けると動くようです

  • データをどこにロードさせるかはっきりさせる (一時変数を宣言する)
  • 演算がどこで行われるかはっきりさせる
    • 二項演算子 +, -, *, >>, << のかわりに +=, -=, *=, >>=, <<= を使う。
    • 一行一演算、一行に二つの演算を入れない

「ようです」が多いのが恐縮です。このことが書かれているドキュメントを読んだわけではないので、コンパイラの挙動から推測しています。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください