아두이노 우노로 OLED에 실시간 ECG 파형 표시하기
부품 및 소모품
| 부품 | 수량 |
| 아두이노 우노 | 1 |
| uECG 장치 | 1 |
| nRF24 모듈 (범용) | 1 |
| 0.96" OLED 128×64 디스플레이 (SSD1306) | 1 |
프로젝트 개요
uECG 장치에서 무선(nRF24)으로 심전도(ECG) 데이터를 수신해 아두이노 우노로 실시간 파형을 OLED에 표시하는 프로젝트입니다. 기존의 지저분한 코드를 정리해 uECG 아두이노 라이브러리(GitHub: ultimaterobotics/uECG_library)를 공개했으며, RF24 라이브러리와 Adafruit SSD1306 라이브러리가 필요합니다.
회로도
- nRF24: SPI 버스(D13, D12, D11) + CS(D10), CE(D9), **반드시 3.3V 전원** 사용 (5V 금지). 1~10µF 커패시터로 3.3V 라인 안정화 권장.
- OLED: I2C (SDA→A4, SCL→A5), 5V 전원. 내장 풀업 저항이 없으면 4.7kΩ 외부 풀업 필요.

코드 (라이브러리 예제 포함)
#include
#include
#include
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
int rf_cen = 9, rf_cs = 10;
uint8_t ecg_screen[128];
float ecg_avg = 0, ecg_max = 1, ecg_min = -1;
int ecg_size = 40;
uint32_t prev_data_count = 0, prev_displ = 0;
int displ_phase = 0;
void setup() {
Serial.begin(115200);
while(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
}
display.display(); delay(100);
uECG.begin(rf_cs, rf_cen);
delay(100);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.cp437(true);
display.display();
delay(100);
}
void loop() {
uECG.run(); // 가능한 자주 호출해야 데이터 손실 없음
uint32_t data_count = uECG.getDataCount();
int new_data = data_count - prev_data_count;
prev_data_count = data_count;
if(new_data > 0) {
int16_t ecg_data[8];
uECG.getECG(ecg_data, new_data);
for(int x = 0; x < new_data; x++) Serial.println(ecg_data[x]);
// 화면 스크롤 버퍼 업데이트
for(int x = new_data; x < 128; x++) ecg_screen[x-new_data] = ecg_screen[x];
for(int x = 0; x < new_data; x++) {
ecg_avg *= 0.99; ecg_avg += 0.01 * ecg_data[x];
ecg_max = ecg_max*0.995 + ecg_avg*0.005;
ecg_min = ecg_min*0.995 + ecg_avg*0.005;
if(ecg_data[x] > ecg_max) ecg_max = ecg_data[x];
if(ecg_data[x] < ecg_min) ecg_min = ecg_data[x];
int ecg_y = 63 - ecg_size*(ecg_data[x] - ecg_min) / (ecg_max - ecg_min + 1);
ecg_screen[127-new_data+x] = ecg_y;
}
// 30ms마다 디스플레이 업데이트 (4단계로 분할)
if(millis() - prev_displ > 30) {
prev_displ = millis();
if(displ_phase == 0) {
display.clearDisplay();
display.setCursor(0,0);
display.print("BPM: "); display.println(uECG.getBPM());
display.print(" RR: "); display.println(uECG.getLastRR());
display.print("steps: "); display.print(uECG.getSteps());
// 배터리 표시
int batt_mv = uECG.getBattery();
int batt_perc = constrain((batt_mv - 3300)/8, 0, 100);
// 배터리 바 그리기...
}
if(displ_phase == 1) {
for(int x=1; x<64; x++) display.drawLine(x-1, ecg_screen[x-1], x, ecg_screen[x], WHITE);
}
if(displ_phase == 2) {
for(int x=64; x<127; x++) display.drawLine(x-1, ecg_screen[x-1], x, ecg_screen[x], WHITE);
}
if(displ_phase == 3) display.display();
displ_phase = (displ_phase + 1) % 4;
}
}
}
데이터 처리 기능
장치 내부에서 BPM, GSR, RR 간격, HRV 파라미터(16개 빈), 걸음 수, 가속도계(자세 추정용) 등을 계산 제공. 로우 ECG 데이터도 패킷 손실이 있지만 사용 가능 수준.
라이브러리 및 참고
- uECG 아두이노 라이브러리: GitHub (OLED/시리얼 예제 포함)