hello, robot

4축 로봇암을 만든 날의 기록 세 번째예요. 이 글은 남은 한 축, 화면의 슬라이더 네 개가 실제 팔을 움직이기까지 데이터가 흐르는 길이에요.

기체4축 로봇암 · SG90 ×4 모델URDF · robot_state_publisher 사슬슬라이더 → /joint_states → 브리지 → 실물 플래너MoveIt · position_only_ik

로봇을 그리는 데는 노드 두 개면 돼요

ROS 2에서 로봇을 화면에 세우는 최소 구성은 노드 두 개예요. joint_state_publisher가 관절 각도를 /joint_states로 발행하고, robot_state_publisher가 그 각도를 URDF와 결합해요.

URDF는 링크 치수와 관절 축을 적은 로봇 명세 파일인데, robot_state_publisher는 이걸 받아 두 가지를 내보내요.

내보내는 것무엇RViz가 쓰는 곳
/robot_description로봇의 모양 명세 전체를 실은 문자열 토픽생김새
TF (transform)“베이스 기준으로 팔꿈치가 어디를 향하는가”의 좌표 변환 트리현재 자세

launch 파일 하나에 세 노드를 묶으면 슬라이더로 움직이는 가상 팔이 완성돼요.

Node(package='robot_state_publisher', executable='robot_state_publisher',
     parameters=[{'robot_description': xacro_결과}]),
Node(package='joint_state_publisher_gui', executable='joint_state_publisher_gui'),
Node(package='rviz2', executable='rviz2', arguments=['-d', rviz_설정]),

여기서 배운 건 로봇의 모양조차 파일이 아니라 토픽으로 흐른다는 점이에요. 구독자가 누구든 같은 명세를 받아 같은 로봇을 그릴 수 있고, 반대로 발행자가 둘이면 남의 로봇이 내 화면에 뜨는 부작용도 같은 원리에서 나와요.

같은 토픽을 실물이 구독하면 텔레옵이 돼요

가상 팔만 움직이면 시뮬레이션이지만, /joint_states를 구독하는 노드를 하나 더 붙이면 텔레옵이 돼요. 브리지 노드가 라디안 각도를 서보 지령(0~180도)으로 환산해 시리얼로 흘리는 구조예요.

토픽 하나를 사이에 둔 사슬
슬라이더도 플래너도 실물도 전부 같은 자리에서 만나요.
joint_state_publisher슬라이더 4개MoveIt 모의 컨트롤러계획 궤적 재생PyQt 펜던트 · rosbag앞으로 붙일 것/joint_states관절 각도 (rad)모두가 여기서 만나요robot_state_publisher+ URDF → /robot_description, TF브리지 노드rad→도 환산 · 15 HzRViz2화면의 팔우노 펌웨어SG90 ×4로봇에 달린 OLED — 사슬의 관측 지점슬라이더는 도는데 숫자가 안 변하면 통신 구간,숫자는 변하는데 팔이 안 돌면 전원이나 서보 문제부품이 늘어도 사슬이 단순한 이유는 전부 이 토픽 하나를 사이에 두고 만나기 때문이에요.

이 사슬에서 디버깅을 도운 건 로봇에 달린 OLED였어요. 우노가 수신한 관절 각도 네 개를 상시 표시하게 해 두면, 명령이 사슬의 어디까지 도달했는지를 로봇 몸에서 바로 읽을 수 있어요.

OLED 숫자좁혀지는 구간
안 변함통신 구간 (브리지·릴레이·시리얼)
변함안 돎전원이나 서보

시리얼 모니터를 열 수 없는 상황(포트를 브리지가 점유 중)에서 화면 밖 관측 지점이 하나 있다는 게 생각보다 큰 차이를 만들어요.

지령 0에서 180이 물리 각도는 아니었어요

사슬이 이어지고 팔이 슬라이더를 따라 움직였는데, 바닥 쪽으로 깊이 내릴수록 화면 속 팔과 실물의 각도가 어긋났어요.

1

큰 각도로 갈수록 화면과 실물이 벌어짐

원인SG90 계열 하비 서보는 지령 0~180도가 실제 회전각과 1:1이 아니에요. 개체마다 실제 가동폭이 160~175도쯤이라 중앙에서 멀어질수록 오차가 누적돼요
가중혼(서보에 끼우는 팔)을 몇 도 틀어 조립한 오프셋까지 더해져요
조치관절마다 두 점을 재는 선형 캘리브레이션
두 점만 재면 셋이 흡수돼요
물리각 0도와 +90도에서의 지령값을 기록하면, 목표 물리각은 그 사이의 선형 비례로 환산돼요.
03060904080120160물리 각도 (도) — 사람이 자세를 만들어 확인서보 지령값지령=물리라고 가정하면물리 0° 정렬 자세 → 지령 78물리 +90° 직각 자세 → 지령 158관절당 숫자 둘이면· 오프셋 (혼 조립 각도)· 스케일 (개체별 가동폭 160~175°)· 방향 반전 (거꾸로 조립한 관절)셋이 전부 흡수돼요위치 센서가 없는 개루프 서보에서는 기준 자세를 사람이 만들고 그 지점의 지령값을 읽는 것이 캘리브레이션의 전부예요.

오프셋과 스케일, 그리고 조립이 반대로 된 관절의 방향 반전까지 관절당 숫자 두 개가 전부 흡수해요. 위치 센서가 없는 개루프 서보에서는 기준 자세를 사람이 만들어 주고 그 지점의 지령값을 읽는 것, 이게 캘리브레이션의 전부예요. 기준 자세는 수직과 수평이라 폰 수평계 하나로 만들 수 있어요.

플래너도 같은 토픽으로 들어와요

다음 단계로 모션 플래닝 프레임워크인 MoveIt을 올렸어요. 지금까지는 “관절을 몇 도로”라고 직접 말했다면, MoveIt은 “끝점을 이 좌표로”라고 말하면 역기구학과 경로 탐색, 속도 프로파일 계산을 대신해 줘요.

4축이라 끝점의 방향(orientation)까지는 못 맞추고 위치만 풀 수 있어서(position_only_ik), 그리퍼 없이도 성립하는 끝점 터치 과제로 방향을 잡았어요. 계획된 궤적은 모의 컨트롤러가 재생하면서 다시 /joint_states로 흘리기 때문에, 슬라이더 때 만든 브리지가 수정 없이 그대로 실물 전달을 맡아요.

조작 쪽 다음 계획은 키보드 텔레옵의 기능(관절 조그, 포즈 녹화와 재생, 즉시 중단)을 PyQt 슬라이더 펜던트로 옮기는 거예요. 새 기능처럼 보이지만 전부 /joint_states를 발행하거나 구독하는 노드 하나씩이라, 시스템에 더해지는 복잡도는 노드 수만큼만 늘어요.

하루를 통과하고 보니 이 시스템의 뼈대는 결국 토픽 하나예요. 슬라이더도, 화면의 팔도, 실제 팔도, 플래너도, 앞으로 붙일 펜던트와 rosbag도 전부 /joint_states를 사이에 두고 만나요. 부품이 늘어도 사슬이 단순하게 유지되는 이유가 여기 있었어요.

펌웨어 안쪽의 시리얼 프로토콜 문제는 부분 수신과 개루프 서보 부팅 점프 — 시리얼 텔레옵의 함정에, 빌드 경로·가상화·디스커버리 같은 운영 문제는 설치 경로·WSL2 USB·DDS 도메인 — RViz 연동의 운영 함정 셋에 정리했어요.