
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 조건부 컴파일 구성에 많은 시간을 할애했습니다.
- 향후 추상화 계층을 더 견고히 설계할 계획입니다.
'ABC 미래내일 프로젝트' 카테고리의 다른 글
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 6주차 (1) | 2026.05.09 |
|---|---|
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 5주차 (0) | 2026.05.02 |
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 4주차 (0) | 2026.04.26 |
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 3주차 (0) | 2026.04.19 |
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 1주차 (0) | 2026.04.04 |