国产ARM兆易创新GD32F450的USB传输效率
由于美国与咱闹翻了,国家电网要求二次设备采用国产芯片替代国外产品,所以选用了与STM32兼容的GD32单片机做替换。期间涉及到使用USB传输采样数据有个带宽的问题,所以就准备测试一下该芯片USBHS方式下的带宽。先说结果,主机到设备30MB/s以上的速度,设备到主机25MB/s以上的速度,可以满足我的需求。但我感觉还可以优化来提高速度。
USB基础
推荐:
从零开始学USB(七、端点、管道、接口、配置、设备)
从零开始学USB(十、USB的描述符)
这两篇文章对USB的软设置方面做了详细描述,避免对USB协议耗时费力的学习,当然不懂的地方还是要有针对性的看USB2.0协议的原版
软件工具
GD没有自己的集成开发环境,更谈不上STM32CubeIDE了,只能使用Keil,好在GD提供的操作说明还比较详细,也就没有什么困难。只是有些细节设置容易掉坑里,但折腾一阵子还是可以爬出来的。GD32F450的开发板自带CMSIS-DAP Debugger,不用再购买编程器,用起来还算方便。
这里GD提供的资料是Keil5以下版本开发的,所以要注意一下将工程文件的后缀修改一下,将*.uvproj和 *.uvopt改为 *.uvprojx和 *.uvoptx,不然在Keil5中添加不了Device。
另外在上位机这边就是libusb-1.0.24,zadig-2.5和VS2019了,我是写c出身,以下都是使用c或c++语言编写。zadig-2.5可以自动生成windows或linux自定义USB设备的驱动,libusb用来写应用了。
开始
在Keil上新建工程如下图:
main.c,myusb_corc.c是主要的功能文件,其中myusb_corc.c中是USB设备的描述和回调函数,main.c是入口和调试输出。其他文件是从GD提供例子工程CDC_ACM中复制过来的。注意要为先定的端口设置FIFO_HS_SIZE的大小。
myusb_corc.c中USB的描述段如下:有一个接口,包含一进一出两个Bulk方式的端点
__ALIGN_BEGIN const usb_descriptor_device_struct device_descripter __ALIGN_END =
{
.Header =
{
.bLength = USB_DEVICE_DESC_SIZE,
.bDescriptorType = USB_DESCTYPE_DEVICE
},
.bcdUSB = 0x0200,
.bDeviceClass = 0xff, // 厂商定义的描述符
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = USB_MAX_EP0_SIZE,
.idVendor = USBD_VID,
.idProduct = USBD_PID,
.bcdDevice = 0x0100,
.iManufacturer = USBD_MFC_STR_IDX,
.iProduct = USBD_PRODUCT_STR_IDX,
.iSerialNumber = USBD_SERIAL_STR_IDX,
.bNumberConfigurations = USBD_CFG_MAX_NUM
};
/* USB device configuration descriptor */
__ALIGN_BEGIN const usb_descriptor_configuration_set_struct configuration_descriptor __ALIGN_END =
{
.Config = // 9Bytes
{
.Header =
{
.bLength = sizeof(usb_descriptor_configuration_struct),
.bDescriptorType = USB_DESCTYPE_CONFIGURATION
},
.wTotalLength = USB_MY_CONFIG_DESC_SIZE, // 32Bytes
.bNumInterfaces = 0x01,
.bConfigurationValue = 0x01,
.iConfiguration = 0x00,
.bmAttributes = 0xC0, // 自给电源
.bMaxPower = 0x32
},
.Printer_Interface = // 9Bytes
{
.Header =
{
.bLength = sizeof(usb_descriptor_interface_struct),
.bDescriptorType = USB_DESCTYPE_INTERFACE
},
.bInterfaceNumber = 0x00,
.bAlternateSetting = 0x00,
.bNumEndpoints = 0x02,
.bInterfaceClass = 0x00,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.iInterface = 0x00
},
.Printer_IN_Endpoint = // 7Bytes
{
.Header =
{
.bLength = sizeof(usb_descriptor_endpoint_struct),
.bDescriptorType = USB_DESCTYPE_ENDPOINT
},
.bEndpointAddress = DEVICE_IN_EP,
.bmAttributes = 0x02, // Bulk
.wMaxPacketSize = DEVICE_IN_PACKET,
.bInterval = 0x00
},
.Printer_OUT_Endpoint = // 7Bytes
{
.Header =
{
.bLength = sizeof(usb_descriptor_endpoint_struct),
.bDescriptorType = USB_DESCTYPE_ENDPOINT
},
.bEndpointAddress = DEVICE_OUT_EP,
.bmAttributes = 0x02,
.wMaxPacketSize = DEVICE_OUT_PACKET,
.bInterval = 0x00
},
};
/* USB language ID Descriptor */
__ALIGN_BEGIN const usb_descriptor_language_id_struct usbd_language_id_desc __ALIGN_END =
{
.Header =
{
.bLength = sizeof(usb_descriptor_language_id_struct),
.bDescriptorType = USB_DESCTYPE_STRING
},
.wLANGID = ENG_LANGID
};
__ALIGN_BEGIN uint8_t* usbd_strings[] __ALIGN_END =
{
[USBD_LANGID_STR_IDX] = (uint8_t *)&usbd_language_id_desc,
[USBD_MFC_STR_IDX] = USBD_STRING_DESC("GigaDevice"),
[USBD_PRODUCT_STR_IDX] = USBD_STRING_DESC("GD32 USB in HS Mode"),
[USBD_SERIAL_STR_IDX] = USBD_STRING_DESC("GD32F4xx-1.0.0-5f9e10dma")
};
myusb_corc.c中USB的初始化和回调函数如下:注意usbd_ep_rx这个库函数,USB设备的每次读交易transaction,都要调用一次用来初始化一些寄存器,不然不能进行下一次transaction,这是一个坑。usbd_rxnum_get函数是自己增加的函数,库中的usbd_rxcount_get只能得到最后接收包的大小,这个坑很大,导致我开始总是收不到完整的数据。
/*!
\brief initialize the printer device
\param[in] pudev: pointer to usb device instance
\param[in] config_index: configuration index
\param[out] none
\retval usb device operation status
*/
uint8_t mydevice_init (void *pudev, uint8_t config_index)
{
/* initialize Tx endpoint */
usbd_ep_init(pudev, &(configuration_descriptor.Printer_IN_Endpoint));
/* initialize Rx endpoint */
usbd_ep_init(pudev, &(configuration_descriptor.Printer_OUT_Endpoint));
/* prepare to receive data */
usbd_ep_rx(pudev, DEVICE_OUT_EP, data_buffer_in, DATA_IN_BUFSIZE);
return USBD_OK;
}
/*!
\brief de-initialize the printer device
\param[in] pudev: pointer to usb device instance
\param[in] config_index: configuration index
\param[out] none
\retval usb device operation status
*/
uint8_t mydevice_deinit (void *pudev, uint8_t config_index)
{
/* deinitialize HID endpoints */
usbd_ep_deinit (pudev, DEVICE_IN_EP);
usbd_ep_deinit (pudev, DEVICE_OUT_EP);
usbd_ep_rx(pudev, DEVICE_OUT_EP, data_buffer_in, DATA_IN_BUFSIZE);
return USBD_OK;
}
uint8_t mydevice_req_handler (void *pudev, usb_device_req_struct *req)
{
return USBD_OK;
}
/*!
\brief handle data stage
\param[in] pudev: pointer to usb device instance
\param[in] rx_tx: the flag of Rx or Tx
\param[in] ep_id: the endpoint ID
\param[out] none
\retval usb device operation status
*/
uint8_t mydevice_data_handler(void *pudev, usb_dir_enum rx_tx, uint8_t ep_id)
{
uint32_t revnum = 0;
if ((USB_TX == rx_tx) && ((DEVICE_IN_EP & 0x7F) == ep_id))
{ // 当本次发送数据小于缓冲区大小时,表示本次交易结束,或者当待发数据如果是整数倍缓冲区,则发送空包作为结束
usb_ep_struct *ep = &((usb_core_handle_struct *)pudev)->dev.out_ep[DEVICE_OUT_EP];
if ((ep->xfer_len % ep->endp_mps == 0) && (ep->xfer_len != 0))
{
usbd_ep_tx(pudev, ep_id, NULL, 0U); // 发送空包
}
else
{
bSent = 1; // 缓冲区没有待发数据,可进行下次交易
}
return USBD_OK;
}
else if ((USB_RX == rx_tx) && ((DEVICE_OUT_EP & 0x7F) == ep_id))
{
bReceived = 1; // 数据到达,等待处理
revnum = usbd_rxnum_get(ep_id);
usbd_ep_rx(pudev, DEVICE_OUT_EP, data_buffer_in, DATA_IN_BUFSIZE);
total += revnum;
revcnt++;
return USBD_OK;
}
return USBD_FAIL;
}
/*!
\brief receive CDC ACM data
\param[in] pudev: pointer to USB device instance
\param[out] none
\retval USB device operation status
*/
void cdc_acm_data_receive(void *pudev)
{
bReceived = 0;
usbd_ep_rx(pudev, DEVICE_OUT_EP, (uint8_t*)data_buffer_in, DATA_IN_BUFSIZE);
}
/*!
\brief send CDC ACM data
\param[in] pudev: pointer to USB device instance
\param[out] none
\retval USB device operation status
*/
void cdc_acm_data_send (void *pudev, uint32_t data_len)
{
bSent = 0;
usbd_ep_tx(pudev, DEVICE_IN_EP, (uint8_t*)data_buffer_out, data_len);
}
// 修改库函数得到每次接收到的字节数
uint16_t usbd_rxnum_get (uint8_t ep_id)
{
uint32_t xfer_size = 0;
uint16_t revnum = 0;
//uint16_t pcnt = (USB_DOEPxLEN(ep_id) & DEPLEN_PCNT) >> 19U;
xfer_size = USB_DOEPxLEN(ep_id) & DEPLEN_TLEN;
revnum = DATA_IN_BUFSIZE - xfer_size;
return revnum;
}
main.c中是初始化及应用:当接收完100MB的数据后亮灯
#include "usb_delay.h"
#include "myusb_core.h"
#include "string.h"
usb_core_handle_struct usbhs_core_dev =
{
.dev =
{
.dev_desc = (uint8_t *)&device_descripter,
.config_desc = (uint8_t *)&configuration_descriptor,
.strings = usbd_strings,
.class_init = mydevice_init,
.class_deinit = mydevice_deinit,
.class_req_handler = mydevice_req_handler,
.class_data_handler = mydevice_data_handler
},
.udelay = usb_udelay,
.mdelay = usb_mdelay
};
extern int revcnt;
extern int total;
extern uint8_t bSent, bReceived;
extern uint8_t data_buffer_out[DATA_OUT_BUFSIZE];
//void system_clock_config(void);
void usb_clock_config(void);
void usb_gpio_config(void);
void usb_interrupt_config(void);
/*!
\brief main routine will construct a USB keyboard
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
int i;
/* LED initialize */
gd_eval_led_init(LED1);
/* configure USB GPIO */
usb_gpio_config();
/* configure USB clock */
usb_clock_config();
/* USB device stack configure */
usbd_init(&usbhs_core_dev,
#ifdef USE_USBFS
USB_FS_CORE_ID
#elif defined(USE_USBHS)
USB_HS_CORE_ID
#endif
);
/* USB interrupt configure */
usb_interrupt_config();
/* check if USB device is enumerated successfully */
while (usbhs_core_dev.dev.status != USB_STATUS_CONFIGURED) {}
revcnt = 0;
gd_eval_led_off(LED1);
while (1)
{
if (total == 1024 * 1000 * 100)
{
gd_eval_led_on(LED1);
revcnt = total = 0;
}
}
}
PC驱动安装及使用
使用zadig完成驱动生成:
安装好libusb库,在VS中新建C++工程:
#include <iostream>
#include "libusb.h"
#include "string.h"
#define USBD_VID 0x28E9
#define USBD_PID 0x028D
#define EP_IN 0x82
#define EP_OUT 0x02
#define BUF_SIZE 512 * 16 // 8k
int main()
{
libusb_context *usb_context = nullptr;
libusb_device **dev_list;
libusb_device *dev;
libusb_device_handle *dev_handle = NULL;
struct libusb_device_descriptor desc;
libusb_config_descriptor* config_descriptor;
ssize_t cnt;
int i(0), ret(0), length(0), config(0), total(0);
unsigned char* usb_data_buffer;
DWORD start, end;
if (ret = libusb_init(&usb_context) < 0)
{
goto ERR_EXIT;
}
if (ret = libusb_get_device_list(usb_context, &dev_list) < 0)
{
goto ERR_EXIT;
}
while ((dev = dev_list[i++]) != NULL)
{
ret = libusb_get_device_descriptor(dev, &desc);
if (ret < 0)
{
goto ERR_EXIT;
}
if (desc.idVendor == USBD_VID && desc.idProduct == USBD_PID)
{
ret = libusb_open(dev, &dev_handle);
if (ret != 0)
{
libusb_free_device_list(dev_list, 1);
goto ERR_EXIT;
}
break;
}
}
libusb_free_device_list(dev_list, 1);
if (dev_handle == nullptr)
{
goto ERR_EXIT;
}
//dev_handle = libusb_open_device_with_vid_pid(usb_context, USBD_VID, USBD_PID);
ret = libusb_kernel_driver_active(dev_handle, 0);
if (ret == 1)
{
libusb_detach_kernel_driver(dev_handle, 0);
goto ERR_EXIT;
}
if (ret = libusb_claim_interface(dev_handle, 0) < 0)
{
goto ERR_EXIT;
}
//ret = libusb_get_configuration(dev_handle, &config);
//dev = libusb_get_device(dev_handle);
//libusb_get_config_descriptor(dev, 0, &config_descriptor);
start = GetTickCount();
usb_data_buffer = new unsigned char[BUF_SIZE];
memset(usb_data_buffer, 0x55, BUF_SIZE);
for (i = 0; i < 10000; i++)
{
//memcpy(usb_data_buffer, &i, sizeof(int));
ret = libusb_bulk_transfer(dev_handle, EP_OUT, usb_data_buffer, BUF_SIZE, &length, 200);
total += length;
if (ret < 0)
{
goto ERR_EXIT;
}
}
end = GetTickCount();
printf("i = %d, time=%d, total = %d\n", i, end - start, total);
delete[] usb_data_buffer;
ERR_EXIT:
printf("err:%s\r\n", libusb_strerror(ret));
libusb_close(dev_handle);
libusb_exit(usb_context);
getchar();
return 0;
}