因为M30上USB的方案变更,设备端做USB device,采用bulk传输时,设备端的驱动稍微复杂一点。虽然bulk配置与设备板还是有一定相关性,但是采用的linux gadget方案也是比较通用的方法,本文主要是结合官方提供的配置脚本,官方提供的配置脚本是基于DJI manifold 2G(英伟达TX2),结合树莓派4B板来简单整理一下讨论如何打通USB device 的RNDIS和BULK通道。目的是基于特定的板子来探讨通用的方案。临时整理可能不太全面,可以结合官方start_bulk包:
链接: https://terra-1-g.djicdn.com/71a7d383e71a4fb8887a310eb746b47f/psdk/e-port/usb-bulk-configuration-reference.zip 和本文章来了解配置。
树莓派4B的用户,可以直接下载我们配置好的镜像文件进行烧录,该镜像已经配置好USB_BULK,烧录后连接正确即可使用。镜像可在此处下载:
https://pan-sec.djicorp.com/s/ym6MSgHXdnCiLCF
网盘有时效限制,若链接过期,可咨询 dev@dji.com 获取。
要先成功运行PSDK后,才可成功 ping 通无人机IP,
M30/T:192.168.112.1,
M3E/T:192.168.90.1
具体ip可以在PSDK的DjiPayloadCamera_GetVideoStreamRemoteAddress函数中打印查看。
3.3版本后可以支持设备端自己传入USB device的VID,PID,以及bulk链路的bInterfaceNumber ,EP_IN,EP_OUT.
已经配置好但是想了解3.3版本变动的开发者,请移步【PSDK3.3 USB链路优化】
1. 硬件连接
树莓派4B 的USB device接口是电源type C接口,直接使用一个type C转OTG的转接线连接。连接到飞机时转接板可以加大电流,这样树莓派只需要连接USB和串口即可正常运行了。
2. 打开树莓派USB device RNDIS配置
/boot/config.txt 添加:
dtoverlay=dwc2
/boot/cmdline.txt 添加:
modules-load=dwc2
执行
ls /sys/class/udc > UDC
会出现resource busy的错误,导致PID,VID并不能被修改,且我们配置的gadget也不会加载,此时RNDIS实际已经开始工作,使用PSDK 3.2最新的版本(V3.2.0-beta.0-build.1558)是可以进行推流的,但是bulk配置拉流上可能就有问题了,要修改此版本代码中的VID和PID与树莓派USB device的保持一致。
本文主要是要配置好RNDIS和BULK,使用PSDK的推流和拉流都要正常工作,需要使用到gadget functionFS的配置,
因此/boot/cmdline.txt 不要添加g_ether。
3. 配置gadget
PSDK大致原理是通过linux gadget来驱动USB bulk,然后PSDK来通过functionFS来调用驱动。gadget在linux当前的版本中基本都已经支持,各个平台上应该都可以进行配置,与平台相关,本文中不过多介绍。也是比较通用的方案,网上介绍资源也比较多,有兴趣可以自行了解一下。
这里我们主要通过DJI提供的基于妙算2G 提供的配置脚本,结合树莓派平台的配置,讨论一下如何快速的将PSDK的USB 通道打通,方便PSDK的功能开发。
首先可以通过
mount -l | grep configfs
查看configfs是否有挂载,在/sys/kernel/config路径下会有一个usb_gadget的文件夹,后面gadget脚本配置就在这个路径下生成配置文件。
官方提供的start_bulk包中,nv-l4t-usb-device-mode.sh为配置脚本,参考此脚本我们复制一个用于树莓派的脚本,贴出如下:raspi-usb-device-start.sh
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p pi4
cd pi4
echo 0x0955 > idVendor # Linux Foundation
echo 0x7020 > idProduct # Multifunction Composite Gadget
echo 0x0001 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
echo 0xEF > bDeviceClass
echo 0x02 > bDeviceSubClass
echo 0x01 > bDeviceProtocol
mkdir -p strings/0x409
echo "abcdefg1234567890" > strings/0x409/serialnumber
echo "raspberry" > strings/0x409/manufacturer
echo "PI4" > strings/0x409/product
cfg=configs/c.1
mkdir -p "${cfg}"
echo 0x80 > ${cfg}/bmAttributes
echo 250 > ${cfg}/MaxPower
cfg_str=""
udc_dev=fe980000.usb
# The IP address shared by all USB network interfaces created by this script.
net_ip=192.168.55.1
# The associated netmask.
net_mask=255.255.255.0
# Note: RNDIS must be the first function in the configuration, or Windows'
# RNDIS support will not operate correctly.
enable_rndis=1
if [ ${enable_rndis} -eq 1 ]; then
cfg_str="${cfg_str}+RNDIS"
func=functions/rndis.usb0
mkdir -p "${func}"
ln -sf "${func}" "${cfg}"
# Informs Windows that this device is compatible with the built-in RNDIS
# driver. This allows automatic driver installation without any need for
# a .inf file or manual driver selection.
echo 1 > os_desc/use
echo 0xcd > os_desc/b_vendor_code
echo MSFT100 > os_desc/qw_sign
echo RNDIS > "${func}/os_desc/interface.rndis/compatible_id"
echo 5162001 > "${func}/os_desc/interface.rndis/sub_compatible_id"
ln -sf "${cfg}" os_desc
fi
enable_bulk=1
if [ ${enable_bulk}-eq1 ]; then
mkdir-p /dev/usb-ffs
cfg_str="${cfg_str}+BULK1"
mkdir-p /dev/usb-ffs/bulk1
func=functions/ffs.bulk1
mkdir-p"${func}"
ln-sf"${func}""${cfg}"
mount -omode=0777-ouid=2000-ogid=2000-t functionfs bulk1 /dev/usb-ffs/bulk1
/home/kyle/work/DJI/startup_bulk/startup_bulk /dev/usb-ffs/bulk1 &
sleep3
cfg_str="${cfg_str}+BULK2"
mkdir-p /dev/usb-ffs/bulk2
func=functions/ffs.bulk2
mkdir-p"${func}"
ln-sf"${func}""${cfg}"
mount -omode=0777-ouid=2000-ogid=2000-t functionfs bulk2 /dev/usb-ffs/bulk2
/home/kyle/work/DJI/startup_bulk/startup_bulk /dev/usb-ffs/bulk2 &
sleep3
fi
mkdir -p "${cfg}/strings/0x409"
echo "${cfg_str:1}" > "${cfg}/strings/0x409/configuration"
udevadm settle -t 5 || :
ls /sys/class/udc > UDC
/sbin/brctl addbr pi4br0
/sbin/ifconfig pi4br0 ${net_ip} netmask ${net_mask} up
if [ ${enable_rndis} -eq 1 ]; then
/sbin/brctl addif pi4br0 usb0
/sbin/ifconfig usb0 down
/sbin/ifconfig usb0 up
fi
exit 0
对比一下官方提供的Manifold 2G的脚本,基本上是一致的。NAVIDA平台可能系统中已经配置好了UDC,但是树莓派中需要自己创建。主要关注一下bulk配置中的:
mount -o mode=0777 -o uid=2000 -o gid=2000 -t functionfs bulk /dev/usb-ffs/bulk
/home/kyle/work/DJI/startup_bulk/startup_bulk /dev/usb-ffs/bulk &
有一个执行程序:startup_bulk,是将官方提供得stark_bulk包中的.c文件编译出来的。这个C文件也可以从functionFS源码中可以拿到,直接编译DJI提供的即可。记得执行文件的路径,并在此处设置对应的路径。
/home/kyle/work/DJI/startup_bulk/startup_bulk这个是我板子上的执行路径,这个要根据自己的环境修改。
关于编译startup_bulk.c文件
1. 安装libaio
sudo apt-get install libaio-dev
2. 有些平台编译可能会报错htole32,htole16没有,
代码中添加下述部分,然后将C源文件中以下的关键词进行替换
htole32 替换成--> cpu_to_le32()
htole16 替换成--> cpu_to_le16()
/******************** Little Endian Handling ********************************/
/*
* cpu_to_le16/32 are used when initializing structures, a context where a
* function call is not allowed. To solve this, we code cpu_to_le16/32 in a way
* that allows them to be used when initializing structures.
*/
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define cpu_to_le16(x) (x)
#define cpu_to_le32(x) (x)
#else
#define cpu_to_le16(x) ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8))
#define cpu_to_le32(x) \
((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \
(((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24))
#endif
#define le32_to_cpu(x) le32toh(x)
#define le16_to_cpu(x) le16toh(x)
将生成的执行文件以及路径对应填写到上述脚本中对应bulk位置,此时手动运行raspi-usb-device-start.sh脚本要能拉起bulk节点。但是要注意树莓派手动启动这个脚本PC端依旧不能识别到有效的USB device,要配置系统启动时脚本自启动。
4. 脚本自启动
这里介绍两种简单的启动方式,
1. 在/etc/rc.local中添加一行执行脚本(注意脚本存放路径)
/{path}/raspi-usb-device-start.sh
2. 创建systemctl 服务,贴出我这边配置的服务脚本。创建一个路径:/opt/rasp-usb-config,raspi-usb-device-start.sh先放到这个路径下
创建raspigadget.service
[Unit]
Description=Configure USB flashing port for device mode
[Service]
Type=simple
RemainAfterExit=yes
ExecStart=/opt/rasp-usb-config/raspi-usb-device-start.sh
ExecStop=/opt/rasp-usb-config/raspi-usb-device-stop.sh
[Install]
WantedBy=multi-user.target
创建raspi-usb-device-stop.sh
#!/bin/bash
cd /sys/kernel/config/usb_gadget
echo "" > pi4/UDC
rmdir pi4/configs/c.1/strings/0x409
rm -f pi4/configs/c.1/rndis.usb0
rmdir pi4/functions/rndis.usb0/
rm -f pi4/os_desc/c.1
rmdir pi4/configs/c.1/
rmdir pi4/strings/0x409
rmdir pi4
exit 0
设置使能开机自启动service:
systemctl enable raspigadget
5. 检查配置
reboot重启,检查网口初始化
1. ifconfig
说明:
usb0是RNDIS枚举的网口,pi4br0是配置usb0的网桥,建议在PSDK中配置pi4br0,否则可能会出现MOP功能异常的问题,主要是依照start_bulk中的搞法。网口名称需要注意PSDK代码中也要设置。此处在PSDK代码hal_network.h中设置的网口为 pi4br0,仅标注区分于妙算中网桥名,也就是网桥名称和IP是脚本中参考设置的,没有实际意义。
2. ps -ef | grep startup_bulk
3. 检查bulk ep节点
4. BULK配置确认检查
将树莓派USB device接到Ubuntu host上,通过lsusb -d 0955:7020 -v
考虑到PSDK当前版本上还有一些限制,将我这边配置好的lsusb完整贴出如下,节点信息要与下面配置一致,这样才能从M30上拉取到bulk流数据。具体注意点参考后面总结部分。
root@ubuntu:/home/work/Payload-SDK-master/build# lsusb -d 0955:7020 -v
Bus 001 Device 011: ID 0955:7020 NVidia Corp.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 239 Miscellaneous Device
bDeviceSubClass 2 ?
bDeviceProtocol 1 Interface Association
bMaxPacketSize0 64
idVendor 0x0955 NVidia Corp.
idProduct 0x7020
bcdDevice 0.01
iManufacturer 1 raspberry
iProduct 2 PI4
iSerial 3 abcdefg1234567890
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 98
bNumInterfaces 3
bConfigurationValue 1
iConfiguration 4 RNDIS+BULK
bmAttributes 0x80
(Bus Powered)
MaxPower 250mA
Interface Association:
bLength 8
bDescriptorType 11
bFirstInterface 0
bInterfaceCount 2
bFunctionClass 2 Communications
bFunctionSubClass 6 Ethernet Networking
bFunctionProtocol 0
iFunction 7 RNDIS
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 255 Vendor Specific (MSFT RNDIS?)
iInterface 5 RNDIS Communications Control
CDC Header:
bcdCDC 1.10
CDC Call Management:
bmCapabilities 0x00
bDataInterface 1
CDC ACM:
bmCapabilities 0x00
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 9
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0 Unused
bInterfaceProtocol 0
iInterface 6 RNDIS Ethernet Data
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 9 loop input to output
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Device Qualifier (for other device speed):
bLength 10
bDescriptorType 6
bcdUSB 2.00
bDeviceClass 239 Miscellaneous Device
bDeviceSubClass 2 ?
bDeviceProtocol 1 Interface Association
bMaxPacketSize0 64
bNumConfigurations 1
Device Status: 0x0000
(Bus Powered)
确认完成后,最终配置成功还需要在运行PSDK demo程序,APP上可以显示PSDK demo推送的流,可以拉到FPV或相机裸流即配置成功。
总结:
1. 设备上RNDIS启动后,可以将设备端IP设置成192.168.112.X,然后ping M30 飞机IP:ping 192.168.112.1,可以检查链路是不是通的。
2. 树莓派不要通过moudles加载g_ether
3. 当前PSDK中如果只需要使用到推流或拉流其中一个,也务必注册
DjiPlatform_RegHalUsbBulkHandler
DjiPlatform_RegHalNetworkHandler
DjiPlatform_RegSocketHandler
4. VID,PID请使用上述脚本中的配置,主要是当前版本PSDK使用上有限制。VID、PID填写错误或设备找不到,运行PSDK程序会显示USB未连接,连接超时的错误。VID,PID配置正确后会将本地RNDIS网口(代码设置网口)配置成192.168.140.2。
5. 用于bulk通信的通道,这个在当前PSDK版本中是写死的,配置要与此保持一致,只配置RNDIS+BULK就会设置成相同的配置。之前在英伟达NX板子上配置时主要是因为打开了ECM等其他的通道,导致bInterfaceNumber ,EP_IN,EP_OUT对不上。
bInterfaceNumber 2
bEndpointAddress 0x83 EP 3 IN
bEndpointAddress 0x02 EP 2 OUT
6. 另提供一个简单的bulk自测程序,如果设备同时支持USB Host和USB device,用USB线将HOST和DVICE自环连接起来,运行下面程序就会自测USB host(安装libusb)与device的收发。向树莓派现在这种连接方式就没有办法自测,可以将下面的程序分成两个,在ubuntu host主机上运行host部分,设备上运行device部分也可进行bulk链路测试。
编译:
gcc -o bulk_device bulk-device.c -I/usr/include -lusb-1.0 -lpthread
程序中bInterfaceNumber,ep_in, ep_out当前都是写死的,方便检查M30和PSDK通道,暂时不要修改此参数来适配机载设备端。也可以通过代码简单了解一下USB bulk数据传输。
bulk-device.c
#include "stdint.h"
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libusb-1.0/libusb.h>
#include "pthread.h"
void device_Thread()
{
int32_t ret;
int32_t ep1, ep2;
int32_t actualLen, len;
char sendbuf[] = "USB bulk device data ...";
char readbuf[128] = {0};
//device bulk
ep1 = open("/dev/usb-ffs/bulk/ep1", O_RDWR);
if (ep1 < 0)
{
printf("open ep1 failed, ret[%d]", ret);
return -1;
}
ep2 = open("/dev/usb-ffs/bulk/ep2", O_RDWR);
if (ep2 < 0)
{
printf("open ep2 failed, ret[%d]", ret);
return -1;
}
while(1)
{
//read
actualLen = read(ep2, (uint8_t *)&readbuf[0], 128);
if (actualLen < 0)
{
printf("USB device read failed[%d]\n",actualLen);
}
else
{
printf("USB device recive data\t\t[%s], \tlen[%d]\n", (uint8_t *)&readbuf[0], actualLen);
//send
actualLen = write(ep1, &sendbuf[0], strlen(sendbuf));
//printf("device send ... \n");
}
sleep(1);
}
close(ep1);
close(ep2);
return;
}
int main()
{
int32_t ret;
int32_t actualLen, len;
static pthread_t deviceThread = 0;
struct libusb_device_handle *handle = NULL;
char sendbuf[] = "USB bulk host data ...";
char readbuf[128] = {0};
//host libusb
//char sendbuf[] = "bulk linker loop";
//char readbuf[128] = {0};
ret = libusb_init(NULL);
if (ret < 0) {
printf("libusb_init failed\n");
return -1;
}
handle = libusb_open_device_with_vid_pid(NULL, 0x0955, 0x7020);
if (handle == NULL) {
printf("libusb_open_device_with_vid_pid failed\n");
return -1;
}
ret = libusb_claim_interface(handle, 2);//Num
if (ret != LIBUSB_SUCCESS) {
printf("libusb claim interface error, ret[%d]\n", ret);
libusb_close(handle);
return -1;
}
if (pthread_create(&deviceThread, NULL, device_Thread, NULL) != 0) {
printf("create monitor task fail.\n");
return -1;
}
sleep(2);
//send
ret = libusb_bulk_transfer(handle, 0x02, (uint8_t *) sendbuf, strlen(sendbuf), &actualLen, 50);
if (ret < 0) {
printf("libusb_bulk_transfer send failed[%d], endpointOut[%x], len[%d]\n",ret, 0x02, strlen(sendbuf));
//return -1;
}
while(1)
{
//read
memset(&readbuf[0], 0x0, sizeof(readbuf));
ret = libusb_bulk_transfer(handle, 0x83, &readbuf[0], 128, &actualLen, 50);
//printf("libusb_bulk_transfer read_len[%d], ret[%d]\n", actualLen, ret);
if (0 == ret)
printf("USB host recive data\t\t[%s], \tread_len[%d]\n", &readbuf[0], actualLen);
sleep(2);
ret = libusb_bulk_transfer(handle, 0x02, (uint8_t *) sendbuf, strlen(sendbuf), &actualLen, 50);
if (ret < 0) {
printf("USB host send failed[%d]\n",ret);
//return -1;
}
}
return 0;
}
评论
16 条评论
边缘设备nvidia nx,是否还需要进行配置。
亲测有效在3588s上可以拉取到M30TFPV以及主摄像头的视频流
This is really ridiculous
你好,想咨询一下在树莓派上面执行raspi-usb-device-start.sh,提示 “Unable to prepare ffs”,这种是什么原因造成的。
树莓派4B的用户,可以直接下载此镜像文件烧录,已经配置好USB_BULK。这个是烧录的什么东西,也没说明白
为什么要多此一举用type C转OTG,而不是直接而用一条AtoC的线
打开树莓派USB device RNDIS配置
/boot/config.txt 添加:
/boot/cmdline.txt 添加:
NVIDIA jetson nano 配置好USB BULK 连接LINUX主机,查不到NANO的VID PID
大神可否公布一下RK3588 怎么配置?感谢!!!
rk系列如何打开,有偿,联系19822751780
配置好的树莓派4b镜像文件能不能直接给一个,发邮件没人回
您好,我按照这个配置连接树莓派5,没有出现usb0的接口,只有pi4br0,请问是配置出错了吗?
Web User 66cfe01672916a2016a9e894
Did you manage to solve it?
I am having the same issue and i have not been able to fix it yet.
Please let me know what you did.
Emil Rydberg:
I use the Raspberry Pi 5, and i use the onboard UART port which i connect to the dji developer-kit. Nothing appears in the /dev/tty*, but when i disabled Bluetooth, tty/S0 appeared.
On step 5.4 i get the following error when running the sample code:
[0.014][core]-[Info]-[DjiCore_Init:107) Payload SDK Version : V3.11.0-beta.0-build.2206 Mar 12 2025 12:41:40 [3.167][adapter]-[Info]-[DjiAccessAdapter_Init:279) Try identify UART0 connection failed. Probably because SDK adapter or aircraft not finish init or UART connect error.
Web User 66cfe01672916a2016a9e894
Do you know any other solution?
Look at my previous comment please.
请登录写评论。