Files
fori_zltech_motor_test/doc/02-communication-protocol.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

275 lines
17 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.
# 02. RS485 통신 프로토콜 & 레지스터 맵
출처: `resource/ZLAC8015D V4 Series RS485 Communication Version 1.06-20251111.pdf`,
`xbox_motor_control.cpp`
## 2.1 시리얼 포트 설정
| 항목 | 값 |
| :--- | :--- |
| 프로토콜 | Modbus RTU |
| 보드레이트 | 115200 bps (기본값, 코드에서 사용) — 9600/19200/38400/57600/128000 선택 가능 |
| 데이터 비트 | 8 |
| 패리티 | None |
| 스톱 비트 | 1 |
| 드라이버 주소 범위 | 1 ~ 127 (본 프로젝트: 드라이버#1=1, 드라이버#2=2) |
`xbox_motor_control.cpp``SerialPort::openPort()`가 termios로 위 설정(8N1, RAW 모드,
VTIME=1 → 0.1s read timeout)을 그대로 구성한다.
## 2.2 Modbus 프레임 포맷
```
[슬레이브 주소 1B][기능코드 1B][데이터 N B][CRC16 2B (Low,High 순)]
```
CRC는 표준 Modbus CRC16(다항식 0xA001, 초기값 0xFFFF) — `calcCRC16()`이 이를 그대로 구현.
### 지원 기능 코드
| 기능 | 코드 | 에러 응답 코드 |
| :--- | :-: | :-: |
| 다중 레지스터 읽기 | `0x03` | `0x83` |
| 단일 레지스터 쓰기 | `0x06` | `0x86` |
| 다중 레지스터 쓰기 | `0x10` | `0x90` |
| 에러 코드 | 이름 | 의미 |
| :-: | :--- | :--- |
| `0x01` | Illegal function code | 지원하지 않는 기능 코드 |
| `0x02` | Illegal data address | 잘못된 레지스터 주소 |
| `0x03` | Illegal data value | 잘못된 데이터 값 |
### 코드 구현 매핑
| 클래스 함수 | 기능 코드 | 용도 |
| :--- | :-: | :--- |
| `SerialPort::readRegs()` | 0x03 | 피드백/상태/전류한계 조회 |
| `SerialPort::writeReg()` | 0x06 | 제어워드/모드/브레이크 등 단일 레지스터 쓰기 |
| `SerialPort::writeRegs()` | 0x10 | 가감속시간, 목표속도(L/R 동시) 쓰기 |
응답 검증은 CRC16 재계산 + 슬레이브 주소 일치 여부로 이루어지며(`writeReg`/`writeRegs`/`readRegs`
모두 동일), 브로드캐스트(`slave==0`)일 때는 응답을 기다리지 않는다(`--bcast` 옵션).
읽기 타임아웃은 25ms(100Hz=10ms 주기 루프 안에서 여유를 둔 값), 쓰기 응답 타임아웃은 50ms로
설정되어 있다.
## 2.3 코드가 실제로 사용하는 레지스터 (초기화/제어/피드백 경로)
| 주소 | 이름 | 방향 | 코드 사용처 |
| :-- | :--- | :-: | :--- |
| `0x200D` | Control mode | W | `initDriver()`: `3` = 속도 제어 모드 고정 |
| `0x200E` | Control word | W | `initDriver()`: `0x0006` 알람 클리어 → `0x0007` Disable → (가감속 설정 후) `0x0008` Enable |
| `0x2080` / `0x2081` | 가속시간 좌/우 | W | `initDriver(acl_ms)``writeRegs(0x2080,{acl,acl})`로 2레지스터 동시 기록 (기본 150ms) |
| `0x2082` / `0x2083` | 감속시간 좌/우 | W | `initDriver(dcl_ms)``writeRegs(0x2082,{dcl,dcl})`로 동시 기록 (기본 150ms) |
| `0x2088` / `0x2089` | 목표속도 좌/우 (RPM, I16, ±3000) | W | `setRPMs()`: 매 틱 `writeRegs(0x2088,{l,r})`로 좌/우 동시 지령 |
| `0x201A` | 출력단자 B0 (좌 브레이크) | W | `setBrakes()`: `0`=해제, `1`=잠금 |
| `0x201B` | 출력단자 B1 (우 브레이크) | W | `setBrakes()`: 좌와 동일 값 동시 기록 |
| `0x2033` / `0x2034` | 좌 정격전류 / 좌 최대전류 (0.1A) | R/W | `readCurrentLimits()`로 조회, `setMaxCurrent()``0x2034`만 기록 |
| `0x2063` / `0x2064` | 우 정격전류 / 우 최대전류 (0.1A) | R/W | 좌와 동일 패턴 (우 채널) |
| `0x20A5` ~ `0x20AE` | 에러코드~토크 (아래 표 참고) | R | `readFeedback()`**10레지스터 연속 읽기**(0x03, count=10)로 한 번에 확보 |
### `readFeedback()`의 10레지스터 연속 읽기 상세 (`0x20A5` 시작, count=10)
| offset | 주소 | 이름 | 코드 내 변수 | 스케일 |
| :-: | :-- | :--- | :--- | :--- |
| 0 | `0x20A5` | 에러코드(좌) | `err_l` | 비트마스크 (그대로) |
| 1 | `0x20A6` | 에러코드(우) | `err_r` | 비트마스크 (그대로) |
| 2 | `0x20A7` | 실제위치 상위16비트(좌) | `val_l` 상위 | `int32_t` 합성 → `l_tick` |
| 3 | `0x20A8` | 실제위치 하위16비트(좌) | `val_l` 하위 | 〃 |
| 4 | `0x20A9` | 실제위치 상위16비트(우) | `val_r` 상위 | `int32_t` 합성 → `r_tick` |
| 5 | `0x20AA` | 실제위치 하위16비트(우) | `val_r` 하위 | 〃 |
| 6 | `0x20AB` | 실제속도(좌), 단위 0.1r/min | `vl` | `× 0.1f``l_fb` (RPM) |
| 7 | `0x20AC` | 실제속도(우), 단위 0.1r/min | `vr` | `× 0.1f``r_fb` (RPM) |
| 8 | `0x20AD` | 실제토크/전류(좌), 단위 0.1A | `tl` | `× 0.1f``l_torque_a` (A) |
| 9 | `0x20AE` | 실제토크/전류(우), 단위 0.1A | `tr` | `× 0.1f``r_torque_a` (A) |
이 한 번의 통신으로 에러코드·엔코더 위치(주행거리 계산용)·속도 피드백·전류(부하 진단용)를
모두 얻기 때문에, 100Hz 루프에서 드라이버 1대당 통신 1회(읽기)+1회(쓰기)만 필요하다.
2대 드라이버 × 2회 = 틱당 최대 4회 왕복, `comm_ms`로 HUD에 실측 소요시간을 표시한다.
> 위치(`0x20A7`~`0x20AA`)는 엔코더 라인수 1024 기준 4체배(quadrature)로 회전당
> `1024 × 4 = 4096`이 아니라, README 기준 실측 `16,384 ticks/rev`를 사용한다
> (ZLLG80ASM250-**4096**-B 모터명의 4096은 엔코더 라인수이며, 4체배 카운팅 시
> `4096 × 4 = 16384`가 된다. `0x2030`/`0x2060` "Encoder line" 파라미터 공장 기본값 1024와는
> 별개로, 실제 장착된 ZLLG80ASM250-4096-B 모터는 라인수 4096 사양이므로 드라이버의
> Encoder line 파라미터가 모터 사양(4096)에 맞춰 설정되어 있어야 `meters_per_tick` 계산이
> 정확하다. 드라이버 실측정격전류가 15A인 것도 이 모터의 정격과 일치.)
## 2.4 에러코드 (`0x20A5` / `0x20A6`)
| 값 | 의미 | 코드 내 한글 표기 |
| :-- | :--- | :--- |
| `0x0000` | 정상 (No error) | (없음) |
| `0x0001` | 과전압 (Over voltage) | 과전압 |
| `0x0002` | 저전압 (Under voltage) | 저전압 |
| `0x0004` | 과전류 (Over current) | 과전류 |
| `0x0008` | 과부하 (Over load) | 과부하 |
| `0x0010` | 전류 이상 (예약) | 전류이상(예약) |
| `0x0020` | 엔코더 오차 (Encoder out of tolerance) | 엔코더오차 |
| `0x0040` | 속도 이상 (예약) | 속도이상(예약) |
| `0x0080` | 기준전압 오류 (Reference voltage error) | 기준전압오류 |
| `0x0100` | EEPROM 오류 | EEPROM오류 |
| `0x0200` | 홀센서 오류 | 홀센서오류 |
| `0x0400` | 모터 과열 | 모터과열 |
| `0x0800` | 엔코더 오류 | 엔코더오류 |
| `0x2000` | 속도설정 오류 (지령속도가 정격속도 초과) | 속도설정오류 |
여러 비트가 동시에 서면 코드가 `"+"`로 이어붙여 표시한다 (`decodeDriverError()`).
드라이버 알람 상세 진단과 대응은 [05-error-troubleshooting.md](05-error-troubleshooting.md) 참고.
## 2.5 제어워드(`0x200E`)와 초기화 시퀀스
| 값 | 의미 |
| :-- | :--- |
| `0x05` | 비상 정지 (Emergency stop) |
| `0x06` | 알람 클리어 (Clear fault) |
| `0x07` | 정지 (Stop / Disable) |
| `0x08` | 활성화 (Enable) |
| `0x10` | 동기 시작 (Start Synchronous, 포지션 모드 전용) |
| `0x11` | 좌측 단독 시작 (Start Left, 포지션 모드) |
| `0x12` | 우측 단독 시작 (Start Right, 포지션 모드) |
`MotorDriver::initDriver()`의 실제 시퀀스 (각 단계 사이 30ms 대기):
1. `0x200E``0x0006` (알람 클리어)
2. `0x200E``0x0007` (Disable)
3. `0x200D``3` (속도 제어 모드)
4. `0x2080`/`0x2081` ← 가속시간 (좌우 동시, 기본 150ms)
5. `0x2082`/`0x2083` ← 감속시간 (좌우 동시, 기본 150ms)
6. `0x200E``0x0008` (Enable)
7. `setBrakes(true)` — 초기 상태는 브레이크 잠금
이 프로젝트는 속도 제어 모드(Profile Velocity Mode)만 사용하며, 포지션/토크 모드 관련 레지스터
(`0x208A`~`0x208F`, `0x2090`/`0x2091` 등)는 사용하지 않는다. 참고용으로 §2.7에 전체 표를 남긴다.
## 2.6 전류 한계 레지스터
| 주소 | 이름 | 단위 | 기본값 | 범위 |
| :-- | :--- | :-: | :-: | :--- |
| `0x2033` | 좌 정격전류 | 0.1A | 150 (15.0A) | 0-150 |
| `0x2034` | 좌 최대전류 | 0.1A | 300 (30.0A) | 0-300 |
| `0x2063` | 우 정격전류 | 0.1A | 150 (15.0A) | 0-150 |
| `0x2064` | 우 최대전류 | 0.1A | 300 (30.0A) | 0-300 |
`readCurrentLimits()`가 시작 시 1회 조회해 콘솔에 출력하고, `--max_current_a` 인자
(기본 20.0A)로 최대전류를 4바퀴 동일하게 낮춘다. 정격전류(150=15A)는 쓰지 않고 최대전류만
변경한다 — 근거와 튜닝 배경은 [05-error-troubleshooting.md](05-error-troubleshooting.md) 참고.
## 2.7 전체 레지스터 주소록 (Address Directory, 매뉴얼 §4 원문 기준)
### 공통 상수 (Left/Right 공통)
| 주소 | 이름 | 접근 | 기본값 | 설명 |
| :-- | :--- | :-: | :-: | :--- |
| `2000h` | Communication offline time | RW/S | 0 | ms, 0-32000. 시간 내 통신 없으면 정지 |
| `2001h` | RS485 Node ID | RW/S | 1 | 1-127 |
| `2002h` | RS485 Baud Rate | RW/S | 2 | 1:128000 2:115200 3:57600 4:38400 5:19200 6:9600 |
| `2003h` | Input signal status | RO | 0 | bit0-1: X0-X1 입력 레벨 |
| `2004h` | Output signal status | RO | 0 | bit0-1: Y0-Y1 출력 레벨 |
| `2005h` | Clear feedback position | RW | 0 | 0 무효, 1 좌, 2 우, 3 좌우 (미저장) |
| `2006h` | Reset absolute zero point | RW | 0 | 0 무효, 1 좌, 2 우, 3 좌우 (미저장) |
| `2007h` | Shaft state after power on | RW/S | 0 | 0 Enable 안 됨/락 안 됨, 1 Enable 안 됨/락 |
| `2008h` | Maximum motor speed | RW/S | 1000 | r/min, 1-1000 |
| `2009h` | Register parameter settings | RW | 0 | 0 무효, 1 공장 초기화 |
| `200Ah` | CAN Node ID | RW/S | 1 | 1-127 |
| `200Bh` | CAN Baud rate | RW/S | 1 | 0:1000 1:500 2:250 3:125 4:100 Kbit/s |
| `200Ch` | Parking mode | RW/S | 0 | 0 Close, 1 Open |
| `200Dh` | Control mode | RW/S | 0 | 0 undefined, 1 위치(상대), 2 위치(절대), 3 속도, 4 토크 |
| `200Eh` | Control word | RW | 0 | §2.5 참고 |
| `200Fh` | Sync/Async control status | RW/S | 0 | 0 동기, 1 비동기 |
| `2010h` | RW 레지스터 EEPROM 저장 여부 | RW | 0 | 0 무효, 1 저장 |
| `2011h` | Quick stop control | RW/S | 5 | 5 정지, 6 감속시간 적용 급정지, 7 감속시간 미적용 급정지 |
| `2012h` | Close operation control | RW/S | 1 | 0 무효, 1 정상정지 |
| `2013h` | Disable control | RW/S | 1 | 0 무효, 1 정지(switch on 상태로) |
| `2014h` | Halt control | RW/S | 1 | 1 정지, 2 감속 급정지, 3 무감속 급정지 |
| `2016h` | Input effective level | RW/S | 0 | bit0 X0, bit1 X1 (0 기본 High, 1 반전 Low) |
| `2017h` | Input X0 function | RW/S | 9 | 0 none, 1-8 NC, 9 비상정지 |
| `2018h` | Input X1 function | RW/S | 0 | 〃 |
| `2019h` | Output effective level | RW/S | 0 | bit0 Y0, bit1 Y1, bit2 B0, bit3 B1 |
| `201Ah` | **출력 B0 (좌 브레이크)** | RW/S | 0 | 0 Open, 1 Close — **코드 사용** |
| `201Bh` | **출력 B1 (우 브레이크)** | RW/S | 0 | 0 Open, 1 Close — **코드 사용** |
| `201Ch` | Output Y0 function | RW/S | 0 | 0 undefined, 1 알람신호, 2 상태신호, 3 목표위치도달(예약) |
| `201Dh` | Output Y1 function | RW/S | 0 | 〃 |
| `201Eh` | 드라이버 온도 보호 임계값 | RW/S | 800 | 0.1℃, 0-1200 |
| `201Fh` | Alarm PWM 처리 방식 | RW/S | 0 | 0 close, 1 open |
| `2020h` | Overload 처리 방식 | RW/S | 0 | 0 close, 1 open |
| `2021h` | I/O 비상정지 처리 모드 | RW/S | 0 | 0 축고정(우선I), 1 댐핑(우선II), 2 축해제(우선III); low8=INPUT1 high8=INPUT2 |
| `2022h` | Given speed resolution | RW/S | 1 | 1:1RPM ... A:0.1RPM |
| `2023h` | Velocity overshoot | RW/S | 1 | 0 close, 1 open |
| `2024h` | Regen resistance value | RW/S | 50 | 0.1Ω, 0-1000 |
| `2025h` | Regen resistance power | RW/S | 100 | W, 0-1000 |
| `2026h` | Regen opening voltage | RW/S | 650 | 0.1V, 360-750 |
| `2027h` | Regen close voltage | RW/S | 600 | 0.1V, 310-700 |
| `2028h` | Regen function control | RW/S | 1 | 0 close, 1 open |
| `2029h` | Default running direction | RW/S | 0 | 0 CW, 1 CCW |
### 좌모터 파라미터 (`2030h`~`204Ah`) / 우모터 파라미터 (`2060h`~`207Ah`, 좌+0x30 오프셋)
| 좌 주소 | 우 주소 | 이름 | 접근 | 기본값 | 설명 |
| :-- | :-- | :--- | :-: | :-: | :--- |
| `2030h` | `2060h` | Encoder line | RW/S | 1024 | 0-4096 |
| `2031h` | `2061h` | Hall offset angle | RW/S | 0 | 1°, -360~360 |
| `2032h` | `2062h` | Overload factor | RW/S | 200 | %, 0-300 |
| `2033h` | `2063h` | **정격전류** | RW/S | 150 | 0.1A, 0-150 — **코드 사용** |
| `2034h` | `2064h` | **최대전류** | RW/S | 300 | 0.1A, 0-300 — **코드 사용** |
| `2035h` | `2065h` | Overload protection time | RW/S | 300 | 10ms, 0-6553 |
| `2036h` | `2066h` | Position following error threshold | RW/S | 409 | 10counts, 1-6553 |
| `2037h` | `2067h` | Velocity smoothing factor | RW/S | 1000 | 0-30000 |
| `2038h` | `2068h` | Current Loop Kp | RW/S | 600 | 0-30000 |
| `2039h` | `2069h` | Current Loop Ki | RW/S | 300 | 0-30000 |
| `203Ah` | `206Ah` | Feedforward output smoothing | RW/S | 100 | 0-30000 |
| `203Bh` | `206Bh` | Torque output smoothing | RW/S | 100 | 0-30000 |
| `203Ch` | `206Ch` | Velocity Loop Kp | RW/S | 500 | 0-30000 |
| `203Dh` | `206Dh` | Velocity Loop Ki | RW/S | 100 | 0-30000 |
| `203Eh` | `206Eh` | Velocity Loop Kf | RW/S | 500 | 0-30000 |
| `203Fh` | `206Fh` | Position Loop Kp | RW/S | 100 | 0-30000 |
| `2040h` | `2070h` | Position Loop Kf | RW/S | 50 | 0-30000 |
| `2043h` | `2073h` | Initial velocity (속도모드) | RW/S | 1 | r/min, 1-250 |
| `2044h` | `2074h` | Initial velocity (위치모드) | RW/S | 1 | r/min, 1-250 |
| `2045h` | `2075h` | Motor poles | RW/S | 15 | 4-64 |
| `2046h` | `2076h` | Over temperature threshold | RW/S | 800 | 0.1℃, 0-1200 |
| `2047h`-`204Ah` | `2077h`-`207Ah` | Velocity observer coefficients 1-4 | RW/S | 1000/750/350/1000 | 0-30000 |
### 제어 파라미터 (`2080h`~`2091h`)
| 주소 | 이름 | 접근 | 기본값 | 설명 |
| :-- | :--- | :-: | :-: | :--- |
| `2080h` | **S자 가속시간(좌)** | RW/S | 500ms | 0-32767ms — **코드 사용 (150ms)** |
| `2081h` | **S자 가속시간(우)** | RW/S | 500ms | 0-32767ms — **코드 사용 (150ms)** |
| `2082h` | **S자 감속시간(좌)** | RW/S | 500ms | 0-32767ms — **코드 사용 (150ms)** |
| `2083h` | **S자 감속시간(우)** | RW/S | 500ms | 0-32767ms — **코드 사용 (150ms)** |
| `2084h` | 급정지 감속시간(좌) | RW/S | 10ms | 0-32767ms |
| `2085h` | 급정지 감속시간(우) | RW/S | 10ms | 0-32767ms |
| `2086h` | 토크 슬로프(좌) | RW/S | 300ms | mA/s(1/1000초 단위) |
| `2087h` | 토크 슬로프(우) | RW/S | 300ms | 〃 |
| `2088h` | **목표속도(좌)** | RW | 0 | I16, r/min, ±3000 — **코드 사용, 매 틱 기록** |
| `2089h` | **목표속도(우)** | RW | 0 | I16, r/min, ±3000 — **코드 사용, 매 틱 기록** |
| `208Ah`/`208Bh` | 목표위치 상/하위(좌) | RW | 0 | 포지션 모드 전용 (미사용) |
| `208Ch`/`208Dh` | 목표위치 상/하위(우) | RW | 0 | 포지션 모드 전용 (미사용) |
| `208Eh`/`208Fh` | 최대속도(좌/우, 포지션모드) | RW/S | 120 r/min | 포지션 모드 전용 (미사용) |
| `2090h`/`2091h` | 목표토크(좌/우) | RW | 0 | mA, ±30000 — 토크 모드 전용 (미사용) |
### 읽기 전용 상태 (`20A0h`~`20B0h`)
| 주소 | 이름 | 단위/범위 | 코드 사용 여부 |
| :-- | :--- | :--- | :--- |
| `20A0h` | Software version | - | 미사용 |
| `20A1h` | Bus voltage | 0.01V | 미사용 (배터리 전압 모니터링에 활용 가능) |
| `20A2h` | Status word | L:bit7-6, R:bit15-14 → 00 해제/40 축고정/80 비상정지/C0 알람; bit0(L)/bit8(R) 0정지 1구동 | 미사용 |
| `20A3h` | Hall input state | 0-7 (0,7이면 홀 오류), high8=좌 low8=우 | 미사용 |
| `20A4h` | Motor temperature | 1℃, -55~120, high8=좌 low8=우 | 미사용 (과열 사전감지에 활용 가능) |
| `20A5h` | **에러코드(좌)** | 비트마스크 (§2.4) | **코드 사용** |
| `20A6h` | **에러코드(우)** | 비트마스크 (§2.4) | **코드 사용** |
| `20A7h`/`20A8h` | **실제위치 상/하위(좌)** | counts, I16×2 | **코드 사용**`l_tick` |
| `20A9h`/`20AAh` | **실제위치 상/하위(우)** | counts, I16×2 | **코드 사용**`r_tick` |
| `20ABh` | **실제속도(좌)** | 0.1r/min | **코드 사용**`l_fb` |
| `20ACh` | **실제속도(우)** | 0.1r/min | **코드 사용**`r_fb` |
| `20ADh` | **실제토크/전류(좌)** | 0.1A, ±300 | **코드 사용**`l_torque_a` |
| `20AEh` | **실제토크/전류(우)** | 0.1A, ±300 | **코드 사용**`r_torque_a` |
| `20AFh` | Software connected status | `01` | 미사용 |
| `20B0h` | Driver temperature | 0.1℃, -550~1200 | 미사용 (과열 사전감지에 활용 가능) |
> **미사용이지만 유용한 레지스터**: `20A1h`(버스 전압), `20A4h`/`20B0h`(모터/드라이버 온도)는
> 현재 코드가 읽지 않는다. 배터리 저전압 경보나 열 관리 기능을 추가하려면 `readFeedback()`의
> 연속 읽기 범위를 확장하거나 별도 저빈도 폴링을 추가하는 것을 검토할 수 있다.