산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

부품 및 소모품

부품 수량
아두이노 나노 R3 1
TPA81 Devantech 8픽셀 열화상 배열 센서 1
ElectroPeak HC-SR505 적외선 PIR 모션 센서 8
ElectroPeak 0.96인치 SPI 128x64 OLED 디스플레이 모듈 1
브레드보드 (일반) 1
수/암 점퍼 와이어 1

앱 및 온라인 서비스

프로젝트 개요

이 글에서는 PIR 센서와 아두이노 나노 같은 간단한 부품으로 제스처 감지기를 만드는 방법을 보여줍니다. 이 글을 읽고 나면 다음을 할 수 있습니다:

PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

수동 적외선 센서(PIR): 작동 원리

수동 적외선 센서(PIR 센서)는 시야 내 물체에서 방사되는 적외선(IR) 빛을 측정하는 전자 센서입니다. 주로 PIR 기반 모션 감지기에 사용됩니다. 절대 영도 이상의 온도를 가진 모든 물체는 복사 형태로 열 에너지를 방출합니다. 보통 이 복사는 적외선 파장에서 일어나므로 육안으로는 보이지 않지만, 그런 목적으로 설계된 전자 장치로는 감지할 수 있습니다.

여기서 '수동(Passive)'이란 PIR 장치가 감지 목적으로 에너지를 생성하거나 방사하지 않는다는 뜻입니다. 전적으로 물체에서 방출되거나 반사된 적외선 복사를 감지함으로써 작동합니다. 열을 감지하거나 측정하는 것이 아닙니다. (출처: Wikipedia)

PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

TPA81은 2µm~22µm 범위의 적외선을 감지하는 열전퇴 배열 센서입니다. 이는 복사열의 파장 대역입니다. 도난 경보기나 실외 조명 제어에 흔히 쓰이는 파이로전기 센서도 같은 파장 대역의 적외선을 감지합니다. 하지만 이 센서들은 열 레벨의 변화만 감지할 수 있어 모션 감지기로만 쓰입니다. 로봇공학에 유용하지만 정적 열원의 온도를 감지하고 측정할 수 없어 응용이 제한적입니다.

또 다른 유형은 열전퇴 배열(thermopile array)입니다. 비접촉 적외선 온도계에 사용되며, 매우 넓은 감지 각도(FOV, 약 100°)를 가져 보통 12° 정도의 더 유용한 FOV를 얻기 위해 차폐나 렌즈가 필요합니다. 일부는 내장 렌즈를 가지기도 합니다. 최근에는 열전퇴 배열, 내장 전자회로, 실리콘 렌즈를 통합한 센서가 나왔는데 TPA81이 이 유형입니다. 8개의 열전퇴가 한 줄로 배열되어 있어 8개 인접 지점의 온도를 동시에 측정할 수 있습니다. TPA81은 서보를 제어해 모듈을 팬(Pan)하며 열화상 이미지를 구성할 수도 있습니다. 2미터(6피트) 거리에서 촛불 불꽃을 감지하며 주변 광에 영향을 받지 않습니다!

PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

PIR 센서는 따뜻한 것만 볼 수 있는 카메라와 비슷합니다. TPA81은 8픽셀 해상도의 열화상 카메라이고, 인체는 항상 열을 방출합니다. 컨트롤러로 PIR 센서를 제어하고 제스처를 정의하면 간단한 제스처 감지기를 만들 수 있습니다.

이 프로젝트에서는 PIR과 아두이노로 제스처 감지 시스템을 만듭니다. TPA81을 사용했지만 다소 비싸므로, 비용을 아끼려면 흰색 헤드(초점 부분) 없는 단순 PIR 센서 8개를 사용해도 됩니다. TPA81은 I2C 프로토콜을 지원하며 아두이노 나노와 호환됩니다. 이 프로젝트는 이미지 처리와 제스처 감지의 기초를 이해하는 데 도움이 됩니다. 시작해봅시다!

회로도

PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

TPA81 대신 8개 PIR 센서를 사용하려면 각 센서를 아두이노 보드에 연결하고 순차적으로 읽으면 됩니다. 이렇게 하면 비용을 절약할 수 있습니다!

PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

코드

첫 번째 회로(TPA81 사용)용 코드입니다. 아두이노 IDE에 복사하기 전 라이브러리를 추가해야 합니다. 처음 아두이노를 실행한다면 다음 단계를 따르세요:

  1. 아두이노 소프트웨어 다운로드 후 설치
  2. 아두이노 IDE 실행, 에디터 비우기, 아래 코드 복사
  3. 스케치 > 라이브러리 포함 > ZIP 라이브러리 추가로 라이브러리 추가 (아래 링크에서 다운로드)
  4. 툴 > 보드 > 아두이노 나노 선택
  5. 아두이노를 PC에 연결하고 툴 > 포트에서 COM 포트 설정
  6. 업로드(화살표) 버튼 클릭
  7. 완료!

1단계: 기본 온도 읽기

#include "Wire.h"
#include "TPA81.h"

// TPA81 인스턴스 생성
TPA81 tpa;

void setup() {
  Serial.begin(9600);
  // TPA81 라이브러리 사용을 위해 Wire 라이브러리 시작 필요
  Wire.begin();
}

void loop() {
  // 주변 온도 출력
  Serial.print(tpa.getAmbient());
  Serial.print(" ");
  // 8개 포인트 온도 모두 출력
  for (int i = 1; i <= 8; i++) {
    Serial.print(tpa.getPoint(i));
    Serial.print(" ");
  }
  Serial.println(" ");
  delay(100);
}
PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

코드 실행 후 아두이노 IDE 시리얼 터미널을 열고 TPA81이 감지한 8개 배열 온도를 확인하세요. 각 줄의 첫 번째 숫자는 전체 주변 온도입니다. 센서 앞에 손을 대고 체온 변화를 관찰하세요. 손으로 제스처를 정의하려면 손 온도를 오프셋으로 설정해야 합니다. 우리는 29도로 설정했습니다. 다음 코드를 업로드해 TPA81 앞 손 움직임을 확인하세요.

2단계: 손 움직임 시각화 (임계값 29도)

#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);
}
PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

간단한 제스처(소리 볼륨 증가/감소)를 감지하는 알고리즘을 작성했습니다. 다음 코드를 복사해 시도해보세요.

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  // OLED DIN 핀에 연결
#define SPI_SCK 13

uint8_t oled_buf[WIDTH * HEIGHT / 8];
int gesture[8][20];
int tgesture[8];
int sum = 0, sflag, gcount, c, ex, vol = 5;

// 볼륨 레벨별 비트맵 데이터 (PROGMEM 저장) - v1 ~ v11
const uint8_t v1 [] PROGMEM = { ... }; // 생략 (원본 참조)
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;
    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;
    }
    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() {
  // 8개 픽셀 읽기
  for (int i = 0; i <= 7; i++) {
    if (tpa.getPoint(i) > 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); // 8픽셀 합계
  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) > 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("length of gesture: "); Serial.println(gcount);
    delay(5);
    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.print("volume up");
    }
    if (tgesture[4] < tgesture[0]) {
      vol -= (gcount / 2);
      Serial.print("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);
}
PIR 센서와 아두이노 나노로 제스처 및 모션 감지기 만들기

다음 단계는?

위 방법을 확장해 탭, 드래그, 드롭, 스트레칭 등 다른 제스처도 감지할 수 있습니다. 각 모션과 제스처 감지 시 특정 작업을 수행하는 명령을 정의하면 됩니다.

코드 라이브러리

제조공정

  1. 아두이노 센서: 종류, 응용, 실전 프로젝트
  2. PIR 센서 회로 작동 원리: 설계, 동작 및 실세계 응용
  3. 라즈베리 파이에서 PIR 모션 센서로 LED 제어하기
  4. 라즈베리 파이 GPIO 마스터하기: B+/모델 2에서 PIR 모션 센서 인터페이싱
  5. HC-SR501 PIR 모션 센서를 라즈베리파이에 연결, 보정, 프로그래밍하는 방법
  6. DHT11 온습도 센서 + LED 표시등 + 피에조 부저 프로젝트
  7. 아두이노 우노 기반 인간 감지 로봇: 센서 통합 단계별 가이드
  8. 스마트 IoT 자 제작: ESP8266, 아두이노 & 초음파 센서로 실시간 모니터링
  9. PIR 모션 센서 연동 인터랙티브 LED 매트릭스 도어 디스플레이 - 아두이노 홀리데이 인사
  10. 아두이노 클라우드 센서 타워 만들기: 온도, 모션, 가스 모니터링