아두이노 프로젝트 6. 심장 박동 측정: 두 판 사이의 차이

아두위키 : Arduwiki
 
(같은 사용자의 중간 판 15개는 보이지 않습니다)
1번째 줄: 1번째 줄:
{{#seo:|title=아두위키 : 아두이노 프로젝트|title_mode=append|keywords=아두이노, 정보과학, 메이커학습, 수행평가, 초음파 센서, 아두이노 작품, 캡스톤작품, 아두이노 예제코드, 엔트리 아두이노, 파이썬 아두이노|description=아두이노를 활용한 프로젝트(심장 박동 측정)를 소개합니다.|image=https://arduwiki.com/html/images/e/e7/%EC%8A%A4%EB%A7%88%ED%8A%B8_%EC%A7%80%ED%8C%A1%EC%9D%B4_%EC%99%84%EC%84%B1_%EC%82%AC%EC%A7%841.png}}
{{#seo:|title=아두위키 : 아두이노 프로젝트|title_mode=append|keywords=아두이노, 정보과학, 메이커학습, 수행평가, 초음파 센서, 아두이노 작품, 캡스톤작품, 아두이노 예제코드, 엔트리 아두이노, 파이썬 아두이노|description=아두이노를 활용한 프로젝트(심장 박동 측정)를 소개합니다.|image=https://arduwiki.com/html/images/thumb/5/58/%EC%8B%AC%EC%9E%A5%EB%B0%95%EB%8F%99%EC%A0%9C%EC%9E%916.jpg/1600px-%EC%8B%AC%EC%9E%A5%EB%B0%95%EB%8F%99%EC%A0%9C%EC%9E%916.jpg?20250515062045}}


== '''개요''' ==
== '''개요''' ==
11번째 줄: 11번째 줄:
* 12bit 네오픽셀을 활용해 심박 구간에 따라 다른 색깔로 표시되는 게이지를 표현합니다.
* 12bit 네오픽셀을 활용해 심박 구간에 따라 다른 색깔로 표시되는 게이지를 표현합니다.


=== '''사용 하드웨어''' ===
=== 사용 하드웨어 ===


* [https://gongzipsa.com/shop/1699939293 아두이노 나노]
* [https://gongzipsa.com/shop/1699939293 아두이노 나노]
17번째 줄: 17번째 줄:
* [[심박 센서(XD-58C)|심박 펄스 센서]]
* [[심박 센서(XD-58C)|심박 펄스 센서]]
* [[I2C OLED 모듈|0.96inch OLED]]
* [[I2C OLED 모듈|0.96inch OLED]]
== '''배경과 원리''' ==
=== 💓 심장의 박동 ===
* 심장은 '''피를 온몸으로 보내기 위해 수축과 이완을 반복'''합니다.
* 이를 심장 박동(심박)이라고 하며, 분당 박동 수를 BPM (Beats Per Minute)이라고 합니다.
** 예: 1분에 75번 뛰면 '''BPM = 75'''
=== 🔌 심장 박동은 전기적 신호 ===
* 심장이 뛰면 '''혈관 속 피가 펄럭이듯 움직이고''', '''피의 양이 순간적으로 늘거나 줄어듭니다.'''
* 이 변화를 심박 펄스 센서(Pulse Sensor)가 감지합니다.
===== 💡 Pulse Sensor의 작동 원리 =====
* 센서에 LED와 '''빛의 반사를 감지하는 포토 다이오드'''가 있습니다.
* 피가 많아지면 '''더 많은 빛이 흡수'''되고, 피가 적어지면 '''덜 흡수됩니다.'''
* 이 변화가 '''아날로그 전압으로 출력'''됩니다.
=== 📈 센서 값으로 박동을 인식하는 원리 ===
* 센서는 '''계속 전압 값을 출력'''하고, 전압 값의 '''변화 패턴'''을 분석해서 '''심장이 한 번 뛸 때를 감지'''합니다
* 평소보다 갑자기 센서 값이 크게 올라가면(이 때의 기준 값을 미리 체크합니다) → '''심장이 뛴 순간'''이라고 판단합니다.
* 예: 0.8초마다 뛴다면 → BPM = 60 ÷ 0.8 = 75
=== 🧠 센서 출력값 보정 ===
* 센서가 읽는 값은 '''피부 떨림, 손 움직임, 빛 세기 변화 등'''에 의해 노이즈(잡음)가 섞일 수 있습니다.
* 그래서 '''최근 10개 값의 평균'''을 내서 부드럽게 처리하고, 값이 너무 갑자기 바뀌면 무시하는 '''기준값(maxValue)'''과 '''필터링'''을 계산 과정에 포함합니다.
=== 🖥️ OLED 디스플레이와 💡 네오픽셀 ===
=== OLED ===
* 아주 작은 화면으로, 전류로 '''픽셀 하나하나를 켜서 그림을 그릴''' 수 있습니다.
* 본 문서는 '''0.96inch''', '''128 x 64개의 픽셀'''이 있는 OLED를 사용합니다.
=== 네오픽셀 ===
* '''RGB LED가 여러 개 이어진 조명 링 또는 줄'''입니다.
* 원하는 색상과 밝기를 '''하나씩 제어 가능'''합니다.
* 이 프로젝트에서는 '''BPM이 높을수록 더 많은 LED가 초록 - 노랑 - 주황 - 빨강 순'''으로 켜집니다.




149번째 줄: 191번째 줄:
*포트 번호를 잘 모르시는 경우에는 장치 관리자에서 확인해주시면 됩니다.
*포트 번호를 잘 모르시는 경우에는 장치 관리자에서 확인해주시면 됩니다.
[[파일:장치관리자 확인.png|가운데|class=coders100]]
[[파일:장치관리자 확인.png|가운데|class=coders100]]


== '''라이브러리 설치''' ==
== '''라이브러리 설치''' ==
158번째 줄: 201번째 줄:


Install 버튼 클릭 후 뜨는 창에서는 Install All 버튼을 클릭하시면 됩니다.
Install 버튼 클릭 후 뜨는 창에서는 Install All 버튼을 클릭하시면 됩니다.


=== 1. SSD1306 ===
=== 1. SSD1306 ===
[[파일:Ssd1306라이브러리.png]]
[[파일:Ssd1306라이브러리.png|class=coders50]]
 


=== 2. Neopixel ===
=== 2. Neopixel ===
[[파일:Neopixel 라이브러리.png]]
[[파일:Neopixel 라이브러리.png|class=coders50]]




== '''아두이노 코드''' ==
== '''아두이노 코드''' ==
라이브러리를 모두 설치하셨다면 아래 코드를 아두이노에 업로드해주세요. '''(단축키 : CTRL + U)'''<syntaxhighlight lang="c++" line="1">
라이브러리를 모두 설치하셨다면 아래 코드를 아두이노에 업로드해주세요.
 
아두이노 IDE에 코드를 옮기신 후 '''화살표키'''(아래 사진 참고)를 누르거나 '''단축키 CTRL + U'''를 입력하시면 됩니다.
 
[[파일:아두이노업로드 버튼.png]]
 
<syntaxhighlight lang="c++" line="1">
// 파형 그래프 범위, 딜레이 및 BPM 유효 범위 설정
// 파형 그래프 범위, 딜레이 및 BPM 유효 범위 설정
#define BPM_MIN 40
#define BPM_MIN 40
384번째 줄: 435번째 줄:
}
}
</syntaxhighlight>
</syntaxhighlight>


== '''심박 펄스 센서 장착''' ==
== '''심박 펄스 센서 장착''' ==
395번째 줄: 444번째 줄:




[[파일:펄스센서 착용.jpg|329x329픽셀]]
[[파일:펄스센서 착용.jpg|class=coders50]]




402번째 줄: 451번째 줄:




[[파일:심박결과.png|671x671픽셀]]
[[파일:심박결과.png|class=coders50]]


=== 심박수 측정 ===
=== 심박수 측정 ===
413번째 줄: 462번째 줄:


심박 구간에 따라 각기 다른 색의 LED가 켜집니다.
심박 구간에 따라 각기 다른 색의 LED가 켜집니다.
== '''추신''' ==
공집사의 아두이노 프로젝트 결과물은 판매되는 제품이 아니며, 프로젝트 수준에서 간단하게 진행되었습니다.
문의 및 의뢰는 [https://kmong.com/@%EA%B3%B5%EC%A7%91%EC%82%AC 크몽 공집사]로 연락주시면 감사하겠습니다.

2025년 5월 17일 (토) 15:35 기준 최신판


개요

심장 박동 측정하기

손가락에 심장 박동 측정 센서를 끼워 심장 박동을 측정하고, 파형을 그립니다.

고려사항

  • 심박 펄스 센서를 활용해 심박을 측정하고, raw data를 활용해 bpm을 계산합니다.
  • 계산된 bpm과 bpm을 활용한 파형을 OLED에 표시해 심박을 확인합니다.
  • 12bit 네오픽셀을 활용해 심박 구간에 따라 다른 색깔로 표시되는 게이지를 표현합니다.

사용 하드웨어


배경과 원리

💓 심장의 박동

  • 심장은 피를 온몸으로 보내기 위해 수축과 이완을 반복합니다.
  • 이를 심장 박동(심박)이라고 하며, 분당 박동 수를 BPM (Beats Per Minute)이라고 합니다.
    • 예: 1분에 75번 뛰면 BPM = 75

🔌 심장 박동은 전기적 신호

  • 심장이 뛰면 혈관 속 피가 펄럭이듯 움직이고, 피의 양이 순간적으로 늘거나 줄어듭니다.
  • 이 변화를 심박 펄스 센서(Pulse Sensor)가 감지합니다.
💡 Pulse Sensor의 작동 원리
  • 센서에 LED와 빛의 반사를 감지하는 포토 다이오드가 있습니다.
  • 피가 많아지면 더 많은 빛이 흡수되고, 피가 적어지면 덜 흡수됩니다.
  • 이 변화가 아날로그 전압으로 출력됩니다.

📈 센서 값으로 박동을 인식하는 원리

  • 센서는 계속 전압 값을 출력하고, 전압 값의 변화 패턴을 분석해서 심장이 한 번 뛸 때를 감지합니다
  • 평소보다 갑자기 센서 값이 크게 올라가면(이 때의 기준 값을 미리 체크합니다) → 심장이 뛴 순간이라고 판단합니다.
  • 예: 0.8초마다 뛴다면 → BPM = 60 ÷ 0.8 = 75

🧠 센서 출력값 보정

  • 센서가 읽는 값은 피부 떨림, 손 움직임, 빛 세기 변화 등에 의해 노이즈(잡음)가 섞일 수 있습니다.
  • 그래서 최근 10개 값의 평균을 내서 부드럽게 처리하고, 값이 너무 갑자기 바뀌면 무시하는 기준값(maxValue)필터링을 계산 과정에 포함합니다.

🖥️ OLED 디스플레이와 💡 네오픽셀

OLED

  • 아주 작은 화면으로, 전류로 픽셀 하나하나를 켜서 그림을 그릴 수 있습니다.
  • 본 문서는 0.96inch, 128 x 64개의 픽셀이 있는 OLED를 사용합니다.

네오픽셀

  • RGB LED가 여러 개 이어진 조명 링 또는 줄입니다.
  • 원하는 색상과 밝기를 하나씩 제어 가능합니다.
  • 이 프로젝트에서는 BPM이 높을수록 더 많은 LED가 초록 - 노랑 - 주황 - 빨강 순으로 켜집니다.


제작과정

1. MDF 확인

4개의 MDF 판이 준비되어 있습니다.

2. OLED 케이블

먼저 OLED에 연결된 케이블을 상판 홀에 통과시켜주세요. OLED는 아직 붙이지 않으셔도 됩니다.

3. 네오픽셀, OLED 결합

네오픽셀을 그림에 맞춰 부착(연결된 케이블이 위로 향하도록)하신 후 OLED를 네오픽셀 안에 부착해주세요. 부품마다 양면테이프가 붙어있습니다.

4. 아두이노 나노, 브레드보드 준비

회로를 구성하기에 앞서 먼저 아래 그림과 같이 아두이노 나노를 미니 브레드보드에 꽂아주세요.

손을 다치지 않게 조심하고, 나노의 평평한 면을 눌러서 꽂아주시면 됩니다.

5. 회로 구성

아무것도 없는 깨끗한 하트 판에 브레드보드를 부착해주세요.


심박 펄스 센서의 -, +, S 위치는 다음과 같습니다.


아래 표와 회로도에 따라 회로를 구성해주세요.

아두이노 나노 심박 펄스 센서 OLED 네오픽셀
A0 S
A4 SCK
A5 SCA
D6 DI
5V + VDD 5V
GND - GND GND

6. 옆 면 조립

남아있는 옆 면을 활용해 MDF판을 모두 결합해주세요.


조립 시에 케이블은 동그랗게 잘 말아서 정리해주세요. 동봉된 핀을 홀에 꽂아주면 완성입니다.


아두이노 IDE

아두이노(Arduino) IDE(통합 개발 환경)는 아두이노 마이크로컨트롤러를 프로그래밍하기 위한 소프트웨어 환경을 제공하는 툴입니다.

아두이노는 오픈 소스 하드웨어 플랫폼으로, 다양한 프로젝트에서 사용되는 간단한 컴퓨팅 장치를 제작하고 프로그래밍하는 데 사용됩니다.

C++를 기반으로 합니다.

설치

1. 아두이노 홈페이지 접속

2. Software 탭 클릭


3. 원하는 버전 다운로드

  • 2021년 아두이노 IDE 2.0 버전이 출시되면서 기존의 1.x 버전, 새로운 2.x 버전 다운로드가 가능합니다.
  • 2.x 버전에서 여러가지 편의기능들이 추가되었지만 기존의 1.x 버전이 익숙하신 분, 1.x 버전으로 수록된 책이나 참고 자료를 그대로 따라하고 싶으신 분들은 1.x 버전을 그대로 사용하셔도 무방합니다. ※ 본 문서는 2.x 버전으로 작성되었습니다.
  • 사용 중인 운영체제에 맞는 버전으로 설치파일을 다운로드 받은 후, 실행하여 설치해주세요.


사용

1. 설치된 아두이노 IDE를 실행합니다.

2. 컴퓨터(혹은 노트북)에 아두이노 보드를 연결합니다.

3. 아두이노 정품 보드가 아닌 호환 보드의 경우 별도로 드라이버를 설치하셔야 합니다. 드라이버 설치 가이드

※ 창원남산고 : 드라이버 설치 필요합니다.

4. 아두이노 IDE에서 아두이노 보드와 포트를 설정합니다.

※ 창원남산고 : Arduino Nano


  • 보드는 현재 사용 중인 아두이노 보드의 종류에 맞게 선택해주시면 됩니다.
  • 포트 번호를 잘 모르시는 경우에는 장치 관리자에서 확인해주시면 됩니다.


라이브러리 설치

이번 코드를 사용하기 위한 라이브러리를 설치합니다.

아두이노 IDE의 좌측 세 번째 아이콘(스크린샷 참고)이 라이브러리 관리자입니다.

반드시 정확한 라이브러리를 설치해주세요.

Install 버튼 클릭 후 뜨는 창에서는 Install All 버튼을 클릭하시면 됩니다.


1. SSD1306


2. Neopixel


아두이노 코드

라이브러리를 모두 설치하셨다면 아래 코드를 아두이노에 업로드해주세요.

아두이노 IDE에 코드를 옮기신 후 화살표키(아래 사진 참고)를 누르거나 단축키 CTRL + U를 입력하시면 됩니다.

// 파형 그래프 범위, 딜레이 및 BPM 유효 범위 설정
#define BPM_MIN 40
#define BPM_MAX 180

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>

// OLED 디스플레이 초기 설정
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// 심박센서 아날로그 입력 핀
const int pulsePin = A0;

// 네오픽셀 설정 (핀 번호, LED 개수)
#define NEOPIXEL_PIN 6
#define NUM_LEDS 12
Adafruit_NeoPixel pixels(NUM_LEDS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

// OLED 상에서 실시간 파형 그래프 그리기용 변수
int graphX = 0;            // 현재 x좌표
int lastY = SCREEN_HEIGHT - 1;  // 이전 y좌표
int prevBpm = 0;           // 이전에 측정된 BPM

// 심박센서 데이터 평균용 큐 최대 크기
#define MAX_QUEUE_SIZE 100

// 원형 큐 구조체 정의: 아날로그 입력값 평균 산출
struct CircularQueue {
  int data[MAX_QUEUE_SIZE];
  int front = 0;
  int rear = 0;
  int count = 0;

  bool isFull() {
    return count == MAX_QUEUE_SIZE;
  }

  bool isEmpty() {
    return count == 0;
  }

  bool enqueue(int value) {
    if (isFull()) return false;
    data[rear] = value;
    rear = (rear + 1) % MAX_QUEUE_SIZE;
    count++;
    return true;
  }

  int dequeue() {
    if (isEmpty()) return -1; // or any sentinel value
    int value = data[front];
    front = (front + 1) % MAX_QUEUE_SIZE;
    count--;
    return value;
  }

  int peek() {
    if (isEmpty()) return -1;
    return data[front];
  }

  int size() {
    return count;
  }
};

long avg = 0; // 최근 10개의 심박 raw 데이터를 평균한 값

int getBPM() {
  static long sum = 0;                      // 평균 계산용 합계
  static CircularQueue q;                   // 최근 raw 데이터 저장용 큐
  static unsigned long lastBeatTime;        // 마지막 심박 탐지 시각
  static unsigned long pretime;             // maxValue 갱신 주기 체크용 시간

  static long maxValue = 500;               // 기준 맥박 강도 (dynamic threshold)
  static long tmpMaxValue = 0;              // 5초간 측정된 최고 평균값 (기준 업데이트용)
  long bpm = -1;

  int rawValue = analogRead(pulsePin);       // 심박센서 아날로그 읽기
  
  sum += rawValue;                           // 합계 누적
  q.enqueue(rawValue);                       // 큐에 추가
  if(q.size() >= 10){
    sum -= q.dequeue();                      // 10개 유지: 가장 오래된 값 제거
    avg = sum / 10;                          // 평균 계산
  }  
  
  unsigned long currentTime = millis();

  // 평균값이 max 기준값의 90% 이상이면 심박으로 간주
  if(avg >= maxValue * 0.9){
    unsigned long interval = currentTime - lastBeatTime;

    // 500~2000ms 범위면 유효한 심박 (30~120 BPM 범위)
    if (interval > 500 && interval < 2000) {
      bpm = 60000 / interval;      
      lastBeatTime = currentTime;
    }

    // 2.5초 이상 지나면 맥박 놓친 것으로 간주하고 시간만 리셋
    if(interval > 2500){
       lastBeatTime = currentTime;
    }
  }

  // 5초 주기로 maxValue 갱신
  if(currentTime - pretime > 5000){
    pretime = currentTime;
    maxValue = tmpMaxValue;    // 최근 5초간의 최대 평균값을 기준으로 설정
    tmpMaxValue = 0;           // 임시값 초기화
  }

  // 평균이 기존보다 크면 임시 최대값 갱신
  if(tmpMaxValue < avg){
    tmpMaxValue = avg;
  }

  return bpm;
}

// BPM에 따라 네오픽셀 색상 및 점등 개수 조정
void updateNeopixelGauge(int bpm) {
  int ledsToLight = 0;
  if (bpm < 60) ledsToLight = 3;
  else if (bpm < 90) ledsToLight = 6;
  else if (bpm < 120) ledsToLight = 9;
  else ledsToLight = 12;

  for (int i = 0; i < NUM_LEDS; i++) {
    if (i < ledsToLight) {
      uint32_t color;
      if (i < 3) color = pixels.Color(0, 40, 0);         // 초록
      else if (i < 6) color = pixels.Color(30, 30, 0);   // 노랑
      else if (i < 9) color = pixels.Color(40, 15, 0);   // 주황
      else color = pixels.Color(40, 0, 0);               // 빨강
      pixels.setPixelColor(i, color);
    } else {
      pixels.setPixelColor(i, 0);
    }
  }
  pixels.show();
}

void setup() {

  Serial.begin(9600);

  // OLED 디스플레이 초기화
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.display();

  // 네오픽셀 초기화
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(30);  // 밝기 낮춤
  pixels.show();
}

void loop() {
  unsigned long currentTime = millis();
  Serial.println(avg);

  // 파형 y좌표 맵핑 (센서 출력값을 OLED 화면 범위에 맞춤)
  int y = map(avg, 450, 470, 63, 20);
  y = constrain(y, 20, 63);

  // 실시간 그래프 그리기
  display.drawLine(graphX, lastY, graphX + 1, y, SSD1306_WHITE);
  lastY = y;
  graphX++;

  // 화면 오른쪽 끝에 도달하면 그래프 영역 클리어
  if (graphX >= SCREEN_WIDTH) {
    graphX = 0;
    lastY = 63;
    display.fillRect(0, 20, SCREEN_WIDTH, 44, SSD1306_BLACK);
  }

  int ans = getBPM();
  if(ans != -1){
    int bpm = ans;

    // BPM 값이 유효 범위 내에 있고 이전 BPM과의 차이가 크지 않은 경우만 표시
    bool isValid = (bpm >= BPM_MIN && bpm <= BPM_MAX);
 
    if (isValid) {
      prevBpm = bpm;

      // 상단 텍스트 영역 클리어 및 하트 + BPM 출력
      display.fillRect(0, 0, SCREEN_WIDTH, 20, SSD1306_BLACK);
      display.setTextSize(2);
      display.setCursor(0, 0);
      display.write(3);  // ♥ 출력
      display.print(" BPM: ");
      display.print(bpm);
      Serial.print("-------------------bpm : ");
      Serial.println(bpm);

      // 네오픽셀 업데이트
      updateNeopixelGauge(bpm);
    }
  }

  // 디스플레이 출력 적용
  display.display();
  delay(10);
}

심박 펄스 센서 장착

검지손가락의 손바닥 면을 심박 펄스 센서에 덮고 벨크로를 활용해 고정해주세요.

지긋이 눌러주는 느낌으로 조여주시면 됩니다.

혈류량에 따라 값이 다를 수 있기 때문에 검지 외에 중지로 측정하셔도 괜찮습니다.



결과

OLED의 플리커링 현상으로 인해 사진 상으로 잘 담기지 않아 그림으로 대체합니다.


심박수 측정

심박 펄스 센서를 통해 심박수를 측정하여 OLED에 출력합니다.

아래 파형을 통해 심박을 함께 확인할 수 있습니다.

BPM에 따른 네오픽셀 점등

심박수에 따라 네오픽셀이 점등되어 게이지의 역할을 합니다.

심박 구간에 따라 각기 다른 색의 LED가 켜집니다.