1. 程式人生 > >CSR8675學習筆記:USB HID通訊

CSR8675學習筆記:USB HID通訊

為了讓CSR867x的開發更容易,現與思度科技聯合推出CSR867x學習板【淘寶連結:思度科技CSR開發板】

技術交流QQ群號:743434463
開發板會員QQ群號:725398389(憑訂單號入群,贈PPT、專案原始碼、視訊教程)

—————————–正文分割線———————————

1. 引言

常見的PC端與CSR8675的通訊方式有USB HID和UART這兩種。UART通訊方式簡單,但在產品結構上需預留專門的硬體介面,給ID設計帶來不便。USB HID通訊可以與USB音訊播放、USB充電功能共用一個硬體介面,是較理想的通訊方式。

2. 基本概念

2.1. USB HID

Universal Serial Bus(USB)是一種4線連線的通訊介面,用於PC與不同裝置間的通訊互聯。這些裝置分為不同的類。每種裝置有著共同的行為和協議,以提供相似的功能,例如:

裝置類 示例裝置
顯示 監視器
通訊 調製器
音訊 揚聲器
儲存 硬碟
人機介面 鍵盤

Human Interface Device(HID)類裝置用來由人控制電腦系統的執行,典型的HID類裝置分兩大類:

  • 鍵盤、滑鼠、按鍵、開關、旋鈕、進度條、遙控器等與人互動的裝置
  • 一些不需要人蔘與互動,但有著與HID類裝置相似的資料格式的裝置

USB HID類裝置使用相應的HID類驅動來檢索和路由資料。資料的路由和檢索是通過檢查裝置描述符及其提供的資料來完成的。
這裡寫圖片描述

2.2. HID類裝置描述符

HID類裝置描述符定義了HID類描述符的數量和長度,例如報告描述符和物理描述符。
這裡寫圖片描述

報告描述符描述了裝置產生的資料的方方面面,以及什麼樣的資料是正在監控的。通過檢查items,HID類驅動程式可以確定來自HID類裝置的資料報告的大小和組成。
這裡寫圖片描述

物理描述符集是可選的描述符,它提供有關用於啟用裝置上的控制元件的人體部位的資訊。
這裡寫圖片描述

上述HID描述符是裝置描述符結構整體中的一部分:
這裡寫圖片描述

2.3. HID類介面描述符

HID有四種功能特性:

  • Class(類):HID的Class必須是3
  • SubClass(子類):0-不支援Boot裝置,1-支援Boot裝置,
  • Protocol(協議):僅當SubClass為1時有效,0-None,1-鍵盤,2-滑鼠
  • Interface(介面):控制(Endpoint 0),中斷輸入,中斷輸出

CSR8675的介面描述符如下:

#define B_INTERFACE_CLASS_HID 0x03
#define B_INTERFACE_SUB_CLASS_HID_NO_BOOT 0x00
#define B_INTERFACE_PROTOCOL_HID_NO_BOOT 0x00     
#define I_INTERFACE_INDEX 0x00

static const UsbCodes usb_codes_hid_no_boot = {B_INTERFACE_CLASS_HID, /* bInterfaceClass */
                                               B_INTERFACE_SUB_CLASS_HID_NO_BOOT, /* bInterfaceSubClass */
                                               B_INTERFACE_PROTOCOL_HID_NO_BOOT, /* bInterfaceProtocol */
                                               I_INTERFACE_INDEX /* iInterface */
                                               };

其中的I_INTERFACE_INDEX指的是當前介面描述符對應的字串描述符的索引號,CSR8675支援16個字串描述符。可在PSKEY中修改:
這裡寫圖片描述

2.4. HID類報告描述符

HID類報告描述符定義了通過HID裝置傳輸的資料的格式,官方提供了簡易工具用於檢視、編輯和儲存HID類報告描述符(官方下載連結:HID Descriptor Tool),工具介面如下:
這裡寫圖片描述
用這個工具可以生成面向C的程式碼,方便實現自定義的HID類報告描述符。CSR8675的HID類報告描述符的程式碼如下:

typedef struct {
    uint8 report_id;
    uint8 command;
    uint8 data[1021];
} hid_command_t;

typedef struct {
    uint8 report_id;
    uint8 last_command;        
    uint8 last_command_status;
} hid_status_t;

#define REPORT_COMMAND_ID       1
#define REPORT_COMMAND_SIZE     ((sizeof(hid_command_t)/sizeof(uint8))-1)
#define REPORT_STATUS_ID        2
#define REPORT_STATUS_SIZE      ((sizeof(hid_status_t)/sizeof(uint8))-1)

/*
 HID Report Descriptor - HID Control Device */
static const uint8 report_descriptor_hid_control[] = 
{   0x06, 0x00, 0xff,              /* USAGE_PAGE (Vendor Defined Page 1) */
    0x09, 0x01,                    /* USAGE (Vendor Usage 1) */
    0xa1, 0x01,                    /* COLLECTION (Application) */
    0x15, 0x80,                    /*   LOGICAL_MINIMUM (-128) */
    0x25, 0x7f,                    /*   LOGICAL_MAXIMUM (127) */
    0x85, 0x01,                    /*   REPORT_ID (1) */
    0x09, 0x02,                    /*   USAGE (Vendor Usage 2) */
    0x96,                          /*   REPORT_COUNT */
    (REPORT_COMMAND_SIZE&0xff), 
    (REPORT_COMMAND_SIZE>>8), 
    0x75, 0x08,                    /*   REPORT_SIZE (8) */
    0x91, 0x02,                    /*   OUTPUT (Data,Var,Abs) */
    0x85, 0x02,                    /*   REPORT_ID (2) */
    0x09, 0x02,                    /*   USAGE (Vendor Usage 2) */
    0x95,                          /*   REPORT_COUNT */
    (REPORT_STATUS_SIZE&0xff),
    0x75, 0x08,                    /*   REPORT_SIZE (8) */
    0x81, 0x02,                    /*   INPUT (Data,Var,Abs) */
    /*0xb1, 0x02,*/

    0xc0                           /* END_COLLECTION */
}

用工具生成的C程式碼如下:

char ReportDescriptor[33] = {
    0x06, 0x00, 0xff,              // USAGE_PAGE (Vendor Defined Page 1)
    0x09, 0x01,                    // USAGE (Vendor Usage 1)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x15, 0x80,                    //   LOGICAL_MINIMUM (-128)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0x85, 0x01,                    //   REPORT_ID (1)
    0x09, 0x02,                    //   USAGE (Vendor Usage 2)
    0x96, 0xff, 0x03,              //   REPORT_COUNT (1023)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
    0x85, 0x02,                    //   REPORT_ID (2)
    0x09, 0x02,                    //   USAGE (Vendor Usage 2)
    0x95, 0x03,                    //   REPORT_COUNT (3)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0xc0                           // END_COLLECTION

可見兩者格式基本一致。為了便於理解HID報告描述符的資料格式,給出一般的資料結構如下:
這裡寫圖片描述

  • 每個報告描述符中包含1個Application Collection
  • 每個Application Collection中包含多個Report
  • 每個Report包含1組描述,包括報告資料的位數(Report Size),資料的長度(Report Count)等
  • 每個Report可對應多個用途(Usage)

結合CSR8675的HID報告描述符,可以觀察到其中包含2個報告:

  • 報告1:共1024位元組(報告ID佔1位元組+資料佔1023位元組),HID裝置接收此報告
  • 報告2:共4位元組(報告ID佔1位元組+資料佔3位元組),HID裝置傳送此報告

2.5. HID類端點描述符

PC端與HID裝置端的資料傳輸基於“管道——端點“”機制,示意圖如下:
這裡寫圖片描述
PC端的一個執行緒是一個管道的起點,通過USB線對接HID裝置端的一個端點。

PC端通過讀取HID類介面描述符獲取HID類端點描述符以建立合適的通訊管道。每個介面支援最多16個端點。其中端點0是預設控制埠,即每個USB HID介面都有端點0,其餘15個埠可以配置成其他傳輸模式。

HID類端點描述符的主要屬性如下:

  • 端點地址:共16個地址,以1位元組表示。bit 7:1-to host,0- to device;bit 0-3:地址。
  • 端點傳輸模式: 端點有4種傳輸模式,分別是控制傳輸模式、批量傳輸模式、同步傳輸模式和中斷傳輸模式。不同的傳輸模式適用於不同的裝置型別。控制傳輸模式適用於PC端作為主控制檯,批量傳輸模式適用於檔案傳輸,同步傳輸模式適用於音視訊資料傳輸,中斷模式適用於實時性強的控制和資料採集。
  • 最大資料包大小:埠處理資料的能力。不同的控制傳輸模式對應的最大資料包大小不一樣。
  • 輪詢間隔:訪問埠資料緩衝區的時間間隔。

CSR8675的HID端點描述符如下:

#define end_point_int_out   (0x81) /*!< Interrupt ToHost */
#define end_point_bulk_in   (0x02) /*!< Bulk FromHost */
#define end_point_bulk_out  (0x82) /*!< Bulk ToHost */
#define end_point_iso_in    (0x03) /*!< Isochronous FromHost */
#define end_point_iso_out   (0x83) /*!< Isochronous ToHost */
#define end_point_int_out2  (0x85) /*!< Interrupt ToHost */
#define end_point_bulk_in2  (0x06) /*!< Bulk FromHost */
#define end_point_bulk_out2 (0x86) /*!< Bulk ToHost */
#define end_point_iso_in2   (0x07) /*!< Isochronous FromHost */
#define end_point_int_out3  (0x89) /*!< Interrupt ToHost */
#define end_point_bulk_in3  (0x0A) /*!< Bulk FromHost */
#define end_point_bulk_out3 (0x8A) /*!< Bulk ToHost */
#define end_point_int_out4  (0x8D) /*!< Interrupt ToHost */
#define end_point_bulk_in4  (0x0E) /*!< Bulk FromHost */
#define end_point_bulk_out4 (0x8E) /*!< Bulk ToHost */

typedef enum
{
    end_point_attr_ctl = 0,    /*!< Control.*/
    end_point_attr_iso = 1,    /*!< Isochronous.*/
    end_point_attr_bulk = 2,   /*!< Bulk.*/
    end_point_attr_int = 3,    /*!< Interrupt.*/
    end_point_attr_iso_sync = 13  /*!< Isochronous & Synchronisation Type Synchronous (bits 3:2 = 11) */
} EndPointAttr;

/* USB HID endpoint information */
static const EndPointInfo epinfo_hid_control_transport[] =
{
    {        
        end_point_int_out, /* address */
        end_point_attr_int, /* attributes */
        16, /* max packet size */
        1, /* poll_interval */
        0, /* data to be appended */
        0, /* length of data appended */
    },
    {        
        end_point_bulk_in, /* address */
        end_point_attr_int, /* attributes */
        64, /* max packet size */
        1, /* poll_interval */
        0, /* data to be appended */
        0, /* length of data appended */
    }
};

可以看到描述了兩個HID埠,一個用於向PC端傳送資料,工作在中斷傳輸模式;另一個用於接收PC端的資料,工作在批量傳輸模式。

3. USB HID通訊(裝置端)

3.1. 配置PSKEY

將下列PSKEY配置通過PSTool工具寫入CSR8675的內部Flash:

&0001 = 0000 1213 005b 0002
// PSKEY_USB_DATA_PLUS_PULL_CONTROL
&01f0 = 0001// sets D+ when configuration is done (when ready)
// PSKEY_HOST_INTERFACE
&01f9 = 0002// USB link
// PSKEY_USB_DEVICE_CLASS_CODES
&02bd = 0000 0000 0000
// PSKEY_USB_PRODUCT_ID
&02bf = 1243
// PSKEY_USB_PIO_VBUS
&02d1 = fffe// Use VDD_CHG (battery charger)
// PSKEY_USB_CONFIG
&02d9 = 0038
// PSKEY_USB_ALLOW_DEEP_SLEEP
&02fc = 0003
// PSKEY_USB_VM_CONTROL
&03c0 = 0001// True
// PSKEY_ONCHIP_HCI_CLIENT
&03cc = 0001
// PSKEY_INITIAL_BOOTMODE
&03cd = 0001

上述配置是為了確保CSR8675的USB的描述符由VM層設定。另一個關鍵點是要確保boot mode 1的專屬PSKEY段沒有覆蓋上述PSKEY值。

3.2. 列舉裝置

CSR8675作為USB HID device,需要在上電時完成列舉動作:

static const usb_device_class_hid_control_config usb_hid_config_control =
{
    {interface_descriptor_hid_control_transport,
    sizeof(interface_descriptor_hid_control_transport),
    epinfo_hid_control_transport},
    {report_descriptor_hid_control,
    sizeof(report_descriptor_hid_control),
    NULL}
};

static bool usbEnumerateHidControl(void)
{
    if (!usb_hid_control_config)
    {
        usb_hid_control_config = &usb_hid_config_control;        
        PRINT(("USB: HID control default descriptors\n"));
    }

    device->usb_interface[usb_interface_hid_control] = UsbAddInterface(&usb_codes_hid_no_boot, B_DESCRIPTOR_TYPE_HID, usb_hid_control_config->interface.descriptor, usb_hid_control_config->interface.size_descriptor);
    if (device->usb_interface[usb_interface_hid_control] == usb_interface_error)
        return FALSE;

    /* Register HID Control Device report descriptor with the interface */
    PRINT(("USB: HID control UsbAddDescriptor\n"));
    if (UsbAddDescriptor(device->usb_interface[usb_interface_hid_control], B_DESCRIPTOR_TYPE_HID_REPORT, usb_hid_control_config->report.descriptor, usb_hid_control_config->report.size_descriptor) == FALSE)
        return FALSE;

    /* Add required endpoints to the interface */
    PRINT(("USB: HID control UsbAddEndPoints\n"));
    if (UsbAddEndPoints(device->usb_interface[usb_interface_hid_control], 2, usb_hid_control_config->interface.end_point_info) == FALSE)
        return FALSE;

    device->usb_task[usb_task_hid_control].handler = hidControlHandler;
    (void) VmalMessageSinkTask(StreamUsbClassSink(device->usb_interface[usb_interface_hid_control]), &device->usb_task[usb_task_hid_control]);
    (void) VmalMessageSinkTask(StreamUsbEndPointSink(end_point_bulk_in), &device->usb_task[usb_task_hid_control]);

    return TRUE;
}

上述程式碼中可以看到,初始化USB HID時需要用到HID介面描述符、報告描述符、埠描述符,且將hidControlHandler作為USB sink的訊息處理函式。

3.3. 下行資料接收(host to device)

hidControlHandler用來與PC端通過HID介面交換資料。其原始碼如下:

static void hidControlHandler(Task task, MessageId id, Message message)
{
    MessageMoreData *msg = (MessageMoreData*)message;
    uint16 packet_size;
    hid_status_t status_report;
    const uint8 *in;

    if (id == MESSAGE_MORE_DATA)
    {
        PRINT(("USB: MESSAGE_MORE_DATA hid consumer\n"));
        if (msg->source == StreamUsbClassSource(device->usb_interface[usb_interface_hid_control]))
        {
            handleHidClassRequest(StreamUsbClassSource(device->usb_interface[usb_interface_hid_control]), USB_DEVICE_CLASS_TYPE_HID_CONTROL);
        }
        else if (msg->source == USB_SOURCE)
        {
            while ((packet_size = SourceBoundary(msg->source)) != 0) 
            {
                in = SourceMap(msg->source);
                PRINT(("USB MORE INT DATA: %d\n",packet_size));
                PRINT(("command: %d\n",((hid_command_t*)in)->command));

                status_report.report_id = REPORT_STATUS_ID;
                status_report.last_command = ((hid_command_t*)in)->command;
                status_report.last_command_status= STATUS_CMD_FAILED;

                SourceDrop(msg->source, packet_size);
                HidSendStatus(&status_report);
            }
        }
    }
}

3.4. 上行資料傳送(device to host)

USB源收到新的資料後,hidControlHandler會收到MESSAGE_MORE_DATA訊息。此時判斷USB資料來源是預設埠0還是埠end_point_bulk_in。如果是埠end_point_bulk_in,讀取埠資料並呼叫HidSendStatus(&status_report)返回訊息狀態。HidSendStatus原始碼如下:

/* send a status report over the interrupt endpoint */
static void HidSendStatus(hid_status_t *status_report)
{
    Sink sink = StreamUsbEndPointSink(end_point_int_out);
    uint8 *out;

    if ((out = claimSink(sink, sizeof(hid_status_t))) != 0) 
    {
        PRINT(("Last command status=%d\n",status_report->last_command_status)); 
        memmove(out, status_report,  sizeof(hid_status_t));
        PRINT(("USB sending %d bytes\n", sizeof(hid_status_t))); 
        PanicFalse(SinkFlush(sink, sizeof(hid_status_t)));
    }
    else
    {
        PRINT(("USB cannot claim sink space\n")); 
    }
}

StreamUsbEndPointSink(end_point_int_out)的意思是將訊息狀態資料通過USB埠end_point_int_out傳送給PC端程式。

4. USB HID通訊(PC端)

Windows平臺為USB HID提供了通用的API支援,實現與HID類裝置間的USB介面通訊。用VC++編寫應用程式呼叫此API,即可方便地實現定製化的USB HID功能開發。

4.1. 搭建環境

  • 下載安裝WinDDK(官方連結
  • 新建Win32專案,參考如下配置修改工程屬性:
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <Optimization>Disabled</Optimization>
      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_DDK_;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <MinimalRebuild>true</MinimalRebuild>
      <BasicRuntimeChecks>Default</BasicRuntimeChecks>
      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
      <PrecompiledHeader>Use</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
      <AdditionalIncludeDirectories>$(WDKPATH)\inc\ddk;$(WDKPATH)\inc\api;$(WDKPATH)\inc\crt;D:\WinDDK\7600.16385.1\inc\ddk;D:\WinDDK\7600.16385.1\inc\api;D:\WinDDK\7600.16385.1\inc\crt;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <StructMemberAlignment>1Byte</StructMemberAlignment>
      <BufferSecurityCheck>false</BufferSecurityCheck>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <CallingConvention>Cdecl</CallingConvention>
      <TreatWarningAsError>false</TreatWarningAsError>
    </ClCompile>
    <Link>
      <AdditionalDependencies>Setupapi.lib;Hid.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <SubSystem>Console</SubSystem>
      <TargetMachine>MachineX86</TargetMachine>
      <AdditionalLibraryDirectories>$(WDKPATH)\lib\win7\i386;D:\WinDDK\7600.16385.1\lib\win7\i386;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <IgnoreAllDefaultLibraries>false</IgnoreAllDefaultLibraries>
      <Driver>NotSet</Driver>
      <EntryPointSymbol>
      </EntryPointSymbol>
      <RandomizedBaseAddress>false</RandomizedBaseAddress>
      <DataExecutionPrevention>false</DataExecutionPrevention>
      <GenerateMapFile>true</GenerateMapFile>
    </Link>
    <ProjectReference>
      <LinkLibraryDependencies>false</LinkLibraryDependencies>
    </ProjectReference>
  </ItemDefinitionGroup>

4.2. 查詢目標HID類裝置

嘗試開啟HID類裝置:

/* returns handle when device found or NULL when not found */
HANDLE OpenDevice(void) {
    wchar_t device_path[MAX_PATH];
    HANDLE DeviceHandle;
    if (EnumerateDevices(device_path)) {
        /* create handle to the device */
        DeviceHandle=CreateFile(device_path, 
                                GENERIC_READ | GENERIC_WRITE, 
                                FILE_SHARE_READ|FILE_SHARE_WRITE, 
                                (LPSECURITY_ATTRIBUTES)NULL,
                                OPEN_EXISTING, 
                                FILE_ATTRIBUTE_NORMAL, 
                                NULL);
        if (DeviceHandle!=INVALID_HANDLE_VALUE) {
            return(DeviceHandle);
        }
    }
    return(NULL);
}

列舉裝置時會查詢每個HID裝置的介面描述符中的Product ID、Vendor ID、HID類報告描述符的Usage Page和Usage屬性值是否與目標HID裝置的相符。當檢查到匹配裝置後,返回裝置的控制代碼DeviceHandle。

int EnumerateDevices(wchar_t *device_path) {
    SP_DEVICE_INTERFACE_DATA devInfoData;
    int MemberIndex;
    ULONG Length;
    GUID HidGuid;
    HANDLE hDevInfo;
    HANDLE LocDevHandle;
    HIDD_ATTRIBUTES Attributes;
    PSP_DEVICE_INTERFACE_DETAIL_DATA detailData;
    PHIDP_PREPARSED_DATA PreparsedData;
    HIDP_CAPS Capabilities;
    int result=0;

    /* get HID GUID */
    HidD_GetHidGuid(&HidGuid);
    /* get pointer to the device information */
    hDevInfo = SetupDiGetClassDevs(&HidGuid,
                                   NULL, 
                                   NULL, 
                                   DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
    /* go through all the device infos and find devices we are interested in */
    devInfoData.cbSize = sizeof(devInfoData);
    MemberIndex = 0;
    while((SetupDiEnumDeviceInterfaces(hDevInfo, 
                                      0, 
                                      &HidGuid, 
                                      MemberIndex, 
                                      &devInfoData))&&(result==0)) {
        /* first get the size of memory needed to hold the device interface info */
        SetupDiGetDeviceInterfaceDetail(hDevInfo, 
                                        &devInfoData, 
                                        NULL, 
                                        0, 
                                        &Length, 
                                        NULL);
        /* allocate memory */
        detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(Length);
        /* and set the size in the structure */
        detailData -> cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        /* now get the actual device interface info */
        SetupDiGetDeviceInterfaceDetail(hDevInfo, 
                                        &devInfoData, 
                                        detailData, 
                                        Length, 
                                        NULL, 
                                        NULL);
#ifdef DEBUG
        wprintf(L"%s\n",detailData->DevicePath);
#endif
        /* create handle to the device */
        LocDevHandle=CreateFile(detailData->DevicePath, 
                                GENERIC_READ | GENERIC_WRITE, 
                                FILE_SHARE_READ|FILE_SHARE_WRITE, 
                                (LPSECURITY_ATTRIBUTES)NULL,
                                OPEN_EXISTING, 
                                FILE_ATTRIBUTE_NORMAL, 
                                NULL);
        /* set the size in the structure */
        Attributes.Size = sizeof(Attributes);
        /* get and test the VID and PID */
        HidD_GetAttributes(LocDevHandle,&Attributes);
        if ((Attributes.ProductID == 0x1243) &&
            (Attributes.VendorID == 0xa12)) {
            /* found the right device */
            /* is it the right HID collection? */
            HidD_GetPreparsedData(LocDevHandle, &PreparsedData);
            HidP_GetCaps(PreparsedData, &Capabilities);
#if 1
            wprintf(L"%04x %04x\n",Capabilities.UsagePage,Capabilities.Usage);
#endif
            if ((Capabilities.UsagePage == 0xFF00) &&
                (Capabilities.Usage == 0x0001)) {
                    /* this is the correct HID collection */
                if (device_path!=NULL) {
                    wcscpy(device_path,detailData->DevicePath);
                }
#ifdef DEBUG                
                wprintf(L"Device Found\n");
#endif
                result=1;
            }
        }
        /* close the device handle again */
        CloseHandle(LocDevHandle);
        /* and free the memory used to hold device info */
        free(detailData);
        /* try the next device */
        MemberIndex++;
    }
    /* free memory used for the device information set */
    SetupDiDestroyDeviceInfoList(hDevInfo);
    return result;
}

4.3. 下行資料傳送(host to device)

PC端呼叫API函式向device傳送資料:

    /* reboot to bootmode 0 */
    command_report.report_id=REPORT_COMMAND_ID;
    command_report.command=COMMAND_NOP;
    command_report.data[0]=0x00;
    status_response.last_command_status=-1;
    if (!WriteFile(DeviceHandle,&command_report,sizeof(hid_command_t),&count,NULL)) {
        /* cannot write */
        return(FALSE);
    }

這裡的REPORT_COMMAND_ID與CSR8675程式中定義的值相同。

4.4. 上行資料接收(device to host)

PC端呼叫API函式查詢接收device的上行資料:

    /* wait for response */
    if (!ReadFile(DeviceHandle,&status_response,sizeof(hid_status_t),&count,NULL)) {
        /* cannot read */
        return(FALSE);
    }
    wprintf(L"Response is %d.\n", status_response.last_command_status);

這裡需要注意的是,如果上行資料未能傳送成功,程式會一直阻塞在ReadFile函式,不能往下執行。容易犯的錯誤是,裝置端未按照HID類報告描述符中規定的資料格式傳送資料。

5. 總結

  • 除錯過程遇到問題時,可藉助Bus Hound工具捕捉PC端的USB HID通訊資料包來分析定位問題。
  • CSR8675的PID和VID儲存在PSKEY中,可使用PSTool工具修改。

6. 參考文章