Bouw een gebaren- en bewegingdetector met PIR-sensor & Arduino Nano
Onderdelen en benodigdheden
| Arduino Nano R3 | 1 |
| TPA81 Devantech 8-pixel thermische array sensor | 1 |
| ElectroPeak HC-SR505 Infrarood PIR-bewegingsensor | 8 |
| ElectroPeak 0.96 inch SPI 128x64 OLED-display | 1 |
| Breadboard (standaard) | 1 |
| Male/Female jumperdraden | 1 set |
Apps en online diensten
- Arduino IDE
Over dit project
In dit artikel laten we zien hoe je een gebarendetector maakt met eenvoudige componenten: PIR-sensor en Arduino Nano. Na het lezen kun je:
- PIR-sensor toepassingen en werking uitleggen
- Een 8-pixel PIR-sensor (TPA81) uitlezen
- Beweging en gebaren detecteren met Arduino
- Een coole detector bouwen die bv. speaker-volume regelt

Passieve Infrarood Sensor: Hoe werkt het?

De TPA81 is een thermopile-array die infrarood in het 2-22 µm bereik detecteert (warmtestraling). Gewone pyro-elektrische sensoren (zoals in inbraakmelders) detecteren alleen *veranderingen* in warmte (beweging). De TPA81 meet daarentegen de absolute temperatuur van 8 aangrenzende punten simultaan. Het kan een kaarsvlam op 2 meter detecteren en is ongevoelig voor omgevingslicht.

PIR-sensoren zijn als camera's die alleen warme dingen zien. De TPA81 is dus een thermische camera met 8-pixel resolutie. Omdat het menselijk lichaam altijd warmte uitstraalt, kunnen we door de sensor te besturen en patronen te definiëren, een gebarendetector bouwen.
In dit project gebruiken we de TPA81 (I2C, compatibel met Arduino Nano). Als budgetterugvalt, kan men ook 8 losse PIR-sensoren (zonder de witte Fresnellens) gebruiken en deze achtereenvolgens uitlezen.


Code
Voor de eerste schakeling (TPA81), kopieer de code in de Arduino IDE. Eerst de TPA81-bibliotheek installeren (ZIP toevoegen via Schets → Bibliotheek gebruiken → .ZIP-bibliotheek toevoegen). Stappen:
- Download Arduino IDE van arduino.cc en installeer.
- Open IDE, wis editor, plak code.
- Installeer bibliotheken (TPA81, SSD1306, DHT).
- Selecteer Board: Arduino Nano.
- Stel COM-poort in.
- Klik Upload (pijltje).
Test 1: Ruwe temperatuurwaarden uitlezen
#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);
}

Open de Seriële Plotter. Het eerste getal is de omgevingstemperatuur. Houd je hand voor de sensor om de temperatuurstijging te zien. Stel een drempel in (bijv. 29°C) voor gebarendetectie.
Test 2: Gebaren visualiseren (# = warmte gedetecteerd)
#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);
}

Test 3: Volume-regeling met gebaren (volledige code met OLED)
Deze code detecteert een wuifbeweging links/rechts om volume omhoog/omlaag te regelen. Resultaat wordt op OLED getoond met balkgrafieken (v1 tot v11).
#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;
int sflag = 0; // standby flag
int gcount = 0;
int c = 0;
int ex = 0;
int vol = 5;
// Bitmap data voor volume-niveaus 1-11 (v1 ... v11) - in PROGMEM opgeslagen
const uint8_t v1 [] PROGMEM = { ... }; // (verkort voor overzicht, volledige arrays in broncode)
const uint8_t v2 [] PROGMEM = { ... };
const uint8_t v3 [] PROGMEM = { ... };
const uint8_t v4 [] PROGMEM = { ... };
const uint8_t v5 [] PROGMEM = { ... };
const uint8_t v6 [] PROGMEM = { ... };
const uint8_t v7 [] PROGMEM = { ... };
const uint8_t v8 [] PROGMEM = { ... };
const uint8_t v9 [] PROGMEM = { ... };
const uint8_t v10 [] PROGMEM = { ... };
const uint8_t v11 [] PROGMEM = { ... };
void dsply(int input) {
int n1, n2;
if (input < 10) {
SSD1306_char3216(10, 25, '0', oled_buf);
switch(input) {
case 0: SSD1306_char3216(26, 25, '0', oled_buf); break;
case 1: SSD1306_char3216(26, 25, '1', oled_buf); break;
case 2: SSD1306_char3216(26, 25, '2', oled_buf); break;
case 3: SSD1306_char3216(26, 25, '3', oled_buf); break;
case 4: SSD1306_char3216(26, 25, '4', oled_buf); break;
case 5: SSD1306_char3216(26, 25, '5', oled_buf); break;
case 6: SSD1306_char3216(26, 25, '6', oled_buf); break;
case 7: SSD1306_char3216(26, 25, '7', oled_buf); break;
case 8: SSD1306_char3216(26, 25, '8', oled_buf); break;
case 9: SSD1306_char3216(26, 25, '9', oled_buf); break;
}
} else {
n1 = input / 10;
n2 = input % 10;
// print tientallen
switch(n1) {
case 0: SSD1306_char3216(10, 25, '0', oled_buf); break;
case 1: SSD1306_char3216(10, 25, '1', oled_buf); break;
case 2: SSD1306_char3216(10, 25, '2', oled_buf); break;
case 3: SSD1306_char3216(10, 25, '3', oled_buf); break;
case 4: SSD1306_char3216(10, 25, '4', oled_buf); break;
case 5: SSD1306_char3216(10, 25, '5', oled_buf); break;
case 6: SSD1306_char3216(10, 25, '6', oled_buf); break;
case 7: SSD1306_char3216(10, 25, '7', oled_buf); break;
case 8: SSD1306_char3216(10, 25, '8', oled_buf); break;
case 9: SSD1306_char3216(10, 25, '9', oled_buf); break;
}
// print eenheden
switch(n2) {
case 0: SSD1306_char3216(26, 25, '0', oled_buf); break;
case 1: SSD1306_char3216(26, 25, '1', oled_buf); break;
case 2: SSD1306_char3216(26, 25, '2', oled_buf); break;
case 3: SSD1306_char3216(26, 25, '3', oled_buf); break;
case 4: SSD1306_char3216(26, 25, '4', oled_buf); break;
case 5: SSD1306_char3216(26, 25, '5', oled_buf); break;
case 6: SSD1306_char3216(26, 25, '6', oled_buf); break;
case 7: SSD1306_char3216(26, 25, '7', oled_buf); break;
case 8: SSD1306_char3216(26, 25, '8', oled_buf); break;
case 9: SSD1306_char3216(26, 25, '9', oled_buf); break;
}
}
}
void setup() {
Serial.begin(9600);
Wire.begin();
SSD1306_begin();
SSD1306_clear(oled_buf);
}
void loop() {
// Lees 8 pixels
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);
// Standby: alle 8 pixels actief = rustpositie
if (sum == 8) {
sflag = 1;
Serial.println("standby");
} else {
sum = 0;
}
// Gebaar detectie
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("lengte gebaar: "); Serial.println(gcount);
delay(5);
// Bereken som per pixel over tijd
for (c = 0; c <= 7; c++) {
for (int z = 0; z <= gcount; z++) {
tgesture[c] += gesture[c][z];
delay(5);
}
Serial.println("verwerken");
}
// Linker helft (0-3) vs rechter helft (4-7)
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;
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);
}

Wat is er nog mogelijk?
Met deze methode kun je de code uitbreiden naar andere gebaren: tikken, slepen, loslaten, uitrekken, etc. Definieer voor elk gebaar een actie (bijv. licht dimmen, menu navigeren, etc.).
Code
- TPA81 Bibliotheek (C/C++) – download ZIP
- SSD1306 Bibliotheek (C/C++) – download ZIP
Beide bibliotheken zijn vereist voor de voorbeelden. Plaats ze in de libraries-map van de Arduino IDE.
Productieproces
- Een LED Besturen met een PIR-bewegingssensor op Raspberry Pi
- Raspberry Pi GPIO onder de knie: Een PIR-bewegingsensor interfacen op B+/Model 2
- Hoe de HC‑SR501 PIR-bewegingsensor aan te sluiten, kalibreren en programmeren met een Raspberry Pi
- DHT11 Temperatuur- & Vochtigheidsensor Project met LED-indicatoren en Piezo-luidspreker
- Arduino Uno Gebaseerde Mensendetectie Robot: Stap-voor-stap Sensorintegratie
- Bouw een Slimme IoT Pot met ESP8266, Arduino & Ultrasonische Sensor – Real-time Monitoring
- Interactieve LED-matrix deurdisplay met PIR-bewegingsensor – Arduino vakantiegroet
- Bouw een Sensor Toren met Arduino Cloud: Temperatuur, Beweging & Gas Monitoring
- Slimme beweegactiverde waterpistool gebouwd met Arduino Nano
- Arduino Sensoren: Typen, Toepassingen en Real-World Projecten
- Hoe PIR-sensor Schakelingen Werken: Ontwerp, Werking en Real-World Toepassingen