1mm의 정밀함! 레이저 거리 센서 VL53L0X 초보 가이드 (1/2)

안녕하세요! 오늘은 스마트 부화기 프로젝트의 쓰일 VL53L0X ToF(Time-of-Flight) 거리 센서를 ESP32-C3에 연결하고 정밀도를 높이는 과정을 공유합니다. 기존의 초음파 센서보다 훨씬 작고 정확한 이 녀석, 어떻게 다루는지 함께 보시죠.


1. 사용 부품 소개

이번 프로젝트에 사용된 주요 하드웨어입니다.

  • MCU: ESP32-C3 SuperMini
    • 초소형 사이즈에 Wi-Fi와 Bluetooth를 지원하며, RISC-V 아키텍처 기반으로 가성비가 매우 뛰어난 보드입니다.
  • Sensor: VL53L0X (ToF 방식)
    • Time-of-Flight(빛의 비행시간) 원리를 이용합니다. 보이지 않는 적외선 레이저를 쏘고 반사되어 돌아오는 시간을 측정해 거리를 계산합니다.
    • 장점: 초음파 센서(HC-SR04)처럼 소리에 의존하지 않아 주변 소음이나 공기 흐름에 강하고, mm 단위의 정밀한 측정이 가능합니다.
  • Display: 0.96″ SSD1306 OLED
    • I2C 통신을 통해 실시간 거리 데이터를 시각화합니다.
  • 기타: 90도 꺾임 헤더 핀, 브레드보드, 점퍼 와이어.

2. 하드웨어 팁: 왜 90도 핀(Right Angle)인가?

보통 센서를 사면 일자형 헤더 핀이 들어있지만, 저는 90도 꺾임 핀을 별도로 준비해 납땜했습니다.

  • 이유: 일자형 핀을 브레드보드에 꽂으면 센서가 하늘을 보게 됩니다. 하지만 90도 핀을 쓰면 센서가 브레드보드 정면을 바라보게 되어, 사물과의 거리를 측정하는 실험 환경을 만들기 훨씬 수월합니다.
  • 주의: 일반 핀을 펜치로 꺾으면 길이가 짧아져 접촉 불량이 생기니, 꼭 기성품 90도 핀을 사용하세요!

3. 회로 구성 (Wiring)

ESP32-C3의 I2C 핀은 자유롭게 설정 가능하지만, 저는 관리가 편하도록 아래와 같이 연결했습니다.

센서/OLED 핀ESP32-C3 핀역할
VCC (VIN)3.3V전원 공급
GNDGND접지
SCLGPIO 0I2C 클럭 라인
SDAGPIO 1I2C 데이터 라인

4. 소프트웨어 트러블슈팅: ImportError 해결

MicroPython 환경에서 PC용 라이브러리를 그대로 쓰면 ImportError: no module named 'ctypes'라는 에러를 만나게 됩니다.

  • 해결책: ESP32의 가벼운 메모리에 맞게 설계된 경량화된 MicroPython 전용 드라이버를 사용해야 합니다. ctypes 모듈 없이 machine.I2C만으로 작동하는 코드를 적용하여 문제를 해결했습니다.

5. 정밀도 높이기: 오프셋 보정과 필터링

레이저 센서라고 해서 완벽하진 않습니다. 테스트 결과 두 가지 보정이 필요했습니다.

  1. 오프셋(Offset) 보정: 실제 거리보다 약 20mm(2cm) 정도 길게 측정되는 경향이 있어, 코드에서 raw_distance - 20을 해주어 영점을 잡았습니다.
  2. 지터(Jitter) 제거: 숫자가 미세하게 떨리는 현상을 잡기 위해 **가중 평균 필터(Low Pass Filter)**를 적용했습니다.
    • smooth_dist = (alpha * current_raw) + ((1 - alpha) * smooth_dist)
    • 이 공식을 통해 OLED의 바 그래프가 아주 부드럽게 움직이도록 만들었습니다.

6. 결과 확인 (Thonny 플로터)

Thonny의 Plotter 기능을 켜면 실시간으로 거리 변화가 그래프로 그려집니다. 손을 센서 앞에 가져다 대면 반응 속도가 얼마나 빠른지, 그리고 필터가 얼마나 부드럽게 작동하는지 한눈에 확인할 수 있습니다.


마치며

이번 1편에서는 단일 센서의 기초 설정과 최적화 방법을 알아봤습니다. mm 단위로 딱딱 끊어지는 데이터를 보니 벌써 부화기 프로젝트가 반쯤 성공한 기분이네요!

이어지는 2편에서는 오늘 배운 내용을 바탕으로 I2C 주소 충돌을 해결하고 센서 3개를 동시에 제어하는 멀티 센서 시스템을 구축해 보겠습니다.

Python vl53l0x.py

import time
from machine import I2C

class VL53L0X:
    def __init__(self, i2c, address=0x29):
        self.i2c = i2c
        self.address = address
        self.init()

    def _read_reg(self, reg, length=1):
        return self.i2c.readfrom_mem(self.address, reg, length)

    def _write_reg(self, reg, data):
        if isinstance(data, int):
            data = bytes([data])
        self.i2c.writeto_mem(self.address, reg, data)

    def init(self):
        # 기본 초기화 시퀀스 (단순화 버전)
        self._write_reg(0x88, 0x00)
        self._write_reg(0x80, 0x01)
        self._write_reg(0xFF, 0x01)
        self._write_reg(0x00, 0x00)
        self._write_reg(0x91, self._read_reg(0x91))
        self._write_reg(0x00, 0x01)
        self._write_reg(0xFF, 0x00)
        self._write_reg(0x80, 0x00)

    def start_continuous(self):
        self._write_reg(0x00, 0x02) # 계속 측정 모드 시작

    def read(self):
        # 거리 데이터 읽기 (High byte, Low byte 조합)
        data = self._read_reg(0x14, 12)
        dist = (data[10] << 8) | data[11]
        if dist == 20 or dist == 8190: # 측정 오류 또는 범위 초과 값 처리
            return 0
        return dist
    # vl53l0x.py 클래스 내부에 추가
    def set_address(self, new_address):
        # I2C 주소 설정 레지스터(0x8A)에 새 주소 쓰기
        self._write_reg(0x8A, new_address & 0x7F)
        self.address = new_address

from machine import Pin, SoftI2C
import ssd1306
import vl53l0x
import time

# 1. I2C 및 장치 설정
i2c = SoftI2C(scl=Pin(0), sda=Pin(1), freq=400000)

try:
    display = ssd1306.SSD1306_I2C(128, 64, i2c)
    sensor = vl53l0x.VL53L0X(i2c)
    sensor.start_continuous()
    print("Devices Ready!")
except Exception as e:
    print("Init Error:", e)

# 2. 필터 변수 설정
smooth_dist = 0
alpha = 0.2  # 0.1에 가까울수록 부드럽고, 1.0에 가까울수록 반응이 빠름
OFFSET = 72-48  # 아까 확인한 2cm 오차 보정값

def update_display(raw, smooth):
    display.fill(0)
    display.text("ToF Filter Test", 5, 0)
    display.hline(0, 12, 128, 1)
    
    # 원본 값과 필터 값 표시
    display.text("RAW   : {:4d}mm".format(raw), 0, 25)
    display.text("SMOOTH: {:4d}mm".format(int(smooth)), 0, 40)
    
    # 시각화 바 (필터링된 값 기준)
    bar_width = min(int(smooth / 300 * 120), 120)
    display.rect(4, 55, 122, 7, 1)
    display.fill_rect(5, 56, bar_width, 5, 1)
    
    display.show()

# 3. 메인 루프
while True:
    try:
        # 센서 읽기 및 기본 보정
        current_raw = max(0, sensor.read() - OFFSET)
        
        # 가중 평균 필터 계산
        if smooth_dist == 0: 
            smooth_dist = current_raw
        else:
            # 공식: 새 값(20%) + 이전 값(80%)
            smooth_dist = (alpha * current_raw) + ((1 - alpha) * smooth_dist)
        
        # 플로터 출력 (파란색: 원본, 주황색: 필터값)
        # Thonny 플로터에서 두 선이 겹쳐서 보입니다.
        print((current_raw, smooth_dist))
        
        # OLED 업데이트
        update_display(current_raw, smooth_dist)
        
    except Exception as e:
        print("Loop Error:", e)
        
    time.sleep(0.05)

코멘트

“1mm의 정밀함! 레이저 거리 센서 VL53L0X 초보 가이드 (1/2)” 에 하나의 답글

  1. micro2iot 아바타

    [구매정보] 블로그에서 사용한 부품 구매정보입니다.
    아래 링크를 통해 구매 시 소정의 수수료를 제공받으며, 채널 운영에 큰 도움이 됩니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다