はじめに

加速度センサは、スマートフォンの画面回転や活動量計など、日常的なデバイスに広く使われています。しかし、その動作原理である**「重力加速度の検出」**を理解し、実際にアナログ値から角度を算出する工作は、センサーの本質を学ぶ良い機会です。

今回は、3軸アナログ加速度センサKXR94-2050を使用して、物体の傾斜角を±90度の範囲で検出し、11個のLEDバーグラフで視覚化するデジタル水平器を構築します。単にライブラリを使うのではなく、ADC(アナログ-デジタル変換)の生データから逆三角関数を用いて角度を算出する過程を詳しく解説します。

Arduino UNO R4 Minima

Arduino UNO R4 Minima

Arduino UNO R4 Minima starter kit

Arduino UNO R4 Minima starter kit


必要な部品と技術仕様

部品名 仕様 数量
Arduino UNO ATmega328P / 10bit ADC 1
3軸加速度センサ KXR94-2050 (±2g / アナログ出力) 1
赤色LED Vf=1.8-2.2V / If=20mA 11
カーボン抵抗 220Ω (電流制限用) 11
ブレッドボード 標準サイズ 2
ジャンパー線 オス-オス / オス-メス 適量

KXR94-2050:3軸アナログ加速度センサの技術的特性

KXR94-2050 3軸加速度センサモジュール

KXR94-2050 3軸加速度センサモジュール

主要スペック

  • 測定範囲: ±2g(g = 9.8m/s²)
  • 出力方式: アナログ電圧出力(各軸独立)
  • 感度: 約600mV/g(Vcc=5V時)
  • ゼロg出力電圧: 約2.5V(Vcc/2)
  • 動作電圧: 2.7V - 5.5V
  • パッケージ: DIP変換済み(ブレッドボードに直挿し可能)

重力加速度による角度検出の原理

加速度センサは、内部のMEMS(微小電気機械システム)構造により、慣性力を電気信号に変換します。静止状態では、センサには**重力加速度(1g = 9.8m/s²)**のみが作用します。

加速度センサの座標系と重力ベクトル

加速度センサの座標系と重力ベクトル

センサをX軸周りに角度θだけ傾けた場合、各軸に加わる重力成分は以下のように分解されます:

X軸成分: g_x = g × sin(θ)
Y軸成分: g_y = g × cos(θ) × sin(φ)  ※φはY軸周りの回転
Z軸成分: g_z = g × cos(θ) × cos(φ)

今回はX軸の1軸のみを使用するため、X軸出力電圧から逆正弦関数(asin)を用いて角度θを算出します:

θ = asin(V_x - V_zero) / 感度) × (180 / π)

アナログ出力の特性

KXR94-2050は、加速度に比例したアナログ電圧を出力します:

  • 0g(水平): 約2.5V(Arduino ADCで512付近)
  • +1g(90度傾斜): 約3.1V(Arduino ADCで635付近)
  • -1g(-90度傾斜): 約1.9V(Arduino ADCで389付近)

重要: この出力電圧は個体差や温度特性により変動するため、実機でのキャリブレーション(校正)が必須です。


回路設計と実装

回路構成の全体像

配線図:センサとLEDバーグラフを分離配置

配線図:センサとLEDバーグラフを分離配置

設計のポイント:

  1. センサモジュールの独立配置

    • センサを別ブレッドボードに配置し、ジャンパー線で接続
    • これにより、センサを自由に傾けて動作確認が可能
    • 実用時は、センサのみを測定対象物に固定する使い方も想定
  2. 3軸すべての読み取り

    • 今回の水平器ではX軸のみ使用しますが、Y軸・Z軸もArduinoに接続
    • シリアルモニタで3軸の値を確認できるため、センサの動作検証やキャリブレーションが容易
    • 将来的に2軸水平器(X軸+Y軸)へ拡張する際の下準備
  3. LED配置の考慮

    • 11個のLEDを一直線に配置し、傾斜方向を直感的に把握
    • 中央(6番目)のLEDが点灯 = 水平状態
    • 左右に5個ずつ = ±90度まで約18度刻みで表示

電流制限抵抗の計算

LEDを安全に点灯させるため、各LEDに直列で220Ωの電流制限抵抗を接続します。

順方向電流の計算:
I = (Vcc - Vf) / R
  = (5V - 2.0V) / 220Ω
  ≒ 13.6mA

赤色LEDの標準動作電流(20mA)以下に収まっており、十分な明るさと安全性を両立しています。

ピン配置

Arduino Pin 接続先 備考
A0 X軸出力 今回使用(傾斜角検出)
A1 Y軸出力 読み取りのみ(拡張用)
A2 Z軸出力 読み取りのみ(拡張用)
D2-D12 LED1-11 各220Ω抵抗経由でGND
5V センサVcc センサの電源供給
GND センサGND 共通グランド
実装写真:センサ部とLED表示部を分離した構成

実装写真:センサ部とLED表示部を分離した構成


プログラム実装と技術解説

完全版ソースコード

// ============================================
// 3軸加速度センサによるデジタル水平器
// KXR94-2050 + Arduino UNO
// ============================================

#define AXISXPIN  0  // X軸:アナログピンA0
#define AXISYPIN  1  // Y軸:アナログピンA1
#define AXISZPIN  2  // Z軸:アナログピンA2

// 円周率の定義(math.hで未定義の場合に備える)
#ifndef M_PI
#define M_PI  (3.1415926535897932384626433832795)
#endif

// LED配列とグローバル変数
int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int currentLed, previousLed;
int numberOfLeds = sizeof(ledPins) / sizeof(ledPins[0]);

void setup() {
    // 全LEDピンを出力モードに設定
    for (int i = 0; i < numberOfLeds; i++) {
        pinMode(ledPins[i], OUTPUT);
    }
    
    // デバッグ用シリアル通信(オプション)
    // Serial.begin(9600);
}

/**
 * ADC値を任意の範囲にマッピングする関数
 * Arduino標準のmap()は整数のみだが、これは浮動小数点に対応
 * 
 * @param iIn        入力値(ADC値)
 * @param iIn1       入力最小値(-90度時のADC値)
 * @param iIn2       入力最大値(+90度時のADC値)
 * @param dOut1      出力最小値(-1.0 = sin(-90°))
 * @param dOut2      出力最大値(+1.0 = sin(+90°))
 * @param bConstrain 出力を範囲内に制限するか
 * @return           変換後の値(sin(θ)の値)
 */
double Map(int iIn, int iIn1, int iIn2, double dOut1, double dOut2, boolean bConstrain = false) {
    // 線形補間による変換
    double dValue = (iIn - iIn1) * (dOut2 - dOut1) / (iIn2 - iIn1) + dOut1;
    
    if (bConstrain) {
        double dOutMin = min(dOut1, dOut2);
        double dOutMax = max(dOut1, dOut2);
        dValue = constrain(dValue, dOutMin, dOutMax);
    }
    return dValue;
}

void loop() {
    // =======================================
    // 1. ADC値の読み取り(10bit: 0-1023)
    // =======================================
    int ADC_X = analogRead(AXISXPIN);
    int ADC_Y = analogRead(AXISYPIN);
    int ADC_Z = analogRead(AXISZPIN);

    // =======================================
    // 2. ADC値をsin(θ)に変換
    // =======================================
    // 【重要】292と732は要キャリブレーション!
    // 292 = X軸を-90度に傾けた時のADC値
    // 732 = X軸を+90度に傾けた時のADC値
    double rotateX = Map(ADC_X, 292, 732, -1.0, 1.0, true);
    
    // =======================================
    // 3. 逆正弦関数で角度を算出
    // =======================================
    // asin()は-1.0〜+1.0をラジアン(-π/2〜+π/2)に変換
    // さらに180/πを乗じて度数法(-90°〜+90°)に変換
    double angleX = asin(rotateX) * 180.0 / M_PI;

    // =======================================
    // 4. LED番号の計算
    // =======================================
    // ADC値292-732(範囲440)を11個のLED(2-12番ピン)にマッピング
    // 440 / 11 = 40 → 約40刻みで1つのLEDに対応
    currentLed = 12 - ((ADC_X - 292) / 40);
    currentLed = constrain(currentLed, 2, 12);
    
    // =======================================
    // 5. LED制御(排他点灯)
    // =======================================
    digitalWrite(currentLed, HIGH);
    
    // 前回と異なるLEDの場合、前回のLEDを消灯
    if (currentLed != previousLed) {
        digitalWrite(previousLed, LOW);
    }
    previousLed = currentLed;

    // =======================================
    // 6. デバッグ出力(オプション)
    // =======================================
    /*
    Serial.print("ADC_X:");
    Serial.print(ADC_X);
    Serial.print(" | Angle:");
    Serial.print(angleX);
    Serial.print(" | LED:");
    Serial.println(currentLed);
    */

    delay(5);  // 5msウェイト(約200Hzでサンプリング)
}

コードの技術的解説

1. キャリブレーション値(292, 732)の取得方法

この値はセンサ個体ごとに異なるため、以下の手順で実測が必須です:

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

void loop() {
    int ADC_X = analogRead(AXISXPIN);
    Serial.println(ADC_X);
    delay(100);
}

上記のプログラムでシリアルモニタを開き、

  1. センサをX軸を中心に-90度(左に倒す)→ 出力される値を記録(例:292)
  2. センサをX軸を中心に+90度(右に倒す)→ 出力される値を記録(例:732)
  3. この2つの値をMap()関数の引数に代入

理論値との対応:

-90度時:1g × sin(-90°) = -1g → 2.5V - 0.6V = 1.9V → ADC値 約389
  0度時:1g × sin(0°)    =  0g → 2.5V         → ADC値 約512
+90度時:1g × sin(+90°)  = +1g → 2.5V + 0.6V = 3.1V → ADC値 約635

実測値(292, 732)が理論値と異なるのは、センサのオフセット誤差電源電圧の個体差によるものです。


2. ADC値からsin(θ)への線形変換

Map()関数は、ADC値を-1.0〜+1.0(sin関数の値域)に線形補間します:

double rotateX = Map(ADC_X, 292, 732, -1.0, 1.0, true);

数式:

rotateX = (ADC_X - 292) × (1.0 - (-1.0)) / (732 - 292) + (-1.0)
        = (ADC_X - 292) × 2.0 / 440 - 1.0
        = (ADC_X - 292) / 220 - 1.0

例:ADC_X = 512(水平)の場合

rotateX = (512 - 292) / 220 - 1.0 = 220/220 - 1.0 = 0.0

これはsin(0°) = 0に対応し、正しく水平を検出しています。


3. 逆正弦関数による角度算出

double angleX = asin(rotateX) * 180.0 / M_PI;

asin()関数の特性:

  • 入力範囲:-1.0 〜 +1.0
  • 出力範囲:-π/2 〜 +π/2(ラジアン)
  • 180/πを乗じることで度数法(-90° 〜 +90°)に変換

注意点:

  • rotateXが±1.0を超えるとNaN(非数)を返すため、Map()の第6引数でtrueを指定し、範囲外の値をクリッピング

4. LED番号の直接計算

角度を経由せず、ADC値から直接LED番号を算出しています:

currentLed = 12 - ((ADC_X - 292) / 40);

計算ロジック:

  • ADC範囲440(732-292)を11個のLEDに分割 → 440 / 11 ≒ 40
  • ADC値が40増えるごとに1つLEDが移動
  • 12から引いているのは、ADC値が増える方向とLED番号が逆だから
ADC値 計算式 LED番号(ピン) 傾斜角
292 12 - 0/40 = 12 D12 -90°
332 12 - 40/40 = 11 D11 -72°
512 12 - 220/40 = 7 D7
732 12 - 440/40 = 2 D2 +90°

5. サンプリング周期の考慮

delay(5);  // 5msウェイト → 約200Hzサンプリング

人間の視覚では30fps(約33ms周期)で滑らかに見えるため、5msは十分高速です。ただし、ADCの連続読み取りノイズを考慮し、移動平均フィルタを追加するとさらに安定します:

// 簡易移動平均フィルタ(3点)
static int adc_history[3] = {0, 0, 0};
adc_history[2] = adc_history[1];
adc_history[1] = adc_history[0];
adc_history[0] = analogRead(AXISXPIN);
int ADC_X = (adc_history[0] + adc_history[1] + adc_history[2]) / 3;

動作検証とキャリブレーション

実機動作デモ

動作の確認ポイント:

  1. 水平状態(0度)

    • 中央付近のLED(D6またはD7)が点灯
    • センサを静止させても点灯位置が安定している
  2. 左右への傾斜

    • センサを左に傾けると右側のLEDが点灯(-90度でD12)
    • センサを右に傾けると左側のLEDが点灯(+90度でD2)
    • LEDの移動が滑らかで、急激なジャンプがない
  3. 応答速度

    • センサの傾きに対してLEDがほぼリアルタイム(約5ms)で応答
    • チャタリング(点滅)が発生しない

トラブルシューティング

問題1: LEDが端(D2またはD12)から動かない

原因: キャリブレーション値(292, 732)が実機と合っていない

対策:

// デバッグコードで実測値を確認
void loop() {
    Serial.println(analogRead(AXISXPIN));
    delay(100);
}
// -90度、0度、+90度での値を記録し、コードを修正

問題2: LEDがランダムに点滅する

原因: ADCノイズによる不安定な読み取り

対策:

// センサ電源ラインに0.1μFのパスコンを追加
// またはプログラムに移動平均フィルタを実装

問題3: 角度が理論値とずれる

原因: センサの非線形性や温度ドリフト

対策:

// 複数点(-90°, -45°, 0°, +45°, +90°)で実測し、
// 線形補間ではなくルックアップテーブルを使用

まとめと技術的考察

本プロジェクトで学べる技術要素

  1. アナログセンサーの基礎

    • 加速度センサの動作原理(重力加速度の分解)
    • 10bit ADC(0-1023)の分解能と実用精度
    • センサ個体差とキャリブレーションの重要性
  2. 数学的処理

    • 三角関数(sin, asin)の実用例
    • ラジアンと度数法の変換
    • 線形補間による値域変換
  3. 組み込みプログラミング

    • ADC読み取りのタイミング制御
    • GPIO(デジタル出力)の排他制御
    • 浮動小数点演算とメモリ使用量のトレードオフ

精度の限界と改善案

現在の精度:

  • 角度分解能:±90度を11分割 → 約16.4度/LED
  • ADCノイズ:約±2 LSB(最下位ビット)→ ±0.4度程度の揺らぎ

改善案:

  1. LEDの増加

    • 11個 → 20個に増やせば分解能が約2倍(約8度/LED)
    • Arduino Megaなど、ピン数の多いマイコンを使用
  2. デジタル表示

    • LEDバーグラフの代わりに7セグLEDやLCD(16x2)を使用
    • 小数点以下1桁まで表示(例:+23.4°)
  3. 2軸水平器への拡張

    • Y軸も同時に読み取り、8x8のLEDマトリクスで2次元表示
    • XY平面での傾きをドットの位置で表現

応用プロジェクトの可能性

今回構築した「ADC値 → 角度変換」の技術は、以下のプロジェクトにも応用できます:

  • 倒立振子ロボット: 姿勢角をPID制御にフィードバック
  • ジンバル制御: カメラを常に水平に保つ機構
  • 車両の傾斜計: オフロード走行時の横転警告
  • 地震計: Z軸の急激な変化を検知

次のステップ

  1. シリアル出力の有効化

    • コメントアウトされているSerial.print()を有効化し、Processingなどで角度をグラフ化
  2. Y軸との組み合わせ

    • X軸とY軸の2軸で、前後左右の傾きを同時検出
    • 8x8 LEDマトリクス(MAX7219使用)で2次元表示
  3. ジャイロセンサとの融合

    • 加速度センサ(静的角度)+ ジャイロセンサ(動的角速度)
    • 相補フィルタで高精度な姿勢推定を実現

加速度センサの原理を理解することで、スマートフォンやドローンに使われている高度な姿勢制御技術への第一歩が踏み出せます。ぜひ実装して、センサーの世界を体感してください!