2 điểm bởi GN⁺ 2024-06-04 | 1 bình luận | Chia sẻ qua WhatsApp
  • Bài viết tổng hợp luồng công việc cần thiết khi lần đầu làm thiết bị USB, từ đấu dây vật lý, PCB, tốc độ USB 2.0, các tầng giao thức đến thực hành với STM32 để có thể làm theo trọn vẹn trong một mạch nội dung
  • Kết nối cơ bản của USB 2.0 gồm bốn dây +5V, GND, D+, D-, trong đó D+D- tạo thành một cặp vi sai để cùng truyền 1 bit
  • Trên PCB, các yếu tố quan trọng là cân bằng chiều dài của cặp vi sai, bố trí gần nhau và trở kháng, nhưng với nguyên mẫu full speed 12 Mbit/s thì hệ thống có thể hoạt động khá dễ tính
  • Phần thực hành cấu hình PA12 thành USB_DP, PA11 thành USB_DM trên NUCLEO-F103RB, tạo một thiết bị USB serial được nhận diện như cổng COM ảo CDC, rồi bật LED khi nhập 1
  • Cách làm với STM32CubeIDE gây nặng nề vì tạo mã bằng UI và gắn chặt với boilerplate, trong khi cách triển khai thiết bị USB dựa trên Linux có thể cung cấp API chuẩn hóa hơn và cấu trúc tách biệt hơn

Phạm vi khi lần đầu triển khai USB

  • Mục tiêu là tự làm đến cùng một thiết bị USB ở dạng đơn giản nhất, sao cho khi cắm vào máy tính thì thiết bị được nhận diện
  • Ở đây, thiết bị là một ngoại vi mở rộng chức năng của máy tính, còn máy tính được gọi là host
  • Phạm vi bao gồm từ kết nối vật lý giữa thiết bị USB và host cho đến một ứng dụng đơn giản trên host để tương tác với thiết bị USB
  • Nội dung này gần với một dự án tạo thiết bị USB E2E đơn giản nhất kèm chỉ mục tài liệu tham khảo hơn là phần diễn giải theo đặc tả có tính thẩm quyền

Khái niệm cơ bản về USB

  • USB là một tiêu chuẩn công nghiệp cho phép trao đổi dữ liệu và cấp nguồn giữa nhiều thiết bị điện tử
  • USB là một bus nối tiếp, nên bit không được truyền song song mà đi lần lượt từng bit trên bus
  • Các bus hiện đại chủ yếu dùng kiểu nối tiếp, và có thể hiểu USB là cách host và device trao đổi bit theo kiểu nối tiếp
  • USB không chỉ là đặc tả vật lý để nối hai thiết bị và truyền nhận bit, mà còn bao gồm giao thức truyền thông cho việc truyền dữ liệu và cấp nguồn
  • Phần giải thích tập trung vào USB 2.0

Đấu dây USB và cặp vi sai

  • Một kết nối USB 2.0 thông thường được mô tả với tối thiểu bốn dây
    • +5 V: đường mà host dùng để cấp nguồn cho device
    • D-, D+: cặp vi sai gồm hai dây cùng hoạt động để truyền 1 bit
    • GND: mass
  • Một số kết nối có thể có thêm chân như ID, nhưng phần thực hành chỉ dùng bốn dây trên
  • Lưu ý về USB-C

    • USB-C cũng dùng cặp vi sai ở bên trong, nhưng khác với mô tả USB 2.0 thông thường ở chỗ phải hoạt động cả khi cắm đảo chiều
    • Việc dùng đầu nối USB-C không tự quyết định tốc độ hay phiên bản USB
    • Ngay cả thiết bị USB-C vẫn có thể là USB 2.0, hoặc cũng có thể là thiết bị USB 3.0 hiện đại hơn
    • Các phần thực hành sau đó sẽ không bàn thêm về USB-C
  • Cách cặp vi sai giảm nhiễu

    • Một dây đơn biểu diễn giá trị bit bằng cách so sánh điện áp với GND, nhưng trên kết nối dây dài thì nhiễu có thể ảnh hưởng lớn
    • Cặp vi sai dùng hai dây, một dây mang V, dây kia mang -V
    • Phía nhận sẽ nhìn vào độ chênh điện áp giữa hai dây
    • Nếu đơn giản hóa rằng cùng một nhiễu Vn được cộng vào cả hai dây, thì (V + Vn) - (-V + Vn) = 2V, nghĩa là nhiễu bị triệt tiêu
    • Mô hình này là phần giải thích đã được đơn giản hóa rất nhiều; để hiểu sâu hơn về cặp vi sai có thể tham khảo video YouTube của Zach Peterson trên Altium
    • Video 15

Khi xử lý USB trên PCB

  • Nếu không tự làm phần cứng mà dùng board phát triển, có thể bỏ qua phần PCB, nhưng các nguyên tắc cơ bản khi đặt đầu nối USB lên PCB vẫn rất hữu ích
  • Linh kiện thư viện cho đầu nối USB có các chân đã nêu ở trên, và cặp vi sai cần được route đến các chân liên quan nằm gần nhau trên vi điều khiển hoặc SoC
  • Có ba nguyên tắc cơ bản khi route cặp vi sai
    • Cân bằng chiều dài giữa trace đi từ D+ đến chân plus của chip và trace còn lại
    • Đặt hai trace thật gần nhau
    • Tính đến trở kháng phù hợp với tín hiệu
  • Vì hai dây đi qua môi trường gần như giống hệt nhau, nên trong mô hình đơn giản phía trên có thể giả định nhiễu trên hai dây là như nhau
  • Việc tính toán trở kháng có thể thực hiện bằng cách nhập trở kháng mục tiêu, khoảng cách đến ground plane, khoảng cách giữa cặp vi sai... vào công cụ tính của nhà sản xuất để tìm ra độ rộng trace cần thiết
  • Các video của Zach Peterson về cặp vi sai và USB routing được đưa vào làm tài liệu tham khảo

Tốc độ của USB 2.0

  • USB 2.0 không có nghĩa là tốc độ bị cố định ở một mức duy nhất
  • USB 2.0 có thể có, chẳng hạn, hai mức tốc độ
    • full speed: 12 Mbit/s
    • high speed: 480 Mbit/s
  • Device và host phải quyết định sẽ dùng tốc độ nào khi kết nối
  • Để đạt high speed ổn định trên PCB, việc khớp trở kháng là rất quan trọng
  • Với một nguyên mẫu cơ bản, full speed 12 Mbit/s có thể là đủ, và nếu trace từ đầu nối USB đến chip ngắn thì có thể tương đối dễ tính hơn về độ rộng trace và các chi tiết tương tự

Giao thức và các tầng phần mềm

  • USB không chỉ là phần cứng mà còn cần xem xét các tầng phần mềm ở cả host lẫn device
  • Có một video dài khoảng 45 phút giải thích USB từ góc nhìn Linux được đưa vào làm tài liệu tham khảo
  • Video này nói về USB frame, endpoint, configuration, và cách một device có thể thực hiện nhiều chức năng USB
  • Góc nhìn coi USB như một mạng lưới các thiết bị là điều quan trọng
  • Vì việc hệ điều hành host triển khai riêng driver cho từng thiết bị USB là không thực tế, hệ điều hành sẽ nhận diện nhiều USB device class
    • mass storage device
    • serial device
    • và nhiều class khác
  • Thiết bị thực hành sẽ hoạt động như một thiết bị serial port dưới góc nhìn của host

Tạo thiết bị USB serial port với STM32

  • Thiết bị thực hành là một USB device đơn giản bật LED khi nhận được yêu cầu từ host
  • Host sẽ nhận diện thiết bị này như một serial port device
  • Có thể chia cách triển khai thành hai hướng lớn
    • Dùng vi điều khiển có hỗ trợ USB
    • Dùng SoC có thể chạy Linux để kernel xử lý phần lớn công việc
  • Phần thực hành dùng cách vi điều khiển để giữ sự đơn giản
  • Board NUCLEO-F103RB

    • Board phát triển được dùng là NUCLEO-F103RB
    • Tại Mỹ, có thể mua ở những nơi như Digikey với giá chỉ hơn 10 USD một chút
    • Board này có cấu trúc gồm hai phần gắn liền với nhau
    • Phần nhỏ là khu vực programmer, phần lớn là khu vực main MCU thực sự được lập trình
    • Kết nối USB mặc định của board không nối với main MCU mà nối với MCU ở phía programmer
    • Programmer này dùng giao thức ST-LINK để trao đổi thông điệp USB với máy tính rồi lập trình cho main MCU
    • Nếu tự làm PCB dựa trên STM32, có thể chỉ đặt một con chip trên board, lập trình qua USB rồi dùng chính cổng USB đó cho logic của thiết bị
    • Có kèm video của Phil’s Lab làm tài liệu tham khảo về PCB dùng STM32 và lập trình qua USB
    • Video 19
    • Video 20

Đấu dây cổng USB thực tế

  • Vì kết nối USB mặc định của board Nucleo không nối với main MCU, cần tự nối cổng USB của main MCU trực tiếp với host
  • Sau khi nạp chương trình, hãy ngắt ST-LINK programmer khỏi máy tính và cấp nguồn cho main MCU qua cổng USB thực tế
  • STM32CubeIDE được dùng để viết phần mềm, còn STM32CubeProgrammer được dùng để nạp lên board
  • Các chân và cấu hình cần thiết

    • Cần bốn chân để tạo thiết bị USB 2.0
    • nguồn 5V đi vào từ host
    • GND
    • D+
    • D-
    • Để main MCU trên NUCLEO-F103RB nhận nguồn từ cổng USB của chính nó, cần đổi jumper JP5 để board nhận nguồn external 5 V
    • Trong STM32CubeIDE, cấu hình chân như sau
    • PA12USB_DP
    • PA11USB_DM
    • Chip trên board này cần một điện trở pull-up 1.5 kΩ bên ngoài cho kết nối USB
    • Điện trở sẽ kéo PA12 lên 3.3V
    • Mạch được lắp trên breadboard
    • Để nối MacBook Pro với từng chân riêng lẻ, tác giả dùng một USB breakout cable mua trên Amazon, tháo terminal block để lộ các chân USB rồi dùng jumper wire nối với board Nucleo và breadboard
    • Dù không tuân thủ nghiêm ngặt việc cân bằng chiều dài cặp vi sai hay kiểm soát trở kháng, phần thực hành này vẫn đạt được kết nối 12 Mbit/s

Viết phần mềm USB CDC

  • Khi cấu hình chân USB trong CubeIDE, sẽ có thông báo liên quan đến cấu hình clock, và có thể chọn để CubeIDE tự xử lý việc này
  • Trong Pinout & Configuration, dưới Middleware and Software Packs, hãy cấu hình USB_DEVICE
  • Chế độ quan trọng trong phần thực hành là
Communication Device Class (Virtual Port Com)
  • Với cấu hình này, board Nucleo sẽ hoạt động như một thiết bị CDC serial port dưới góc nhìn của host
  • Dựa trên thông tin class này, host có thể thiết lập driver phù hợp để giao tiếp với custom device
  • CubeIDE sẽ sinh mã C, và trong main.c sẽ có lời gọi khởi tạo sau
MX_USB_DEVICE_Init();
  • Để bật LED, hãy thêm đoạn mã sau vào routine CDC_Receive_FS
/* USER CODE BEGIN 6 */
if (Buf[0] == '1') {
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
}
  • Lời gọi HAL này sẽ bật LED onboard nối với pin 5 của port A
  • Video tham khảo theo từng bước như sau

Flash và chạy

  • Sau khi build file ELF, dùng CubeProgrammer để nạp mã lên board
  • Sau đó tháo programmer ra và đấu dây cho board dùng nguồn 5V ngoài như đã mô tả ở trên
  • Khi board khởi động, trong device manager của hệ điều hành nó sẽ xuất hiện như một mục tương tự COM port hoặc Serial port
  • Có thể đổi giá trị Device Descriptor trong menu middleware USB_DEVICE của CubeIDE để đặt tên custom device hiển thị trong device manager của hệ điều hành
  • Trên Mac OS, thiết bị mới sẽ xuất hiện dưới hệ thống tệp /dev
    • Ví dụ đường dẫn: /dev/tty.usbmodem497A0F6739561
  • Trên Linux, nó có thể hiện ra với tên như /dev/ttyUSB0
  • Ví dụ kết nối tới serial device bằng Minicom như sau
minicom --device /dev/tty.usbmodem497A0F6739561
  • Sau khi kết nối, nhập 1 từ bàn phím thì LED xanh trên board Nucleo sẽ sáng lên

Khác biệt phần mềm giữa cách STM32 và cách Linux

  • Kết quả của phần thực hành là tạo ra một USB serial port device được các hệ điều hành phổ biến nhận diện
  • Cách làm dựa trên STM32CubeIDE có những bất tiện từ góc nhìn kỹ nghệ phần mềm
    • Phải nhấp qua nhiều menu UI để sinh ra rất nhiều boilerplate
    • Đây không phải kiểu dùng thư viện được tham số hóa linh hoạt bằng mã như InitUsbDevice(UsbClass.CDC)
    • Mã sinh ra có thể gắn chặt với mã người dùng, khiến việc review code khó hơn
    • Khi có phiên bản mới, không rõ nên cập nhật phần boilerplate như thế nào
    • Cấu hình hiện tại bị ràng buộc rất mạnh với hệ sinh thái STM32
  • Cách Linux hoạt động như một USB device được đánh giá là hướng tiếp cận gọn gàng hơn
    • API của Linux vững chắc và chuẩn hóa hơn
    • Có thể dựa trên tương tác với pseudo-file và system call
    • user space và kernel space được tách biệt
    • Có thể xem Linux như một lớp HAL
  • Tuy vậy, nếu cần một thiết bị USB nhẹ, rẻ và dễ sản xuất thì SoC chạy Linux có thể quá nặng và vượt quá nhu cầu trong nhiều trường hợp
  • Từ đó dẫn đến kết luận rằng sẽ rất tốt nếu có một framework cho phát triển USB device bare metal vừa portable hơn vừa ít áp đặt hơn

1 bình luận

 
GN⁺ 2024-06-04
Ý kiến trên Hacker News
  • Đây là một bài nhập môn USB rất hay, nhưng khá thiên về việc dùng vi điều khiển ST, và so với các cách hiện nay trong hệ sinh thái ESP32, nơi có thể gắn thiết bị USB để dùng khá dễ dàng, thì có vẻ cần nhiều bước và toolchain hơn hẳn
    Ngoài ra, với tư cách người đã tự làm nhiều bo USB, tôi thấy cặp vi sai không phải là phần người mới cần quá lo lắng; nó chủ yếu trở nên quan trọng trong các tác vụ tốc độ cao
    IC điều khiển USB thường dùng với Arduino/ESP xử lý nhiều chi tiết giúp bạn, nên bắt người làm thiết bị đầu tiên phải tính toán cả những thứ đó thì hơi quá

    • Gần đây tôi đã làm một macro pad đi dây thủ công bằng Arduino Pro Micro có ATmega32U4, và có vẻ nó khá phổ biến trong cộng đồng chơi bàn phím tùy chỉnh
      Đây là một dự án nhanh và thú vị cho người mới, phần phiền nhất là đục đẽo vỏ gỗ
    • Tôi nghĩ có hiểu biết cơ bản về cặp vi sai và trở kháng thì vẫn tốt
      Thực tế tôi gần như chưa từng tự tính toán, và phần mềm ECAD tôi dùng có cung cấp công cụ định tuyến cặp vi sai và phân tích tín hiệu trở kháng
      Dù vậy, nếu giữ trace thật ngắn thì thường sẽ không có vấn đề lớn
    • Tôi muốn được gợi ý IC điều khiển USB
      Thường thì tôi chỉ dùng vi điều khiển tích hợp USB
  • Tôi từng làm kiểm thử tuân thủ USB từ lâu, và một trong những hạng mục thường gặp vấn đề là kiểm thử dòng khởi động
    Về cơ bản là do có quá nhiều tụ bypass ở phía 5V, và có vẻ bài viết không đề cập phần này
    Rất dễ tập trung vào thiết kế số tốc độ cao, nhưng trong kiểm thử tuân thủ, đôi khi những phần trông kém “ngầu” hơn lại làm mình vấp
    Tôi không rõ trong tiêu chuẩn mới nhất nó hoạt động thế nào, nhưng có vẻ bài kiểm thử này vẫn còn; bản thân bài viết thì hay
    https://compliance.usb.org/index.asp?UpdateFile=Electrical#:...

    • Tôi tò mò không biết người ta dùng linh kiện nào để giới hạn dòng khởi động
      Có thể làm một mạch giới hạn dòng bằng vài transistor, nhưng có lẽ sẽ có giải pháp tích hợp tốt hơn kèm các tính năng như bảo vệ nhiệt
  • Bổ sung cho nội dung bài viết về USB-C: cần nối chân CC với điện trở phù hợp
    Nếu không thì rất có thể nó sẽ không hoạt động
    Còn định tuyến vi sai và trở kháng thì với USB 2.0 không phải chuyện quá lớn
    Chỉ cần làm cho chiều dài trace tương đương nhau, nối trực tiếp một cách hợp lý, và nếu có thể thì đặt chúng gần nhau
    Không cần quá ám ảnh với việc tinh chỉnh chiều dài/độ rộng trace, kiểm soát trở kháng hay các thực hành tốt nhất kiểu RF; cứ nối các net là được

    • 480Mbit/s là đủ nhanh nên vẫn nên chú ý các thực hành tốt nhất và ghép trở kháng trong khoảng chừng 10%
      Nhiều MCU cũng cần điện trở nối tiếp giữa chân USB PHY và đầu nối
      Có lẽ bạn sẽ không phải làm lại bo chỉ vì cần dùng trace 20mil thay vì 24mil, nhưng layout cặp vi sai USB 2.0 tuy tương đối ít áp lực, cố làm cho đúng vẫn là một bài luyện tập tốt
  • Nếu lo ngại các linh kiện khó hàn như vi xử lý ARM, bạn không nhất thiết phải dùng linh kiện lớn như vậy
    STM32 rất tốt khi cần hiệu năng, nhưng với các tác vụ nhỏ thì một bộ điều khiển nhỏ hơn có thể phù hợp hơn
    Ví dụ VUSB là thư viện bit-bang USB trên các vi điều khiển Atmel nhỏ: https://www.obdev.at/products/vusb/index.html
    Cũng có sơ đồ mạch của bo ví dụ mà tôi dùng khi dạy sinh viên lập trình module nhân Linux: https://gitlab.cs.fau.de/i4/passt/passtboard-v2
    Firmware ở http://www.poempelfox.de/ds1820tousb/https://gitlab.cs.fau.de/i4/passt/ds1820tousb
    Nếu thích kiểu lập trình Arduino hơn, cũng có nhiều bo có thể dùng như thiết bị USB chỉ với vài dòng thư viện include; ví dụ https://www.az-delivery.de/en/products/digispark-board

    • Nếu lo chuyện hàn, bạn có thể mua bo phát triển đã có sẵn cổng USB của vi điều khiển và hoạt động được với giá khá hợp lý
      Ví dụ có NUCLEO-F429ZI: https://www.st.com/en/evaluation-tools/nucleo-f429zi.html
      Nó rất giống NUCLEO-F103RB mà tác giả dùng, nhưng ngoài cổng USB phía trên cho programmer/debugger tích hợp, phía dưới còn có một đầu nối USB nối trực tiếp với vi điều khiển
      Nếu muốn tham khảo các lựa chọn như bảo vệ ESD, bạn cũng có thể tải sơ đồ mạch của bo
    • Cũng có những thứ như CH32V203
      Đây là TSSOP-20 có USB phần cứng, giá linh kiện lẻ khoảng 0,81 USD
      Tuy nhiên phần mềm có thể khó hơn một chút
      https://www.wch-ic.com/products/CH32V203.html?
  • Tôi từng viết mã USB bare-metal trên MCU, và so với các giao thức số đơn giản như SPI hay I2C thì khá choáng
    Tầng vật lý và tầng liên kết dữ liệu không hẳn phức tạp hơn CAN quá nhiều, nhưng ngay sau đó là đụng vào bức tường descriptor, endpoint và cấu hình driver
    USB ngay từ đầu đã được thiết kế như một hệ sinh thái plug-and-play cho PC, và đặc điểm đó thể hiện rất rõ
    Nếu có thể, dùng tối đa phần mềm do vendor cung cấp chắc chắn là hướng đi tốt
    Một mẹo nhớ mang máng là: với thông lượng cao thì dùng bulk transfer, còn isochronous transfer thì tốt nhất đừng ngó tới
    USB là giao thức master/slave, nên nếu không đạt thông lượng tối đa thì thường nguyên nhân nằm ở phía host, tức PC
    Nếu license LGPL phù hợp với yêu cầu thì libusb khá dễ dùng
    Nếu không dùng driver của vendor, một hardware USB protocol analyzer sẽ cực kỳ hữu ích, và USB in a NutShell là tài liệu tham khảo web khá ổn để hiểu giao thức: https://www.beyondlogic.org/usbnutshell/usb1.shtml

  • Nhân tiện đang xử lý USB trên STM32, tôi muốn hỏi có ai biết cách hỗ trợ nhận hơn 64 byte trong một frame không
    Hiện tại tôi đang xử lý lặp lại các frame 64 byte trong phần mềm, nhưng tôi biết là có thể lớn hơn
    Hình như tối đa tới 1MB
    Vấn đề là trong Reference Manual, các thiết lập này không xuất hiện như register thông thường mà như một dạng pseudo-register
    Tôi muốn biết có cách workaround đơn giản nào không
    Đang nói tới USB peripheral không phải OTG

    • USB full-speed bulk transfer bị giới hạn ở 64 byte
      Không rõ bạn đang nói tới STM32 nào, nhưng stm32g4 chỉ hỗ trợ full-speed
  • Tôi chủ yếu dùng ESP32, nhưng có một cách hack rẻ tiền phù hợp với nhiều dự án
    Nếu cần một controller tùy biến cực nhanh và dễ, cũng đáng cân nhắc tháo bo mạch điều khiển của bàn phím USB bỏ đi để dùng
    Thay vì hàn, có thể gắn dây vào các tiếp điểm bằng keo dẫn điện rồi cố định chắc bằng súng bắn keo
    Tôi từng làm một tay cầm game 1 nút dựa trên nút arcade để gửi phím space, rẻ nhưng khá bền; debounce các thứ đều được xử lý sẵn và không cần code

  • Tôi muốn biết có dev board hỗ trợ USB 3 nào không
    Tôi đang muốn prototype một USB-C monitor sink, nhưng khó tìm được board đủ hiệu năng để nhận DisplayPort qua USB

    • Nhận DisplayPort qua USB không cần hiệu năng lớn
      Với điều kiện là đã có thể nhận tín hiệu DisplayPort hoặc route nó ra màn hình ngoài
      Chỉ cần triển khai USB billboard device và báo đúng alternate mode
      Bản thân việc hoạt động thì là tùy chọn, nhưng theo trí nhớ của tôi thì đặc tả yêu cầu
      Khi đó tín hiệu DisplayPort sẽ xuất hiện trên đầu cắm USB-C, và chỉ cần nối các đường AUX phù hợp vào đầu nối DisplayPort là được
    • Có board này: https://octopart.com/cyusb3kit-003-cypress+semiconductor-494...
      Giá cũng có vẻ hợp lý
      Hai năm trước khi tôi tìm thứ tương tự thì giá đắt hơn một bậc thập phân
    • Hầu hết FPGA có transceiver đều làm được
  • Tôi đang cắm Raspberry Pi vào PC và dùng https://github.com/xairy/raw-gadget để tạo thiết bị USB ảo
    Hiện tôi dùng nó để giả lập camera MTP nhằm đánh lừa một phần mềm độc quyền

  • Tôi từng prototype vài thiết bị USB bằng Raspberry Pi Zero và composite USB của Linux kernel
    Ít nhất thì thiết bị lưu trữ và thiết bị serial có thể chạy khá dễ
    Cần một thứ như shell script để khởi tạo composite kernel module trên Raspberry Pi, còn mã boilerplate có thể tìm trong tài liệu kernel

    • Tôi muốn thử tạo thiết bị USB ảo như webcam bằng Pi
      pikvm cũng là một dự án thú vị
      Khi kết nối với PC, kết nối USB không chỉ hoạt động như bàn phím và chuột mà còn có thể trở thành USB drive dùng để boot hệ thống
      Đây là một cách khá thú vị cho việc cài đặt