2P by GN⁺ | ★ favorite | 댓글 1개
  • USB 기기를 처음 만들 때 필요한 흐름을 물리 배선부터 PCB, USB 2.0 속도, 프로토콜 계층, STM32 실습까지 한 번에 따라갈 수 있게 정리함
  • USB 2.0의 기본 연결은 +5V, GND, D+, D- 네 선이며, D+D-는 차동 쌍으로 함께 1비트를 전달함
  • PCB에서는 차동 쌍의 길이 맞춤, 가까운 배치, 임피던스가 중요하지만, 12 Mbit/s full speed 프로토타입은 비교적 관대하게 동작할 수 있음
  • 실습은 NUCLEO-F103RB에서 PA12USB_DP, PA11USB_DM으로 설정하고, CDC 가상 COM 포트로 인식되는 USB serial 기기를 만든 뒤 1 입력으로 LED를 켬
  • STM32CubeIDE 방식은 UI 기반 코드 생성과 보일러플레이트 결합이 부담이며, Linux 기반 USB 기기 구현은 더 표준화된 API와 분리된 구조를 제공할 수 있음

USB를 처음 구현할 때의 범위

  • 목표는 컴퓨터에 꽂으면 인식되는 USB 기기를 가장 단순한 형태로 끝까지 만들어 보는 것임
  • 여기서 기기는 컴퓨터의 기능을 확장하는 주변 장치를 뜻하고, 컴퓨터는 host로 부름
  • 범위는 USB 기기와 host 사이의 물리 연결부터 host에서 USB 기기와 상호작용하는 간단한 애플리케이션까지 포함함
  • 권위 있는 명세 해설보다는 가장 단순한 E2E USB 기기 제작 프로젝트와 참고 자료 색인에 가까움

USB의 기본 개념

  • USB는 여러 전자기기 사이에서 데이터 교환과 전원 공급을 가능하게 하는 산업 표준임
  • USB는 직렬 버스라서 비트가 병렬이 아니라 하나씩 버스 위로 전달됨
  • 현대 버스는 주로 직렬 방식이며, USB는 host와 device 사이에서 비트를 직렬로 교환하는 방식으로 이해할 수 있음
  • USB는 두 장치를 연결하고 비트를 주고받는 물리 규격뿐 아니라, 데이터 전송과 전원 전달을 위한 통신 프로토콜도 포함함
  • 설명의 초점은 USB 2.0에 맞춰짐

USB 배선과 차동 쌍

  • 일반적인 USB 2.0 연결은 최소 네 선으로 설명됨
    • +5 V: host가 device에 전원을 공급하는 선
    • D-, D+: 두 선이 함께 동작해 1비트를 전달하는 차동 쌍
    • GND: 접지
  • 일부 연결에는 ID 같은 추가 핀이 있을 수 있지만, 실습은 위 네 선만 다룸
  • USB-C에 대한 주의점

    • USB-C도 내부적으로 차동 쌍을 사용하지만, 방향을 바꿔 꽂아도 동작해야 하는 점 등에서 일반 USB 2.0 설명과 차이가 있음
    • USB-C 커넥터를 쓴다고 해서 속도나 USB 버전이 정해지는 것은 아님
    • USB-C 기기라도 USB 2.0일 수 있고, 더 현대적인 USB 3.0 기기일 수도 있음
    • 이후 실습에서는 USB-C를 더 다루지 않음
  • 차동 쌍이 노이즈를 줄이는 방식

    • 단일 선은 전압을 GND와 비교해 비트 값을 표현하지만, 긴 유선 연결에서는 노이즈 영향이 커질 수 있음
    • 차동 쌍은 두 선을 사용하며, 한 선은 V, 다른 선은 -V를 전달함
    • 수신 측은 두 선의 전압 차이를 봄
    • 두 선에 같은 노이즈 Vn이 더해진다고 단순화하면, (V + Vn) - (-V + Vn) = 2V가 되어 노이즈가 상쇄
    • 이 모델은 매우 단순화된 설명이며, 더 깊은 차동 쌍 이해를 위해 Zach Peterson의 Altium YouTube 영상을 참고할 수 있음
    • Video 15

PCB에서 USB를 다룰 때

  • 직접 하드웨어를 만들지 않고 개발 보드를 쓴다면 PCB 섹션은 건너뛸 수 있지만, USB 커넥터를 PCB에 올릴 때의 기본 원칙은 유용함
  • USB 커넥터 라이브러리 부품에는 앞서 언급한 핀들이 있고, 차동 쌍은 마이크로컨트롤러나 SoC의 관련 인접 핀으로 라우팅해야 함
  • 차동 쌍 라우팅의 기본 원칙은 세 가지임
    • D+에서 칩의 plus 핀까지 가는 트레이스와 다른 쪽 트레이스의 길이를 같게 맞춤
    • 두 트레이스를 서로 매우 가깝게 배치함
    • 신호에 맞는 임피던스를 고려함
  • 두 선이 거의 동일한 환경을 지나가도록 만들기 때문에, 앞선 단순 모델에서 두 선의 노이즈가 같다고 가정할 수 있음
  • 임피던스 계산은 제조사의 계산기에 목표 임피던스, 그라운드 플레인과의 거리, 차동 쌍 사이 거리 등을 입력해 필요한 트레이스 폭을 구하는 방식으로 처리할 수 있음
  • Zach Peterson의 차동 쌍 및 USB 라우팅 영상이 참고 자료로 포함됨

USB 2.0의 속도

  • USB 2.0이라고 해서 속도가 하나로 고정되지는 않음
  • USB 2.0은 예를 들어 두 속도 수준을 가질 수 있음
    • full speed: 12 Mbit/s
    • high speed: 480 Mbit/s
  • device와 host는 연결 시 어떤 속도를 사용할지 결정해야 함
  • PCB 위에서 high speed를 안정적으로 달성하려면 임피던스를 맞추는 것이 중요함
  • 기본 프로토타입에서는 12 Mbit/s full speed가 충분할 수 있고, USB 커넥터에서 칩까지의 트레이스가 짧다면 트레이스 폭 등에 비교적 관대할 수 있음

프로토콜과 소프트웨어 계층

  • USB는 하드웨어뿐 아니라 host와 device 양쪽의 소프트웨어 계층도 함께 고려해야 함
  • Linux 관점에서 USB를 설명하는 약 45분 영상이 추천 자료로 포함됨
  • 이 영상은 USB 프레임, endpoint, configuration, 하나의 device가 여러 USB 기능을 수행할 수 있는 방식 등을 다룸
  • USB를 기기들의 네트워크처럼 생각하는 관점이 중요함
  • host 운영체제가 모든 USB 기기별 드라이버를 개별 구현하는 것은 비현실적이므로, 운영체제는 여러 USB device class를 인식함
    • mass storage device
    • serial device
    • 그 외 여러 클래스
  • 실습 기기는 host 관점에서 serial port 기기로 동작함

STM32로 USB serial port 기기 만들기

  • 실습 기기는 host에서 요청을 받으면 LED를 켜는 단순한 USB 기기임
  • host는 이 기기를 serial port device로 인식함
  • 구현 방식은 크게 두 가지로 나뉨
    • USB 지원 마이크로컨트롤러 사용
    • Linux를 실행할 수 있는 SoC를 사용해 커널이 많은 작업을 처리하게 함
  • 실습은 단순성을 위해 마이크로컨트롤러 방식을 사용함
  • NUCLEO-F103RB 보드

    • 개발 보드는 NUCLEO-F103RB를 사용함
    • 미국에서는 Digikey 같은 곳에서 10달러 조금 넘는 가격에 구매할 수 있음
    • 이 보드는 두 부분이 붙어 있는 구조임
    • 작은 쪽은 programmer 영역, 큰 쪽은 실제로 프로그래밍할 main MCU 영역임
    • 보드의 기본 USB 연결은 main MCU가 아니라 programmer 쪽 MCU와 연결됨
    • 이 programmer는 ST-LINK 프로토콜을 사용해 컴퓨터와 USB 메시지를 주고받고, 이후 main MCU를 프로그래밍함
    • 직접 STM32 기반 PCB를 만든다면 보드에 칩 하나만 두고, USB로 프로그래밍한 뒤 같은 USB 포트를 기기 로직에 사용할 수도 있음
    • STM32 기반 PCB와 USB 프로그래밍 참고 자료로 Phil’s Lab 영상이 포함됨
    • Video 19
    • Video 20

실제 USB 포트 배선

  • Nucleo 보드의 기본 USB 연결은 main MCU에 연결된 것이 아니므로, main MCU의 USB 포트를 직접 host와 연결해야 함
  • 프로그램을 올린 뒤에는 ST-LINK programmer를 컴퓨터에서 분리하고, main MCU를 실제 USB 포트로 전원 공급함
  • 소프트웨어 작성에는 STM32CubeIDE, 보드에 올리는 작업에는 STM32CubeProgrammer를 사용함
  • 필요한 핀과 설정

    • USB 2.0 기기를 만들기 위해 필요한 핀은 네 가지임
    • host에서 들어오는 5V 전원
    • GND
    • D+
    • D-
    • NUCLEO-F103RB에서 main MCU 쪽 USB 포트로 전원을 받으려면 JP5 점퍼를 바꿔 보드가 external 5 V 공급을 받도록 설정해야 함
    • STM32CubeIDE에서 다음처럼 핀을 설정함
    • PA12USB_DP
    • PA11USB_DM
    • 이 보드의 칩은 USB 연결을 위해 외부 1.5 kΩ 풀업 저항이 필요함
    • 저항은 PA12를 3.3V로 pull-up함
    • breadboard로 이 회로를 구성함
    • MacBook Pro와 개별 핀을 연결하기 위해 Amazon의 USB breakout cable을 사용했고, terminal block을 제거해 USB 핀을 노출시킨 뒤 jumper wire로 Nucleo 보드와 breadboard에 연결함
    • 차동 쌍 길이 맞춤과 임피던스 제어를 엄격히 지키지 않아도, 이 실습에서는 12 Mbit/s 연결이 가능했음

USB CDC 소프트웨어 작성

  • CubeIDE에서 USB 핀을 설정하면 clock 설정 관련 알림이 나오며, CubeIDE가 자동으로 처리하는 옵션을 선택할 수 있음
  • Pinout & ConfigurationMiddleware and Software Packs 아래에서 USB_DEVICE를 설정함
  • 실습에서 중요한 모드는 다음과 같음
Communication Device Class (Virtual Port Com)
  • 이 설정으로 Nucleo 보드는 host 관점에서 CDC serial port 기기로 동작함
  • host는 이 클래스 정보를 바탕으로 custom device와 통신할 적절한 드라이버를 설정할 수 있음
  • CubeIDE는 C 코드를 생성하며, main.c에는 다음 초기화 호출이 포함됨
MX_USB_DEVICE_Init();
  • LED를 켜기 위해 CDC_Receive_FS 루틴에 다음 코드를 추가함
/* USER CODE BEGIN 6 */
if (Buf[0] == '1') {
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
}
  • 이 HAL 호출은 port A의 pin 5에 연결된 onboard LED를 켬
  • 단계별 참고 영상은 다음과 같음

플래싱과 실행

  • ELF 파일을 빌드한 뒤 CubeProgrammer로 보드에 코드를 올림
  • 이후 programmer를 분리하고, 앞서 설명한 방식대로 external 5V 공급을 사용해 보드를 배선함
  • 보드가 켜지면 운영체제의 device manager에서 COM port나 Serial port와 비슷한 항목으로 나타나야 함
  • CubeIDE의 USB_DEVICE middleware 메뉴에서 Device Descriptor 값을 바꾸면 운영체제 device manager에 표시될 custom device name을 설정할 수 있음
  • Mac OS에서는 새 기기가 /dev 파일 시스템 아래에 나타남
    • 예시 경로: /dev/tty.usbmodem497A0F6739561
  • Linux에서는 /dev/ttyUSB0 같은 이름으로 보일 수 있음
  • Minicom으로 serial device에 연결하는 예시는 다음과 같음
minicom --device /dev/tty.usbmodem497A0F6739561
  • 연결 후 키보드에서 1을 입력하면 Nucleo 보드의 초록 LED가 켜짐

STM32 방식과 Linux 방식의 소프트웨어 차이

  • 실습 결과는 mainstream 운영체제에서 인식되는 USB serial port device를 만드는 것임
  • STM32CubeIDE 기반 방식에는 소프트웨어 엔지니어링 관점의 불편함이 있음
    • UI 메뉴를 클릭해 많은 보일러플레이트를 생성해야 함
    • InitUsbDevice(UsbClass.CDC)처럼 코드에서 유연하게 파라미터화된 라이브러리를 쓰는 방식이 아님
    • 생성 코드가 사용자 코드와 강하게 결합되어 코드 리뷰가 어려워질 수 있음
    • 새 버전이 나왔을 때 보일러플레이트를 어떻게 갱신할지 명확하지 않음
    • 현재 설정은 STM32 생태계에 강하게 묶임
  • Linux가 USB device처럼 동작하는 방식은 더 깔끔한 접근으로 평가됨
    • Linux API는 더 견고하고 표준화되어 있음
    • pseudo-file 및 system call과의 상호작용을 기반으로 할 수 있음
    • user space와 kernel space가 분리됨
    • Linux를 HAL 계층처럼 볼 수 있음
  • 다만 가볍고 저렴하며 생산하기 쉬운 USB 기기가 필요한 경우 Linux SoC는 무거울 수 있고, 많은 사용 사례에서는 과할 수 있음
  • bare metal USB 기기 개발에는 더 portable하고 덜 opinionated한 프레임워크가 있으면 좋겠다는 결론으로 이어짐

댓글과 토론

Hacker News 의견들
  • USB 입문 글로는 아주 좋지만 ST 마이크로컨트롤러 사용에 많이 치우쳐 있고, 최근 ESP32 생태계처럼 USB 장치를 쉽게 붙여 쓸 수 있는 방식에 비하면 단계와 도구 체인이 훨씬 많아 보임
    차동 쌍은 직접 여러 USB 보드를 만들어 본 입장에선 초보자가 크게 걱정할 부분은 아니고, 주로 고속 작업에서 중요해짐
    Arduino/ESP에서 흔히 쓰는 USB 컨트롤러 IC가 많은 세부사항을 처리해 주므로, 첫 장치를 만드는 사람에게 계산까지 시키는 건 과한 느낌

    • 최근 ATmega32U4가 달린 Arduino Pro Micro로 손배선 매크로 패드를 만들었는데, 커스텀 키보드 만드는 취미층에서 꽤 인기 있는 듯함
      초보자에게 빠르고 재미있는 프로젝트였고, 제일 귀찮았던 부분은 나무 케이스를 깎는 일이었음
    • 차동 쌍과 임피던스에 대한 기본 이해는 있으면 좋다고 봄
      실제 계산은 거의 해본 적 없고, 쓰는 ECAD 소프트웨어가 차동 쌍 라우팅과 임피던스 신호 분석 도구를 제공함
      그래도 트레이스를 아주 짧게 유지하면 보통은 큰 문제가 안 됨
    • USB 컨트롤러 IC를 추천받고 싶음
      보통은 USB 내장 마이크로컨트롤러만 써왔음
  • 오래전에 USB 적합성 테스트를 했는데, 자주 문제를 본 항목 중 하나가 돌입 전류 테스트였음
    기본적으로 5V 쪽 바이패스 커패시터가 너무 많은 경우였고, 글에서는 이 부분을 못 본 듯함
    고속 디지털 설계에 집중하기 쉽지만, 적합성에서는 가끔 덜 멋져 보이는 부분이 발목을 잡음
    최신 표준에서는 어떻게 동작하는지 모르겠지만 여전히 테스트는 있는 것 같고, 글 자체는 좋았음
    https://compliance.usb.org/index.asp?UpdateFile=Electrical#:...

    • 돌입 전류 제한에는 어떤 부품을 쓰는지 궁금함
      트랜지스터 몇 개로 전류 제한기를 만들 수는 있겠지만, 온도 보호 같은 기능이 붙은 더 나은 집적 솔루션이 있을 것 같음
  • USB-C에 대해 글의 내용을 보태자면, CC 핀을 적절한 저항에 연결해야 함
    안 그러면 아마 동작하지 않을 가능성이 큼
    그리고 차동 라우팅과 임피던스는 USB 2.0에서는 그렇게 큰일은 아님
    트레이스 길이를 비슷하게 맞추고, 합리적으로 직접 연결하고, 가능하면 서로 붙여 두면 됨
    길이·트레이스 폭 미세 조정, 임피던스 제어, RF 모범 사례까지 과하게 신경 쓸 필요는 없고 그냥 네트를 연결하면 됨

    • 480Mbit/s면 충분히 빠르기 때문에 모범 사례와 대략 10% 이내의 임피던스 정합은 신경 써야 함
      많은 MCU는 USB PHY 핀과 커넥터 사이에 직렬 저항도 필요함
      24mil 대신 20mil 트레이스를 써야 해서 보드를 다시 만들 일까지는 아마 없겠지만, USB 2.0 차동 쌍 레이아웃은 부담이 낮은 편이어도 제대로 하려고 노력하는 게 좋은 연습이 됨
  • ARM 프로세서처럼 납땜이 까다로운 부품이 걱정된다면, 꼭 그렇게 큰 부품을 쓸 필요는 없음
    STM32는 성능이 필요할 때 좋지만, 작은 작업에는 더 작은 컨트롤러가 나을 수 있음
    예를 들어 VUSB는 작은 Atmel 마이크로컨트롤러에서 USB를 비트뱅잉하는 라이브러리임: https://www.obdev.at/products/vusb/index.html
    학생들에게 Linux 커널 모듈 프로그래밍을 가르칠 때 쓴 예제 보드 회로도도 있음: https://gitlab.cs.fau.de/i4/passt/passtboard-v2
    펌웨어는 http://www.poempelfox.de/ds1820tousb/https://gitlab.cs.fau.de/i4/passt/ds1820tousb
    Arduino식 프로그래밍을 선호한다면 포함 라이브러리 몇 줄만으로 USB 장치처럼 쓸 수 있는 보드도 많고, 예를 들면 https://www.az-delivery.de/en/products/digispark-board가 있음

    • 납땜이 걱정이라면 마이크로컨트롤러 USB 포트가 이미 붙어 있고 동작하는 개발 보드를 꽤 합리적인 가격에 살 수 있음
      예를 들면 NUCLEO-F429ZI가 있음: https://www.st.com/en/evaluation-tools/nucleo-f429zi.html
      글쓴이가 쓴 NUCLEO-F103RB와 매우 비슷하지만, 내장 프로그래머/디버거용 상단 USB 커넥터 외에 하단에도 마이크로컨트롤러에 바로 연결된 USB 커넥터가 있음
      ESD 보호 같은 선택을 참고하고 싶다면 보드 회로도도 내려받을 수 있음
    • CH32V203 같은 것도 있음
      하드웨어 USB가 들어간 TSSOP-20이고, 단품 가격이 약 0.81달러임
      다만 소프트웨어 쪽은 조금 더 까다로울 수 있음
      https://www.wch-ic.com/products/CH32V203.html?
  • MCU에서 베어메탈 USB 코드를 작성해 봤는데, SPI나 I2C 같은 단순 디지털 프로토콜과 비교하면 꽤 충격적이었음
    물리 계층과 데이터 링크 계층은 CAN보다 훨씬 복잡한 정도는 아니지만, 그 이후로 바로 디스크립터, 엔드포인트, 드라이버 설정의 벽을 만남
    USB는 처음부터 PC용 플러그 앤 플레이 생태계로 설계됐고 그 특징이 뚜렷함
    가능하면 벤더 제공 소프트웨어를 최대한 쓰는 게 확실히 좋은 길임
    희미하게 기억나는 팁으로는, 높은 처리량에는 벌크 전송을 쓰고 등시성 전송은 쳐다보지도 않는 게 좋음
    USB는 마스터/슬레이브 프로토콜이라 최대 처리량이 안 나오면 보통 호스트인 PC 쪽 원인이 많음
    라이선스인 LGPL이 요구사항과 맞는다면 libusb는 꽤 쓰기 쉬움
    벤더 드라이버를 쓰지 않는다면 하드웨어 USB 프로토콜 분석기가 정말 유용하고, USB in a NutShell은 프로토콜 이해용 웹 참고자료로 괜찮음: https://www.beyondlogic.org/usbnutshell/usb1.shtml

  • STM32에서 USB를 다루는 김에 묻자면, 한 프레임에서 64바이트 초과 수신을 어떻게 지원하는지 아는 사람이 있는지 궁금함
    지금은 소프트웨어에서 64바이트 프레임을 반복 처리하고 있지만, 더 크게 가능한 건 알고 있음
    아마 최대 1MB까지였던 것 같음
    문제는 Reference Manual에 이 설정들이 일반 레지스터가 아니라 일종의 의사 레지스터로 나와 있다는 점임
    쉬운 우회 방법이 있는지 궁금함
    OTG가 아닌 USB 주변장치 기준임

    • USB 풀스피드 벌크 전송은 64바이트로 제한됨
      어떤 STM32를 말하는지는 모르겠지만, stm32g4는 풀스피드만 지원함
  • 주로 ESP32를 쓰지만, 여러 프로젝트에 맞는 저렴한 해킹 방식이 있음
    아주 빠르고 쉬운 커스텀 컨트롤러가 필요하면 버려진 USB 키보드의 컨트롤 보드를 떼어 쓰는 것도 고려할 만함
    납땜 대신 전도성 접착제로 접점에 선을 붙이고, 글루건으로 단단히 고정하면 됨
    스페이스바 입력을 보내는 아케이드 버튼 기반 1버튼 게임 컨트롤러를 저렴하지만 꽤 튼튼하게 만들어 봤고, 디바운스 등은 다 처리되며 코드는 필요 없음

  • USB 3 지원 개발 보드가 있는지 궁금함
    USB-C 모니터 싱크를 프로토타이핑하려고 하는데, USB로 DisplayPort를 받을 만한 성능의 보드를 찾기 어렵고 있음

    • USB로 DisplayPort 수신을 하는 데는 큰 성능이 필요하지 않음
      이미 DisplayPort 신호를 받을 수 있거나 외부 모니터로 라우팅할 수 있다는 전제임
      USB 빌보드 장치를 구현하고, 올바른 대체 모드를 신호하면 됨
      동작 자체에는 선택 사항이지만, 기억상 명세상으로는 필요함
      그러면 USB-C 플러그에 DisplayPort 신호가 나오고, 맞는 AUX 선을 DisplayPort 커넥터에 연결하면 됨
    • 이런 보드가 있음: https://octopart.com/cyusb3kit-003-cypress+semiconductor-494...
      가격도 합리적으로 보임
      2년 전에 비슷한 걸 찾았을 때는 가격이 한 자릿수 더 비쌌음
    • 트랜시버가 있는 대부분의 FPGA로 가능함
  • Raspberry Pi를 PC에 꽂아 두고 https://github.com/xairy/raw-gadget으로 가상 USB 장치를 만들고 있음
    지금은 독점 소프트웨어를 속이기 위해 MTP 카메라를 에뮬레이션하는 데 쓰는 중임

  • Raspberry Pi Zero와 Linux 커널의 복합 USB로 USB 장치를 몇 가지 프로토타이핑해 봤음
    적어도 저장장치와 직렬 장치는 꽤 쉽게 동작시킬 수 있었음
    Raspberry Pi에서 복합 커널 모듈을 초기화하는 셸 스크립트 같은 것이 필요하고, 상용구 코드는 커널 문서에서 찾을 수 있음

    • Pi로 웹캠 같은 가상 USB 장치를 만들어 보고 싶음
      pikvm도 흥미로운 프로젝트임
      PC에 연결하면 USB 연결이 키보드와 마우스처럼 동작할 뿐 아니라, 시스템 부팅에 쓸 수 있는 USB 드라이브가 될 수도 있음
      설치 작업에 꽤 흥미로운 방식임