제6부 하드웨어 인터페이스
6.8 micro-ROS — MCU와 ROS 2
6.8.1 이 장에서 배우는 것 / 선수 지식
로봇에는 리눅스 컴퓨터만 있는 것이 아니다. 모터 드라이버 보드, 센서 인터페이스, 배터리 관리, 비상 정지 회로 — 이런 것들은 MCU(마이크로컨트롤러)에서 실행된다. 램이 수십~수백 KB이고 OS가 없거나 RTOS인 환경이다.
그 MCU를 ROS 2 그래프에 직접 참여시키는 것이 micro-ROS다. MCU가 토픽을 발행하고 서비스를 제공하며, 리눅스 쪽에서 보면 평범한 ROS 2 노드로 보인다.
매력적으로 들리지만 판단이 필요하다. 이 장은 구조를 설명하고, micro-ROS와 "커스텀 프로토콜 + 브리지 노드" 중 무엇을 택할지의 기준까지 다룬다. 후자가 맞는 경우가 생각보다 많다.
배우는 것:
- 왜 필요한가 — 일반 ROS 2를 MCU에서 못 실행하는 이유.
- 아키텍처 — 클라이언트·에이전트·XRCE-DDS.
rclc— MCU용 클라이언트 라이브러리와 실행 모델.- 자원 제약 — 정적 할당·메모리 예산.
- 전송 — 시리얼·UDP·CAN.
- 시간 동기화 — MCU와 리눅스의 시계.
- 한계와 대안 — 언제 브리지 노드가 나은가.
선수 지식: 2.1.7(rclc), 3.1(DDS), 6.7(시리얼·CAN), 1.10.8(시계 동기화), 6.6.7(실시간 판단).
정직: micro-ROS는 이 책에서 다루는 주제 중 생태계 변화가 빠른 편이다. 지원 보드·RTOS·빌드 도구가 계속 바뀌며, Jazzy 지원 상태와 도구 체계는 반드시 현재 문서(micro.ros.org)로 확인해야 한다
(확인 필요). 이 환경에서 빌드·실행 검증하지 않았다(목차 3). 아래는 구조와 판단 기준에 집중한다. apt 저장소에 무엇이 있고 없는지는 확인했고(실행 확인 — 6.8.13), MCU가 필요한 항목은 확인하지 못했다(6.8.13.4에 목록). 확인 못 한 항목의(확인 필요)표기는 그대로 두었다.
6.8.2 왜 필요한가 — MCU의 제약
일반 ROS 2(rclcpp·rclpy)를 MCU에서 실행할 수 없는 이유:
| 요구 | 일반 ROS 2 | 전형적 MCU |
|---|---|---|
| RAM | 수십~수백 MB | 수십~수백 KB |
| OS | 리눅스(스레드·소켓·파일) | 없음 또는 RTOS |
| 동적 할당 | 전제 | 대개 회피 |
| C++ 런타임 | 예외·RTTI·STL | 제한적 |
| DDS 구현 | 수 MB | 불가 |
| 네트워크 스택 | 완전한 TCP/IP | 제한적 |
핵심은 DDS다. 3.1~3.3에서 본 DDS는 디스커버리·QoS·히스토리 캐시를 위해 상당한 메모리와 처리를 요구한다. MCU에서 실행 불가능하다.
micro-ROS의 답: DDS 대신 XRCE-DDS.
6.8.3 아키텍처
| 구성 요소 | 위치 | 역할 |
|---|---|---|
| micro-ROS 클라이언트 | MCU | 경량 ROS 2 API |
| XRCE-DDS 프로토콜 | 사이 | 클라이언트↔에이전트 통신 |
| micro-ROS Agent | 리눅스 | MCU를 대신해 DDS에 참여 |
핵심 통찰: 에이전트가 프록시다. MCU는 DDS를 모르고, 에이전트가 MCU 대신 DDS 엔티티를 만들어 그래프에 참여한다. 다른 ROS 2 노드에서 보면 평범한 노드로 보인다.
# 에이전트 실행 (전송 방식에 따라)
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyACM0 -b 115200
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888
# 주의: micro_ros_agent 는 apt 에 없다 — 소스 빌드가 필요하다 (실행 확인, 6.8.13)
XRCE-DDS(eXtremely Resource-Constrained Environments DDS) 는 OMG 표준이며, 클라이언트가 최소한의 상태만 유지하도록 설계됐다 — 디스커버리·QoS 관리·히스토리를 에이전트가 담당한다.
| 항목 | 클라이언트(MCU) | 에이전트(리눅스) |
|---|---|---|
| 메모리 | 수십 KB | 일반 |
| 디스커버리 | ❌ 에이전트만 | ✅ |
| QoS 관리 | 요청만 | 실제 적용 |
| 히스토리 | 최소 | DDS가 |
함정: 에이전트가 단일 장애점이다. 에이전트가 종료되면 그 MCU의 모든 통신이 끊긴다. 3.4.5에서 Discovery Server를 두고 논의한 것과 같은 성질이며, 에이전트 재시작·클라이언트 재연결 전략이 필요하다.
6.8.4 rclc와 실행 모델
2.1.7에서 언급한 rclc가 여기서 쓰인다.
#include <rcl/rcl.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>
rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;
void timer_callback(rcl_timer_t * timer, int64_t last_call_time)
{
(void)last_call_time;
if (timer != NULL) {
rcl_publish(&publisher, &msg, NULL);
msg.data++;
}
}
void appMain(void * arg)
{
rcl_allocator_t allocator = rcl_get_default_allocator();
rclc_support_t support;
// 초기화 — 실패 처리 필수 (MCU에서는 예외가 없다)
rclc_support_init(&support, 0, NULL, &allocator);
rcl_node_t node;
rclc_node_init_default(&node, "mcu_node", "", &support);
rclc_publisher_init_default(
&publisher, &node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32), "mcu_counter");
rcl_timer_t timer;
rclc_timer_init_default(&timer, &support, RCL_MS_TO_NS(100), timer_callback);
// executor — 핸들 수를 미리 선언 (정적 할당)
rclc_executor_t executor;
rclc_executor_init(&executor, &support.context, 1, &allocator);
rclc_executor_add_timer(&executor, &timer);
msg.data = 0;
while (1) {
rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));
}
}
rclc executor가 rclcpp와 다른 점:
| 축 | rclcpp executor | rclc executor |
|---|---|---|
| 할당 | 동적 | 정적(핸들 수 미리 선언) |
| 실행 순서 | 구현 정의(2.2.9) | 명시 지정 가능 |
| 스레드 | 멀티 가능(2.3) | 단일이 일반적 |
| 트리거 조건 | 준비된 것 실행 | 조건 지정 가능(전부 준비되면 등) |
| 결정성 | 낮음 | 높음 |
rclc의 결정적 실행이 장점이다(12.5에서 다시 다룬다). "구독 A와 B가 모두 도착하면 콜백 실행" 같은 조건을 명시할 수 있어, 제어 애플리케이션에서 예측 가능성이 높다.
// 실행 순서·조건 지정 (개념 — 확인 필요: API 세부)
rclc_executor_set_trigger(&executor, rclc_executor_trigger_all, NULL);
견해:
rclc의 결정성은 MCU가 아닌 곳에서도 매력적일 수 있다. 다만rclcpp생태계(컴포넌트·라이프사이클·표준 컨트롤러)를 포기해야 하므로, 리눅스에서는 대개rclcpp+ executor 분리(2.3.8)를 택한다.
6.8.5 자원 제약
모든 것이 컴파일 시점에 정해져야 한다.
| 항목 | 설정 |
|---|---|
| 발행자·구독자 수 | 최대치 선언 |
| 서비스·클라이언트 수 | 최대치 선언 |
| 메시지 크기 | 최대 크기 고정 |
| executor 핸들 수 | 미리 선언 |
| 히스토리 깊이 | 작게 |
# colcon.meta 또는 유사 설정 파일 (확인 필요: 형식)
{
"names": {
"rmw_microxrcedds": {
"cmake-args": [
"-DRMW_UXRCE_MAX_NODES=1",
"-DRMW_UXRCE_MAX_PUBLISHERS=5",
"-DRMW_UXRCE_MAX_SUBSCRIPTIONS=3",
"-DRMW_UXRCE_MAX_SERVICES=1",
"-DRMW_UXRCE_MAX_CLIENTS=1",
"-DRMW_UXRCE_MAX_HISTORY=4",
"-DRMW_UXRCE_TRANSPORT=serial"
]
}
}
}
함정: 한도를 넘으면 런타임에 조용히 실패한다. 발행자를 6개 만들려 하는데 최대가 5면 여섯 번째 초기화가 실패하고, 반환값을 확인하지 않으면 아무 표시 없이 동작하지 않는 노드가 된다. MCU에는 예외가 없으므로 모든 반환값을 검사해야 한다.
// 반환값 검사 매크로 관용구
#define RCCHECK(fn) { \
rcl_ret_t rc = fn; \
if (rc != RCL_RET_OK) { error_loop(); } }
RCCHECK(rclc_publisher_init_default(&publisher, &node, type_support, "topic"));
메시지 크기 제약이 설계에 영향을 준다:
| 문제 | 대처 |
|---|---|
| 가변 길이 배열·문자열 | 고정 크기로 설계(1.2.3) |
| 큰 메시지 | 분할하거나 요약 |
| 표준 메시지가 큼 | 커스텀 경량 타입 |
1.2.3에서 "유한 배열로 설계하라"고 한 것이 여기서 필수가 된다. micro-ROS에서 무한 배열은 사실상 쓸 수 없다.
// 메시지 안의 문자열·배열은 미리 메모리를 할당해야 한다
std_msgs__msg__String msg;
static char buffer[64];
msg.data.data = buffer;
msg.data.capacity = sizeof(buffer);
msg.data.size = 0;
함정: 메시지 필드에 메모리를 할당하지 않고 쓰면 크래시한다. rclcpp에서는 자동으로 되던 것이 여기서는 수동이다. 특히 구독 메시지도 미리 버퍼를 할당해야 수신할 수 있다.
6.8.6 전송
| 전송 | 특징 | 용도 |
|---|---|---|
| 시리얼(UART·USB CDC) | 단순·널리 지원 | 가장 흔함 |
| UDP(이더넷·Wi-Fi) | 다중 클라이언트·거리 | 네트워크 있는 MCU |
| CAN | 기존 CAN 버스 재사용(확인 필요) | 차량형 |
| 커스텀 | 직접 구현 | 특수 링크 |
# 시리얼 에이전트 (micro_ros_agent 는 apt 에 없다 — 6.8.13.1)
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/mcu_board -b 921600
# UDP 에이전트 (여러 MCU가 연결될 수 있다)
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888
대역폭이 제약이다:
$$B_{\text{필요}} = \sum_i S_{\text{msg},i} \cdot f_i \cdot (1 + \text{오버헤드})$$
115200 bps 시리얼은 초당 약 11 KB다. 100바이트 메시지를 100 Hz로 보내면 10 KB/s — 이미 포화에 가깝다. 보율을 높이거나(921600 이상) 발행률·크기를 줄여야 한다.
함정: 6.7.3.1의 USB-시리얼 지연 타이머가 여기서도 적용된다. micro-ROS 통신 주기가 이유 없이 낮다면 이것부터 확인하라.
견해: 전송 선택이 시스템 구조를 정한다. 시리얼은 MCU 하나당 포트 하나이므로 MCU가 여러 개면 포트가 부족하다. UDP는 여러 MCU가 한 에이전트에 연결될 수 있어 확장성이 낫지만, MCU에 이더넷·Wi-Fi가 있어야 한다.
6.8.7 시간 동기화
MCU의 시계는 리눅스와 다르다. 1.10.8의 문제가 여기서 더 심하다 — MCU에는 NTP도 없다.
// micro-ROS의 시간 동기화 API (개념)
rmw_uros_sync_session(1000); // 에이전트와 동기화, 타임아웃 ms
if (rmw_uros_epoch_synchronized()) {
int64_t time_ns = rmw_uros_epoch_nanos();
msg.header.stamp.sec = time_ns / 1000000000;
msg.header.stamp.nanosec = time_ns % 1000000000;
}
// (확인 필요: API 이름·동작)
동기화 없이 발행하면 무슨 일이 생기는가:
| 결과 | 설명 |
|---|---|
| tf 외삽 오류 | 스탬프가 미래·과거로(5.2.4) |
message_filters 동기화 실패 | 짝이 안 지어짐(7.7) |
| 센서 융합 오차 | 시간 정합 붕괴 |
| bag 재생 이상 | 시간 순서 뒤엉킴 |
견해: 센서 데이터를 발행하는 MCU라면 시간 동기화는 필수다. 그리고 주기적으로 재동기화해야 한다 — MCU의 크리스털은 드리프트하며, 온도에 따라 달라진다. 동기화 상태를 진단으로 내보내는 것도 좋다(6.9).
정직: micro-ROS의 시간 동기 정밀도는 전송 지연에 좌우된다
(확인 필요). 시리얼 왕복이 수 ms면 그만큼의 불확실성이 있다. 정밀한 시각이 필요하면 하드웨어 트리거·PPS 같은 별도 수단을 검토해야 한다(7.7).
6.8.8 한계 — 정직하게
micro-ROS로 못 하거나 어려운 것들:
| 한계 | 설명 |
|---|---|
| 완전한 QoS | DDS QoS의 일부만·에이전트가 대신 적용 |
| 대용량 데이터 | 메모리·대역 제약 |
| 복잡한 그래프 | 엔티티 수 한도 |
| 에이전트 의존 | 단일 장애점 |
| 디버깅 | MCU 디버깅 + ROS 디버깅이 겹침 |
| 빌드 복잡도 | 크로스 컴파일·RTOS 통합(4.8) |
| 생태계 | 지원 보드·RTOS가 한정(확인 필요) |
| 실시간 보장 | 전송 지연이 예측 불가할 수 있음 |
마지막 항목이 중요하다. micro-ROS는 MCU를 ROS 그래프에 편입시키는 것이지, 실시간 제어 채널을 만드는 것이 아니다. 시리얼 왕복 지연·에이전트 처리·DDS 전달이 겹치면 지연이 수 ms~수십 ms가 될 수 있다.
견해: 고속 제어 루프를 micro-ROS로 닫으려 하지 말라. MCU에서 제어를 하고(MCU 안에서 닫힌 루프), micro-ROS로는 명령·상태·설정을 주고받는 구조가 맞다. 6.6.7의 "안전 감시를 MCU로 분리"도 같은 발상이다 — 빠른 것은 MCU 안에, 느린 것은 ROS로.
6.8.9 대안 — 커스텀 프로토콜 + 브리지 노드
micro-ROS를 쓰지 않고 같은 목적을 달성하는 방법이다.
비교:
| 축 | micro-ROS | 커스텀 + 브리지 |
|---|---|---|
| MCU 코드 복잡도 | 높음(ROS 스택) | 낮음(단순 프로토콜) |
| MCU 메모리 | 수십 KB+ | 수 KB |
| 브리지 노드 | 불필요(에이전트가) | 작성 필요 |
| 메시지 정의 | ROS 타입 재사용 | 직접 정의 + 변환 |
| 새 토픽 추가 | MCU 코드 수정 | MCU + 브리지 수정 |
| 디버깅 | 스택이 두꺼움 | 단순(바이트를 직접 봄) |
| 표준 준수 | ROS 2 표준 | 자체 |
| 지원 MCU | 제한적 | 거의 모든 MCU |
| 학습 곡선 | 가파름 | 완만 |
커스텀 + 브리지가 나은 경우:
| 상황 | 이유 |
|---|---|
| MCU가 매우 작다 | 8비트·수 KB RAM |
| 주고받는 것이 단순 | 관절 명령·상태만 |
| 이미 프로토콜이 있다 | 벤더 보드·기존 펌웨어 |
| 하드웨어 컴포넌트로 충분 | ros2_control과 통합(6.2) |
| 팀이 MCU 쪽에 익숙 | 학습 비용 |
| 극한 실시간 | 프로토콜을 직접 최적화 |
micro-ROS가 나은 경우:
| 상황 | 이유 |
|---|---|
| MCU가 여러 토픽·서비스를 다룬다 | 매번 프로토콜 확장은 부담 |
| ROS 표준 메시지를 쓰고 싶다 | 타입 재사용 |
| MCU가 상대적으로 크다 | STM32F4급 이상 |
| 파라미터·라이프사이클 필요 | ROS 기능 활용 |
| 여러 MCU를 일관되게 | 표준화 |
| 팀이 ROS 쪽에 익숙 | — |
견해: "모터 드라이버 MCU"라면 커스텀 프로토콜 + ros2_control 하드웨어 컴포넌트(6.2)가 대개 낫다. 주고받는 것이 관절 명령·상태로 정해져 있고, 제어 주기가 중요하며, ros2_control이 이미 추상을 제공한다. micro-ROS의 이점(다양한 토픽·표준 타입)이 발휘될 여지가 적다.
견해: 반대로 "센서 허브 MCU"나 "보조 기능 MCU" — 배터리 상태·환경 센서·LED 제어·버튼 입력을 여러 토픽으로 다루는 경우 — 는 micro-ROS가 어울린다. 토픽이 늘어날 때마다 프로토콜을 확장하는 부담이 없다.
6.8.10 개발 실무
빌드 통합:
# micro_ros_setup 워크플로 (개념 — 확인 필요: 현재 절차)
ros2 run micro_ros_setup create_firmware_ws.sh <RTOS> <보드>
ros2 run micro_ros_setup configure_firmware.sh <앱> -t <전송>
ros2 run micro_ros_setup build_firmware.sh
ros2 run micro_ros_setup flash_firmware.sh
플랫폼별 통합도 있다 — PlatformIO·Arduino·Zephyr·ESP-IDF 등에서 micro-ROS 라이브러리를 쓰는 경로(확인 필요: 지원 상태).
디버깅:
| 방법 | 설명 |
|---|---|
| 에이전트 상세 로그 | -v6 등으로 프로토콜 수준 관찰 |
| MCU UART 로그 | 별도 포트로 디버그 출력 |
| 반환값 검사 + LED | error_loop()에서 LED 패턴 |
| 하드웨어 디버거 | SWD·JTAG |
| 에이전트만 먼저 시험 | ros2 topic list로 노드 등장 확인 |
# 에이전트 상세 로그
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyACM0 -v6
# MCU 노드가 보이는가
ros2 node list
ros2 topic list
ros2 topic echo /mcu_counter
견해:
error_loop()에 LED 깜빡임 패턴을 넣으라. MCU에는 로그가 없거나 보기 어려우므로, 초기화 실패를 눈으로 알 수 있게 하는 것이 개발 속도를 크게 높인다. 오류 코드별로 다른 패턴을 쓰면 더 좋다.
연결 복구:
// 에이전트가 종료됐다 살아나는 경우 처리 (개념)
enum states { WAITING_AGENT, AGENT_AVAILABLE, AGENT_CONNECTED, AGENT_DISCONNECTED };
switch (state) {
case WAITING_AGENT:
state = (RMW_RET_OK == rmw_uros_ping_agent(100, 1))
? AGENT_AVAILABLE : WAITING_AGENT;
break;
case AGENT_AVAILABLE:
state = create_entities() ? AGENT_CONNECTED : WAITING_AGENT;
if (state == WAITING_AGENT) destroy_entities();
break;
case AGENT_CONNECTED:
if (RMW_RET_OK != rmw_uros_ping_agent(100, 1)) state = AGENT_DISCONNECTED;
else rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100));
break;
case AGENT_DISCONNECTED:
destroy_entities();
state = WAITING_AGENT;
break;
}
// (확인 필요: API 이름)
이 상태 기계가 사실상 필수다 — 6.7.2의 드라이버 상태 기계와 같은 구조이며, 없으면 에이전트 재시작 후 MCU가 영영 연결되지 않는다.
6.8.13 apt에 있는 것과 없는 것 — 실측
micro-ROS는 MCU 쪽 실물이 있어야 제대로 검증된다. 이 환경에는 보드가 없으므로 확인할 수 있는 것만 확인하고, 나머지는 못 했다고 밝힌다(실행 확인, Jazzy apt 저장소 조회).
6.8.13.1 에이전트는 apt에 없다
가장 실용적인 확인 결과다. ros2 run micro_ros_agent …를 그대로 따라 하면 패키지를 찾을 수 없다.
| 패키지 | apt(ros-jazzy-*) |
|---|---|
micro_ros_agent | 없음 |
microxrcedds_agent | 없음 |
micro_ros_msgs | 있음 |
micro_ros_diagnostic_msgs | 있음 |
micro_ros_diagnostic_bridge | 있음 |
rclc · rclc_lifecycle · rclc_parameter · rclc_examples | 있음 |
즉 "ROS 2 쪽 조각"은 배포되지만 에이전트는 직접 빌드해야 한다. micro-ROS 공식 문서가 안내하는 대로 워크스페이스에 소스를 받아 colcon build하거나, 제공되는 컨테이너 이미지를 쓰는 것이 실제 경로다.
함정: 이 장의 실행 예시를 그대로 복사하면 첫 줄에서 막힌다.
ros2 run micro_ros_agent는 에이전트를 이미 빌드해 둔 워크스페이스를 소싱한 상태를 전제한다. 명령 자체는 맞지만 선행 단계가 빠져 있었다 — 그 단계가 micro-ROS 도입에서 가장 먼저 만나는 한계다.
6.8.13.2 rclc는 정식으로 배포된다
반대로 rclc 계열은 apt에 정식으로 들어 있다. MCU용 C 클라이언트 라이브러리로 알려져 있지만, 리눅스에서도 그대로 설치·사용할 수 있다는 뜻이다.
| 패키지 | 내용 |
|---|---|
rclc | C 클라이언트 라이브러리 본체(executor 포함) |
rclc_lifecycle | 라이프사이클 노드의 C 판(1.12) |
rclc_parameter | 파라미터 서버의 C 판(1.9) |
rclc_examples | 예제 |
견해: MCU에 탑재하기 전에 리눅스에서
rclc로 먼저 짜 보는 것이 좋은 순서다. 같은 API로 동작을 확인한 뒤 크로스 컴파일로 넘어가면, "MCU라서 안 되는 것"과 "코드가 틀린 것"을 분리할 수 있다. 디버거·로그·ros2CLI를 다 쓸 수 있는 환경에서 논리를 검증하는 편이 훨씬 비용이 낮다.
6.8.13.3 micro_ros_msgs가 드러내는 것
이 패키지가 정의하는 인터페이스는 셋뿐이다(실행 확인):
| 타입 | 뜻 |
|---|---|
micro_ros_msgs/msg/Entity | 노드가 가진 엔티티(발행자·구독자 등) 하나 |
micro_ros_msgs/msg/Node | 노드 하나와 그 엔티티 목록 |
micro_ros_msgs/msg/Graph | 전체 그래프 |
MCU 쪽 노드를 ROS 2 그래프에 드러내기 위한 것이다. 6.8.5가 말한 "에이전트가 디스커버리를 대행한다"는 구조가 여기서 보인다 — MCU는 자기 엔티티 정보를 이 메시지로 알리고, 그래프 관리는 리눅스 쪽이 한다.
함정: 그래서
ros2 node list에 MCU 노드가 보인다고 해서 그 노드가 DDS 참가자인 것은 아니다. 실제 DDS 참가자는 에이전트이고, MCU는 그 뒤에 있다(3.3.5의 "프로세스당 참가자 1개"와 함께 읽으면 구조가 정리된다). 그래서 에이전트가 종료되면 MCU 노드 전체가 그래프에서 사라진다.
6.8.13.4 확인하지 못한 것
정직하게 남긴다. 아래는 이 회차에서 검증하지 못했다 — MCU 보드·시리얼 장치·크로스 컴파일 툴체인이 필요하다.
- 에이전트와 클라이언트의 실제 통신(시리얼·UDP 왕복, 재연결 거동)
- 시간 동기 정밀도(6.8.7) — 전송 지연에 좌우되므로 실측이 필수인 항목
- 메모리 사용량·정적 할당 설정(
colcon.meta)의 실제 효과 - 지원 보드·RTOS 목록의 현재 상태
이 항목들에 붙은 (확인 필요) 표기는 그대로 둔다. 확인할 수 없는 것을 확인한 척하지 않는 편이 낫다(목차 3).
6.8.11 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 반환값 미검사 | 조용히 동작 안 함 | RCCHECK 관용구 |
| 2 | 엔티티 한도 초과 | 초기화 실패 | 빌드 설정 확인 |
| 3 | 메시지 버퍼 미할당 | 크래시 | 수동 메모리 부착 |
| 4 | 무한 배열 사용 | 사실상 불가 | 고정 크기(1.2.3) |
| 5 | 대역폭 초과 | 지연·유실 | 보율·발행률 계산 |
| 6 | USB 지연 타이머 기본값 | 주기 제한 | latency_timer=1(6.7.3.1) |
| 7 | 시간 동기화 없음 | tf·융합 오류 | 주기적 동기화 |
| 8 | 에이전트 재연결 미처리 | 재시작 후 영영 연결되지 않음 | 상태 기계(6.8.10) |
| 9 | 고속 제어를 micro-ROS로 | 지연·지터 | MCU 안에서 제어 |
| 10 | 에이전트 단일 장애점 무시 | 통신 전면 중단 | 감시·재시작(14.8) |
| 11 | 오류 표시 수단 없음 | 디버깅 지옥 | LED 패턴 |
| 12 | 판단 없이 도입 | 복잡도만 증가 | 대안 비교(6.8.9) |
| 13 | ros2 run micro_ros_agent 바로 실행 | 패키지 없음 — apt에 없다 | 소스 빌드 후 워크스페이스 소싱(6.8.13.1) |
9번을 강조한다. micro-ROS는 통신 경로이지 제어 경로가 아니다. 빠른 루프는 MCU 안에서 닫고, ROS와는 명령·상태만 주고받는 구조가 옳다.
6.8.12 체크포인트
- 일반 ROS 2를 MCU에서 못 실행하는 이유 여섯 가지를 말할 수 있는가. 핵심 걸림돌은 무엇인가.
- micro-ROS 아키텍처의 세 구성 요소와, 에이전트가 하는 일을 설명할 수 있는가.
- 에이전트가 단일 장애점이라는 사실의 함의는?
rclcexecutor가rclcpp와 다른 다섯 가지를 말할 수 있는가. 결정적 실행이 왜 장점인가.- micro-ROS에서 컴파일 시점에 정해야 하는 것 다섯 가지는? 한도를 넘으면 어떻게 되는가.
- 메시지 버퍼를 수동으로 할당해야 하는 이유와, 1.2.3의 조언이 여기서 필수가 되는 이유는?
- 전송 선택이 시스템 구조에 미치는 영향을 설명할 수 있는가. 대역폭을 어떻게 계산하는가.
- MCU 시간 동기화가 필요한 이유와, 동기화 없이 발행할 때의 네 가지 결과는?
- micro-ROS의 한계 여덟 가지를 들 수 있는가. "통신 경로이지 제어 경로가 아니다"의 의미는?
- 커스텀 프로토콜 + 브리지가 나은 경우와 micro-ROS가 나은 경우를 각각 네 가지 들 수 있는가.
- "모터 드라이버 MCU"에 어느 쪽을 권하며 왜인가.
- 에이전트 재연결 상태 기계가 필수인 이유는?
- micro-ROS에서 apt로 오는 것과 직접 빌드해야 하는 것은 각각 무엇인가.
micro_ros_msgs가 드러내는 구조는(6.8.13).
다음 장 6.9은 제6부의 마지막 — 진단이다. 이 부에서 반복해 "진단으로 내보내라"고 한 것들을 실제로 어떻게 구현하고 운영에서 활용하는가.