로봇 제어를 이야기할 때 사람들은 제어 법칙을 말합니다. PID, ADRC, 역동역학, 학습된 정책. 그런데 현장에서 먼저 무너지는 것은 대개 그보다 앞에 있는, 훨씬 심심한 것입니다. 명령이 제때 나가지 않았다는 것.
일본의 한 로봇 상사가 UFACTORY xArm으로 만든 휴머노이드 텔레오퍼레이션 장치를 들고 찾아왔습니다. 조작자의 손을 따라가려고 촘촘한 목표점을 팔에 밀어 넣으면 팔이 더듬거리고, 떨리고, 튑니다. 박스의 다른 인터페이스도 써 봤고, 역동역학 피드포워드도 설계해 봤지만 흔들리는 케이블 때문에 모델이 맞지 않았습니다. 저희는 몇 주 전에 샌프란시스코에서 VLA 모델을 연구하는 한 프론티어 랩의 현장에서 같은 팔로 같은 문제를 이미 풀었습니다. 벤더 박스의 설정은 하나도 바꾸지 않았습니다. 바꾼 것은 시간이었습니다.
"박스가 위치 루프를 다 닫아 주는데, 왜 빨리 밀어 넣으면 팔이 떨릴까?"
짧은 답: 위치 스트리밍에서는 송신 시각이 곧 궤적입니다. 늦게 나간 명령은 점프가 되고, 일정한 지연은 속도에 비례하는 추종 오차가 됩니다. 하드 실시간 호스트는 모든 송신을 5 µs 안에 지켜 첫 번째를 없애고, 그 정밀도가 두 번째를 고칠 수 있게 합니다. 박스의 지연을 6.5 ms로 측정해 모든 점을 그만큼 일찍 보냈습니다. 벤더 박스 단독과 비교하면 지각은 1.57초에서 0으로, 같은 순간의 툴 끝 오차는 8자 경로에서 165 mm에서 0.3 mm로(3차원 위빙에서는 283 mm에서 0.64 mm로) 줄었습니다. 그리퍼에 1.57 kg 가방을 매달아 흔들리는 채로, 30회 모두 박스 오류 0건이었습니다. 경로는 약 140배 촘촘해졌고, 어느 순간의 오차든 약 500배 작아졌습니다. 팔과 펌웨어와 제어기는 출고 상태 그대로였고, 이 팔을 위해 쓴 제어 코드는 한 줄도 없습니다. 짐이 흔들리는 채로 1,000 mm/s까지 밀어도 툴 끝은 1 mm RMS 안에 있었습니다. 이 스택에는 이 팔에만 해당하는 것이 하나도 없습니다. TCP로 위치를 받는 어떤 로봇 앞에도 그대로 놓을 수 있고, EtherCAT 서보를 1 kHz, 지터 1.2 µs로 돌리는 바로 그 계층입니다.
이 글의 대부분은 실시간 계층 자체에 관한 것입니다. 왜 시간이 움직임이 되는지, 무엇을 설정하는지, 어떻게 검증하는지, 얻은 결정성을 어디에 쓰는지. 현장 사례는 그 증거입니다. 모든 그림은 현장 원본 로그에서 그렸습니다.
왜 시간이 움직임이 되는가
시간 문제가 움직임 문제로 바뀌는 길은 세 가지입니다. 세 가지 모두 이 팔에서 보였습니다.
1. 늦은 주기는 점프다
벤더 박스에 위치를 스트리밍하면 박스는 4 ms마다 "어디로"를 받을 뿐 "어떻게"는 결정하지 않습니다. 보간기가 없습니다. 최대 180°/s로 가장 새 점을 쫓아갈 뿐입니다. 그래서 목표점의 순서와 도착 순간이 곧 속도 프로파일입니다. 송신이 멈추면 팔이 멈추고, 송신이 다시 시작되면 다음 목표가 멀리 있어 팔이 그쪽으로 덜컥 움직입니다. 실시간 보장이 없는 주기적 위치 명령이 로봇을 튀게 만드는 이유입니다.

파랑: 격리 코어에서 실제로 125 °/s로 보낸 J4 명령열. 빨강: 같은 명령열에 28 ms 정지 한 번을 넣은 것(27,808 µs는 그날 격리하지 않은 실행에서 한 번 측정된 최악의 깨어남 지연). 정지 동안 박스는 0 °/s를 요구받고, 직후 한 주기에 999 °/s를 요구받습니다. 180 °/s 추종 한계의 5.5배입니다. 팔은 멈췄다가 덜컥 움직입니다. 그것이 떨림입니다.
2. 일정한 지연은 속도에 비례하는 오차다
송신이 완벽해도 팔은 늦게 도착합니다. 순수 지연 L인 장치는 L × 속도만큼의 추종 오차를 남깁니다. 아래에서 측정한 L = 6.5 ms라면 125 °/s로 도는 관절은 늘 명령보다 0.81° 뒤에 있습니다. 이 오차는 없앨 수 있지만, 정확히 L만큼 일찍 보낼 때만 그렇고, "정확히"는 흔들리지 않는 송신 시계를 요구합니다.
3. 낡은 피드백으로는 루프를 닫을 수 없다
박스는 자기 일정대로 약 10 ms마다(99.6 Hz) 상태를 보고합니다. 루프가 보는 측정값은 평균 5 ms, 최악 11 ms 전 것입니다(피드백 나이 p50 4,987 µs, p99 10,399 µs). 그만큼 낡은 정보에 피드백 이득을 올리면 과거를 교정하는 셈이라 팔이 흔들립니다. 모델 기반 제어기(역동역학, 페이로드 모델)가 시간을 못 지키는 호스트를 구하지 못하는 이유도 같습니다. 그 항들 하나하나가 제때 도착해야 의미가 있습니다.
제어 법칙을 튜닝하기 전에 시간을 결정적으로 만드세요. 그다음 남은 지연을 측정하고, 그 결정성을 피드백이 아니라 피드포워드에 쓰세요.
실시간 스택, 계층별로
Jetson AGX Orin에서 저희가 돌리는 설정입니다. "현장"은 이 글의 팔에서 설정하고 측정한 것, "블로그"는 그 계층만 따로 측정한 이전 글입니다.
| 계층 | 설정 | 이유 | 측정 효과 |
|---|---|---|---|
| 커널 | PREEMPT_RT(현장 6.8.12-rt-tegra, 이전 글 5.15.148-rt-tegra), /sys/kernel/realtime = 1 | 커널 코드 대부분을 선점 가능하게 해 고우선 스레드가 제때 깨어남 | 복합 부하: 비RT 318 µs → RT 56 µs 최대(블로그) |
| CPU 격리 | isolcpus=managed_irq,domain,11 irqaffinity=0-10 kthread_cpus=0-10 | 스케줄러, 관리 IRQ, 커널 스레드를 제어 코어에서 떼어 놓음 | 격리 CPU11 최대 5 µs vs CPU5 13 µs(현장). GPU 부하 159 → 15 µs, EtherCAT 113 → 6 µs, 스토리지 145 → 7 µs(블로그) |
| 틱과 RCU | nohz_full=8-11 rcu_nocbs=8-11 rcu_nocb_poll(커스텀 커널 필요, OTA 커널은 무시) | 격리 코어에서 스케줄러 틱과 RCU 콜백 제거 | 스트레스 중 56 → 24 µs(블로그) |
| 스케줄링 | 제어 스레드를 격리 코어에 고정, SCHED_FIFO 80; rtprio 90, memlock unlimited; sched_rt_runtime_us=-1 | 기본 IRQ 스레드(50)보다 위, RT 스로틀링 없음 | 생산 주기 40,953회에서 깨어남 지연 p99 0 µs(현장) |
| 메모리 | affinity·FIFO 전에 mlockall(), 스택 프리폴트, RT 경로에서 할당 없음 | 루프 안 페이지 폴트 없음 | 현장에서 메모리 잠금 한도 해제, 측정은 블로그 |
| 전원·클럭 | nvpmodel MAXN, jetson_clocks, CPU governor performance(2.2016 GHz), 제어 코어 cpuidle 끔 | 주파수·유휴 상태 탈출 지연 없음 | 5 µs 기준선의 일부(현장) |
| 시간 기준 | 모든 곳에서 CLOCK_MONOTONIC, 디바이스 트리에 nvpps ptp_tsc_sync_dis, NTP 정리 | Jetson 기본값은 호스트 카운터를 이더넷 PHC에 잠가 약 110초마다 ±140 ppm씩 속도를 바꿈 | EtherCAT DC 편차 328 → 3.5 µs(블로그) |
| IPC | RT와 비RT 사이 lock-free SPSC 큐, 고정 크기 메시지, release/acquire 순서, 64바이트 정렬 인덱스 | 뮤텍스는 잡은 쪽에 우선순위를 넘김 | 뮤텍스 78 |
| 모니터링 | RT 스레드는 큐에 샘플만 넣고 비RT 스레드가 ROS 2로 발행 | 마감을 건드리지 않는 관측 | push 평균 0.3 µs, p99 0.7 µs(블로그) |
| 네트워크 | 장치 전용 유선 NIC, 고정 IP, 게이트웨이 없음, 같은 박스에 두 번째 클라이언트 없음 | 공유 링크와 경쟁 클라이언트는 스케줄할 수 없는 큐잉을 만듦 | 왕복 평균 721 µs, p99 841 µs(현장) |
어떻게 검증하는가
설정은 그 기계 위에서, 실제로 받을 부하 아래에서 측정하기 전까지는 가설입니다. 네 가지를 이 순서로 봅니다.
기준선: cyclictest, 왕복, 보고 주기

(a) cyclictest, 1 kHz 60초: 격리 CPU11 최대 5 µs, 비격리 CPU5 최대 13 µs. (b) 박스 왕복, 250 Hz로 2,000회: 평균 721 µs, 최대 916 µs. (c) 박스의 상태 보고: 10.04 ms마다(99.6 Hz), 약 9.67과 10.75 ms로 갈림. 저희는 4 ms마다 명령합니다.
그날 첫 cyclictest는 다른 두 측정과 동시에 돌았고 27,808 µs 깨어남 한 번을 맞았습니다. 250 Hz 주기 일곱 개가 한꺼번에 사라진 것이고, 첫 그림에서 시뮬레이션한 정지가 그것입니다. 단독으로 다시 돌리니 5 µs였습니다. 기준선은 다른 모든 작업을 멈춘 상태에서만 유효하고, 생산 점검은 실제 작업이 모두 도는 상태에서만 유효합니다.
생산 중: 루프 자신의 기록
제어 루프는 매 주기 자기 실행 시간, 깨어남 지연, 마감 초과 플래그를 기록합니다. 36회 실행 381,255주기에서 마감 초과는 2건이었고, 각각 한 주기가 약 10.5 ms 걸린 단발 정지였습니다. 나머지 모든 주기는 4,000 µs 예산 안에서 1,951 µs 이내에 끝났습니다.

생산 실행 4회, 40,953주기. (a) 주기당 실행 시간, 최대 1,951 µs. 900 µs 근처 봉우리는 SDK 왕복. (b) 깨어남 지연: p99 0 µs, 최대 46 µs.
플랜트: 장치는 얼마나 늦는가
호스트가 결정적이 된 뒤에야 그 뒤의 장치를 잴 수 있습니다. 피드포워드와 피드백을 끄고 관절마다 사인파를 넣어(0.24 Hz와 610 Hz, 14회) H(jω) = e^(−jωL) / (1 + jωτ)에 맞췄습니다.

위상은 주파수에 선형으로 떨어집니다. 등가 지연은 모든 주파수에서 6.17.0 ms, 이득은 10 Hz까지 평평합니다. 14회 피팅의 L+τ는 6.176.96 ms, 중앙값 6.54 ms. 박스는 순수 지연입니다.
명령에서 모터까지, 전체 예산
| 단계 | 시간 | 상태 |
|---|---|---|
| 텔레옵·플래너 입력(데카르트 목표) | 장치·카메라 지연, 보통 수십 ms | 이 글의 범위 밖 |
| TF, IK(7자유도), 관절 궤적·스플라인 250 Hz | 4 ms 주기 안 | 단계별 시간은 따로 재지 않음 |
| 실시간 송신 루프 | 깨어남 ≤ 5 µs, 실행 ≤ 1,951 µs | 측정 |
| 박스까지 네트워크(TCP) | 왕복 평균 0.72 ms, p99 0.84 ms | 측정 |
| 박스 서보 → 관절 → 보고 | 명령 계산 → 관측: 6.54 ms | 측정(내부는 분해하지 않음) |
| 리드 적용 뒤 | 계획 대비 −0.04 / +0.09 ms | 측정 |
6.54 ms 중 네트워크가 0.72 ms(11%), 호스트 지터가 0.08%입니다. 나머지는 박스 몫이고, 250 Hz의 영차 홀드 약 2 ms가 그 안에 들어 있습니다.
결정성을 쓰기: 제어 레버로서의 시간
5 µs로 시간을 지키는 호스트는 흔들리는 호스트가 못 하는 일을 합니다. 미래를 보내는 것. 장치가 6.5 ms 늦는다는 것을 알면, 6.5 ms 뒤에 팔이 있어야 할 각도를 지금 보냅니다.
u = q_ref + k_ff · q̇_ref · t_lead + k_p · e + k_i · ∫e
# 서보 이득, 전 관절
kff: 1.0
t_lead_s: 0.0065
kp: 0.0 # 피드백은 5~11 ms 낡았다: 쓰지 않는다
ki: 0.0
송신 시각이 밀리초 단위로 흔들리면 리드도 매 주기 그만큼 틀리고 오차는 노이즈로 돌아옵니다. 5 µs에서 리드는 자기 값의 0.08% 정확도입니다.

3차원 위빙, J4가 가장 빠른 순간(125 °/s) ±40 ms. 명령(주황)은 기준보다 6.5 ms, 약 0.81° 앞서 달리고, 측정점은 기준선 위에 놓입니다. 박스 보고 주기 때문에 측정점은 10 ms마다 하나입니다.
같은 결정성이 쉐이핑도 가능하게 합니다. 모든 생성기는 가속도 연속(S커브, 저크 제한 PTP)이고, 250 Hz 보간기가 q̇_ref를 공급하며, 1.03 Hz로 흔들리는 페이로드에 맞춘 ZVD 입력 쉐이퍼가 정지 램프에 붙습니다. 스트리밍된 텔레옵 목표에는 드라이버 안의 온라인 안전 쉐이퍼가 붙습니다. 4 ms 격자 자체가 고르지 않으면 이 중 아무것도 동작하지 않습니다.
무엇을 얻었나: xArm 현장 사례
구성: UFACTORY xArm 7(XS13, 펌웨어 v2.6.0), TCP 연결(4 ms 명령 스트림, 10 ms마다 상태 보고), 위 스택을 올린 Jetson AGX Orin, UFACTORY F/T 센서와 Gripper G2, 그리퍼에 매단 1.573 kg 가방, 2026년 10월 샌프란시스코에서 VLA 모델을 연구하는 한 프론티어 랩의 현장에서 나흘. 벤더 박스는 출고 상태 그대로 뒀습니다.
벤더 박스 vs 실시간 계층
벤더 박스에 먼저 모든 기회를 줬습니다. 내장 플래너에 촘촘한 경유점과 블렌딩을 주니 계획보다 1.57초 늦고 경로에서 1.1~1.6 mm 흔들렸습니다. 모든 꺾임마다 정지시키면 경로는 가까워지지만 시간이 2.26배 걸렸습니다. 데카르트 속도 인터페이스는 툴을 약 70 mm/s²로 슬루 제한하고 관절이 최대 72°까지 흘러갔습니다. 같은 박스에 WIM 실시간 계층이 먹이자 경로 위에, 정시에 있었습니다.

8자, 165 mm/s. 기록된 관절각을 xArm 7 순기구학으로 돌린 툴 끝 경로, 계획과의 거리는 10배 확대. 벤더 박스는 계획 주위를 흔들립니다(1.11 mm RMS, 최대 3.47 mm). 이것이 고객이 겪던 떨림입니다. 실시간 계층에서는 툴 끝이 선 위에 머뭅니다(0.008 mm RMS).

3차원 위빙, 590 mm/s, 같은 방법. 벤더 박스는 꺾임에서 지그재그를 그립니다(1.56 mm RMS, 최대 4.82 mm). 실시간 계층은 경로 위에 있습니다(0.041 mm RMS, 되돌림 지점의 꺾임은 확대 전 0.79 mm 이하).

같은 이야기를 시간 축에서. 벤더 박스는 8자를 계획보다 약 1.57초 늦게 그립니다. 실시간 계층은 계획 위에 있습니다.
숫자

같은 팔, 같은 가방, 같은 박스에서 각 1회. (a) 계획 대비 지각: 벤더 박스 1,575 / 1,080 ms, 실시간 계층 0(0.5 ms 측정 격자 안). (b) 같은 순간의 툴 끝 오차(순기구학): 8자 165 → 0.305 mm, 위빙 283 → 0.645 mm. (c) 시간과 무관한 경로 거리: 1.11 → 0.008 mm, 1.56 → 0.041 mm.
경로당 실시간 계층 15회 전체에서 툴 끝 오차 RMS 평균은 0.267 mm(8자)와 0.576 mm(위빙), 지각은 −0.04 ms와 +0.09 ms, 정지 뒤 가방 흔들림은 0.037과 0.062 N·m였습니다.

같은 가방으로 속도 사다리: 툴 끝 오차 RMS 646.5 mm/s에서 0.50 mm, 775에서 0.55, 900에서 0.66, 1,000 mm/s에서 0.86 mm(최대 3.80 mm), 박스 오류 0건. 속도당 1회.
가방을 단 채 실시간 계층 30회 전부 박스 오류 0건으로 끝났고, 손목 F/T 최대는 2.99 N·m(정격 4 N·m)였습니다.
TCP 박스 vs 직접 EtherCAT
xArm은 어려운 경우입니다. 박스가 TCP로만 열리니 6.5 ms의 장치 지연은 남고, 호스트가 할 수 있는 최선은 리드로 상쇄하는 것입니다. 저희 제어기가 EtherCAT으로 서보를 직접 구동하면 같은 실시간 계층이 루프 전체를 마이크로초로 끌어내립니다. 같은 Orin에서 1 kHz 루프 지터 1.2 µs 이하, 주기 누락 0, nvpps 잠금을 끈 뒤 드라이브 간 DC 편차 3.5 µs 이하(nvpps 글). 아키텍처는 바뀌지만 일의 순서는 같습니다. 시간을 결정적으로 만들고, 남은 것을 재고, 그다음 제어한다.
여러분의 호스트를 위한 점검표
# 1) 커널과 격리
uname -v | grep PREEMPT_RT
cat /sys/kernel/realtime # 1
cat /proc/cmdline | tr ' ' '\n' | grep -E 'isolcpus|irqaffinity|kthread_cpus|nohz_full|rcu_nocbs'
cat /proc/sys/kernel/sched_rt_runtime_us # -1
# 2) 제어 코어 기준선, 다른 모든 작업 정지
sudo cyclictest -m -p 90 -i 1000 -a 11 -t 1 -D 60 -q -h 2000
# 3) 실제 부하(GPU, 카메라, 로깅, ROS 2)를 돌리면서 같은 시험
# 4) Jetson 시간 기준: 호스트 카운터가 이더넷 PHC에 잠겨 있나
dmesg | grep "TSC config ptx"
ls /sys/firmware/devicetree/base/bus@0/nvpps/
# 5) 장치: 왕복, 보고 주기, 관절별 사인 스윕 -> 지연 -> 리드
- 실행 시간, 깨어남 지연, 마감 초과 플래그를 루프 자신이 매 주기 lock-free 큐로 기록하게 하세요.
- 시험 중에는 같은 장치에 두 번째 클라이언트를 절대 붙이지 마세요.
- 스트리밍을 시작하기 전에 페이로드를 선언하세요. xArm에서는 페이로드나 TCP 오프셋을 바꾸면 다시 활성화할 때까지 박스가 홀드 상태로 들어갑니다.
정리
- 위치 스트리밍에서 송신 시각은 곧 궤적입니다. 28 ms 정지 한 번이 팔에 999 °/s를 요구하고, 그것은 팔이 쫓을 수 있는 속도의 5.5배입니다.
- PREEMPT_RT, 코어 격리, FIFO 스케줄링, 잠근 메모리, 고정 클럭, 깨끗한 시간 기준, lock-free IPC가 송신 루프를 5 µs 안에 지켰습니다. 생산 중 381,255주기에서 마감 초과 2건.
- 결정성이 장치를 측정 가능하게(순수 6.5 ms 지연) 만들었고, 일찍 보내서 그 지연을 상쇄할 수 있게 했습니다.
- 벤더 박스를 한 번도 건드리지 않은 xArm에서, 그것만으로 계획 대비 1.57초 지각이 정시로, 같은 순간의 툴 끝 오차가 165 mm에서 0.3 mm로, 경로 오차가 1.11 mm RMS에서 0.008 mm로 줄었고, 흔들리는 1.57 kg 짐 아래 30회 모두 박스 오류 0건이었습니다.
- 밀 때마다 떨리던 휴머노이드 텔레오퍼레이션 장치가 이제 조작자의 손을 최대 1 m/s로, 1 mm 안에서 따라갑니다. 바뀐 것은 시간을 지키는 컴퓨터 하나뿐입니다.
참고
- WIM Tech, PREEMPT_RT면 충분한가? Jetson Orin 실시간 성능 검증
- WIM Tech, EtherCAT 동기가 110초마다 무너진 이유
- WIM Tech, Lock-free vs Mutex: 로봇 제어 시스템 IPC 성능 벤치마크
- WIM Tech, ARM64에서 std::memory_order의 실제 비용
- WIM Tech, 1 kHz 실시간 로봇 제어 시스템 모니터링 아키텍처
- UFACTORY xArm Developer Manual, xArm-Python-SDK(위치 스트리밍, 상태 보고)
- N. C. Singer, W. P. Seering, "Preshaping Command Inputs to Reduce System Vibration," ASME J. Dyn. Sys., Meas., Control, 1990