ROS 2에서 센서 보드, 라이다, 모터 컨트롤러, 아두이노, STM32 보드 장치 등을 사용할 때 자주 만나는 문제가 있습니다.
장치를 연결할 때마다 포트 이름이 바뀌는 문제입니다.
예를 들어 어떤 날은 라이다가 다음처럼 잡힙니다.
/dev/ttyUSB0
그런데 다음에 다시 연결하면 이렇게 바뀔 수 있습니다.
/dev/ttyUSB1
또는 여러 USB 장치를 동시에 연결하면 순서에 따라 포트 번호가 달라집니다.
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyACM0
ROS 2 노드에서 /dev/ttyUSB0을 사용하도록 설정해두었는데, 실제 장치가 /dev/ttyUSB1로 잡히면 센서 데이터를 받을 수 없습니다.
이 문제를 해결하는 대표적인 방법이 udev rules를 이용해서 USB 장치에 고정된 심볼릭 링크를 만드는 것입니다.
예를 들어 라이다 장치를 항상 다음 이름으로 접근하게 만들 수 있습니다.
/dev/lidar
센서 보드는 다음처럼 고정할 수 있습니다.
/dev/sensor_board
이렇게 하면 ROS 2 launch 파일이나 YAML 설정 파일에서 매번 바뀌는 /dev/ttyUSB0 대신 고정된 이름을 사용할 수 있습니다.
1. 심볼릭 링크가 필요한 이유
우분투에서 USB-Serial 장치를 연결하면 보통 다음과 같은 장치 파일이 생성됩니다.
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyACM0
하지만 이 이름은 고정되어 있지 않습니다.
USB 장치를 꽂는 순서, 포트 위치, 부팅 타이밍, 다른 USB 장치의 존재 여부에 따라 번호가 바뀔 수 있습니다.
예를 들어 ROS 2 로봇에 다음 장치들이 연결되어 있다고 가정해보겠습니다.
| 장치 | 실제 장치 파일 |
|---|---|
| 라이다 | /dev/ttyUSB0 |
| 센서 보드 | /dev/ttyUSB1 |
| 모터 컨트롤러 | /dev/ttyACM0 |
/dev/ttyACM0는 보통 USB CDC ACM 방식으로 동작하는 시리얼 장치가 인식된 결과입니다.
쉽게 말하면, 장치가 USB로 연결되었지만 리눅스 입장에서는 가상 시리얼 포트처럼 보이는 경우입니다.
[대표적으로 /dev/ttyACM0로 잡히는 장치]
STM32 USB CDC 장치 : STM32 보드가 USB CDC 펌웨어로 동작할 때
Arduino Uno / Mega 계열 : ATmega16U2 USB-Serial 사용 모델
OpenCR 보드 : TurtleBot3에서 사용하는 제어 보드
일부 센서 보드 : USB CDC ACM 클래스로 구현된 보드
USB CDC ACM은 USB 장치를 시리얼 포트처럼 보이게 만드는 표준 방식입니다.
다음은 풀네임입니다.
USB CDC ACM
= USB Communications Device Class – Abstract Control Model
간단히 말하면:
USB 장치인데, PC에서는 COM 포트 / 시리얼 포트처럼 보이게 하는 표준 규격
우분투에서는 보통 이렇게 잡힙니다.
/dev/ttyACM0
윈도우에서는 보통 이렇게 보입니다.
COM3
COM4
COM5
일반 UART 시리얼은 이런 구조입니다.
MCU UART → USB-Serial 칩 → PC
예:
STM32 UART → CP2102 → Ubuntu
이 경우 우분투에서는 보통:
/dev/ttyUSB0
로 잡힙니다.
반면 USB CDC ACM은 MCU가 직접 USB 장치로 동작합니다.
MCU USB Peripheral → PC
예:
STM32 USB FS → Ubuntu
이 경우 우분투에서는 보통:
/dev/ttyACM0
로 잡힙니다.
그런데 재부팅 후에는 다음처럼 바뀔 수 있습니다.
| 장치 | 실제 장치 파일 |
|---|---|
| 센서 보드 | /dev/ttyUSB0 |
| 라이다 | /dev/ttyUSB1 |
| 모터 컨트롤러 | /dev/ttyACM0 |
이 상태에서 라이다 노드가 여전히 /dev/ttyUSB0을 열려고 하면, 실제로는 센서 보드를 열게 됩니다.
결과적으로 다음과 같은 문제가 발생합니다.
Failed to open port /dev/ttyUSB0
또는 노드는 실행되지만 데이터가 이상하게 들어올 수 있습니다.
ROS 2 시스템에서는 이런 문제가 치명적입니다. 특히 라이다, IMU, GPS, 센서 보드, 모터 드라이버처럼 실시간성이 필요한 장치에서는 포트 이름을 고정하는 것이 좋습니다.
2. udev rules란?
udev는 리눅스에서 장치가 연결되거나 제거될 때 장치 파일을 자동으로 관리하는 시스템입니다.
USB 장치를 꽂으면 /dev/ttyUSB0 같은 파일이 생깁니다. 이 작업을 udev가 처리합니다.
udev rules를 사용하면 특정 USB 장치가 연결되었을 때 다음과 같은 동작을 지정할 수 있습니다.
이 제조사의 USB 장치가 연결되면 /dev/lidar 라는 이름도 같이 만들어라.
즉, 실제 장치 파일은 여전히 /dev/ttyUSB0일 수 있지만, 사용자는 항상 다음 이름으로 접근할 수 있습니다.
/dev/lidar
이 /dev/lidar는 실제 장치를 가리키는 심볼릭 링크입니다.
확인해보면 다음과 비슷하게 보입니다.
ls -l /dev/lidar
출력 예시는 다음과 같습니다.
lrwxrwxrwx 1 root root 7 Jun 14 10:20 /dev/lidar -> ttyUSB0
여기서 /dev/lidar는 실제 장치 파일 /dev/ttyUSB0을 가리킵니다.
3. USB 장치 연결 확인하기
먼저 USB 장치를 우분투 PC 또는 라즈베리파이에 연결합니다.
그리고 다음 명령어를 실행합니다.
ls /dev/ttyUSB*
또는 다음 명령어도 확인합니다.
ls /dev/ttyACM*
USB-Serial 칩셋에 따라 장치 이름이 다르게 잡힙니다.
| 장치 형태 | 자주 나타나는 이름 |
|---|---|
| CP210x | /dev/ttyUSB0 |
| CH340 | /dev/ttyUSB0 |
| FTDI | /dev/ttyUSB0 |
| STM32 USB CDC | /dev/ttyACM0 |
| Arduino Uno | /dev/ttyACM0 |
| Pixhawk USB | /dev/ttyACM0 |
| 일부 라이다 | /dev/ttyUSB0 |
예를 들어 라이다를 연결했을 때 다음처럼 보인다고 가정하겠습니다.
/dev/ttyUSB0
4. dmesg로 연결 로그 확인하기
USB 장치를 꽂은 직후 다음 명령어를 실행하면 어떤 장치가 연결되었는지 확인할 수 있습니다.
sudo dmesg | tail -n 30
출력 예시는 다음과 같습니다.
usb 1-2: new full-speed USB device number 5 using xhci_hcd
usb 1-2: cp210x converter now attached to ttyUSB0
여기서 중요한 부분은 다음입니다.
ttyUSB0
즉, 현재 이 장치는 /dev/ttyUSB0로 잡혀 있습니다.
5. USB 장치의 vendor ID와 product ID 확인하기
udev rule을 만들려면 USB 장치를 구분할 수 있는 정보가 필요합니다.
가장 많이 사용하는 정보는 다음 두 가지입니다.
idVendor
idProduct
다음 명령어를 실행합니다.
lsusb
출력 예시는 다음과 같습니다.
Bus 001 Device 005: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
여기서 중요한 값은 다음입니다.
idVendor = 10c4
idProduct = ea60
즉, 이 장치는 Silicon Labs CP210x USB-Serial 장치입니다.
라이다나 센서 보드에서 CP2102, CH340, FTDI 같은 USB-Serial 칩을 사용하면 비슷한 방식으로 보입니다.

6. udevadm으로 더 자세히 확인하기
lsusb만으로도 간단한 rule을 만들 수 있지만, 정확하게 만들기 위해서는 udevadm을 사용하는 것이 좋습니다.
현재 장치가 /dev/ttyUSB0라고 가정하고 다음 명령어를 실행합니다.
udevadm info -a -n /dev/ttyUSB0

만약 장치가 /dev/ttyACM0라면 다음처럼 실행합니다.
udevadm info -a -n /dev/ttyACM0
출력 내용이 길게 나옵니다. 그중에서 다음과 같은 부분을 찾습니다.
ATTRS{idVendor}=="10c4"
ATTRS{idProduct}=="ea60"
ATTRS{serial}=="0001"


장치에 따라 serial 값이 없을 수도 있습니다.
일반적으로 udev rule에서는 다음 정보를 조합해서 사용합니다.
SUBSYSTEM
KERNEL
ATTRS{idVendor}
ATTRS{idProduct}
ATTRS{serial}
7. 가장 기본적인 udev rule 작성하기
이제 udev rule 파일을 만듭니다.
udev rule 파일은 보통 다음 경로에 저장합니다.
/etc/udev/rules.d/

예를 들어 라이다용 rule 파일을 만들겠습니다.
sudo nano /etc/udev/rules.d/99-lidar.rules
다음 내용을 입력합니다.
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="tb3_lidar", MODE="0666"

저장 후 종료합니다.
nano에서는 다음 순서로 저장합니다.
Ctrl + O
Enter
Ctrl + X
이 rule의 의미는 다음과 같습니다.
| 항목 | 의미 |
|---|---|
SUBSYSTEM=="tty" | 시리얼 장치에 적용 |
ATTRS{idVendor}=="10c4" | 제조사 ID가 10c4인 장치 |
ATTRS{idProduct}=="ea60" | 제품 ID가 ea60인 장치 |
SYMLINK+="tb3_lidar" | /dev/lidar 링크 생성 |
MODE="0666" | 일반 사용자도 읽기/쓰기 가능 |
이제 해당 USB 장치가 연결되면 다음 링크가 만들어집니다.
/dev/tb3_lidar
8. udev rules 다시 로드하기
rule 파일을 만들었다고 바로 적용되지는 않습니다.
다음 명령어로 udev rules를 다시 로드합니다.
sudo udevadm control --reload-rules
그다음 udev를 다시 트리거합니다.
sudo udevadm trigger
또는 가장 확실한 방법은 USB 장치를 뽑았다가 다시 꽂는 것입니다.
USB 장치 제거 → 2~3초 대기 → 다시 연결
9. 심볼릭 링크 생성 확인하기
USB 장치를 다시 연결한 후 다음 명령어를 실행합니다.
ls -l /dev/tb3_lidar
정상적으로 설정되었다면 다음과 비슷하게 출력됩니다.
lrwxrwxrwx 1 root root 7 Jun 14 10:20 /dev/tb3_lidar -> ttyUSB0

이제 실제 장치가 /dev/ttyUSB0이든 /dev/ttyUSB1이든 ROS 2에서는 항상 다음 경로를 사용하면 됩니다.
/dev/tb3_lidar
10. ROS 2 노드에서 사용하기
예를 들어 라이다 ROS 2 패키지에서 포트를 설정하는 부분이 있다고 가정해보겠습니다.
기존 설정이 다음과 같았다면,
serial_port: /dev/ttyUSB0
이제 다음처럼 변경합니다.
serial_port: /dev/tb3_lidar
또는 launch 파일에서 다음처럼 사용할 수 있습니다.
Node(
package='lidar_driver',
executable='lidar_node',
name='lidar_node',
parameters=[
{'serial_port': '/dev/tb3_lidar'}
]
)
이렇게 하면 USB 포트 번호가 바뀌어도 ROS 2 노드는 항상 같은 경로로 장치를 열 수 있습니다.
11. 여러 USB 장치에 각각 심볼릭 링크 만들기
로봇에서는 보통 USB 장치를 하나만 쓰지 않습니다.
예를 들어 다음 장치들이 동시에 연결될 수 있습니다.
라이다
IMU
센서 보드
모터 컨트롤러
Pixhawk
GPS
Arduino
STM32 보드
각 장치에 대해 별도 rule을 만들 수 있습니다.
예시입니다.
sudo nano /etc/udev/rules.d/99-robot-usb.rules
파일 내용:
# Lidar
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="lidar", MODE="0666"
# Sensor Board
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="sensor_board", MODE="0666"
# Motor Controller
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="motor_controller", MODE="0666"
적용합니다.
sudo udevadm control --reload-rules
sudo udevadm trigger
USB 장치를 다시 연결한 뒤 확인합니다.
ls -l /dev/lidar
ls -l /dev/sensor_board
ls -l /dev/motor_controller
ls -l /dev/pixhawk
12. 같은 USB-Serial 칩을 여러 개 사용할 때 문제
주의할 점이 있습니다.
여러 장치가 같은 USB-Serial 칩을 사용하면 idVendor와 idProduct가 같을 수 있습니다.
예를 들어 라이다와 센서 보드가 둘 다 CP2102를 사용하면 둘 다 다음과 같이 나올 수 있습니다.
idVendor = 10c4
idProduct = ea60
이 경우 단순히 vendor ID와 product ID만으로는 장치를 구분할 수 없습니다.
이럴 때는 serial 값을 같이 사용해야 합니다.
udevadm info -a -n /dev/ttyUSB0 | grep serial
예시 출력:
ATTRS{serial}=="A50285BI"
이 값을 udev rule에 추가합니다.
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="A50285BI", SYMLINK+="tb3_lidar", MODE="0666"
다른 장치는 다른 serial 값을 사용합니다.
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="A50285CJ", SYMLINK+="sensor_board", MODE="0666"
이렇게 하면 같은 CP2102 칩을 사용하는 장치라도 정확히 구분할 수 있습니다.
13. serial 값이 없는 장치 처리 방법
일부 저가형 USB-Serial 칩은 고유 serial 값을 제공하지 않는 경우가 있습니다.
특히 CH340 계열에서 이런 경우가 있습니다.
이때는 다음 방법을 사용할 수 있습니다.
방법 1. USB 포트 위치로 구분하기
장치가 항상 같은 USB 포트에 연결된다는 조건이라면 물리 포트 경로를 사용할 수 있습니다.
다음 명령어로 장치 정보를 확인합니다.
udevadm info -a -n /dev/ttyUSB0
출력 중 다음과 같은 값을 찾습니다.
KERNELS=="1-2.3"

이 값을 이용해서 rule을 작성할 수 있습니다.
SUBSYSTEM=="tty", KERNELS=="1-2.3", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="sensor_board", MODE="0666"
단, 이 방식은 USB 포트를 바꾸면 링크가 바뀌지 않을 수 있습니다. 로봇 내부 배선이 고정되어 있을 때는 실용적입니다.
방법 2. USB-Serial 칩을 바꾸기
실무적으로는 serial 값을 가진 USB-Serial 칩을 사용하는 것이 좋습니다.
추천되는 칩셋은 다음과 같습니다.
FTDI
CP2102
CP2104
CP2105
일부 STM32 USB CDC
로봇 시스템에서는 값싼 CH340 모듈 여러 개를 섞어 쓰면 나중에 유지보수가 귀찮아질 수 있습니다. 장비가 많아질수록 고유 serial이 있는 USB 장치를 쓰는 편이 안정적입니다.

14. 권한 문제 해결하기
USB 장치를 열 때 다음과 같은 에러가 날 수 있습니다.
Permission denied: '/dev/ttyUSB0'
또는 다음과 같은 메시지가 나올 수 있습니다.
Failed to open serial port
이 경우 두 가지를 확인해야 합니다.
첫 번째는 사용자를 dialout 그룹에 추가하는 것입니다.
sudo usermod -aG dialout $USER
그 다음 로그아웃 후 다시 로그인합니다.
또는 시스템을 재부팅합니다.
sudo reboot
현재 사용자가 dialout 그룹에 들어갔는지 확인합니다.
groups
출력에 dialout이 포함되어 있어야 합니다.
dragon adm dialout sudo
두 번째는 udev rule에 MODE="0666" 또는 GROUP="dialout"을 넣는 것입니다.
예시:
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="lidar", MODE="0666"
또는 다음처럼 작성할 수 있습니다.
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="lidar", GROUP="dialout", MODE="0660"
개발 PC에서는 MODE="0666"을 많이 사용합니다.
제품이나 현장 장비에서는 GROUP="dialout", MODE="0660" 방식이 더 깔끔합니다.
15. TurtleBot3의 udev rules 예시
터틀봇3에서도 라이다나 OpenCR 보드를 사용하기 위해 udev rules 파일을 복사하는 과정이 있습니다.
터틀봇3 설정 과정에서 다음과 유사한 작업을 수행합니다.
sudo cp ./99-turtlebot3-cdc.rules /etc/udev/rules.d/
그 후 udev rules를 다시 로드합니다.
sudo udevadm control --reload-rules
sudo udevadm trigger
이 방식도 원리는 같습니다.
16. udev rule 문법 정리
자주 사용하는 udev rule 문법은 다음과 같습니다.
SUBSYSTEM=="tty"
시리얼 장치에 적용합니다.
KERNEL=="ttyUSB*"
ttyUSB 계열 장치에 적용합니다.
KERNEL=="ttyACM*"
ttyACM 계열 장치에 적용합니다.
ATTRS{idVendor}=="10c4"
USB 제조사 ID를 기준으로 구분합니다.
ATTRS{idProduct}=="ea60"
USB 제품 ID를 기준으로 구분합니다.
ATTRS{serial}=="0001"
USB 장치의 고유 시리얼 번호를 기준으로 구분합니다.
SYMLINK+="lidar"
/dev/lidar 심볼릭 링크를 생성합니다.
MODE="0666"
모든 사용자에게 읽기/쓰기 권한을 부여합니다.
GROUP="dialout", MODE="0660"
dialout 그룹 사용자에게만 읽기/쓰기 권한을 부여합니다.
17. ROS 2 패키지에 udev rules 포함하기
직접 만든 ROS 2 센서 패키지에 udev rules 파일을 포함할 수도 있습니다.
예를 들어 패키지 구조를 다음처럼 구성합니다.
my_sensor_driver/
├── CMakeLists.txt
├── package.xml
├── launch/
├── config/
├── src/
└── udev/
└── 99-my-sensor.rules
99-my-sensor.rules 파일 내용:
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="my_sensor", GROUP="dialout", MODE="0660"
설치할 때는 사용자가 직접 다음 명령으로 복사하게 안내할 수 있습니다.
sudo cp udev/99-my-sensor.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
이 방식은 TurtleBot3에서 udev rules 파일을 복사하는 구조와 비슷합니다.
18. install 스크립트 만들기
반복 작업을 줄이기 위해 설치 스크립트를 만들 수도 있습니다.
예를 들어 install_udev_rules.sh 파일을 만듭니다.
#!/bin/bash
RULE_FILE="99-my-robot.rules"
TARGET_DIR="/etc/udev/rules.d"
echo "Copying udev rules..."
sudo cp ${RULE_FILE} ${TARGET_DIR}/
echo "Reloading udev rules..."
sudo udevadm control --reload-rules
sudo udevadm trigger
echo "Done. Please reconnect USB devices."
실행 권한을 줍니다.
chmod +x install_udev_rules.sh
19. udev rule 삭제 방법
잘못 만든 rule을 삭제하려면 해당 파일을 지우면 됩니다.
예:
sudo rm /etc/udev/rules.d/99-lidar.rules
그 후 udev rules를 다시 로드합니다.
sudo udevadm control --reload-rules
sudo udevadm trigger
USB 장치를 다시 연결합니다.
남아 있는 링크가 있다면 재부팅하거나 장치를 다시 꽂으면 정리됩니다.