PIRセンサーとArduino Nanoで作るジェスチャー&モーション検出器
必要な部品と材料
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino Nano R3 | 1 | |
| TPA81 Devantech 8ピクセルサーマルアレイセンサー | 1 | I2C接続 |
| HC-SR505 PIRモーションセンサー | 8 | TPA81代替用(単体PIR×8) |
| 0.96インチ SPI 128x64 OLEDディスプレイ | 1 | |
| ブレッドボード | 1 | |
| ジャンパーワイヤー(オス/メス) | 1式 |
使用ツール
- Arduino IDE
プロジェクト概要
PIRセンサーとArduino Nanoを使ってジェスチャー検出器を作ります。この記事を読むと以下が理解できます:
- PIRセンサーの原理と応用
- 8ピクセルサーマルアレイTPA81の使い方
- PIRセンサーでモーションとジェスチャーを検出する方法
- スピーカー音量を上げ下げするクールな検出器の作り方

受動型赤外線センサー(PIR)の仕組み
PIRセンサーは視野内の物体から放射される赤外線(IR)を測定します。絶対零度以上の物体はすべて熱エネルギーを赤外線として放射します。これは人間の目には見えませんが、電子デバイスで検出可能です。
「受動型」と呼ばれるのは、検出のために自らエネルギーを放射しないためです。物体から放射または反射される赤外線を検出するだけで、熱そのものを測定するわけではありません(出典: Wikipedia)。

TPA81は2µm-22µmの赤外線を検出するサーモパイルアレイです。一般的な焦電型PIR(防犯センサー等)は「熱変化」しか検出できませんが、TPA81は静止した熱源の温度も測定できます。8個のサーモパイルが一列に並び、8点の温度を同時測定可能。サーボ連動で熱画像構築もできます。ロウソクの炎を2m先で検出可能で、可視光の影響を受けません。

PIRセンサーは「暖かいものだけ見えるカメラ」のようなもの。TPA81は8ピクセルの熱画像カメラと言えます。人体は常に熱を放射しているため、これを制御してジェスチャーを定義すればシンプルなジェスチャー検出器が作れます。
TPA81はI2C対応でArduino Nanoと互換性あり。コスト削減したい場合は、レンズなしの単体PIRセンサー8個を並べて代用可能です(順次読み取り)。


コード
最初にTPA81ライブラリをインストールしてください。Arduino IDE初心者の方は以下の手順で:
- Arduino IDEダウンロード・インストール
- IDE起動、エディタをクリアしてコード貼り付け
- スケッチ→ライブラリをインクルード→ZIP形式のライブラリを追加
- ツール→ボード→Arduino Nano選択
- ArduinoをPC接続、COMポート設定
- アップロード(→矢印ボタン)
ステップ1: 生データ確認
#include "Wire.h"
#include "TPA81.h"
TPA81 tpa;
void setup() {
Serial.begin(9600);
Wire.begin();
}
void loop() {
Serial.print(tpa.getAmbient());
Serial.print(" ");
for (int i = 1; i <= 8; i++) {
Serial.print(tpa.getPoint(i));
Serial.print(" ");
}
Serial.println(" ");
delay(100);
}
シリアルモニタで8ピクセルの温度を観察。最初の数値は周囲温度。手をかざすと温度上昇が見えます。

ステップ2: 閾値処理で手の検出可視化
#include "Wire.h"
#include "TPA81.h"
TPA81 tpa;
void setup() {
Serial.begin(9600);
Wire.begin();
}
void loop() {
Serial.print(tpa.getAmbient());
Serial.print(" ");
for (int i = 1; i <= 8; i++) {
if (tpa.getPoint(i) > 29) Serial.print("# ");
else Serial.print(". ");
}
Serial.println(" ");
delay(100);
}
29℃以上を「#」で表示。手の動きが視覚化されます。

ステップ3: ジェスチャー認識アルゴリズム(音量制御デモ)
手の左右移動で音量アップ/ダウンを検出するアルゴリズムを実装。OLEDに音量バーを表示します。
#include "Wire.h"
#include "TPA81.h"
#include "ssd1306.h"
#include "DHT.h"
TPA81 tpa;
#define VCCSTATE SSD1306_SWITCHCAPVCC
#define WIDTH 128
#define HEIGHT 64
#define PAGES 8
#define OLED_RST 9
#define OLED_DC 8
#define OLED_CS 10
#define SPI_MOSI 11
#define SPI_SCK 13
uint8_t oled_buf[WIDTH * HEIGHT / 8];
int gesture[8][20];
int tgesture[8];
int sum = 0, sflag = 0, gcount = 0, c = 0, ex = 0, vol = 5;
// 音量レベル用ビットマップデータ(PROGMEM格納)
const uint8_t v1[] PROGMEM = { ... }; // 省略
const uint8_t v2[] PROGMEM = { ... };
// v3〜v11まで同様
void dsply(int input) {
// 2桁数字表示関数
// ... 実装省略 ...
}
void setup() {
Serial.begin(9600);
Wire.begin();
SSD1306_begin();
SSD1306_clear(oled_buf);
}
void loop() {
// 8ピクセル読み取り
for (int i = 0; i <= 7; i++) {
if (tpa.getPoint(i+1) > 29) {
Serial.print("#"); Serial.print(" ");
gesture[i][0] = 1;
} else {
Serial.print("."); Serial.print(" ");
gesture[i][0] = 0;
}
}
for (int y = 0; y <= 7; y++) sum += gesture[y][0];
Serial.println(sum);
Serial.println(" ");
delay(100);
// スタンバイ検出(全ピクセル検出時)
if (sum == 8) { sflag = 1; Serial.println("standby"); }
else { sum = 0; }
// ジェスチャー進行検出
if (sflag == 1 && sum != 8) {
Serial.println("start gesture");
gcount = 0; c = 0; sum = 0;
while (sum < 8) {
sum = 0;
for (int i = 0; i <= 7; i++) {
if (tpa.getPoint(i+1) > 29) {
Serial.print("#"); Serial.print(" ");
gesture[i][gcount] = 1;
} else {
Serial.print("."); Serial.print(" ");
gesture[i][gcount] = 0;
}
}
for (int y = 0; y <= 7; y++) { sum += gesture[y][gcount]; delay(5); }
Serial.println(" "); delay(50);
gcount++;
}
Serial.print("sum: "); Serial.println(sum);
Serial.print("gesture length: "); Serial.println(gcount); delay(5);
// 左右4ピクセルずつ集計
for (c = 0; c <= 7; c++) {
for (int z = 0; z <= gcount; z++) { tgesture[c] += gesture[c][z]; delay(5); }
Serial.println("processing");
}
tgesture[0] += tgesture[1];
tgesture[2] += tgesture[3];
tgesture[4] += tgesture[5];
tgesture[6] += tgesture[7];
tgesture[0] += tgesture[2]; // 左半分
tgesture[4] += tgesture[6]; // 右半分
if (tgesture[0] < tgesture[4]) { vol += (gcount/2); Serial.println("volume up"); }
if (tgesture[4] < tgesture[0]) { vol -= (gcount/2); Serial.println("volume down"); }
sflag = 0;
}
sum = 0;
// OLED表示更新
SSD1306_clear(oled_buf);
switch (vol) {
case 1: SSD1306_bitmap(0, 0, v1, 128, 64, oled_buf); break;
case 2: SSD1306_bitmap(0, 0, v2, 128, 64, oled_buf); break;
case 3: SSD1306_bitmap(0, 0, v3, 128, 64, oled_buf); break;
case 4: SSD1306_bitmap(0, 0, v4, 128, 64, oled_buf); break;
case 5: SSD1306_bitmap(0, 0, v5, 128, 64, oled_buf); break;
case 6: SSD1306_bitmap(0, 0, v6, 128, 64, oled_buf); break;
case 7: SSD1306_bitmap(0, 0, v7, 128, 64, oled_buf); break;
case 8: SSD1306_bitmap(0, 0, v8, 128, 64, oled_buf); break;
case 9: SSD1306_bitmap(0, 0, v9, 128, 64, oled_buf); break;
case 10: SSD1306_bitmap(0, 0, v10, 128, 64, oled_buf); break;
case 11: SSD1306_bitmap(0, 0, v11, 128, 64, oled_buf); break;
}
SSD1306_display(oled_buf);
}

今後の拡張
この手法を拡張して、タップ、ドラッグ、ドロップ、ストレッチなどのジェスチャー検出も可能です。検出されたジェスチャーに対応するコマンドを定義すれば、様々なタスクを実行できます。
ライブラリ
- TPA81ライブラリ(ダウンロードのみ)
- SSD1306ライブラリ(ダウンロードのみ)
製造プロセス
- Raspberry PiでPIRモーションセンサーとLEDを制御
- Raspberry Pi GPIOマスター:PIRモーションセンサーをB+/Model 2でインターフェース
- HC-SR501 PIRモーションセンサーをRaspberry Piで接続・調整・プログラミングする方法
- DHT11温湿度センサーで作る温度アラート – LEDとピエゾブザーで視覚/聴覚通知
- Arduino Unoベースの人間検知レスキューロボット:センサー統合ステップバイステップ
- ESP8266・Arduino・超音波センサーで作るスマートIoTジャー:リアルタイム中身監視
- PIRセンサー連動 インタラクティブLEDマトリクスドアディスプレイ - ホリデーグリーティング
- ESP32で作るIoT環境モニター:温湿度・気圧・CO2をWebダッシュボードで可視化
- Arduino Nanoで作るスマートな動体検知水鉄砲
- Arduinoセンサー:種類、応用、実世界プロジェクト
- PIRセンサー回路の仕組み:設計、動作、実世界の応用