NUC980_Non-OS_BSP_v1.03.000 SDK中没有提供USB bulk传输的例程,基于USBD_HID_Mouse例程进行修改。大体思路参考这个文章,先修改设备、配置、接口、端点等描述符,再安装驱动,最后编写下位机和上位机程序进行验证。
USB Device Controller的框图如下:
特性:
- USB Specification reversion 2.0 compliant
- Supports 12 configurable endpoints in addition to Control Endpoint
- Each of the endpoints can be Isochronous, Bulk or Interrupt and either IN or OUT direction
- Three different operation modes of an in-endpoint - Auto Validation mode, Manual Validation mode, Fly mode
- Supports DMA operation
- 4096 Bytes Configurable RAM used as endpoint buffer
- Supports Endpoint Maximum Packet Size up to 1024 bytes
1.修改描述符
关于描述符的详细说明参考博客。
各种描述符之间的关系:
修改descriptors.c中DeviceDescriptor:
uint8_t gu8DeviceDescriptor[] __attribute__((aligned(4))) =
{
#endif
LEN_DEVICE, /* bLength */
DESC_DEVICE, /* bDescriptorType */
0x00, 0x02, /* bcdUSB */ //0x10,0x01->0x00,0x20
0xFF, /* bDeviceClass */ //0x00->0xFF
0x00, /* bDeviceSubClass */
0x00, /* bDeviceProtocol */
CEP_MAX_PKT_SIZE, /* bMaxPacketSize0 */
/* idVendor */
USBD_VID & 0x00FF,
((USBD_VID & 0xFF00) >> 8),
/* idProduct */
USBD_PID & 0x00FF,
((USBD_PID & 0xFF00) >> 8),
0x00, 0x00, /* bcdDevice */
0x01, /* iManufacture */
0x02, /* iProduct */
0x00, /* iSerialNumber - no serial */
0x01 /* bNumConfigurations */
};
修改hid_mouse.h中VID和PID的值(不是必须要修改的):
#define USBD_VID 0x303A //0x0416->0x303A
#define USBD_PID 0x3002 //0x8249->0x3001
在hid_mouse.h中增加OUT节点的定义:
/* Define EP maximum packet size */
#define CEP_MAX_PKT_SIZE 64
#define CEP_OTHER_MAX_PKT_SIZE 64
#define EPA_MAX_PKT_SIZE 512 //修改,由64改为512
#define EPA_OTHER_MAX_PKT_SIZE 64
#define EPB_MAX_PKT_SIZE 512 //新增
#define EPB_OTHER_MAX_PKT_SIZE 64
#define CEP_BUF_BASE 0
#define CEP_BUF_LEN CEP_MAX_PKT_SIZE
#define EPA_BUF_BASE 0x200 //新增
#define EPA_BUF_LEN EPA_MAX_PKT_SIZE
#define EPB_BUF_BASE 0x400 //新增
#define EPB_BUF_LEN EPB_MAX_PKT_SIZE //新增
/* Define the interrupt In EP number */
#define INT_IN_EP_NUM 0x01
#define INT_OUT_EP_NUM 0x01 //新增
修改descriptors.c中ConfigDescriptor :
uint8_t gu8ConfigDescriptor[] __attribute__((aligned(4))) =
{
#endif
LEN_CONFIG, /* bLength */
DESC_CONFIG, /* bDescriptorType */
/* wTotalLength */
LEN_CONFIG_AND_SUBORDINATE & 0x00FF,
((LEN_CONFIG_AND_SUBORDINATE & 0xFF00) >> 8),
0x01, /* bNumInterfaces */
0x01, /* bConfigurationValue */
0x00, /* iConfiguration */
0x80 | (USBD_SELF_POWERED << 6) | (USBD_REMOTE_WAKEUP << 5),/* bmAttributes */
USBD_MAX_POWER, /* MaxPower */
/* I/F descr: HID */
LEN_INTERFACE, /* bLength */
DESC_INTERFACE, /* bDescriptorType */
0x00, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x02, /* bNumEndpoints */ //0x01->0x02
0xFF, /* bInterfaceClass */ //0x03->0xFF
0x01, /* bInterfaceSubClass */
0x00, /* bInterfaceProtocol */ //HID_MOUSE->0x00
0x00, /* iInterface */
/* HID Descriptor */ //不使用报告描述符,将此段配置注释。
// LEN_HID, /* Size of this descriptor in UINT8s. */
// DESC_HID, /* HID descriptor type. */
// 0x10, 0x01, /* HID Class Spec. release number. */
// 0x00, /* H/W target country. */
// 0x01, /* Number of HID class descriptors to follow. */
// DESC_HID_RPT, /* Descriptor type. */
/* Total length of report descriptor. */
// sizeof(HID_MouseReportDescriptor) & 0x00FF,
// ((sizeof(HID_MouseReportDescriptor) & 0xFF00) >> 8),
/* EP Descriptor: bulk in. */
LEN_ENDPOINT, /* bLength */
DESC_ENDPOINT, /* bDescriptorType */
(INT_IN_EP_NUM | EP_INPUT), /* bEndpointAddress */
EP_BULK, /* bmAttributes */ //EP_INT->EP_BULK
/* wMaxPacketSize */
EPA_MAX_PKT_SIZE & 0x00FF,
((EPA_MAX_PKT_SIZE & 0xFF00) >> 8),
0x00, /* bInterval */ //HID_DEFAULT_INT_IN_INTERVAL->0x00
/* EP Descriptor: bulk out. */ //新增一个输出端点。
LEN_ENDPOINT, /* bLength */
DESC_ENDPOINT, /* bDescriptorType */
(INT_OUT_EP_NUM | EP_OUTPUT), /* bEndpointAddress */
EP_BULK, /* bmAttributes */
/* wMaxPacketSize */
EPB_MAX_PKT_SIZE & 0x00FF,
((EPB_MAX_PKT_SIZE & 0xFF00) >> 8),
0x00 /* bInterval */
};
修改LEN_CONFIG_AND_SUBORDINATE的值(减去了一个REPORT,增加了一个END):
#define LEN_CONFIG_AND_SUBORDINATE (LEN_CONFIG+LEN_INTERFACE+LEN_ENDPOINT+LEN_ENDPOINT)
修改hid_mouse.c中的HID_InitForHighSpeed()、HID_InitForFullSpeed(),增加OUT端点配置:
/**
* @brief USBD Endpoint Config.
* @param None.
* @retval None.
*/
void HID_InitForHighSpeed(void)
{
/* EPA ==> Interrupt IN endpoint, address 1 */
USBD_SetEpBufAddr(EPA, EPA_BUF_BASE, EPA_BUF_LEN);
USBD_SET_MAX_PAYLOAD(EPA, EPA_MAX_PKT_SIZE);
USBD_ConfigEp(EPA, INT_IN_EP_NUM, USB_EP_CFG_TYPE_BULK, USB_EP_CFG_DIR_IN);
/* EPB ==> Interrupt OUT endpoint, address 1 */
USBD_SetEpBufAddr(EPB, EPB_BUF_BASE, EPB_BUF_LEN);
USBD_SET_MAX_PAYLOAD(EPB, EPA_MAX_PKT_SIZE);
USBD_ConfigEp(EPB, INT_OUT_EP_NUM, USB_EP_CFG_TYPE_BULK, USB_EP_CFG_DIR_OUT);
USBD_ENABLE_EP_INT(EPB, USBD_EPINTEN_RXPKIEN_Msk);
}
void HID_InitForFullSpeed(void)
{
/* EPA ==> Interrupt IN endpoint, address 1 */
USBD_SetEpBufAddr(EPA, EPA_BUF_BASE, EPA_BUF_LEN);
USBD_SET_MAX_PAYLOAD(EPA, EPA_OTHER_MAX_PKT_SIZE);
USBD_ConfigEp(EPA, INT_IN_EP_NUM, USB_EP_CFG_TYPE_BULK, USB_EP_CFG_DIR_IN);
/* EPB ==> Interrupt OUT endpoint, address 1 */
USBD_SetEpBufAddr(EPB, EPB_BUF_BASE, EPB_BUF_LEN);
USBD_SET_MAX_PAYLOAD(EPB, EPB_OTHER_MAX_PKT_SIZE);
USBD_ConfigEp(EPB, INT_OUT_EP_NUM, USB_EP_CFG_TYPE_BULK, USB_EP_CFG_DIR_IN);
USBD_ENABLE_EP_INT(EPB, USBD_EPINTEN_RXPKIEN_Msk);
}
修改UsbString:
uint8_t gu8ProductStringDesc[] __attribute__((aligned(4))) =
{
#endif
34,
DESC_STRING,
'B', 0, 'U', 0, 'L', 0, 'K', 0,' ', 0, 'T', 0, 'r', 0, 'a', 0, 'n', 0, 's', 0, 'a', 0, 'c', 0, 't', 0, 'i', 0, 'o', 0, 'n', 0
};
编译程序,重新烧录。电脑->设备管理器>其它设备 下多了一个名称为BULK Transaction的设备:
2.生成USB 驱动
运行inf-wizard.exe应用,点击 Next:
选择Vendor ID为0x303A、Product ID为0x3002的设备,点击Next:
Manufacturer Name、Device Name可修改为其它类容,点击Next:
选择一个路径存放驱动文件。
点击Done完成并退出。
3.安装驱动
右键 ->更新驱动程序->浏览我的电脑以查找驱动程序(R),选择刚才存放驱动的文件夹:
点击 下一页。
一般会遇到以上问题,这是因为驱动没有包含数字签名,解决办法。
驱动安装成功之后,设备管理器展示如下:
4.使用工具查看设备描述符
运行testlibusb-win.exe,设备描述符如下:
5.编写NUC980 bulk读写函数及测试程序
在hid_mouse.c中增加bulk读写函数:
/**
* @brief USBD Endpoint Write.
* @param None.
* @retval None.
*/
void BulkWriteData( uint8_t *Datas, uint16_t Len )
{
uint16_t i;
for ( i=0; i<Len; i++)
{
USBD->EP[EPA].ep.EPDAT_BYTE = Datas[i];
}
USBD->EP[EPA].EPRSPCTL = USB_EP_RSPCTL_SHORTTXEN;
USBD_ENABLE_EP_INT(EPA, USBD_EPINTEN_INTKIEN_Msk);
}
/**
* @brief USBD Endpoint Read.
* @param None.
* @retval None.
*/
uint16_t BulkReadData( uint8_t *Datas, uint16_t Len )
{
uint16_t i;
uint16_t DataCnt = USBD->EP[EPB].EPDATCNT;
if( DataCnt > 0 )
{
for( i = 0; i < DataCnt; i++ )
{
Datas[i] = USBD->EP[EPB].ep.EPDAT_BYTE;
}
}
return DataCnt;
}
main.c中增加接收和发送数据的逻辑,main()函数如下:
int32_t main (void)
{
uint8_t i, Cnt = 0;
uint8_t DataBuf[EPB_MAX_PKT_SIZE];
uint16_t RxLen;
sysDisableCache();
sysFlushCache(I_D_CACHE);
sysEnableCache(CACHE_WRITE_BACK);
UART_Init();
printf("\n");
printf("=======================\n");
printf(" USB HID Mouse \n");
printf("=======================\n");
sysInstallISR(IRQ_LEVEL_1, IRQ_UDC, (PVOID)USBD_IRQHandler);
/* enable CPSR I bit */
sysSetLocalInterrupt(ENABLE_IRQ);
sysEnableInterrupt(IRQ_UDC);
USBD_Open(&gsInfo, HID_ClassRequest, NULL);
/* Endpoint configuration */
HID_Init();
/* Start transaction */
while(1)
{
if (USBD_IS_ATTACHED())
{
USBD_Start();
break;
}
}
while(1)
{
RxLen = BulkReadData(DataBuf, EPB_MAX_PKT_SIZE );
if( RxLen > 0 )
{
BulkWriteData( DataBuf, EPA_MAX_PKT_SIZE );
}
}
}
编译之后烧写到NUC980。
6.编写windows应用程序测试数据通信
基于libusb.lib编写一个测试应用,进行简单的数据的发送和接收。代码如下:
#include "lusb0_usb.h"
#include <stdio.h>
#include "stdint.h"
#include <iostream>
#include <sys/timeb.h>
#include "windows.h"
using namespace std;
// Enables this example to work with a device running the
// libusb-win32 PIC Benchmark Firmware.
//#define BENCHMARK_DEVICE
//
// TEST SETUP (User configurable)
// Issues a Set configuration request
#define TEST_SET_CONFIGURATION
// Issues a claim interface request
#define TEST_CLAIM_INTERFACE
// Use the libusb-win32 async transfer functions. see
// transfer_bulk_async() below.
#define TEST_ASYNC
// Attempts one bulk read.
#define TEST_BULK_READ
// Attempts one bulk write.
#define TEST_BULK_WRITE
// Device vendor and product id.
//#define MY_VID 0x05AC
//#define MY_PID 0x12A0
#define MY_VID 0x303A
#define MY_PID 0x3002
// Device configuration and interface id.
#define MY_CONFIG 1 //bConfigurationValue --lwz
#define MY_INTF 0 //bInterfaceNumber --lwz
// Device endpoint(s)
#define EP_IN 0x81 //0x81
#define EP_OUT 0x01
// Device of bytes to transfer.
#define RX_BUF_SIZE 512
#define TX_BUF_SIZE 512
#define TEST_BULK_SPEED
usb_dev_handle *dev = NULL; /* the device handle */
//
usb_dev_handle *open_dev(void);
uint16_t usb_dev_read_sync( uint8_t *Buf, uint16_t bufsz );
bool usb_dev_write_async( uint8_t *Datas, uint16_t DataLen, int timeout );
int usb_dev_read_async( uint8_t *Buf, uint16_t bufsz, int timeout );
static int transfer_bulk_async(usb_dev_handle *dev,
int ep,
char *bytes,
int size,
int timeout);
usb_dev_handle *open_dev(void)
{
struct usb_bus *bus;
struct usb_device *dev;
for (bus = usb_get_busses(); bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if (dev->descriptor.idVendor == MY_VID
&& dev->descriptor.idProduct == MY_PID)
{
return usb_open(dev);
}
}
}
return NULL;
}
//
// usb_dev_init.
//
void usb_dev_init( void )
{
usb_init(); /* initialize the library */
}
//
// usb_dev_open.
//
bool usb_dev_open( void )
{
usb_find_busses(); /* find all busses */
usb_find_devices(); /* find all connected devices */
if (!(dev = open_dev()))
{
printf("error opening device: \n%s\n", usb_strerror());
return false;
}
if (usb_set_configuration(dev, MY_CONFIG) < 0) //MY_CONFIG是配置描述符中的bConfigurationValue
{
printf("error setting config #%d: %s\n", MY_CONFIG, usb_strerror());
usb_close(dev);
return false;
}
if (usb_claim_interface(dev, MY_INTF) < 0) //注册与操作系统通信的接口 --lwz
{
printf("error claiming interface #%d:\n%s\n", MY_INTF, usb_strerror());
usb_close(dev);
return false;
}
return true;
}
//
// usb_dev_close.
//
void usb_dev_close( void )
{
if (dev)
{
usb_release_interface(dev, MY_INTF); //注销被usb_claim_interface函数调用后的接口,释放资源 --lwz
usb_close(dev);
}
}
bool usb_dev_isopen( void )
{
if( dev != NULL )
{
return true;
}
return false;
}
void usb_dev_reset_epin( void )
{
usb_clear_halt( dev, EP_IN );
}
void usb_dev_reset_epout( void )
{
usb_clear_halt( dev, EP_OUT );
}
int main(char argc,char **argv)
{
usb_dev_handle *dev = NULL; /* the device handle */
char tmp[RX_BUF_SIZE];
int ret;
void* async_read_context = NULL;
void* async_write_context = NULL;
usb_init(); /* initialize the library */
usb_find_busses(); /* find all busses */
usb_find_devices(); /* find all connected devices */
if (!(dev = open_dev()))
{
printf("error opening device: \n%s\n", usb_strerror());
return 0;
}
else
{
printf("success: device %04X:%04X opened\n", MY_VID, MY_PID);
}
#ifdef TEST_SET_CONFIGURATION
if (usb_set_configuration(dev, MY_CONFIG) < 0) //MY_CONFIG是配置描述符中的bConfigurationValue
{
printf("error setting config #%d: %s\n", MY_CONFIG, usb_strerror());
usb_close(dev);
return 0;
}
else
{
printf("success: set configuration #%d\n", MY_CONFIG);
}
#endif
#ifdef TEST_CLAIM_INTERFACE
if (usb_claim_interface(dev, 0) < 0) //注册与操作系统通信的接口 --lwz
{
printf("error claiming interface #%d:\n%s\n", MY_INTF, usb_strerror());
usb_close(dev);
return 0;
}
else
{
printf("success: claim_interface #%d\n", MY_INTF);
}
#endif
#ifdef TEST_BULK_SPEED
char tmpOut[TX_BUF_SIZE];
char tmpIn[RX_BUF_SIZE];
uint8_t Count = 0;
uint64_t ComCnt = 0;
uint64_t errCnt = 0;
struct timeb rawtime;
ftime(&rawtime);
uint64_t StartTime = rawtime.time;
for( ;; )
{
for( uint16_t i = 0; i < sizeof(tmpOut); i++ )
{
tmpOut[i] = Count++;
}
ComCnt ++;
ret = transfer_bulk_async(dev, EP_OUT, tmpOut, sizeof(tmpOut), 5000);
if (ret < 0)
{
// printf("error writing:\n%s\n", usb_strerror());
}
else
{
// printf("success: bulk write %d bytes\n", ret);
}
// Running an async read test
ret = transfer_bulk_async(dev, EP_IN, tmpIn, sizeof(tmpIn), 5000);
if (ret < 0)
{
printf("error reading:\n%s\n", usb_strerror());
}
else
{
printf("success: bulk read %d bytes\n", ret);
}
//if( memcmp( tmpOut, tmpIn, BUF_SIZE ) != 0 )
//{
// errCnt ++;
//}
for( int j = 0; j < TX_BUF_SIZE; j++ )
{
if( tmpOut[j] != tmpIn[j] )
{
errCnt ++;
break;
}
}
if( (ComCnt % 2000) == 0 )
{
printf("ComCnt:%d ",ComCnt );
printf("errCnt:%d \n",errCnt );
struct timeb rawtime1;
ftime(&rawtime1);
uint64_t endTime = rawtime1.time;
printf("transfer speed:%0.2f bps\n", ((float)(ComCnt * TX_BUF_SIZE * 2 * 8) / (endTime - StartTime)) );
}
// Sleep(3);
}
#endif
#ifdef TEST_BULK_WRITE
#ifdef BENCHMARK_DEVICE
ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
14, /* set/get test */
2, /* test type */
MY_INTF, /* interface id */
tmp, 1, 1000); //从默认的管道发送和接受控制数据--lwz
#endif
#ifdef TEST_ASYNC
// Running an async write test
for( int i = 0; i < sizeof(tmp); i++ )
{
tmp[i] = i;
}
ret = transfer_bulk_async(dev, EP_OUT, tmp, sizeof(tmp), 5000);
#else
// Running a sync write test
ret = usb_bulk_write(dev, EP_OUT, tmp, sizeof(tmp), 5000);
#endif
if (ret < 0)
{
printf("error writing:\n%s\n", usb_strerror());
}
else
{
printf("success: bulk write %d bytes\n", ret);
}
#endif
memset(tmp, 0 , TX_BUF_SIZE );
#ifdef TEST_BULK_READ
#ifdef BENCHMARK_DEVICE
ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
14, /* set/get test */
1, /* test type */
MY_INTF, /* interface id */
tmp, 1, 1000);
#endif
#ifdef TEST_ASYNC //有声明--lwz
// Running an async read test
ret = transfer_bulk_async(dev, EP_IN, tmp, sizeof(tmp), 5000);
#else
// Running a sync read test
ret = usb_bulk_read(dev, EP_IN, tmp, sizeof(tmp), 5000);
#endif
if (ret < 0)
{
printf("error reading:\n%s\n", usb_strerror());
}
else
{
printf("success: bulk read %d bytes\n", ret);
for(int i = 0; i < ret; i++ )
{
printf( "%02X ", tmp[i]);
}
printf("\n");
}
#endif
#ifdef TEST_BULK_READ
#ifdef BENCHMARK_DEVICE
ret = usb_control_msg(dev, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
14, /* set/get test */
1, /* test type */
MY_INTF, /* interface id */
tmp, 1, 1000);
#endif
#ifdef TEST_ASYNC
// Running an async read test
ret = transfer_bulk_async(dev, EP_IN, tmp, sizeof(tmp), 5000);
#else
// Running a sync read test
ret = usb_bulk_read(dev, EP_IN, tmp, sizeof(tmp), 5000);
#endif
if (ret < 0)
{
printf("error reading:\n%s\n", usb_strerror());
}
else
{
printf("success: bulk read %d bytes\n", ret);
}
#endif
#ifdef TEST_CLAIM_INTERFACE
usb_release_interface(dev, 0); //注销被usb_claim_interface函数调用后的接口,释放资源 --lwz
#endif
if (dev)
{
usb_close(dev);
}
printf("Done.\n");
return 0;
}
/*
* Read/Write using async transfer functions.
*
* NOTE: This function waits for the transfer to complete essentially making
* it a sync transfer function so it only serves as an example of how one might
* implement async transfers into thier own code.
*/
static int transfer_bulk_async(usb_dev_handle *dev,
int ep,
char *bytes,
int size,
int timeout)
{
// Each async transfer requires it's own context. A transfer
// context can be re-used. When no longer needed they must be
// freed with usb_free_async().
//
void* async_context = NULL;
int ret;
// Setup the async transfer. This only needs to be done once
// for multiple submit/reaps. (more below)
//
ret = usb_bulk_setup_async(dev, &async_context, ep);
if (ret < 0)
{
printf("error usb_bulk_setup_async:\n%s\n", usb_strerror());
goto Done;
}
// Submit this transfer. This function returns immediately and the
// transfer is on it's way to the device.
//
ret = usb_submit_async(async_context, bytes, size);
if (ret < 0)
{
printf("error usb_submit_async:\n%s\n", usb_strerror());
usb_free_async(&async_context);
goto Done;
}
// Wait for the transfer to complete. If it doesn't complete in the
// specified time it is cancelled. see also usb_reap_async_nocancel().
//
ret = usb_reap_async(async_context, timeout);
// Free the context.
usb_free_async(&async_context);
Done:
return ret;
}
//
// usb_dev_read_async.
//
int usb_dev_read_async( uint8_t *Buf, uint16_t bufsz, int timeout )
{
if( dev == NULL )
{
return 0;
}
int ret;
char tmp[RX_BUF_SIZE];
ret = transfer_bulk_async(dev, EP_IN, tmp, RX_BUF_SIZE, timeout );
if( ret > 0 )
{
if( bufsz >= ret )
{
memcpy(Buf, tmp, ret );
}
}
return ret;
}
//
// usb_dev_write_async.
//
bool usb_dev_write_async( uint8_t *Datas, uint16_t DataLen, int timeout )
{
if( dev == NULL )
{
return false;
}
int ret;
ret = transfer_bulk_async(dev, EP_OUT, (char *)Datas, DataLen , timeout);
if( ret < 0 )
{
return false;
}
return true;
}
//
// usb_dev_read_sync.
//
uint16_t usb_dev_read_sync( uint8_t *Buf, uint16_t bufsz )
{
if( dev == NULL )
{
return 0;
}
int ret;
char tmp[RX_BUF_SIZE];
ret = usb_bulk_read(dev, EP_IN, tmp, RX_BUF_SIZE,5000);
if( ret > 0 )
{
if( bufsz >= ret )
{
memcpy(Buf, tmp, ret );
}
}
return ret;
}
上位机运行日志如下:
计算出的速率大约2.7MB/s,比全速高一点,这个速度还有待提升。
NUC980技术交流QQ群:912184327