클릭하면 유튜브 시연 영상으로 이동합니다.
라인 스캔 카메라(TLS1401)로 흰색 라인을 인식하고, 스텝 모터 2개를 PD 제어로 조향하는 라인 트레이서입니다.
정지 상태에서 출발하여 설정한 바퀴 수(기본 2바퀴)를 완주한 뒤 출발선에서 자동 정지합니다.
| 항목 | 내용 |
|---|---|
| 센싱 | TLS1401 라인 스캔 카메라 (128 픽셀) |
| 제어 | Arduino Mega 2560 |
| 구동 | 바이폴라 스텝 모터 x2 + A4988 드라이버 |
| 차체 | 3D 프린팅 바디 |
| 전원 | 12V, 3A DC 어댑터 |
| 카메라 핀 | Arduino 핀 | 설명 |
|---|---|---|
| CLK | 22 (PA0) | 클럭 신호 |
| SI | 23 (PA1) | 프레임 시작 신호 |
| AO | A0 (PF0, ADC0) | 아날로그 픽셀 출력 |
| VDD | 5V | 전원 |
| GND | GND | 접지 |
결선 오류 시 센서 파손. 핀 배열 반드시 확인.
카메라가 바라보는 방향 기준 왼쪽 = 1번 픽셀, 오른쪽 = 128번 픽셀.
| Arduino 핀 | 드라이버 신호 | 설명 |
|---|---|---|
| PD0 (21) | STEP_L | 왼쪽 모터 스텝 펄스 |
| PD1 (20) | DIR_L | 왼쪽 모터 방향 |
| PD2 (19) | STEP_R | 오른쪽 모터 스텝 펄스 |
| PD3 (18) | DIR_R | 오른쪽 모터 방향 |
| PH0 (17) | /EN | 모터 드라이버 활성화 (LOW = 활성) |
전원(12V)은 DC 어댑터를 드라이버 보드에 직접 공급.
Arduino 전원은 드라이버 보드 출력 단자(J3)에서 공급받음.
메인 컨트롤러. ATmega2560 기반으로 타이머 인터럽트, ADC, 다수의 GPIO를 동시 활용.
128 x 1 포토다이오드 어레이 센서. SI/CLK 신호로 한 프레임을 읽고 AO 핀으로 각 픽셀의 밝기를 아날로그 전압으로 출력.
흰색 위에서 약 2.8V, 검정 위에서 약 0.96V (노출 10ms 기준).
노출 시간(tint) 조절로 조명 환경에 대응 가능하며 최대 100ms까지 지원.
2개의 A4988 드라이버 IC 내장. STEP/DIR 신호만 보내면 드라이버가 코일 여자 순서를 자동 처리.
마이크로스텝 설정은 R1/R2/R3 점퍼로 Full ~ 1/16 스텝 선택 가능. 전류량은 드라이버 IC의 가변저항으로 조절.
기존의 단순 가중 평균(Weighted Average) 방식은 트랙 주변의 빛 반사나 노이즈를 라인으로 오인해 무게중심이 흔들리는 문제가 있었다. 이를 해결하기 위해 Largest Blob Detection 기법을 직접 고안하여 적용하였다.
처리 순서:
- ROI 제한: 픽셀 2~125 범위만 사용 (끝단 노이즈 제거)
- Dynamic Threshold: 매 프레임마다 min/max를 갱신하고
threshold = min + (max - min) × 0.45로 조명 변화에 적응- max - min < 60이면 라인 미검출로 판단
- Blob Labeling: 임계값을 넘는 연속 픽셀 구간을 하나의 Blob으로 인식
- Noise Filtering: 3픽셀 미만 Blob은 노이즈로 무시, 가장 긴 Blob만 유효 라인으로 선택
- 무게중심 계산 (선택된 Blob 내부에서만):
center = Σ(픽셀위치 × (밝기 - threshold)) / Σ(밝기 - threshold) error = center - 63.5 (왼쪽 = 음수, 오른쪽 = 양수) - 자동 노출(AE): 최대 밝기 < 750이면 노출 +50µs, > 900이면 -50µs (범위: 100~6000µs)
스텝 모터의 목표 주파수가 캐리어 주파수의 정수 분주비로 딱 떨어지지 않는 문제를 정수 연산만으로 해결하는 알고리즘.
A = round(목표주파수 × B / 캐리어주파수) // B = 1024 고정
ISR (40kHz마다 실행):
누적기 += A
if (누적기 >= B):
STEP 펄스 발생
누적기 -= B
평균적으로 원하는 주파수와 일치하는 스텝 펄스를 실수 연산 없이 생성. 좌/우 모터 독립적으로 적용.
u = KP × e + KD × (e - 이전_e)
왼쪽 모터 = 현재속도 + u
오른쪽 모터 = 현재속도 - u
| 파라미터 | 값 | 설명 |
|---|---|---|
| KP_TURN | 140.0 | 비례 게인 |
| KD_TURN | 64.0 | 미분 게인 — 급격한 오차 변화에 댐핑 효과 |
speed_scale = 1.0 - K_SPEED × |error| (K_SPEED = 0.01)
speed_scale = clamp(speed_scale, MIN_SCALE=0.20, 1.0)
target_speed = BASE_F × speed_scale
에러가 클수록(급커브일수록) 속도를 줄이고, 직선에서는 최대 속도로 주행. 최저 속도는 기본 속도의 20%까지 허용.
출발 시 바퀴 헛도는 현상 방지를 위해 속도를 점진적으로 올림.
if (현재속도 < 목표속도):
현재속도 += ACCEL_RATE // 900 Hz / 10ms
else:
현재속도 = 목표속도 // 감속은 즉시 반영 (코너 진입 밀림 방지)
라인을 완전히 놓쳤을 때 직전에 주행하던 방향을 기억해두었다가, 해당 방향으로 핸들을 끝까지 꺾어(FAILSAFE_ERROR = 100) 트랙 복귀를 유도.
흰색 Blob의 길이가 18픽셀 미만으로 짧아지면 정지선으로 판단. 조명 변화에 강한 방식. 마지막 인식 후 2000ms 동안 중복 인식 방지(Debounce).
문제: 단순 가중 평균 방식은 트랙 주변 빛 반사 등 노이즈를 라인으로 오인해 무게중심이 흔들렸다.
해결: Largest Blob Detection 도입. 연속된 흰색 픽셀 Blob 중 가장 긴 것 하나만 찾아내어, 그 내부에서만 무게중심을 계산해 노이즈를 원천 배제.
문제: 라인을 놓쳤을 때 대처 로직이 없어 직전 에러값이 유지되거나 로봇이 직진해 트랙 밖으로 튕겨 나갔다.
해결: 정상 주행 중 라인 방향(left/right)을 지속 기억. 라인이 사라지는 순간 해당 방향으로 최대 오차값을 부여해 강제 복귀.
문제: 검은색 픽셀 수를 세는 방식은 그림자나 조명 변화에 취약해 정상 도로를 정지선으로 착각하는 문제가 발생했다.
해결: 흰색 Blob 길이 결과를 재사용. "흰색 선이 매우 짧아졌는가?"를 기준으로 판단해 조명 변화에 강인하게 개선.
문제: Kp를 높이면 직선에서 좌우 진동 발생, Kp를 낮추면 코너에서 이탈. 비례 제어만으로는 반응성과 안정성을 동시에 달성 불가.
해결: PD 제어 도입. Kd = 64로 급격한 에러 변화에 댐핑 효과를 부여, Kp = 140.0의 높은 게인에서도 직선 주행 시 진동을 효과적으로 억제.
문제: 게인 최적화 후에도 기본 주행 속도가 빠를 경우 코너에서 원심력을 이기지 못해 이탈 현상이 지속됐다.
해결: 가변 속도 프로파일 도입. 에러 절댓값이 클수록 목표 속도를 줄이고, 최저 속도를 기본 속도의 20%(MIN_SCALE = 0.2)까지 허용해 급커브도 안정적으로 통과.
| 파라미터 | 값 | 설명 |
|---|---|---|
| BASE_F | 12000 Hz | 기본 주행 속도 |
| F_MAX | 18000 Hz | 최대 속도 제한 |
| F_CARRIER | 40000 Hz | DDA 캐리어 주파수 |
| DDA_B | 1024 | DDA 분모 (고정) |
| KP_TURN | 140.0 | PD 비례 게인 |
| KD_TURN | 64.0 | PD 미분 게인 |
| K_SPEED | 0.01 | 코너 감속 민감도 |
| MIN_SCALE | 0.20 | 최소 속도 비율 |
| ACCEL_RATE | 900 | 가속도 (Hz/10ms) |
| FAILSAFE_ERROR | 100.0 | 라인 유실 시 복귀 오차 |
| TARGET_LAPS | 2 | 목표 완주 바퀴 수 |
- 라인 색상: 검정 (배경: 흰색)
- 라인 두께: 20mm
- 최소 원호 반경: 185mm
- 트랙 크기: 약 900 × 600mm

