CAN 을 다뤄 본 사람이 ROS2 의 토픽을 처음 보면 낯익은 느낌을 받는다. 각 장치가 자기 식별자를 달아 계속 내보내고, 필요한 쪽은 그 식별자만 알면 집어 간다 — 딱 CAN 버스다.
그 직관은 옳다. 다만 어디까지 옳고 어디서부터 어긋나는지를 알아야 ROS2 를 제대로 쓸 수 있다. 이 글은 CAN 을 이미 아는 사람을 위해 두 방식을 나란히 놓고 비교한다.
1. 왜 닮아 보이는가 — 데이터 중심 주소지정
두 방식이 닮은 것은 우연이 아니다. 둘 다 데이터 중심 주소지정(data-centric addressing)이라는 같은 계열에 속한다. 「누구에게 보낼 것인가」가 아니라 「무슨 데이터인가」로 주소를 삼는다는 뜻이다.
[연결 중심 — TCP 소켓, ROS1 의 연결 단계]
노드 A ────── 직접 연결 ──────→ 노드 B
"B 에게 보낸다" 상대를 알아야 한다
[데이터 중심 — CAN, ROS2 토픽]
┌──→ 노드 B (0x1A0 을 받아 간다)
노드 A ──0x1A0──→ ├──→ 노드 C (0x1A0 을 받아 간다)
"0x1A0 을 낸다" └──✕ 노드 D (관심 없음, 버린다)
받는 쪽을 모른다
<그림 1> 연결 중심과 데이터 중심
CAN 노드는 자기 프레임을 누가 받는지 모르고, 알 필요도 없다. ROS2 퍼블리셔도 마찬가지로 구독자가 몇인지 모른 채 그냥 발행한다. 송신 쪽 코드를 전혀 건드리지 않고 수신 노드를 하나 더 붙일 수 있다는 것, 이것이 두 방식이 공유하는 가장 큰 미덕이다.
여기서 파생되는 성질도 같다. 다대다가 자연스럽고, 연결 수립 절차가 응용 코드에 보이지 않으며, 주된 쓰임새가 요청·응답이 아니라 주기적인 상태 방송이다.
2. ROS1 은 오히려 덜 닮았다
재미있는 것은 ROS1 보다 ROS2 가 CAN 에 훨씬 더 가깝다는 점이다.
ROS1 에는 roscore 라는 중앙 마스터가 있었다. 퍼블리셔가 마스터에 등록하면, 구독자가 마스터에게 물어 퍼블리셔의 주소를 받아 온 뒤 그쪽으로 직접 TCP 연결을 맺는 구조였다. 이름만 데이터 중심이지 실제 데이터는 점대점으로 흘렀다. 버스라기보다 전화 교환대에 가까웠고, 마스터가 죽으면 새 연결을 맺을 수 없었다.
ROS2 는 마스터를 없앴다. 그 자리에 DDS 의 분산 디스커버리가 들어왔다. 노드들이 멀티캐스트로 자기 존재와 관심사를 알리고, 조건이 맞는 짝끼리 알아서 연결한다. 각자 떠들고 각자 골라 듣는 구조가 된 것이다. CAN 버스의 감각이 정확히 여기서 나온다.
★주 1. 대신 ROS2 는 디스커버리 트래픽이라는 새 문제를 얻었다. 노드가 수백 개로 늘면 서로를 찾는 패킷만으로 망이 시끄러워진다. 이를 다루려고 도입된 것이 ROS_DOMAIN_ID 로 망을 나누는 방법, 그리고 디스커버리 서버(discovery server)를 두는 방법이다. CAN 에는 디스커버리라는 개념 자체가 없으므로 이런 고민도 없다 — 대신 배선도와 식별자 표를 사람이 관리해야 한다.
3. 결정적인 차이 — 식별자는 주소이자 우선순위다
여기가 두 방식이 갈라지는 지점이고, CAN 을 아는 사람이 ROS2 에서 가장 먼저 허전함을 느끼는 부분이다.
CAN 의 식별자는 주소인 동시에 우선순위다.
CAN 은 여러 노드가 같은 순간에 송신을 시작해도 충돌로 프레임이 깨지지 않는다. 물리 계층이 와이어드 AND 로 동작하기 때문이다. 한 노드라도 0(도미넌트)을 내보내면 버스 전체가 0 이 된다. 1(리세시브)은 아무도 0 을 내지 않을 때만 유지된다.
송신 중인 노드는 자기가 낸 비트와 버스에서 읽히는 비트를 계속 비교한다. 자기는 1 을 냈는데 버스가 0 으로 읽히면, 나보다 우선순위 높은 노드가 말하고 있다는 뜻이므로 즉시 물러난다. 이때 이미 나간 비트들은 이긴 노드의 비트와 똑같았으므로 프레임은 전혀 손상되지 않는다. 진 노드는 버스가 비는 즉시 다시 시도한다.
이것을 비파괴 중재(non-destructive arbitration)라 하며, 결과적으로 식별자가 작은 노드가 항상 이긴다. 아래에서 직접 돌려 볼 수 있다. 세 노드의 식별자를 바꿔 가며 「한 비트」로 한 칸씩 진행해 보라.
<그림 2> CAN 비트 중재. 식별자가 작을수록 먼저 말한다.
이 성질 덕분에 CAN 은 고우선순위 메시지의 최악 지연시간을 계산으로 보증할 수 있다. 식별자 배분이 곧 실시간 설계가 되는 것이다. 차량 안전계통이 수십 년째 CAN 을 쓰는 이유가 여기 있다.
ROS2 의 토픽 이름에는 이런 성질이 전혀 없다. 이름은 그냥 이름이다. /cmd_vel 이 /camera/image_raw 보다 먼저 간다는 보장이 없고, 커다란 영상 메시지가 망을 채우는 동안 짧은 제어 명령이 뒤에서 기다릴 수 있다. DDS QoS 에 TRANSPORT_PRIORITY 항목이 있으나 일반적인 이더넷·UDP 환경에서는 CAN 같은 보증이 되지 않는다.
정리하면 이렇다. CAN 의 식별자는 설계 결정이고, ROS2 의 토픽 이름은 작명이다.
★주 2. ROS2 에서 실시간성이 필요하면 토픽 이름이 아니라 다른 층에서 풀어야 한다. 실시간 커널(PREEMPT_RT), 같은 프로세스 안에서 복사를 없애는 인트라프로세스 통신, 실행기(executor) 의 콜백 그룹과 스레드 우선순위, 그리고 이더넷이라면 TSN 이 그 수단이다. 망 계층이 알아서 우선순위를 지켜 주던 CAN 과 달리, ROS2 에서는 사람이 여러 층을 직접 조립해야 한다.
4. 나머지 차이
| 항목 |
CAN |
ROS2 토픽 |
| 페이로드 |
8 바이트 (CAN FD 는 64) |
사실상 제한 없음 |
| 속도 |
1 Mbit/s (FD 데이터 구간은 수 Mbit/s) |
이더넷 그대로. 기가비트 이상 |
| 형식 계약 |
DBC 파일 — 버스 밖의 약속 |
.msg — 디스커버리 때 형식이 맞아야 연결된다 |
| 디스커버리 |
없음. 설계 시 고정 |
동적. 붙었다 떨어졌다 한다 |
| 늦게 켠 노드 |
다음 주기까지 아무것도 모른다 |
TRANSIENT_LOCAL 이면 마지막 값을 즉시 받는다 |
| 재전송 |
하드웨어가 자동. 에러 카운터·버스오프 |
RELIABLE / BEST_EFFORT 선택 |
| 실시간성 |
해석 가능한 하드 실시간 |
일반적으로 보증 없음 |
| 물리 계층 |
버스 하나에 종단 저항 120 Ω. 길이와 속도가 맞물린다 |
전송 방식과 무관. 보통 UDP, 같은 PC 안이면 공유 메모리 |
<표 1> CAN 과 ROS2 토픽의 차이
실무에서 의외로 크게 체감되는 것이 「늦게 켠 노드」 항목이다. CAN 에서는 나중에 전원을 넣은 장치가 모터 상태 프레임이 올 때까지 아무것도 모른 채 기다려야 한다. ROS2 는 퍼블리셔가 TRANSIENT_LOCAL 로 발행하면 구독을 시작하는 순간 마지막 값을 받는다. 지도, 로봇 기술(URDF), 설정값처럼 자주 변하지 않지만 항상 알아야 하는 데이터에 쓴다. CAN 에는 대응물이 없어서, 보통 새 노드가 SDO 로 직접 읽어 오거나 주기를 짧게 가져가는 식으로 우회한다.
5. CANopen 으로 보면 정확히 맞아떨어진다
날것의 CAN 보다 CANopen 과 비교하면 훨씬 깔끔하게 대응된다. CANopen 이 CAN 에 없던 것 — 형식 계약과 상태 관리 — 을 얹은 규약이기 때문이다.
| CANopen |
ROS2 |
공통점 |
PDO Process Data Object |
토픽 |
주기적·무확인. 빠르고 가볍다 |
SDO Service Data Object |
서비스 |
요청에 응답이 따라온다. 느리지만 확실하다 |
NMT Pre-op → Operational |
라이프사이클 노드 |
상태 전이를 거쳐야 데이터가 흐른다 |
| 하트비트 / 노드 가딩 |
LIVELINESS QoS |
상대가 살아 있는지 주기적으로 확인 |
| EMCY (긴급 메시지) |
/diagnostics |
이상 상황을 따로 알린다 |
| 오브젝트 딕셔너리 (EDS) |
.msg + 파라미터 |
주고받을 내용의 형식 계약 |
| SYNC |
(대응물 없음) |
여러 노드의 동시 샘플링·동시 구동 |
<표 2> CANopen 과 ROS2 의 대응
특히 NMT ↔ 라이프사이클 노드 대응이 볼만하다. CANopen 장치는 전원을 넣었다고 바로 PDO 를 쏟아내지 않는다. 초기화를 거쳐 Pre-operational 에 머물다가, 마스터가 Operational 로 올려 줘야 비로소 데이터를 낸다. ROS2 의 라이프사이클 노드도 똑같이 Unconfigured → Inactive → Active 를 거치며, Active 가 되기 전에는 발행하지 않는다.
둘 다 「켜자마자 떠들기 시작하면 곤란하다」는 현장 경험에서 나온 구조다. 센서 보정이 끝나기 전 값, 모터 드라이버가 준비되기 전 지령은 없느니만 못하다. ROS2 를 쓰면서 라이프사이클 노드를 왜 쓰는지 모르겠다는 사람에게는 이 비유가 가장 잘 통한다.
6. 토픽인가 서비스인가 — PDO 인가 SDO 인가
ROS2 입문자가 가장 오래 헤매는 물음이 「이건 토픽으로 해야 하나 서비스로 해야 하나」다. CAN 을 아는 사람에게는 이 물음을 바꿔 물으면 답이 바로 나온다.
이것은 PDO 인가, SDO 인가?
- 주기적으로 계속 흘려보낼 상태인가 → PDO → 토픽
모터 현재 속도, 엔코더 값, 센서 측정치, 조인트 상태
- 어쩌다 한 번 물어보거나 바꾸는 설정인가 → SDO → 서비스
가속도 한계 설정, 원점 복귀 실행, 모드 전환, 파라미터 읽기
판별 기준도 같다. 「응답을 기다려야 하는가」와 「한 번 놓쳐도 다음 주기에 만회되는가」이다. 모터 속도는 한 번 놓쳐도 10 ms 뒤에 또 온다 — 토픽이다. 원점 복귀 명령은 놓치면 안 되고 완료 여부를 알아야 한다 — 서비스, 혹은 오래 걸린다면 액션(action)이다.
★주 3. ROS2 에는 CANopen 에 없는 세 번째 기구인 액션(action)이 있다. 오래 걸리고, 중간 경과를 알려 주어야 하며, 도중에 취소할 수 있어야 하는 작업에 쓴다. 로봇 팔을 목표 자세로 보내는 일이 전형적이다. SDO 로 억지로 흉내 내려면 「시작 명령 SDO + 진행률 PDO + 중단 명령 SDO」를 손으로 조립해야 하는데, 액션은 그 조합을 하나의 규약으로 묶어 둔 것이라 보면 된다.
7. 계약이 틀렸을 때 무슨 일이 생기는가
날마다 가장 크게 체감되는 차이는 성능도 구조도 아니고 틀렸을 때의 증상이다.
CAN — 조용히 틀린 값이 나온다
CAN 프레임에는 형식 정보가 없다. 8 바이트의 의미는 전적으로 DBC 파일이라는 버스 밖의 약속에 달려 있다. 스케일 인자를 잘못 넣으면 온도가 25 ℃ 대신 250 ℃ 로 나오는데, 아무도 오류를 내지 않는다. 바이트 순서를 거꾸로 읽어도, 부호 있는 값을 없는 값으로 읽어도 마찬가지다. 그럴듯하게 틀린 숫자가 태연히 흘러간다.
그래서 CAN 디버깅은 파형과 원시 바이트를 눈으로 좇는 일이 된다. 오실로스코프, CAN 분석기, DBC 를 놓고 대조한다.
ROS2 — 아예 연결이 안 된다
ROS2 는 형식이 디스커버리 단계에서 대조된다. 퍼블리셔가 sensor_msgs/msg/Temperature 를 내고 구독자가 std_msgs/msg/Float32 를 기다리면 둘은 애초에 이어지지 않는다. 잘못된 값이 흘러갈 일이 없다.
대신 ROS2 에는 CAN 에 없던 함정이 하나 생긴다. 형식이 맞는데도 QoS 가 안 맞아 연결되지 않는 경우다. 초보자를 가장 많이 잡는 대목이고, 증상이 「퍼블리시는 되는데 아무도 못 받는다」라서 원인을 짐작하기 어렵다.
| 퍼블리셔가 제공 |
구독자가 요구 |
결과 |
| RELIABLE |
RELIABLE |
연결됨 |
| RELIABLE |
BEST_EFFORT |
연결됨 |
| BEST_EFFORT |
RELIABLE |
연결 안 됨 |
| TRANSIENT_LOCAL |
VOLATILE |
연결됨 |
| VOLATILE |
TRANSIENT_LOCAL |
연결 안 됨 |
<표 3> QoS 짝 맞추기 — 제공이 요구보다 약하면 연결되지 않는다
규칙은 하나다. 퍼블리셔가 제공하는 품질이 구독자가 요구하는 품질보다 약하면 연결되지 않는다.
가장 흔한 사고가 이것이다. 라이다나 카메라 드라이버는 대개 SensorDataQoS, 즉 BEST_EFFORT 로 발행한다. 그런데 직접 짠 노드에서 기본값(RELIABLE)으로 구독하면 토픽 목록에는 보이는데 콜백이 한 번도 안 불린다. ros2 topic echo 로는 잘 보여서 더 헷갈리는데, echo 가 자동으로 QoS 를 맞춰 주기 때문이다.
확인 방법은 간단하다.
$ ros2 topic info -v /scan
Publishers: 1
...
QoS profile:
Reliability: BEST_EFFORT ← 여기
Durability: VOLATILE
Subscribers: 1
...
QoS profile:
Reliability: RELIABLE ← 여기. 짝이 안 맞는다
Durability: VOLATILE
<그림 3> QoS 불일치 확인
CAN 으로 치면 「배선도 다 맞고 종단 저항도 맞는데, 통신 속도만 서로 다르게 잡아 놓은 상태」에 가깝다. 물리적으로는 다 연결되어 있는데 한 마디도 오가지 않는다.
8. 도구 대응표와 실제 연결
쓰던 도구를 기준으로 짝지으면 적응이 빠르다.
| CAN (SocketCAN) |
ROS2 |
| candump can0 |
ros2 topic echo /토픽 |
| cansniffer can0 |
ros2 topic hz /토픽 |
| candump -l |
ros2 bag record |
| canplayer |
ros2 bag play |
| cansend can0 123#DEADBEEF |
ros2 topic pub /토픽 … |
| DBC 파일 |
.msg 파일 |
| 버스 부하율 측정 |
ros2 topic bw /토픽 |
| 노드 목록 (설계도) |
ros2 node list |
<표 4> 도구 대응
그리고 둘을 실제로 이어 붙일 수도 있다. 리눅스의 SocketCAN 위에서 동작하는 다리 역할 패키지들이 있다.
- 원시 프레임 수준 — CAN 프레임을 그대로
can_msgs/msg/Frame 토픽으로 올리고 내린다. 해석은 직접 해야 하지만 무엇이든 붙일 수 있다.
- CANopen 수준 — EDS 파일을 읽어 오브젝트 딕셔너리를 파악하고, PDO 를 토픽으로 SDO 를 서비스로 자동 노출해 준다. 표 2 의 대응을 코드로 구현해 둔 셈이다.
기존에 CANopen 서보 드라이브를 쓰던 장비를 ROS2 로 올릴 때는 후자가 압도적으로 편하다. 다만 3 절에서 말한 실시간성은 다리를 건너는 순간 사라진다. CAN 버스 안에서 보장되던 우선순위는 이더넷과 리눅스 스케줄러를 지나며 흩어진다. 안전 관련 정지 회로를 ROS2 토픽으로 옮기는 것은 거의 언제나 나쁜 생각이다. 그런 신호는 CAN 이나 하드와이어로 남겨 두고, ROS2 에는 상태 보고만 올리는 구성이 안전하다.
맺으며
정리하면 이렇다.
- ROS2 토픽과 CAN 은 데이터 중심 주소지정이라는 같은 뿌리를 공유한다. 그 직관은 믿어도 된다.
- 다만 CAN 의 식별자는 우선순위까지 겸하고 ROS2 의 토픽 이름은 그렇지 않다. 실시간성이 필요하면 다른 층에서 따로 풀어야 한다.
- 대응을 정확히 보려면 CANopen 과 견주는 편이 낫다. PDO ↔ 토픽, SDO ↔ 서비스, NMT ↔ 라이프사이클이다.
- 틀렸을 때 CAN 은 조용히 틀린 값을, ROS2 는 연결 실패를 준다. 후자의 원인은 형식 아니면 QoS 다.
CAN 을 오래 만진 사람이 ROS2 를 배울 때 가장 큰 자산은 문법이 아니라 「버스에 데이터를 흘린다」는 사고방식이다. 그건 이미 몸에 있다. 새로 익힐 것은 형식 계약과 QoS, 그리고 잃어버린 우선순위를 어디서 되찾을 것인가 하는 문제뿐이다.
다뤄 주었으면 하는 주제나 이상한 점이 있으면 댓글로 남겨 주기 바란다.