sin() の解説と実装リファレンス

記事内には広告が含まれています。

【概要]

sin() 関数は、与えられた角度(ラジアン)に対して正弦(sine)の値を計算する標準数学関数です。Arduinoの環境では、C言語の標準ライブラリである <math.h>(Arduino環境では Arduino.h を通じて利用可能)に含まれています。主に、周期的な波形の生成、信号処理、物理シミュレーション、およびモーターの滑らかな加速・減速制御などの動的挙動の実装に使用されます。

【仕様]

  • 引数: float または double 型の数値。単位はラジアン(radians)です。
  • 戻り値: -1.0 から 1.0 の範囲の float または double 型。
  • データ型: 実行環境のアーキテクチャに依存します。
    • AVR(Uno, Nanoなど): double は float(32ビット)として扱われるため、実質的に float 精度の計算が行われます。
    • ESP32 / ARM Cortex-M系: 32ビットまたは64ビットの浮動小数点演算ユニット(FPU)の有無により、double 型の精度が保持される場合があります。

【技術的特徴]

1. 計算アルゴリズム

sin() の内部実装はコンパイラおよびハードウェアに依存しますが、一般的には以下のいずれかの手法で近似または計算されます:
– テイラー展開 (Taylor Series): 多項式を用いて値を算出。計算負荷と精度のトレードオフが存在します。
– CORDICアルゴリズム: 角度を回転と加算の繰り返しで計算するアルゴリズム。演算リソースの乏しいマイクロコントローラでよく採用されます。
– ルックアップテーブル (LUT): あらかじめ計算された値をメモリから参照する手法。関数自体ではなく、最適化の過程で使用されることがあります。

2. ハードウェアによる実行特性

  • 8bit AVRプロセッサ: 浮動小数点演算ユニット(FPU)を搭載していないため、すべての演算はソフトウェアエミュレーションによって行われます。このため、計算コストは非常に高く、サイクル数(Clock Cycles)を大幅に消費します。
  • 32bit / 64bitプロセッサ: FPUを搭載している場合、ハードウェアレベルでの高速な演算が可能となり、実行速度は劇的に向上します。

3. sin() と sinf() の比較

  • sin(): double 型を対象とする標準関数。高い精度を求める場合に適していますが、8bit機では sinf() と同等のコストがかかることが多いです。
  • sinf(): float 型を対象とする関数。32bit以上のプロセッサにおいて、double 型を扱う sin() よりも高速に動作します。

【注意点]

  • ラジアン変換の必須性: Arduinoの sin() 関数は「度(Degrees)」ではなく「ラジアン」を受け取ります。度からラジアンへの変換式:rad = deg * (PI / 180.0) を適用する必要があります。
  • 計算コストとジッタ: タイマー割り込み内や、極めて高いサンプリングレートが必要な制御ループ内で sin() を呼び出すと、CPUリソースを枯渇させ、実行時間のバラツキ(ジッタ)の原因となります。高頻度の計算が必要な場合は、事前計算したルックアップテーブル(LUT)の使用を検討してください。
  • 累積誤差: 時間の経過とともに加算される変数(例:time += 0.01)を引数に直接使用する場合、浮動小数点の精度限界により、長時間の稼働で位相がずれる可能性があります。これを防ぐには、角度を 0 から 2π の範囲にモジュロ演算でリセットする手法が有効です。

【サンプルコード]

#include <math.h>
#include <Arduino.h>

const float PI = 3.141592653589793;
const float frequency = 1.0;    // 周波数 (1Hz)
const float amplitude = 100.0;   // 最大振幅

void setup() {
  Serial.begin(9600);
}

void loop() {
  static unsigned long lastMillis = 0;
  unsigned long currentMillis = millis();

  // 10msごとに計算を実行
  if (currentMillis - lastMillis >= 10) {
    lastMillis = currentMillis;

    // 現在の時間を秒に変換し、ラジアンに変換
    float time_sec = (currentMillis / 1000.0) * frequency;
    float angle = time_sec * 2.0 * PI;

    // sin() 関数を使用してサイン波を計算
    float value = amplitude * sin(angle);

    Serial.print("Value: ");
    Serial.println(value);
  }
}

【活用例]

  1. オーディオ信号生成: 正弦波を生成し、PWM出力を通じて電子音やトーンを生成する。
  2. モーターの滑らかな動作: 物理的な加速・減速をシミュレートするためのS字カーブに近い制御信号の生成。
  3. センサーデータの信号処理: 交流信号の波形解析や、特定の周波数成分の抽出における位相計算。
  4. LEDフェード演出: 周期的な輝度の変化を、線形補間よりも滑らかに表現する際の係数計算。

コメント

タイトルとURLをコピーしました