What is USB?
PC의 구조를 조금 이해해 보겠습니다.
조금 극단적으로 말해서 ,아두이노의 Atmega328P가 더 커지고, 기능에 따라 분화되면 우리는 이걸 PC라고 부를 수 있다. SoC 내부의 요소들이 특정 기능에 따라 분화되면 그걸 PC라고도 부를 수 있는 것입니다.
아두이노를 사용해봤다면, 여기에서 주변 기기(peripheral)과 통신할 수 있는 다양한 방법들(TTL, I2C, RS2323/422,485, SPI, PWM) 등이 있다는 것을 알 수 있을 것입니다. MCU 레벨에서는 GPIO를 통해 이런 방식으로 통신한다 하면, PC에서는 이런 역할을 PCI-E 레인을 통해서 수행한다고도 볼 수 있습니다.
PCI-E 레인을 통해서 PC는 주변 기기와 통신할 수 있습니다. USB, 그래픽카드, 사운드카드가 그 중 하나인데, CPU에서 할당된 pci 레인은 메인보드에 내장된 DAC와 연동됩니다. 그렇게 우리는 소리를 들을 수 있습니다.
마찬가지로, PCI-E 레인을 통해서 우리는 USB를 사용할 수 있습니다. 참고로 USB는 범용 직렬 버스(Universal Serial Bus)의 약자입니다. 윈도우 98을 본 적 있는 아재들이라면 알 수 있을텐데, USB 이전에는 8핀이 사용되는 RS232 Parallel port 가 사용되었습니다. 통신 핀을 8개를 쓰는게 너무 번거로워서 이후에 사용된게 바로 usb 인 것입니다.
PC의 주변장치는 어떤 통신 방식을 활용하는가
요구되는 통신 대역폭에 따라 사용되는 연결 방식이 달라집니다. 대부분의 경우는 USB 자체가 제공하는 대역폭이 충분하기에, 문제가 되는 경우는 없습니다. 다만, 웹캠이 대역폭을 크게 잡아먹는 요소 중 하나가 되니, 웹캠을 3-4개 이상 연결하는 경우는 우리가 usb의 대역폭에 대해 고민해봐야 할 것입니다.
LiDAR은 pointcloud를 UTP 케이블을 통해 UDP로 전송합니다. Velodyne VLP-16 Puck 의 경우는 약 25Mbps 정도의 대역폭을 가집니다. 이정도 대역폭이면 USB를 활용해도 무방합니다. 물론 당장의 장비 성능을 고려한다면 USB로 충분할 수 있습니다. 그러나 추후 32채널 혹은 64채널 LiDAR의 활용, 그리고 다수의 LiDAR 활용을 고려한다면 UTP 케이블 활용도 전략적인 선택임을 알 수 있습니다.
우분투에서 USB 장치 인식하고 사용하기
우분투에서는 일반 Serial 통신을 위한 포트를 /dev/ttyUSB*에 할당한다. 그리고 *에는 숫자가 들어간다. 숫자는 먼저 연결한 순서대로 매겨집니다.
여기에 장치 하나를 더 추가하면 추가된 장치는 /dev/ttyUSB1의 경로를 통해 통신할 수 있습니다. 여기에서 문제는…뭐가 어떤 장치인치 구분이 안되는 것입니다. 따라서 여기에서 우리는 각 장치의 특성을 파악하여 udev rule을 지정하거나, 혹은 연결된 장치의 위치에 따라 고유값을 부여받을 수 있습니다. 예를 들어, 위의 /dev/ttyUSB0은 다음의 경로로도 접근할 수 있습니다.
ls /dev/serial/by-id를 사용하면, usb-1a86_USB_Serial-if00-port0 가 나오는 것을 확인할 수 있습니다. 여기에서 우리는 1a86을 눈여겨 봅시다.
lsusb를 통해 장치에 연결된 장비들을 확인해봅시다. 아까 봤던 1a86에 해당하는 장비의 이름이 QinHeng Electronics HL-340 USB-Serial Adapter로 나타난 것을 볼 수 있습니다.
💡 여기에서 1a86:7523은 각각 VendorID와 ProductID를 의미합니다. 이 값을 중복될 수도 있기 때문에, 연결된 장비의 고유값이라 생각해서는 안됩니다. 예를 들어, FT232 칩을 사용하고, 그 칩에는 동일한 VendorID와 ProductID, S/N가 부여되어 있기에, 구분할 수 없습니다.
ls /dev/serial/by-id는 장비를 구분할 수는 있는 좋은 방법이지만. 동일한 정보를 가진 가짜 칩을 사용하는 경우에는 그게 불가능함을 확인했습니다. 보통 멀쩡하게 만드는 공식적인 logitech 웹캠 등은 이런 식으로 구분이 되지만, 정체불명의 RS232 to Serial/USB 케이블들 사용하는 경우 이런 문제를 겪을 확률이 높습니다.
그럼 연결된 장비가 gps인지 imu인지 뭔지 어떻게 아느냐? 정말 다행히도, 연결된 usb 장비의 고유값을 소프트웨어적으로 활용할 수 있는 방법이 단 하나 남아있습니다. 바로 연결된 위치를 통해 구분하는 것입니다. 특정 위치의 usb 포트에 연결된 장비를 ls /dev/serial/by-path를 사용하여 접근할 수 있습니다. 한가지 아쉬운 게 있다면, ls /dev/serial/by-id 는 포트에 상관 없이 움직이지만, ls /dev/serial/by-path는 그 포트가 아니면 접근이 불가능하다는것입니다. 물론 장비라는게 한 번 연결하면 그 순서를 바꾸는 일이 없다는 것을 위안 삼아야겠습니다..
다음의 결과를 보시면 앞서 빌드업 쌓아놓은것에 따라, pc는 pci 레인에 연결된 usb 드라이버의 특정 포트에 연결된 장비를 이렇게 할당하고 있습니다.
이제 여러분들께서는 연결된 장비를 두 가지 방법으로 활용하실 수 있습니다.
- 장비를 /dev/ttyUSB0혹은 1로 불러오기
- 장비를 /dev/serial/by-id/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0로 불러오기
- 장비를 /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0로 불러오기
당연히, 장비의 고유성을 확보하기 위해서는 후자를 써야 합니다. 아니면 별도의 udev rule을 지정하여, 특정 장비의 연결 주소를 /dev/imu 등을 통해 확인할 수 있습니다.
카메라의 대역폭
우리가 흔히 사용하는 UVC 카메라는 USB의 D+, D- 단 두 포트를 통해 PC로 영상 정보를 송신합니다. 이게 가능하냐구요? 놀랍게도 당장 10년 전까지만 해도 우리는 아래의 컴포지트 포트로 스카이라이프연결해서 짱구 핑구 데빌구 뽀로로 잘만 봤습니다.
다만, 우리가 한 가지 알아야 할 게 있습니다. 선 두개로 통신하는데는 어느정도 제약이 있다는 겁니다.
우리가 영상 한 픽셀을 표현하는데 어느 정도의 정보가 필요할까요? 한 픽셀은 RGB, 각각 8비트씩, 총 24비트 정보가 필요합니다. 한 픽셀에 3byte가 필요하고, vga(640x480) 해상도는 가로 640픽셀, 세로 480픽셀의 정보가 필요합니다. 만약 우리가 640480 픽셀의 전체 화면을 업데이트 하는데는 921,600byte의 대역폭이 필요합니다. 7.372Mbit 의 정보가 필요합니다. 카메라는 여러 장을 실시간으로 송출하기에, 초당 15프레임의 데이터를 송출한다면, 110.58Mbps의 대역폭을 소모합니다. 초당 30프레임을 사용한다면 220Mbps를 사용할 것입니다. 이렇게 카메라 2개만 사용해도 USB 2.0의 이론상 대역폭인 540Mbps에 근접해집니다.
물론, 웹캠에서도 나름대로 MJPG, H264같은 포맷을 압축해서 보냅니다만.. 결국에는 vga 해상도 카메라 2개만 연결해도 이론상, 이론상의 대역폭에 근접해버린다는 겁니다. 실제 대역폭은 이론상 대역폭에 한참 못미치기에, 사실 카메라 2개 640*480@30fps으로 연결하는 것도 벅차다는 결론에 이를 수 있습니다.
네? 그러면 카메라 6개씩 연결하고 그런건 어떻게 했냐구요? 그거야 PC에 usb 루트 허브가 단 하나만 있었다면 불가능했겠지만,
당장 라즈베라파이만 해도 usb 3.0 버스, 2.0 버스가 구분되어 있고, 보시면 아시겠지만 각각의 Bandwidth가 5G, 480M 으로 고정되어 있습니다. 라즈베리파이보다 성능이 월등히 좋은 PC는 당연히 더 많은 버스를 확보하고 있습니다. 예를 들어 제 PC를 한 번 보겠습니다.
이 부분을 제가 깊게 파보지는 않았지만, 라즈베리파이처럼 usb 버전별로 하나의 버스만 연결되어 있는 것은 아님을 알 수 있습니다.
/dev/video* 문제들
카메라를 연결하면 /dev/ttyUSB와 비슷하게 /dev/video으로 웹캠 데이터에 접근할 수 있습니다. 이 사례가 너무 많아서 openCV에서는 VideoCapture(’/dev/video0’)을 그냥 VideoCapture(0)이라고도 해도 작동할 정도입니다.
다만.. 여기에서도 위에서 말씀드렸던 /dev/ttyUSB* 처럼 /dev/video*로 웹캠에 접근하면 연결된 장치의 고유성을 확인할 수 없는 문제가 재현됩니다. /dev/ttyUSB0이 imu, /dev/ttyUSB1을 gps라 생각하고 개발을 해도 막상 실사용시에는 그게 꼬일 수 도 있다는 겁니다. 하다 못해 imu나 gps는 데이터 포맷이 아예 다르니 조기에 문제를 발견한다 쳐도, 웹캠은 그러면 조금 귀찮아집니다.
따라서, 바로 /dev/v4l/by-path 혹은 /dev/v4l/by-id에 할당된 경로를 바로 사용하면 이러한 문제를 피할 수 있습니다.
웹캠 사용시 /dev/video0 대신 /dev/v4l/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-video-index0을 사용하면 연결된 웹캠의 고유성을 확보할 수 있습니다
v4l2ctl 명령어들
위의 웹캠 명령어를 보면 v4l이 있는것을 볼 수 있습니다. v4l은 video4linux의 약자입니다.
sudo apt install v4l-utils
설치하시면 다음의 명령어들을 사용하실 수 있습니다.
v4l2-ctl --list-devices
.
사용가능한 /dev/video* 경로를 보여줍니다.
v4l2-ctl --list-formats-ext
웹캠이 지원하는 해상도와 프레임을 보여줍니다.
Done!
평소에 이런 고민을 할 분들은 PC를 하드웨어 레벨에서 적극 활용하는 것이 필요한분들일 것입니다. 제 경우 자율주행차의 IMU, GPS, LiDAR, Encoder, webcam 데이터를 받아오느라 이런 문제를 겪다가 조금 정리해볼 필요가 있어서 이렇게 남겨둡니다. 여러분의 개발과 시스템의 이해에 도움이 되었으면 좋겠습니다.
긴 글 읽어주셔서 감사합니다. ❤️와 광고 클릭으로 고마움을 간단히 표현할 수 있습니다.
개발환경(Desktop) | Ryzen 5900X, RTX3080
개발환경(Laptop) | M1 MacBook Air / Mac OS 13 Ventura, Python 3.10
제품 개발 및 기타 문의 | dokixote@wklabs.io 혹은 오른쪽 아래 채팅을 통해 문의