Files
fori_zltech_motor_test/doc/04-software-control.md
T
Dongubak 2f21d86193 Add ZLAC8015D driver docs, IMU integration plan, and reference resources
Documents the ZLAC8015D V4 dual-channel driver (hardware spec, RS485
Modbus register map, 4WD skid-steer system architecture, the
xbox_motor_control.cpp control loop, and error/troubleshooting) under
doc/, plus a phased plan for adding IMU-based heading/slip feedback
(doc/06) informed by Clearpath Husky's sensor fusion stack and
skid-steer ICR literature. QnA/ records the Q&A that refined that plan.
Also adds the underlying manual PDFs and reference links under
resource/ that the docs cite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:17:24 +09:00

195 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04. 소프트웨어 제어 구조 (`xbox_motor_control.cpp`)
출처: `xbox_motor_control.cpp` 전체 (1382줄), `Makefile`, `run_4wd.sh`
## 4.1 빌드 & 실행
```bash
make # g++ -O3 -std=c++17 -Wall -Wextra ... -lSDL2 -lpthread -llivox_lidar_sdk_shared
./run_4wd.sh [PORT] # USB latency_timer 1ms 튜닝 + 빌드(필요시) + 실행
```
`run_4wd.sh`는 실행 전 `set_low_latency.sh``/sys/bus/usb-serial/devices/<dev>/latency_timer`
16ms(리눅스 기본값) → 1ms로 낮춰, 100Hz 제어 루프에서 시리얼 read 지연이 병목이 되지 않게 한다.
## 4.2 클래스 구조
```
SerialPort — 포트 open/close, Modbus RTU read/write (writeReg/writeRegs/readRegs)
MotorDriver — SerialPort + slave_id를 감싸는 드라이버 1대(좌우 2채널) 추상화
initDriver / setBrakes / setRPMs / readFeedback /
readCurrentLimits / setMaxCurrent
LidarObstacleDetector — Livox Mid-360S SDK 콜백 기반 장애물 거리 추적 (전/후/좌/우 4방향)
```
드라이버 2대(`driver_front`, `driver_rear`)가 각각 `MotorDriver` 인스턴스로 생성되며, 브로드캐스트
모드가 아니면 후방 드라이버 포인터(`driver_rear_ptr`)가 유효해 개별 제어/피드백이 가능하다.
## 4.3 100Hz 메인 루프 개요
```
매 틱 (10ms):
1. SDL 이벤트 처리 (버튼: A=트립리셋, X=라이다 토글, B/Back=종료)
2. 조이스틱 입력 읽기 + 데드존 적용
3. 라이다 장애물 회피 로직 (속도 스케일링 + 조향 오프셋)
4. 스키드 스티어 운동학으로 target_fl/fr/rl/rr(RPM) 계산
5. airborne(들뜸) 판정된 바퀴는 target를 0으로 강제
6. 상태머신(STOPPED/RUNNING/STOPPING) + 저크 제한 S-curve로 cmd_* 갱신
7. setRPMs()로 드라이버에 지령 전송
8. readFeedback()으로 드라이버에서 피드백 수신
9. 드라이버 알람(에러코드) 감지 및 콘솔 경고
10. 휠 상태(정상/들뜸/걸림) 판정, 주행거리 갱신
11. (옵션) CSV 로그 기록
12. HUD 한 줄 출력 (\r로 갱신)
```
## 4.4 상태 머신
| 상태 | 전이 조건 | 동작 |
| :--- | :--- | :--- |
| `STOPPED` | 초기 상태 | `target_*` 절대값이 0.1 RPM 초과하면 브레이크 해제(`setBrakes(false)`) 후 50ms 대기, `RUNNING`으로 전이 |
| `RUNNING` | `STOPPED`에서 전이 | 저크 제한 프로파일로 `cmd_*``target_*` 추종. `target_*``cmd_*`가 모두 임계값 이하면 `STOPPING`으로 전이 |
| `STOPPING` | `RUNNING`에서 전이 | 감속 프로파일로 0으로 수렴. `cmd_fl/fr`이 0.1 RPM 미만이면 즉시 정지 지령(`setRPMs(0,0)`) 후 50ms 뒤 브레이크 잠금, `STOPPED`로 복귀 |
브레이크 해제/잠금 시 50ms 대기를 두는 이유는 전자식 브레이크(솔레노이드)의 기계적 응답 시간을
확보하기 위함이다.
## 4.5 저크 제한(Jerk-Limited) S-curve 프로파일
```cpp
jerkLimitedStep(current_vel, target_vel, current_accel&, max_accel, max_decel, jerk_limit)
```
* 목표 속도까지 **가속도 자체를 매 틱 `jerk_limit × dt`만큼만 변화**시켜 부드럽게 목표 가속도에
도달시킨다. 정지 상태에서 출발할 때 첫 틱부터 풀가속도가 걸려 정지마찰이 급격히 파괴되는
문제(예: 저마찰 노면에서 순간 슬립)를 막기 위한 구조.
* 오버슈트 방지: 다음 틱 속도가 목표를 넘어서면 그 자리에서 목표값으로 스냅하고 가속도를 0으로
리셋한다.
* 가속/감속/저크 한계값은 CLI 인자로 조정 가능 (§4.8).
* 제자리 회전은 `spin_jerk_rate`(기본 220 RPM/s²)로 직진/커브 선회(`jerk_rate`, 기본 600 RPM/s²)
보다 훨씬 완만하게 적용된다 — 고마찰 노면에서 제자리 선회 시 정지마찰이 급격히 깨지지 않도록
가속에 도달하는 시간을 더 길게 늘린 것.
## 4.6 착지(Landing) 스냅 처리
들뜸(airborne) 상태에서 해제된(착지) 직후 한 틱은, 저크 제한 프로파일을 건너뛰고 `cmd_*`
`target_*`로 즉시 스냅한다(`just_landed_*` 플래그). 차체는 이미 그 속도로 이동 중인데 지령이
정지출발용 완만한 가속 램프를 다시 타면, 그 사이 바퀴가 지면 이동 속도를 못 따라가며 끌리는
현상을 없애기 위함.
## 4.7 라이다(Livox Mid-360S) 장애물 회피
`LidarObstacleDetector`가 포인트클라우드 콜백에서 좌표를 17° 하향 피치 보정 후 4개 구역
(전방/후방/좌측/우측)의 최소 거리를 EMA(지수이동평균, 접근 시 즉각 반응·회복 시 완만)로 추적한다.
| 파라미터 | 기본값 | 의미 |
| :--- | :-: | :--- |
| `front_stop_dist` | 0.35 m | 전방 완전 정지 |
| `front_warn_dist` | 0.50 m | 전방 감속 시작 (선형 스케일) |
| `side_dodge_dist` | 0.50 m | 측면 회피 조향 시작 |
| `side_stop_dist` | 0.20 m | 측면 회피 강도 100% 도달 거리 |
| `max_dodge_omega` | 0.35 rad/s | 자동 회피 최대 조향 각속도 |
* 전진 중 전방 장애물 → 속도 스케일링(0~1) 또는 완전 정지.
* 후진 중 후방 장애물 → 완전 정지만 적용(감속 스케일링 없음).
* 전진 중이면서 사용자가 좌우 조향(rx) 입력이 없을 때만 측면 회피 조향 개입 (요청 사양: 후진 시
측면 회피 미적용).
* 라이다 데이터 800ms 이상 미수신 시 `connected=false`로 판단, HUD에 `LIDAR:WAITING` 표시.
* X 버튼으로 라이다 회피 기능 자체를 런타임에 토글 가능.
## 4.8 휠 이상(들뜸/걸림) 진단
속도 폐루프 특성상(부하 무관하게 지령 RPM을 추종하려 함) "지령 대비 실제속도" 하나만으로는
무부하(들뜸)를 판정할 수 없다. 두 개의 독립된 판정 축을 사용한다:
### 걸림/과부하 판정 (`classifyStall`)
```cpp
vel_ratio = |실제RPM| / |지령RPM|
stall = (vel_ratio < stall_vel_ratio) && (|전류| >= stall_current_a)
```
지령속도가 `min_active_rpm`(기본 3 RPM) 미만이면 판정 보류. 기본값: `stall_vel_ratio=0.5`,
`stall_current_a=12.0A`(정격 15A 근접).
### 무부하/들뜸 판정 (`classifyAirPair`)
같은 지령속도를 받는 **같은쪽 앞뒤 페어**(FL↔RL, FR↔RR)의 전류를 비교한다. 좌/우를 통째로
비교하면 정상 커브 선회에서 좌우 지령속도가 원래 다르기 때문에 오탐이 발생하므로, 반드시
`target_fl==target_rl`, `target_fr==target_rr`가 항상 성립하는 앞뒤 페어끼리만 비교한다.
```cpp
hi = max(|amp_a|, |amp_b|)
if (hi < 0.5A) return; // 둘 다 무전류(관성 주행)면 판정 보류
if (a < hi * airborne_current_ratio) air_a = true; // 기본 0.35
if (b < hi * airborne_current_ratio) air_b = true;
```
**디바운스**: 노이즈성 순간 전류 편차로 즉시 개입하지 않도록, `airborne_debounce_ticks`
(기본 5틱=50ms) 연속으로 조건이 유지될 때만 실제 `airborne_*` 상태가 확정되고 해당 바퀴
`target`이 0으로 강제된다. 접지력이 회복돼 전류가 정상화되면 다음 판정 틱에서 자동 해제되고,
§4.6의 착지 스냅 로직으로 즉시 원래 지령을 재개한다.
HUD의 `WHL(FL/FR/RL/RR)` 4글자는 각 바퀴 상태를 `O`(정상)/`A`(들뜸,Airborne)/`S`(걸림,Stall)로 표시.
## 4.9 드라이버 알람 감지
`readFeedback()`로 받은 `err_f_l/err_f_r/err_r_l/err_r_r` 중 하나라도 0이 아니면 알람 발생으로
간주, **상태 전이 시(0→알람) 1회만** 콘솔에 `decodeDriverError()` 결과와 함께 큰 경고를 출력한다
(매 틱 갱신되는 HUD 줄과 별개 라인). 알람 발생 시 드라이버가 명령을 무시하는 잠금 상태가 되어
소프트웨어 재시작만으로는 해제되지 않는 경우가 많음을 코드 주석이 명시 — 하드웨어 알람 클리어
또는 전원 재투입이 필요할 수 있다. 상세 대응은
[05-error-troubleshooting.md](05-error-troubleshooting.md) 참고.
## 4.10 CSV 로깅 (`--log <경로>`)
매 틱 아래 컬럼을 기록한다:
```
t_ms,state,ly,lx,rx,
cmd_fl,cmd_fr,cmd_rl,cmd_rr,
fb_fl,fb_fr,fb_rl,fb_rr,
amp_fl,amp_fr,amp_rl,amp_rr,
stat_fl,stat_fr,stat_rl,stat_rr,
err_fl,err_fr,err_rl,err_rr,
dist_axle,comm_ms
```
실외에서 로봇을 조종하며 화면을 동시에 읽기 어려우므로, 문제가 된 구간(턱 넘는 지점 등)을
나중에 잘라서 분석하기 위한 용도.
## 4.11 CLI 인자 전체 목록
| 인자 | 기본값 | 설명 |
| :--- | :-- | :--- |
| `--port`, `--port1` | `/dev/ttyUSB0` | 전방 드라이버(및 기본 후방) 시리얼 포트 |
| `--port2` | (없음, port1 재사용) | 후방 드라이버 전용 포트 지정 시 |
| `--id1` | 1 | 전방 드라이버 Modbus 슬레이브 주소 |
| `--id2` | 2 | 후방 드라이버 Modbus 슬레이브 주소 |
| `--bcast` / `--broadcast` | off | 두 드라이버 모두 주소 0(브로드캐스트)으로 동시 제어 |
| `--track_width` | 0.576 m | 좌우 바퀴 중심거리 |
| `--wheelbase` | 0.3684 m | 전후 바퀴 중심거리 |
| `--radius` | 0.131517 m | 휠 유효 반경 |
| `--bumper` | 0.045 m | 휠 축→전면 범퍼 거리 |
| `--accel` | 120 RPM/s | 최대 가속도 |
| `--decel` | 180 RPM/s | 최대 감속도 |
| `--jerk` | 600 RPM/s² | 직진/커브 저크 한계 |
| `--spin_jerk` | 220 RPM/s² | 제자리 회전 저크 한계 |
| `--spin_arc_bias` | 0.12 | 제자리 회전 시 혼합할 미세 전진 비율 (0~1) |
| `--min_active_rpm` | 3.0 RPM | 이상판정 보류 임계 지령속도 |
| `--airborne_current_ratio` | 0.35 | 무부하(들뜸) 판정 전류비 |
| `--stall_vel_ratio` | 0.5 | 걸림 판정 속도비 |
| `--stall_current_a` | 12.0 A | 걸림 판정 전류 임계값 |
| `--airborne_debounce_ticks` | 5 (50ms) | 들뜸 판정 디바운스 |
| `--log <path>` | (비활성) | CSV 로그 파일 경로 |
| `--max_current_a` | 20.0 A | 4바퀴 최대전류 일괄 설정 (음수=미변경) |
| `--no_lidar` | (라이다 사용) | 라이다 비활성화 |
| `--lidar_config` | `mid360s_config.json` | Livox SDK 설정 파일 |
| `--front_stop` | 0.35 m | 전방 정지거리 |
| `--front_warn` | 0.50 m | 전방 경고(감속)거리 |
| `--side_dodge` | 0.50 m | 측면 회피 감지거리 |
| `--robot_width` | 0.410 m | 라이다 회피 폭 계산용 로봇 전폭 |
| `--lidar_height` | 0.460 m | 라이다 설치 높이 |
| `--lidar_pitch` | 17.0 deg | 라이다 하향 피치각 |
| `--lidar_x_offset` | 0.250 m | 라이다 전방 오프셋 |
| `--min_z` / `--max_z` | -0.40 / 0.80 m | 라이다 필터링 Z축 범위 |