国产ARM兆易创新GD32F450的USB传输效率

国产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上新建工程如下图:这些是GD提供的库和开发板定义
在这里插入图片描述
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;
}
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值