nu_bridge是新唐的牛桥,借助该USB工具,pc端可以方便的调试外部的SPI, I2C设备。
新唐提供的驱动包中已经包含了winusb的驱动。
在测试下面的程序之前需要安装驱动。
这里是用vs2013 开发一个简单的winUSB控制台测试程序,帮助入门winUSB的API。
新建一个控制台工程:nu_bridge_simple
点击完成
添加文件:
文件:device.cpp
#include "pch.h"
#include <SetupAPI.h>
HRESULT
RetrieveDevicePath(
_Out_bytecap_(BufLen) LPTSTR DevicePath,
_In_ ULONG BufLen,
_Out_opt_ PBOOL FailureDeviceNotFound
);
HRESULT
OpenDevice(
_Out_ PDEVICE_DATA DeviceData,
_Out_opt_ PBOOL FailureDeviceNotFound
)
/*++
Routine description:
Open all needed handles to interact with the device.
If the device has multiple USB interfaces, this function grants access to
only the first interface.
If multiple devices have the same device interface GUID, there is no
guarantee of which one will be returned.
Arguments:
DeviceData - Struct filled in by this function. The caller should use the
WinusbHandle to interact with the device, and must pass the struct to
CloseDevice when finished.
FailureDeviceNotFound - TRUE when failure is returned due to no devices
found with the correct device interface (device not connected, driver
not installed, or device is disabled in Device Manager); FALSE
otherwise.
Return value:
HRESULT
--*/
{
HRESULT hr = S_OK;
BOOL bResult;
DeviceData->HandlesOpen = FALSE;
hr = RetrieveDevicePath(DeviceData->DevicePath,
sizeof(DeviceData->DevicePath),
FailureDeviceNotFound);
if (FAILED(hr)) {
return hr;
}
DeviceData->DeviceHandle = CreateFile(DeviceData->DevicePath,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE == DeviceData->DeviceHandle) {
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
bResult = WinUsb_Initialize(DeviceData->DeviceHandle,
&DeviceData->WinusbHandle);
if (FALSE == bResult) {
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
DeviceData->HandlesOpen = TRUE;
return hr;
}
VOID
CloseDevice(
_Inout_ PDEVICE_DATA DeviceData
)
/*++
Routine description:
Perform required cleanup when the device is no longer needed.
If OpenDevice failed, do nothing.
Arguments:
DeviceData - Struct filled in by OpenDevice
Return value:
None
--*/
{
if (FALSE == DeviceData->HandlesOpen) {
//
// Called on an uninitialized DeviceData
//
return;
}
WinUsb_Free(DeviceData->WinusbHandle);
CloseHandle(DeviceData->DeviceHandle);
DeviceData->HandlesOpen = FALSE;
return;
}
//下面的OSR_DEVICE_INTERFACE是从牛桥的pc端软件copy过来的
static const GUID OSR_DEVICE_INTERFACE =
{ 0x1f4911f2, 0xc55d, 0x4561, { 0x98, 0x7a, 0xbc, 0xfa, 0x74, 0xd5, 0xbb, 0x6d } };
HRESULT
RetrieveDevicePath(
_Out_bytecap_(BufLen) LPTSTR DevicePath,
_In_ ULONG BufLen,
_Out_opt_ PBOOL FailureDeviceNotFound
)
/*++
Routine description:
Retrieve the device path that can be used to open the WinUSB-based device.
If multiple devices have the same device interface GUID, there is no
guarantee of which one will be returned.
Arguments:
DevicePath - On successful return, the path of the device (use with CreateFile).
BufLen - The size of DevicePath's buffer, in bytes
FailureDeviceNotFound - TRUE when failure is returned due to no devices
found with the correct device interface (device not connected, driver
not installed, or device is disabled in Device Manager); FALSE
otherwise.
Return value:
HRESULT
--*/
{
BOOL bResult = FALSE;
HDEVINFO deviceInfo;
SP_DEVICE_INTERFACE_DATA interfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;
ULONG length;
ULONG requiredLength=0;
HRESULT hr;
if (NULL != FailureDeviceNotFound) {
*FailureDeviceNotFound = FALSE;
}
printf("RetrieveDevicePath \n");
//
// Enumerate all devices exposing the interface
//
/*
deviceInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USBApplication1,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
*/
deviceInfo = SetupDiGetClassDevs(&OSR_DEVICE_INTERFACE,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfo == INVALID_HANDLE_VALUE) {
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
//
// Get the first interface (index 0) in the result set
//
bResult = SetupDiEnumDeviceInterfaces(deviceInfo,
NULL,
&OSR_DEVICE_INTERFACE,
0,
&interfaceData);
if (FALSE == bResult) {
//
// We would see this error if no devices were found
//
if (ERROR_NO_MORE_ITEMS == GetLastError() &&
NULL != FailureDeviceNotFound) {
*FailureDeviceNotFound = TRUE;
}
hr = HRESULT_FROM_WIN32(GetLastError());
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
//
// Get the size of the path string
// We expect to get a failure with insufficient buffer
//
bResult = SetupDiGetDeviceInterfaceDetail(deviceInfo,
&interfaceData,
NULL,
0,
&requiredLength,
NULL);
if (FALSE == bResult && ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
hr = HRESULT_FROM_WIN32(GetLastError());
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
//
// Allocate temporary space for SetupDi structure
//
detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
LocalAlloc(LMEM_FIXED, requiredLength);
if (NULL == detailData)
{
hr = E_OUTOFMEMORY;
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
length = requiredLength;
//
// Get the interface's path string
//
bResult = SetupDiGetDeviceInterfaceDetail(deviceInfo,
&interfaceData,
detailData,
length,
&requiredLength,
NULL);
if(FALSE == bResult)
{
hr = HRESULT_FROM_WIN32(GetLastError());
LocalFree(detailData);
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
//
// Give path to the caller. SetupDiGetDeviceInterfaceDetail ensured
// DevicePath is NULL-terminated.
//
hr = StringCbCopy(DevicePath,
BufLen,
detailData->DevicePath);
LocalFree(detailData);
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
其中下面这句话比较关键是和驱动配合使用的:
//下面的OSR_DEVICE_INTERFACE是从牛桥的pc端软件copy过来的
static const GUID OSR_DEVICE_INTERFACE =
{ 0x1f4911f2, 0xc55d, 0x4561, { 0x98, 0x7a, 0xbc, 0xfa, 0x74, 0xd5, 0xbb, 0x6d } };
文件:device.h
//
// Define below GUIDs
//
#include <initguid.h>
//
// Device Interface GUID.
// Used by all WinUsb devices that this application talks to.
// Must match "DeviceInterfaceGUIDs" registry value specified in the INF file.
// cce59234-f9f6-43c8-9007-a61e760fb59f
//
//DEFINE_GUID(GUID_DEVINTERFACE_USBApplication1,
// 0xcce59234,0xf9f6,0x43c8,0x90,0x07,0xa6,0x1e,0x76,0x0f,0xb5,0x9f);
//下面这句话没有用到
DEFINE_GUID(GUID_DEVINTERFACE_USBApplication1,
0x1f4911f2, 0xc55d, 0x4561, 0x98, 0x7a, 0xbc, 0xfa, 0x74, 0xd5, 0xbb, 0x6d);
typedef struct _DEVICE_DATA {
BOOL HandlesOpen;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE DeviceHandle;
TCHAR DevicePath[MAX_PATH];
} DEVICE_DATA, *PDEVICE_DATA;
HRESULT
OpenDevice(
_Out_ PDEVICE_DATA DeviceData,
_Out_opt_ PBOOL FailureDeviceNotFound
);
VOID
CloseDevice(
_Inout_ PDEVICE_DATA DeviceData
);
文件:pch.h
#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <winusb.h>
#include <usb.h>
#include "device.h"
将nu_bridge_simple.cpp中的所有内容删掉:
替换为如下内容:
// nu_bridge_simple.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include "pch.h"
#include <stdio.h>
LONG __cdecl
_tmain(
LONG Argc,
LPTSTR * Argv
)
/*++
Routine description:
Sample program that communicates with a USB device using WinUSB
--*/
{
DEVICE_DATA deviceData;
HRESULT hr;
USB_DEVICE_DESCRIPTOR deviceDesc;
BOOL bResult;
BOOL noDevice;
ULONG lengthReceived;
UNREFERENCED_PARAMETER(Argc);
UNREFERENCED_PARAMETER(Argv);
//
// Find a device connected to the system that has WinUSB installed using our
// INF
//
hr = OpenDevice(&deviceData, &noDevice);
if (FAILED(hr)) {
if (noDevice) {
printf("Device not connected or driver not installed\n");
}
else {
printf("Failed looking for device, HRESULT 0x%x\n", hr);
}
return 0;
}
//
// Get device descriptor
//
bResult = WinUsb_GetDescriptor(deviceData.WinusbHandle,
USB_DEVICE_DESCRIPTOR_TYPE,
0,
0,
(PBYTE)&deviceDesc,
sizeof(deviceDesc),
&lengthReceived);
if (FALSE == bResult || lengthReceived != sizeof(deviceDesc)) {
printf("Error among LastError %d or lengthReceived %d\n",
FALSE == bResult ? GetLastError() : 0,
lengthReceived);
CloseDevice(&deviceData);
return 0;
}
//
// Print a few parts of the device descriptor
//
printf("Device found: VID_%04X&PID_%04X; bcdUsb %04X\n",
deviceDesc.idVendor,
deviceDesc.idProduct,
deviceDesc.bcdUSB);
getchar();
CloseDevice(&deviceData);
return 0;
}
添加完文件之后,需要添加库:
添加的库的名称是:
winusb.lib
setupapi.lib
将牛桥插入PC 的usb端口,然后编译程序,然后执行
运行效果如下:
RetrieveDevicePath
Device found: VID_0416&PID_3101; bcdUsb 0110
然后按下键盘任意键,退出程序。
参考内容:
1.How to Access a USB Device by Using WinUSB Functions
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/usbcon/using-winusb-api-to-communicate-with-a-usb-device
2.Write a Windows desktop app based on the WinUSB template
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/usbcon/how-to-write-a-windows-desktop-app-that-communicates-with-a-usb-device
3.How to Access a USB Device by Using WinUSB Functions
https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/using-winusb-api-to-communicate-with-a-usb-device
参考软件:
[0508] NuBridge SW/NuBridge.exe 源码 vs2008 MFC
继续:
将nu_bridge_simple.cpp中的所有内容删掉:
替换为如下内容:
// nu_bridge_simple.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include "pch.h"
#include <stdio.h>
int NU_SetDeviceSpiMasteMode(_In_ WINUSB_INTERFACE_HANDLE InterfaceHandle)
{
WINUSB_SETUP_PACKET SetupPacket;
SetupPacket.RequestType = 0x42;
SetupPacket.Request = 1;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = 0;
WinUsb_ControlTransfer(InterfaceHandle, SetupPacket, 0, 0, NULL, 0);
return 0;
}
int NU_SetDeviceLED(_In_ WINUSB_INTERFACE_HANDLE InterfaceHandle)
{
WINUSB_SETUP_PACKET SetupPacket;
SetupPacket.RequestType = 0x40;
SetupPacket.Request = 0;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = 0;
printf("NU_SetDeviceLED\n");
WinUsb_ControlTransfer(InterfaceHandle, SetupPacket, 0, 0, NULL, 0);
return TRUE;
}
BOOL GetUSBDeviceSpeed(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pDeviceSpeed)
{
if (!pDeviceSpeed || hDeviceHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
ULONG length = sizeof(UCHAR);
bResult = WinUsb_QueryDeviceInformation(hDeviceHandle, DEVICE_SPEED, &length, pDeviceSpeed);
if (!bResult)
{
printf("Error getting device speed: %d.\n", GetLastError());
goto done;
}
if (*pDeviceSpeed == LowSpeed)
{
printf("Device speed: %d (Low speed).\n", *pDeviceSpeed);
goto done;
}
if (*pDeviceSpeed == FullSpeed)
{
printf("Device speed: %d (Full speed).\n", *pDeviceSpeed);
goto done;
}
if (*pDeviceSpeed == HighSpeed)
{
printf("Device speed: %d (High speed).\n", *pDeviceSpeed);
goto done;
}
done:
return bResult;
}
struct PIPE_ID
{
UCHAR PipeInId;
UCHAR PipeOutId;
};
BOOL QueryDeviceEndpoints(WINUSB_INTERFACE_HANDLE hDeviceHandle, PIPE_ID* pipeid)
{
if (hDeviceHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
USB_INTERFACE_DESCRIPTOR InterfaceDescriptor;
ZeroMemory(&InterfaceDescriptor, sizeof(USB_INTERFACE_DESCRIPTOR));
WINUSB_PIPE_INFORMATION Pipe;
ZeroMemory(&Pipe, sizeof(WINUSB_PIPE_INFORMATION));
bResult = WinUsb_QueryInterfaceSettings(hDeviceHandle, 0, &InterfaceDescriptor);
if (bResult)
{
for (int index = 0; index < InterfaceDescriptor.bNumEndpoints; index++)
{
bResult = WinUsb_QueryPipe(hDeviceHandle, 0, index, &Pipe);
if (bResult)
{
if (Pipe.PipeType == UsbdPipeTypeControl)
{
printf("Endpoint index: %d Pipe type: Control Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
if (Pipe.PipeType == UsbdPipeTypeIsochronous)
{
printf("Endpoint index: %d Pipe type: Isochronous Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
if (Pipe.PipeType == UsbdPipeTypeBulk)
{
if (USB_ENDPOINT_DIRECTION_IN(Pipe.PipeId))
{
printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
pipeid->PipeInId = Pipe.PipeId;
}
if (USB_ENDPOINT_DIRECTION_OUT(Pipe.PipeId))
{
printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
pipeid->PipeOutId = Pipe.PipeId;
}
}
if (Pipe.PipeType == UsbdPipeTypeInterrupt)
{
printf("Endpoint index: %d Pipe type: Interrupt Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
}
else
{
continue;
}
}
}
done:
return bResult;
}
BOOL SendDatatoDefaultEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle)
{
if (hDeviceHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR bars = 0;
WINUSB_SETUP_PACKET SetupPacket;
ZeroMemory(&SetupPacket, sizeof(WINUSB_SETUP_PACKET));
ULONG cbSent = 0;
//Set bits to light alternate bars
for (short i = 0; i < 7; i += 2)
{
bars += 1 << i;
}
//Create the setup packet
SetupPacket.RequestType = 0;
SetupPacket.Request = 0xD8;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = sizeof(UCHAR);
bResult = WinUsb_ControlTransfer(hDeviceHandle, SetupPacket, &bars, sizeof(UCHAR), &cbSent, 0);
if (!bResult)
{
goto done;
}
printf("Data sent: %d \nActual data transferred: %d.\n", sizeof(bars), cbSent);
done:
return bResult;
}
BOOL NU_WritePipe(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID,UCHAR *buf, ULONG len)
{
printf("NU_WritePipe\n");
ULONG nBytesSent = 0;
if (hDeviceHandle == INVALID_HANDLE_VALUE || !pID )
{
return FALSE;
}
nBytesSent = 0;
WinUsb_WritePipe(hDeviceHandle, *pID, buf, len, &nBytesSent, 0);
if (nBytesSent != len)
{
return FALSE;
}
return TRUE;
}
BOOL Nu_WriteCmd(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID)
{
UCHAR data[] = { 0x01,0x01,0x53 ,0x01 ,0x00 ,0x00 ,0x08 ,0x06 ,0x90 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x50 };
NU_WritePipe(hDeviceHandle, pID,data,15);
UCHAR tag[] = { 0x00, 0x01 };
NU_WritePipe(hDeviceHandle, pID, tag, 2);
return TRUE;
}
ULONG NU_ReadPipe(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, UCHAR *buf, ULONG len)
{
ULONG nBytesRead = 0;
printf("NU_ReadPipe\n");
WinUsb_ReadPipe(hDeviceHandle, *pID, (unsigned char*)buf, len, &nBytesRead, 0);
return nBytesRead;
}
BOOL Nu_ReadCmd(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID)
{
UCHAR data[64];
int len;
int i;
memset(data, 0, 64);
len = NU_ReadPipe(hDeviceHandle, pID, data,64);
printf("Nu_ReadCmd len= %d\n", len);
for (i = 0; i < len; i++){
printf("%x,", data[i]);
}
printf("\n");
return TRUE;
}
BOOL NuWriteToBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG* pcbWritten)
{
if (hDeviceHandle == INVALID_HANDLE_VALUE || !pID || !pcbWritten)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR szBuffer[] = "Hello World";
ULONG cbSize = strlen((const char *)szBuffer);
ULONG cbSent = 0;
bResult = WinUsb_WritePipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbSent, 0);
if (!bResult)
{
goto done;
}
printf("Wrote to pipe %d: %s \nActual data transferred: %d.\n", *pID, szBuffer, cbSent);
*pcbWritten = cbSent;
done:
return bResult;
}
BOOL WriteToBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG* pcbWritten)
{
if (hDeviceHandle == INVALID_HANDLE_VALUE || !pID || !pcbWritten)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR szBuffer[] = "Hello World";
ULONG cbSize = strlen((const char *)szBuffer);
ULONG cbSent = 0;
bResult = WinUsb_WritePipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbSent, 0);
if (!bResult)
{
goto done;
}
printf("Wrote to pipe %d: %s \nActual data transferred: %d.\n", *pID, szBuffer, cbSent);
*pcbWritten = cbSent;
done:
return bResult;
}
BOOL ReadFromBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG cbSize)
{
if (hDeviceHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR* szBuffer = (UCHAR*)LocalAlloc(LPTR, sizeof(UCHAR)*cbSize);
ULONG cbRead = 0;
bResult = WinUsb_ReadPipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbRead, 0);
if (!bResult)
{
goto done;
}
printf("Read from pipe %d: %s \nActual data read: %d.\n", *pID, szBuffer, cbRead);
done:
LocalFree(szBuffer);
return bResult;
}
int _tmain(int argc, _TCHAR* argv[])
{
DEVICE_DATA deviceData;
HRESULT hr;
USB_DEVICE_DESCRIPTOR deviceDesc;
BOOL noDevice;
ULONG lengthReceived;
BOOL bResult = TRUE;
PIPE_ID PipeID;
HANDLE hDeviceHandle = INVALID_HANDLE_VALUE;
WINUSB_INTERFACE_HANDLE hWinUSBHandle = INVALID_HANDLE_VALUE;
UCHAR DeviceSpeed;
ULONG cbSize = 0;
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
//
// Find a device connected to the system that has WinUSB installed using our
// INF
//
hr = OpenDevice(&deviceData, &noDevice);
if (FAILED(hr)) {
if (noDevice) {
printf("Device not connected or driver not installed\n");
}
else {
printf("Failed looking for device, HRESULT 0x%x\n", hr);
}
return 0;
}
hWinUSBHandle = deviceData.WinusbHandle;
hDeviceHandle = deviceData.DeviceHandle;
bResult = GetUSBDeviceSpeed(hWinUSBHandle, &DeviceSpeed);
if(!bResult)
{
goto done;
}
bResult = QueryDeviceEndpoints(hWinUSBHandle, &PipeID);
if(!bResult)
{
goto done;
}
#if 0
bResult = NU_SetDeviceLED(hWinUSBHandle);
if(!bResult)
{
goto done;
}
#endif
printf("PipeID.PipeInId = %d\n", PipeID.PipeInId);
printf("PipeID.PipeOutId = %d\n", PipeID.PipeOutId);
NU_SetDeviceSpiMasteMode(hWinUSBHandle);
Nu_WriteCmd(hWinUSBHandle, &PipeID.PipeOutId);
Nu_ReadCmd(hWinUSBHandle, &PipeID.PipeInId);
system("PAUSE");
done:
CloseHandle(hDeviceHandle);
WinUsb_Free(hWinUSBHandle);
return 0;
}
运行效果:
RetrieveDevicePath
Device speed: 1 (Low speed).
Endpoint index: 0 Pipe type: Bulk Pipe ID:
Endpoint index: 1 Pipe type: Bulk Pipe ID:
PipeID.PipeInId = 132
PipeID.PipeOutId = 5
NU_WritePipe
NU_WritePipe
NU_ReadPipe
Nu_ReadCmd len= 10
1,0,6,0,ff,ff,ff,ff,51,14,
请按任意键继续. . .
End