因为M30上USB的方案变更,设备端做USB device,采用bulk传输时,设备端的驱动稍微复杂一点。虽然bulk配置与设备板还是有一定相关性,但是采用的linux gadget方案也是比较通用的方法,本文主要是结合官方提供的配置脚本,官方提供的配置脚本是基于DJI manifold 2G(英伟达TX2),结合树莓派4B板来简单整理一下讨论如何打通USB device 的RNDIS和BULK通道。目的是基于特定的板子来探讨通用的方案。临时整理可能不太全面,可以结合官方start_bulk包:
链接: https://pan-sec.djicorp.com/s/TjzmbgT5BHJsftq 和本文章来了解配置。
树莓派4B的用户,可以直接下载我们配置好的镜像文件进行烧录,该镜像已经配置好USB_BULK,烧录后连接正确即可使用。镜像可在此处下载:
https://pan-sec.djicorp.com/s/5CJWk7JoW3RGJkd
若链接过期,可咨询 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;
}
评论
12 条评论
边缘设备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,请问是配置出错了吗?
请登录写评论。