電子工作ステップアップ実践ガイド
センサー信号の高度な処理
センサーデータの真価を引き出す
センサーからanalogRead()で値を取得するのは、電子工作の第一歩にすぎません。現実世界のデータはノイズが多く、不安定です。モーターが動いた瞬間にセンサー値が跳ね上がったり、何もしていないのに値が小刻みに変動したりするのはよくあることです。プロのエンジニアは、こうした生のデータを、信頼できる情報へと加工する技術を持っています。この先では、単なる読み取りから一歩進んで、センサー信号を「処理」するための高度なテクニックを学びます。
アナログ信号のデジタル化を深く知る
Arduinoがアナログ電圧を読み取る際、内部の(アナログ・デジタル変換器)が使われます。Arduino Unoに搭載されているADCは10ビットの分解能を持ち、0Vから5Vまでの電圧を$2^{10}$、つまり1024段階の数値(0~1023)に変換します。これは基本ですが、重要なのは「サンプリング周期」とのトレードオフです。
ADCは、連続的なアナログ信号を特定の間隔で測定(サンプリング)し、デジタル値に変換します。このサンプリングが速ければ速いほど、信号の素早い変化を捉えられます。しかし、あまりに速くサンプリングしようとすると、変換精度が落ちたり、ノイズを拾いやすくなったりする場合があります。どのような速さで、どの程度の精度でデータを取得する必要があるのか、プロジェクトの目的に合わせて考えることが重要です。
センサーを「正しく」接続する技術
分圧回路でセンサーの電圧を調整するのは一般的な手法ですが、思わぬ落とし穴があります。それは「」です。Arduinoのアナログ入力ピンは、内部に非常に大きな抵抗(約100MΩ)を持っていると考えることができます。これを入力インピーダンスと呼びます。
センサー側にも「出力インピーダンス」があります。センサーの出力インピーダンスとArduinoの入力インピーダンスが、意図しない分圧回路を形成してしまうのです。通常、Arduinoの入力インピーダンスは非常に高いため、ほとんどのセンサーでは問題になりません。しかし、出力インピーダンスが高いセンサー(数10kΩ以上)を使うと、この影響で測定電圧が本来の値よりも低くなってしまいます。
この問題を解決するのが「バッファ回路」です。具体的には、オペアンプを使ったボルテージフォロワ回路をセンサーとArduinoの間に入れます。オペアンプは入力インピーダンスが極めて高く、出力インピーダンスが非常に低いという理想的な特性を持っています。これにより、センサー側にはほとんど負荷をかけずに電圧を読み取り、Arduino側には力強く信号を送り届けることができます。結果として、センサーの出力電圧を正確に測定できるようになります。
ソフトウェアでノイズを取り除く
ハードウェアの接続が完璧でも、電源の変動や周囲の電磁波など、様々な要因でノイズは発生します。ソフトウェアフィルタリングは、こうしたノイズを軽減する強力な手段です。
最もシンプルで効果的なのが「移動平均フィルタ」です。これは、常に最新のいくつかの測定値を保存しておき、その平均値を現在の値として採用する方法です。突発的なノイズ(スパイクノイズ)は平均化によって影響が小さくなり、滑らかなデータが得られます。
// 移動平均フィルタの実装例
const int numReadings = 10;
int readings[numReadings]; // 測定値を保存する配列
int readIndex = 0; // 現在の読み取り位置
long total = 0; // 測定値の合計
int average = 0; // 平均値
void setup() {
Serial.begin(9600);
for (int thisReading = 0; thisReading < numReadings; thisReading++) {
readings[thisReading] = 0;
}
}
void loop() {
// 合計から最も古い測定値を引く
total = total - readings[readIndex];
// 新しい測定値を読み、配列に保存
readings[readIndex] = analogRead(A0);
// 新しい測定値を合計に加える
total = total + readings[readIndex];
// 次の読み取り位置へ
readIndex = readIndex + 1;
// 配列の末尾に達したら、先頭に戻る
if (readIndex >= numReadings) {
readIndex = 0;
}
// 平均値を計算
average = total / numReadings;
Serial.println(average);
delay(1); // 安定させるための短い遅延
}
もう一つの代表的な手法が「ローパスフィルタ」です。これは、急激な変化を「鈍らせる」ことで高周波ノイズを除去するフィルタです。現在のフィルタ出力値と、新しいセンサー測定値とを、特定の比率で混ぜ合わせることで実装できます。数式で書くと少し難しく見えますが、コードは非常にシンプルです。
// ローパスフィルタの実装例
float filteredValue = 0;
float alpha = 0.1; // 平滑化係数(0.0 < alpha < 1.0)
void setup() {
Serial.begin(9600);
// 最初の値でフィルタを初期化
filteredValue = analogRead(A0);
}
void loop() {
int rawValue = analogRead(A0);
// 新しい値と前回の値を混ぜ合わせる
filteredValue = alpha * rawValue + (1.0 - alpha) * filteredValue;
Serial.println(filteredValue);
delay(10);
}
割り込みでタイミングを極める
loop()関数内でdelay()を使う方法は手軽ですが、正確なタイミング制御には向きません。delay()中は他の処理がすべて止まってしまうからです。より高度な制御には「割り込み」を使います。
外部割り込みは、特定のピンの状態変化(立ち上がり、立ち下がりなど)をきっかけに、指定した関数(割り込みサービスルーチン、ISR)を即座に実行する機能です。例えば、ボタンが押された瞬間や、センサーがある値を超えた瞬間に、他の処理を中断して特定の動作を行いたい場合に非常に強力です。
タイマー割り込みは、一定時間ごとにISRを実行する機能です。これにより、delay()に頼らず、非常に正確な周期でセンサーを読み取ったり、モーターを制御したりできます。例えば、1ミリ秒ごとにきっちりデータをサンプリングしたい、といった場合に最適です。メインのloop()では他の処理を行いながら、バックグラウンドで周期的なタスクを実行できるのが大きな利点です。
// 外部割り込みの例:ピン2がLOWになったらLEDを点灯
volatile bool ledState = false;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(2, INPUT_PULLUP); // 内部プルアップを有効化
// attachInterrupt(digitalPinToInterrupt(ピン番号), ISR, トリガー);
attachInterrupt(digitalPinToInterrupt(2), buttonPressed, FALLING);
}
void loop() {
// メインループは他の処理を実行できる
digitalWrite(LED_BUILTIN, ledState);
}
// 割り込みサービスルーチン(ISR)
// ISR内ではSerial通信やdelay()は使えない
void buttonPressed() {
ledState = !ledState;
}
割り込みは非常に便利ですが、注意点もあります。割り込みサービスルーチン(ISR)は、できるだけ短く、高速に処理を終える必要があります。ISR内でdelay()や時間のかかる処理、シリアル通信などを行うことは避けるべきです。割り込み中に別の割り込みが発生しないように、通常、ISRの実行中は他の割り込みは一時的に無効化されます。
割り込み処理で変数を変更する場合、その変数は
volatileキーワードを付けて宣言する必要があります。これにより、コンパイラが意図しない最適化を行うのを防ぎ、変数がいつでも変更されうることを知らせます。
ヒステリシスで誤動作を防ぐ
センサーの値が特定のしきい値を超えたら何かのアクションを起こす、という処理はよくあります。例えば、「温度が25度を超えたらファンをONにする」といった制御です。しかし、もし温度が25度付近で微妙に変動した場合、ファンはON/OFFを高速で繰り返してしまいます。これをチャタリングと呼び、機械的なリレーなどを使っている場合は故障の原因にもなります。
この問題を解決するのが「」という考え方です。これは、ONにするしきい値と、OFFにするしきい値を別々に設ける手法です。例えば、「26度を超えたらON」、「24度を下回ったらOFF」のように、2つのしきい値に差(不感帯)を持たせるのです。これにより、しきい値付近でのわずかな変動では状態が変化しなくなり、システムが安定します。
このヒステリシスのロジックをコードに組み込むことで、センサー値がしきい値付近で不安定な場合でも、出力がバタつくことなく安定したシステムを構築できます。これは、サーモスタットや自動水やり機など、あらゆる「ON/OFF制御」において必須のテクニックです。
Arduino Unoのアナログ・デジタル変換器(ADC)に関する説明として、最も適切なものはどれですか?
出力インピーダンスが高いセンサーをArduinoのアナログ入力ピンに直接接続した際に発生する可能性のある問題は何ですか?
これらのテクニックを組み合わせることで、ノイズが多く不安定な生のセンサーデータから、クリーンで信頼性の高い情報を引き出すことができます。これが、安定して動作する実用的な電子機器を作るための鍵となります。