센서 사용해서 직접 결과보고 예상치 못한 결과를 어떻게하면 값이 튀지 못하게 할지 고민해 보는 부분이 재밌었다
1. 사용한 센서 및 부품
초기에는 가스 센서(MQ-2), 심박수, 기울기 센서까지 모두 붙이려 했으나 전력 한계(Arduino Mega 5V 핀 최대 500mA)에 부딪혔다. DC 모터 혼자 최대 250mA를 소모하므로 핵심 기능을 위주로 진행하기로 결정하고, 아래 6가지로 최적화했다.
| 부품 | 모델명 | 역할 | 연결핀 |
| 온습도 센서 | SHT21 (I2C) | 실내 온도·습도 측정 | SDA(20), SCL(21) (3.3V 전원) |
| 인체감지 센서 | PIR | 인체 움직임 감지 | D3 |
| 초음파센서 | HC-SR04 | 거리 측정 (낙상 감지) | TRIG(7), ECHO(8) |
| DC 모터 | L9110 드라이버 | 환풍기 제어 | A-1A(11 - PWM), A-1B(10) |
| LED | 일반 LED | 긴급 경고등 | D5 |
| 스위치 | 택트 스위치 | SOS 버튼 | D4 |
2. 센서별 트러블슈팅 및 로직 구현
① SHT21 온습도 센서 (I2C 통신)
- 문제 : 처음엔 대중적인 DHT11을 쓰려 했으나, 타이밍에 극도로 민감해 noInterrupts()를 써야 했고 이로 인해 다른 센서들의 동작이 꼬였다.
- 해결: I2C 통신(비동기 방식)을 사용하는 SHT21로 교체. Wire 라이브러리로 직접 레지스터(0x40)를 읽어와 다른 작업과 충돌 없이 매끄럽게 온습도를 추출해 냈다.
② PIR 모션 센서 (채터링 노이즈 제어)
- 문제: 사람이 없는데도 값이 가끔 HIGH로 튀는 현상(채터링 노이즈) 발생.
- 해결: '연속 3회 HIGH(1.5초 이상 지속)'일 때만 실제 감지로 인정하는 방어 로직 구현.
#define PIR_SAMPLE_MS 500 // 500ms마다 샘플링
#define PIR_CONFIRM 3 // 3회 연속 HIGH → 감지 확정
if (now - lastPirSample >= PIR_SAMPLE_MS) {
lastPirSample = now;
int pir = digitalRead(PIR_PIN);
if (pir == HIGH) {
pirHighCount++;
if (pirHighCount >= PIR_CONFIRM && !pirDetected) {
pirDetected = true;
}
} else {
pirHighCount = 0;
pirDetected = false;
}
}
③ HC-SR04 초음파 센서 (낙상 감지 아이디어)
천장에 설치하여 바닥까지의 거리를 재고, 거리가 갑자기 줄어들면(사람이 쓰러지면) 낙상으로 간주하는 방식 적용!
(평상시 85cm → 낙상 시 10cm 미만)
- 문제: 순간적으로 3cm가 찍히는 등 값이 튀는 현상 발생.
- 해결: 배열을 이용한 '3회 이동평균(Moving Average)' 적용. 튀는 값이 들어와도 평균에 희석되어 임계값을 넘지 않도록 안정화.
- 낙상 판정 2단계 로직:
- 1단계: 10cm 미만 30초 지속 → 보호자 주의 알림
- 2단계: 10cm 미만 100초 지속 → 적색 점멸 및 긴급 경보음
④ DC 모터 및 사운드 센서 이슈
- 모터: L9110 모터 드라이버 사용. 속도 제어를 위해 제어 핀(A-1A)을 반드시 Mega의 PWM 지원 핀(11번)에 연결해야 함을 확인.
- 사운드 센서 실패: 낙상 충격음을 감지하려 했으나 센서 불량(항상 0 출력)으로 폐기. 오히려 물리적 거리 기반인 초음파 방식이 환경 노이즈에 강해 신뢰도가 훨씬 높다는 것을 깨달음.
3. 시리얼 통신 고도화: 텍스트에서 '바이너리 패킷'으로
- 문제: 처음엔 Temp: 19.2 C 같은 텍스트로 리눅스 서버에 보낸 뒤 sscanf()로 파싱했다. 그러나 센서가 추가되거나 공백 하나만 바뀌어도 서버 코드가 박살 났다.
- 해결: 데이터를 덩어리째 묶어 보내는 바이너리 구조체 패킷(struct) 방식으로 전환.
typedef struct {
uint8_t preamble; // 항상 0xAA (동기화)
uint8_t type; // 데이터 타입
char sender_id[16]; // "ARDUINO"
union {
SensorData sensor;
EmergencyData emergency;
} payload;
uint32_t timestamp;
} NetPacket;
⭐ 핵심: 데이터 동기화
바이너리 통신 시 서버가 스트림 중간부터 읽기 시작하면 패킷이 꼬이는 치명적 문제가 있다.
이를 막기 위해 구조체 첫 바이트에 항상 0xAA를 박아 넣었다.
서버는 버퍼를 비우다가 0xAA를 만날 때만 "여기서부터 새 패킷이네" 하고 읽기를 시작하여 자동 동기화 복구 진행.
4. 다음 목표
- 리눅스 서버 측 C언어 코드에서 아두이노의 바이너리 구조체(NetPacket) 수신 및 파싱 확인.
- MariaDB 데이터베이스 연동 및 수신된 센서 데이터 실시간 INSERT 쿼리 구현.
- 리눅스 서버와 윈도우 클라이언트 간 TCP 소켓 통신 뼈대 올리기.
'포트폴리오 > [IoT 기반 피지컬 AI 교육일지]' 카테고리의 다른 글
| [ C언어 IoT 프로젝트 ] #4 인코딩 에러수정, WinAPI 비동기 UI 개발 (28일차 기록) (0) | 2026.03.24 |
|---|---|
| [ C언어 IoT 프로젝트 ] #3 MySQL 연동 및 멀티스레드 TCP 소켓 구축(27일차 기록) (0) | 2026.03.24 |
| [ C언어 IoT 프로젝트 ] #1 고령 1인가구 안심 스마트홈 아키텍처 설계 (25일차 기록) (0) | 2026.03.23 |
| [ C++ 윈도우 소켓 프로그래밍 ] 콜백 함수와 WPARAM / LPARAM 차이 정리 (24일차 기록) (0) | 2026.03.20 |
| [ C++ 네트워크 통신 ] 리눅스 멀티스레드(pthread)와 주요 소켓 옵션 및 기타 정리 (23일차 기록) (0) | 2026.03.20 |