포트폴리오/[IoT 기반 피지컬 AI 교육일지]

[ C언어 IoT 프로젝트 ] #2 스마트홈 센서 테스트 & 시리얼 통신 (26일차 기록)

개발자혜콩 2026. 3. 23. 15:37

센서 사용해서 직접 결과보고 예상치 못한 결과를 어떻게하면 값이 튀지 못하게 할지 고민해 보는 부분이 재밌었다

 

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 소켓 통신 뼈대 올리기.