Cấu hình Raspberry Pi Zero 2 W dùng đồng thời USB Ethernet và USB Serial
Sau khi hoàn thành bài hướng dẫn trước, Raspberry Pi đã có thể kết nối với máy tính qua USB Ethernet để SSH tại địa chỉ:
10.12.194.1Tuy nhiên, cấu hình mặc định của rpi-usb-gadget chỉ tạo USB Ethernet. Trong một số trường hợp, đặc biệt khi kết nối Raspberry Pi với Android (trong dự án cá nhân của tôi), điện thoại có thể không nhận Raspberry Pi như một card mạng.
Bài này cấu hình Raspberry Pi thành một USB composite gadget, cho phép cùng một cổng USB cung cấp hai chức năng:
Raspberry Pi Zero 2 W
│
└── USB composite gadget
├── RNDIS Ethernet
│ └── usb0
│ └── SSH và SFTP với Windows
│
└── CDC-ACM Serial
└── /dev/ttyGS0
└── Truyền dữ liệu với AndroidKết quả cuối cùng:
- Máy tính Windows vẫn SSH vào Raspberry Pi qua
10.12.194.1. - Android giao tiếp trực tiếp với Raspberry Pi qua USB serial.
- Không cần Wi-Fi cho đường truyền dữ liệu giữa Android và Raspberry Pi.
- Có thể gửi lệnh, JSON, kết quả cảm biến, trạng thái hệ thống và dữ liệu nhị phân nhỏ.
Hướng dẫn đã được thử nghiệm trên Raspberry Pi Zero 2 W chạy Raspberry Pi OS Trixie.
Chuẩn bị
Cần có:
- Raspberry Pi Zero 2 W.
- Raspberry Pi OS Trixie.
rpi-usb-gadgetđã được cài theo bài hướng dẫn trước.- Raspberry Pi vẫn truy cập được qua Wi-Fi trong lúc thay đổi cấu hình.
- Cáp micro-USB có hỗ trợ truyền dữ liệu.
- Cáp hoặc đầu chuyển USB-C OTG khi kết nối với Android.
Trên Raspberry Pi Zero 2 W, phải sử dụng cổng micro-USB có nhãn USB, nằm gần cổng mini-HDMI. Không sử dụng cổng PWR IN để truyền dữ liệu.
Kiểm tra cấu hình hiện tại
Trước khi thay đổi, kiểm tra USB Ethernet đang hoạt động:
nmcli connection show --activeKết quả thường có:
USB Gadget (shared) ethernet usb0Kiểm tra địa chỉ IP:
ip -4 addr show usb0Kết quả cần có địa chỉ:
10.12.194.1/28Kiểm tra module USB gadget:
lsmod | grep -E "g_ether|dwc2|libcomposite"Với cấu hình mặc định của rpi-usb-gadget, thường sẽ thấy:
g_etherModule g_ether chỉ cung cấp USB Ethernet. Để có thêm USB serial, cần thay nó bằng một composite gadget tạo qua ConfigFS.
Sao lưu cấu hình USB hiện tại
Sao lưu cấu hình module:
sudo cp \
/etc/modules-load.d/usb-gadget.conf \
/etc/modules-load.d/usb-gadget.conf.backupSao lưu cấu hình khởi động:
sudo cp \
/boot/firmware/config.txt \
/boot/firmware/config.txt.backupKiểm tra overlay USB OTG:
grep -n "dtoverlay=dwc2" /boot/firmware/config.txtKết quả cần có dạng:
dtoverlay=dwc2,dr_mode=peripheralNếu chưa có, thêm vào cuối file:
echo "dtoverlay=dwc2,dr_mode=peripheral" \
| sudo tee -a /boot/firmware/config.txtThay g_ether bằng libcomposite
Ghi lại danh sách module được nạp khi Raspberry Pi khởi động:
printf "dwc2\nlibcomposite\n" \
| sudo tee /etc/modules-load.d/usb-gadget.confNội dung file sau khi thay đổi:
dwc2
libcompositeTừ bước này, không chạy lại:
sudo rpi-usb-gadget onLệnh trên có thể khôi phục cấu hình g_ether, làm mất composite gadget vừa tạo.
Các NetworkManager profile của rpi-usb-gadget vẫn được giữ lại để quản lý interface usb0.
Tạo script USB composite gadget
Tạo script:
sudo nano /usr/local/sbin/usb-composite-gadgetDán nội dung sau:
#!/usr/bin/env bash
set -euo pipefail
GADGET_NAME="pi_composite"
GADGET_ROOT="/sys/kernel/config/usb_gadget"
GADGET="${GADGET_ROOT}/${GADGET_NAME}"
stop_gadget() {
if [[ ! -d "${GADGET}" ]]; then
return 0
fi
if [[ -e "${GADGET}/UDC" ]]; then
printf '' > "${GADGET}/UDC" 2>/dev/null || true
fi
find "${GADGET}/configs" -type l -delete 2>/dev/null || true
if [[ -L "${GADGET}/os_desc/c.1" ]]; then
rm -f "${GADGET}/os_desc/c.1"
fi
rmdir "${GADGET}/functions/acm.usb0" 2>/dev/null || true
rmdir "${GADGET}/functions/rndis.usb0" 2>/dev/null || true
rmdir "${GADGET}/configs/c.1/strings/0x409" \
2>/dev/null || true
rmdir "${GADGET}/configs/c.1" 2>/dev/null || true
rmdir "${GADGET}/strings/0x409" 2>/dev/null || true
rmdir "${GADGET}" 2>/dev/null || true
}
start_gadget() {
modprobe libcomposite
if ! mountpoint -q /sys/kernel/config; then
mount -t configfs none /sys/kernel/config
fi
stop_gadget
mkdir -p "${GADGET}"
cd "${GADGET}"
echo 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSB
echo 0xEF > bDeviceClass
echo 0x02 > bDeviceSubClass
echo 0x01 > bDeviceProtocol
mkdir -p strings/0x409
SERIAL="$(
tr -d '\0' < /proc/device-tree/serial-number \
2>/dev/null || true
)"
if [[ -z "${SERIAL}" ]]; then
SERIAL="$(
awk '/Serial/ {print $3}' /proc/cpuinfo
)"
fi
echo "${SERIAL:-00000001}" \
> strings/0x409/serialnumber
echo "Raspberry Pi" \
> strings/0x409/manufacturer
echo "Pi Ethernet and Serial Gadget" \
> strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "RNDIS Ethernet + CDC ACM" \
> configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
mkdir functions/rndis.usb0
echo "02:12:34:56:78:01" \
> functions/rndis.usb0/dev_addr
echo "02:12:34:56:78:02" \
> functions/rndis.usb0/host_addr
mkdir functions/acm.usb0
if [[ -d os_desc ]]; then
echo 1 > os_desc/use
echo 0xCD > os_desc/b_vendor_code
echo "MSFT100" > os_desc/qw_sign
RNDIS_DESC="functions/rndis.usb0/os_desc/interface.rndis"
if [[ -d "${RNDIS_DESC}" ]]; then
echo "RNDIS" \
> "${RNDIS_DESC}/compatible_id"
echo "5162001" \
> "${RNDIS_DESC}/sub_compatible_id"
fi
fi
ln -s functions/rndis.usb0 configs/c.1/
ln -s functions/acm.usb0 configs/c.1/
if [[ -d os_desc ]]; then
ln -s configs/c.1 os_desc/c.1
fi
UDC="$(ls /sys/class/udc | head -n 1)"
if [[ -z "${UDC}" ]]; then
echo "Không tìm thấy USB Device Controller" >&2
exit 1
fi
echo "${UDC}" > UDC
}
case "${1:-start}" in
start)
start_gadget
;;
stop)
stop_gadget
;;
restart)
stop_gadget
start_gadget
;;
*)
echo "Usage: $0 {start|stop|restart}" >&2
exit 1
;;
esacLưu file và cấp quyền thực thi:
sudo chmod +x /usr/local/sbin/usb-composite-gadgetScript tạo hai USB function:
functions/rndis.usb0
functions/acm.usb0Trong đó:
rndis.usb0tạo card mạng USB cho Windows.acm.usb0tạo cổng serial/dev/ttyGS0trên Raspberry Pi.
Hai function được liên kết vào cùng một configuration nên không tranh chấp USB Device Controller.
VID
0x1d6bvà PID0x0104phù hợp cho thử nghiệm và phát triển. Khi phát triển sản phẩm thương mại, cần sử dụng USB VID/PID hợp lệ của sản phẩm.
Tạo systemd service
Tạo service:
sudo nano /etc/systemd/system/usb-composite-gadget.serviceDán nội dung:
[Unit]
Description=Raspberry Pi RNDIS and CDC ACM Composite Gadget
After=systemd-modules-load.service
Before=NetworkManager.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/usb-composite-gadget start
ExecStop=/usr/local/sbin/usb-composite-gadget stop
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetService này tạo composite gadget mỗi khi Raspberry Pi khởi động.
Giữ lại dịch vụ quản lý USB Ethernet
Package rpi-usb-gadget đã tạo NetworkManager profile cho usb0, gồm:
USB Gadget (client)
USB Gadget (shared)Để dịch vụ quản lý mạng chỉ chạy sau khi composite gadget đã tạo xong usb0, thêm systemd drop-in:
sudo mkdir -p \
/etc/systemd/system/rpi-usb-gadget-ics.service.dTạo file:
sudo nano \
/etc/systemd/system/rpi-usb-gadget-ics.service.d/composite.confDán nội dung:
[Unit]
Requires=usb-composite-gadget.service
After=usb-composite-gadget.service NetworkManager.serviceKích hoạt cấu hình
Nạp lại systemd:
sudo systemctl daemon-reloadBật composite gadget:
sudo systemctl enable usb-composite-gadget.serviceBảo đảm dịch vụ USB Ethernet vẫn được bật:
sudo systemctl enable rpi-usb-gadget-ics.serviceKhởi động lại Raspberry Pi:
sudo rebootKhông nên chạy service composite bằng --now trước khi reboot vì module g_ether cũ có thể vẫn đang chiếm USB Device Controller.
Hoàn tác về USB Ethernet mặc định
Nếu cần quay lại cấu hình ban đầu của rpi-usb-gadget, chạy:
sudo systemctl disable usb-composite-gadget.serviceXóa systemd drop-in:
sudo rm -f \
/etc/systemd/system/rpi-usb-gadget-ics.service.d/composite.confKhôi phục file module:
sudo cp \
/etc/modules-load.d/usb-gadget.conf.backup \
/etc/modules-load.d/usb-gadget.confNạp lại systemd:
sudo systemctl daemon-reloadKhởi động lại:
sudo rebootSau khi khởi động lại, kiểm tra:
lsmod | grep g_ether
ip -br addr show usb0Nếu cần, bật lại package:
sudo rpi-usb-gadget on
sudo rebootKết quả cuối cùng
Sau khi hoàn tất, Raspberry Pi Zero 2 W cung cấp đồng thời hai đường giao tiếp qua cùng một cổng USB:
Windows
│
└── RNDIS Ethernet
├── SSH
├── SFTP
└── 10.12.194.1
Android
│
└── CDC-ACM Serial
├── USB Host API
├── Flutter usb_serial
└── /dev/ttyGS0Cách tổ chức này phù hợp khi:
- Cần giữ đường SSH ổn định để phát triển và sửa lỗi.
- Android không nhận Raspberry Pi như card USB Ethernet.
- Ứng dụng Android cần truyền dữ liệu trực tiếp với Raspberry Pi.
- Không muốn phụ thuộc vào Wi-Fi cho lệnh điều khiển và trạng thái hệ thống.