2026 ABC 프로젝트 멘토링

LiDAR 하드웨어 인터페이스


이번 주 목표

  • RPLiDAR S2 센서와 시리얼 통신 연결
  • HQ 스캔 모드로 점구름 데이터 수신
  • PIR 모션 센서 GPIO 인터페이스 구현
  • 빌드 시스템(CMake) 구성 및 크로스플랫폼 대응

1. RPLiDAR S2 센서 개요

항목 사양

방식 ToF (Time of Flight)
측정 범위 0.05m ~ 30m
스캔 주파수 10Hz (초당 10회전)
각도 분해능 ~0.1° (8192 HQ 노드/회전)
인터페이스 USB → UART 변환, 1Mbps
출력 포맷 HQ 노드 (angle_z_q14, dist_mm_q2, quality)

선택 이유

  기존의 삼각측량 방식(A1/A2 시리즈)보다 원거리 정확도가 높은 ToF 방식을 채택했습니다. 30m의 넓은 탐지 범위를 제공하면서도 합리적인 가격대를 유지하며, 제공되는 C++ SDK가 라즈베리파이 환경을 공식 지원하여 통합이 용이했기 때문입니다.


2. 시리얼 통신 연결

디바이스 경로

Linux (Raspberry Pi): /dev/ttyUSB0
macOS (개발용):       /dev/cu.usbserial-*

연결 흐름

1. createLidarDriver() → ILidarDriver 인스턴스 생성
2. createSerialPortChannel("/dev/ttyUSB0", 1000000) → 1Mbps 채널
3. driver->connect(channel) → 물리 연결
4. driver->getDeviceInfo() → 펌웨어 버전, 시리얼 번호 확인
5. driver->getHealth() → 상태 체크 (Good/Warning/Error)
   └ Error 시: driver->reset() → 재시도
6. driver->startScanExpress(false, scanMode.id) → HQ 스캔 시작

핵심 코드 (rplidar_s2.cpp)

bool RplidarS2::Start() {
    driver_ = *sl::createLidarDriver();
    auto channel = *sl::createSerialPortChannel(
        cfg_.serial_port, cfg_.baudrate);  // 1Mbps
    driver_->connect(channel);

    // 헬스 체크 — Error 상태면 리셋 후 재시도
    sl_lidar_response_device_health_t health;
    driver_->getHealth(health);
    if (health.status == SL_LIDAR_STATUS_ERROR) {
        driver_->reset();
        usleep(2000000);  // 2초 대기
    }

    // HQ 스캔 모드로 시작
    std::vector<sl::LidarScanMode> modes;
    driver_->getAllSupportedScanModes(modes);
    driver_->startScanExpress(false, modes[0].id);
}

HQ 노드 데이터 파싱

  SDK로부터 수신되는 sl_lidar_response_measurement_node_hq_t 구조체 데이터는 Fixed-point(고정소수점) 방식으로 인코딩되어 있어 변환 공식이 필요합니다.

angle_deg   = node.angle_z_q14 × 90.0 / 16384.0
distance_mm = node.dist_mm_q2  / 4.0
intensity   = node.quality      (0~255)
bool RplidarS2::GetScanFrame(std::vector<ScanPoint>& out) {
    sl_lidar_response_measurement_node_hq_t nodes[8192];
    size_t count = 8192;
    driver_->grabScanDataHq(nodes, count);
    driver_->ascendScanData(nodes, count);  // 각도 순 정렬

    out.resize(count);
    for (size_t i = 0; i < count; ++i) {
        out[i].angle_deg   = nodes[i].angle_z_q14 * 90.0f / 16384.0f;
        out[i].distance_mm = nodes[i].dist_mm_q2  / 4.0f;
        out[i].intensity   = nodes[i].quality;
    }
}

트러블슈팅: USB 권한

  라즈베리파이 재부팅 시 /dev/ttyUSB0에 대한 접근 권한이 상실되는 문제를 해결하기 위해 다음과 같이 udev 규칙을 설정하여 영구적인 권한을 부여했습니다.

# 일회성
sudo chmod 666 /dev/ttyUSB0

# 영구 설정 (udev 규칙)
echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", MODE="0666"' \
  | sudo tee /etc/udev/rules.d/99-rplidar.rules
sudo udevadm control --reload-rules

 


3. PIR 모션 센서 인터페이스

  보조 센서인 Panasonic PaPIRs 센서는 별도의 라이브러리 의존성을 줄이기 위해 Linux의 sysfs GPIO 방식을 사용하여 구현했습니다.

센서 사양

모델 Panasonic PaPIRs EKMC160111
수량 2개 (GPIO 17, 27)
출력 디지털 HIGH/LOW
인터페이스 Linux sysfs GPIO

GPIO 제어 (Linux sysfs)

/sys/class/gpio/export         ← 핀 번호 쓰기
/sys/class/gpio/gpio17/direction ← "in"
/sys/class/gpio/gpio17/value   ← '0' 또는 '1' 읽기
bool PirSensor::Init() {
    // 1. GPIO export
    std::ofstream("/sys/class/gpio/export") << pin_;
    // 2. 방향 설정
    std::ofstream(base + "/direction") << "in";
    // 3. value 파일 열어두기 (반복 open 방지)
    value_fd_ = open((base + "/value").c_str(), O_RDONLY);
}

bool PirSensor::Read() {
    lseek(value_fd_, 0, SEEK_SET);  // 파일 처음으로
    char c;
    read(value_fd_, &c, 1);
    return (c == '1');
}

디바운싱 전략

  PIR 센서의 특성상 노이즈로 인한 오탐이 발생할 수 있습니다. 이를 방지하기 위해 연속 3프레임 이상 동일한 값이 확인될 때만 상태를 전환하고, 감지 후에는 일정 시간 상태를 유지하는 소프트웨어 디바운스를 적용했습니다.

debounce_frames = 3   → 3연속 같은 값이어야 상태 전환
holdoff_frames  = 50  → HIGH 감지 후 최소 50프레임 유지

비-Linux 폴백

  macOS 등 sysfs가 존재하지 않는 환경에서는 개발 흐름이 끊기지 않도록 항상 true를 반환하도록 처리했습니다.


4. 빌드 시스템 (CMake)

  프로젝트의 확장성과 재현성을 위해 CMake 기반의 빌드 시스템을 구축했습니다. FetchContent를 활용하여 nlohmann/json과 같은 외부 라이브러리를 자동으로 관리하며, 용도에 따라 실행 바이너리와 테스트용 타겟을 분리했습니다.

의존성 구조

CMakeLists.txt
├── RPLiDAR SDK (thirdparty/rplidar_sdk/)
│   ├── Linux:  arch/linux/*.cpp
│   ├── macOS:  arch/macOS/*.cpp
│   └── Win32:  arch/win32/*.cpp
├── nlohmann/json (FetchContent v3.11.3)
├── libcurl (find_package)
└── OpenCV (optional, 카메라 모듈)

빌드 타겟

rplidar_app 프로덕션 실행 바이너리
rplidar_sim 하드웨어 없이 시뮬레이션
check_lidar LiDAR USB 연결 테스트
check_camera 카메라 초기화 테스트
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)

 


5. 이번 주 회고

잘된 점

  • HQ 스캔 모드를 통해 프레임당 8,192개의 정밀한 노드 데이터를 안정적으로 확보했습니다.
  • 별도의 무거운 라이브러리 없이 sysfs만으로 PIR 센서 제어 로직을 통합했습니다.
  • CMake를 통한 의존성 자동화로 팀원 간 동일한 빌드 환경을 구축했습니다.

어려웠던 점

  • 고정소수점 데이터를 파싱하는 과정에서 비트 연산과 스케일 상수에 대한 이해가 필요함을 체감했습니다.
  • 플랫폼별 SDK 소스 코드가 상이하여 CMake 조건부 컴파일 구성에 많은 시간을 할애했습니다.
  • 향후 추상화 계층을 더 견고히 설계할 계획입니다.