Mengakses perangkat USB dengan menggunakan fungsi WinUSB

Artikel ini mencakup panduan terperinci tentang cara menggunakan fungsi WinUSB untuk berkomunikasi dengan perangkat USB yang menggunakan Winusb.sys sebagai driver fungsinya.

Ringkasan

  • Membuka perangkat dan mendapatkan handel WinUSB.
  • Mendapatkan informasi tentang pengaturan perangkat, konfigurasi, dan antarmuka semua antarmuka, dan titik akhirnya.
  • Membaca dan menulis data ke titik akhir massal dan interupsi.

API penting

Jika Anda menggunakan Microsoft Visual Studio 2013, buat aplikasi kerangka Anda dengan menggunakan templat WinUSB. Dalam hal ini, lewati langkah 1 hingga 3 dan lanjutkan dari langkah 4 dalam artikel ini. Templat membuka handel file ke perangkat dan mendapatkan handel WinUSB yang diperlukan untuk operasi berikutnya. Handel tersebut disimpan dalam struktur DEVICE_DATA yang ditentukan aplikasi di device.h.

Untuk informasi selengkapnya tentang templat, lihat Menulis aplikasi desktop Windows berdasarkan templat WinUSB.

Catatan

Fungsi WinUSB memerlukan Windows XP atau yang lebih baru. Anda dapat menggunakan fungsi-fungsi ini di aplikasi C/C++ Anda untuk berkomunikasi dengan perangkat USB Anda. Microsoft tidak menyediakan API terkelola untuk WinUSB.

Sebelum memulai

Item berikut berlaku untuk panduan ini:

  • Informasi ini berlaku untuk Windows versi Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista.
  • Anda telah menginstal Winusb.sys sebagai driver fungsi perangkat. Untuk informasi selengkapnya tentang proses ini, lihat Penginstalan WinUSB (Winusb.sys).
  • Contoh dalam artikel ini didasarkan pada perangkat OSR USB FX2 Learning Kit. Anda dapat menggunakan contoh-contoh ini untuk memperluas prosedur ke perangkat USB lainnya.

Langkah 1: Buat aplikasi kerangka berdasarkan templat WinUSB

Untuk mengakses perangkat USB, mulailah dengan membuat aplikasi kerangka berdasarkan templat WinUSB yang disertakan dalam lingkungan terintegrasi Windows Driver Kit (WDK) (dengan Alat Debugging untuk Windows) dan Microsoft Visual Studio. Anda dapat menggunakan templat sebagai titik awal.

Untuk informasi tentang kode templat, cara membuat, membangun, menyebarkan, dan men-debug aplikasi kerangka, lihat Menulis aplikasi desktop Windows berdasarkan templat WinUSB.

Templat menghitung perangkat dengan menggunakan rutinitas SetupAPI , membuka handel file untuk perangkat, dan membuat handel antarmuka WinUSB yang diperlukan untuk tugas berikutnya. Misalnya kode yang mendapatkan handel perangkat dan membuka perangkat, lihat Diskusi kode templat.

Langkah 2: Mengkueri perangkat untuk deskriptor USB

Selanjutnya, kueri perangkat untuk informasi khusus USB seperti kecepatan perangkat, deskriptor antarmuka, titik akhir terkait, dan pipanya. Prosedur ini mirip dengan yang digunakan driver perangkat USB. Namun, aplikasi menyelesaikan kueri perangkat dengan memanggil WinUsb_GetDescriptor.

Daftar berikut menunjukkan fungsi WinUSB yang bisa Anda panggil untuk mendapatkan informasi khusus USB:

  • Informasi perangkat lainnya.

    Panggil WinUsb_QueryDeviceInformation untuk meminta informasi dari deskriptor perangkat untuk perangkat. Untuk mendapatkan kecepatan perangkat, atur DEVICE_SPEED (0x01) di parameter InformationType . Fungsi mengembalikan LowSpeed (0x01) atau HighSpeed (0x03).

  • Deskriptor antarmuka

    Panggil WinUsb_QueryInterfaceSettings dan lewati handel antarmuka perangkat untuk mendapatkan deskriptor antarmuka yang sesuai. Handel antarmuka WinUSB sesuai dengan antarmuka pertama. Beberapa perangkat USB, seperti perangkat OSR Fx2, hanya mendukung satu antarmuka tanpa pengaturan alternatif. Oleh karena itu, untuk perangkat ini parameter AlternateSettingNumber diatur ke nol dan fungsi hanya dipanggil satu kali. WinUsb_QueryInterfaceSettings mengisi struktur USB_INTERFACE_DESCRIPTOR yang dialokasikan pemanggil (diteruskan dalam parameter UsbAltInterfaceDescriptor ) dengan informasi tentang antarmuka. Misalnya, jumlah titik akhir dalam antarmuka diatur dalam anggota bNumEndpointsdari USB_INTERFACE_DESCRIPTOR.

    Untuk perangkat yang mendukung beberapa antarmuka, panggil WinUsb_GetAssociatedInterface untuk mendapatkan handel antarmuka untuk antarmuka terkait dengan menentukan pengaturan alternatif di parameter AssociatedInterfaceIndex .

  • Titik akhir

    Panggil WinUsb_QueryPipe untuk mendapatkan informasi tentang setiap titik akhir di setiap antarmuka. WinUsb_QueryPipe mengisi struktur WINUSB_PIPE_INFORMATION yang dialokasikan pemanggil dengan informasi tentang pipa titik akhir yang ditentukan. Pipa titik akhir diidentifikasi oleh indeks berbasis nol, dan harus kurang dari nilai di anggota bNumEndpoints dari deskriptor antarmuka yang diambil dalam panggilan sebelumnya ke **WinUsb_QueryInterfaceSettings. Perangkat OSR Fx2 memiliki satu antarmuka yang memiliki tiga titik akhir. Untuk perangkat ini, parameter AlternateInterfaceNumber fungsi diatur ke 0, dan nilai parameter PipeIndex bervariasi dari 0 hingga 2.

    Untuk menentukan jenis pipa, periksa anggota PipeInfo struktur WINUSB_PIPE_INFORMATION. Anggota ini diatur ke salah satu nilai enumerasi USBD_PIPE_TYPE : UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk, atau UsbdPipeTypeInterrupt. Perangkat OSR USB FX2 mendukung pipa interupsi, pipa massal, dan pipa massal, sehingga PipeInfo diatur ke UsbdPipeTypeInterrupt atau UsbdPipeTypeBulk. Nilai UsbdPipeTypeBulk mengidentifikasi pipa massal, tetapi tidak memberikan arah pipa. Informasi arah dikodekan dalam bit tinggi alamat pipa, yang disimpan dalam anggota PipeId struktur WINUSB_PIPE_INFORMATION. Cara paling sederhana untuk menentukan arah pipa adalah dengan meneruskan nilai PipeId ke salah satu makro berikut dari Usb100.h:

    • USB_ENDPOINT_DIRECTION_IN (PipeId) Makro mengembalikan TRUE jika arahnya masuk.
    • USB_ENDPOINT_DIRECTION_OUT(PipeId) Makro mengembalikan TRUE jika arahnya keluar.

    Aplikasi ini menggunakan nilai PipeId untuk mengidentifikasi pipa mana yang akan digunakan untuk transfer data dalam panggilan ke fungsi WinUSB, seperti WinUsb_ReadPipe (dijelaskan di bagian "Masalah Permintaan I/O" dari topik ini), sehingga contoh menyimpan ketiga nilai PipeId untuk digunakan nanti.

Contoh kode berikut mendapatkan kecepatan perangkat yang ditentukan oleh handel antarmuka WinUSB.

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;
}

Contoh kode berikut meminta berbagai deskriptor untuk perangkat USB yang ditentukan oleh handel antarmuka WinUSB. Fungsi contoh mengambil jenis titik akhir yang didukung dan pengidentifikasi pipanya. Contoh menyimpan ketiga nilai PipeId untuk digunakan nanti.

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;
}

Langkah 3: Kirim transfer kontrol ke titik akhir default

Selanjutnya, berkomunikasi dengan perangkat dengan mengeluarkan permintaan kontrol ke titik akhir default.

Semua perangkat USB memiliki titik akhir default selain titik akhir yang terkait dengan antarmuka. Tujuan utama titik akhir default adalah untuk memberi host informasi yang dapat digunakan untuk mengonfigurasi perangkat. Namun, perangkat juga dapat menggunakan titik akhir default untuk tujuan khusus perangkat. Misalnya, perangkat OSR USB FX2 menggunakan titik akhir default untuk mengontrol bilah lampu dan layar digital tujuh segmen.

Perintah kontrol terdiri dari paket penyiapan 8-byte, yang menyertakan kode permintaan yang menentukan permintaan tertentu, dan buffer data opsional. Kode permintaan dan format buffer ditentukan vendor. Dalam contoh ini, aplikasi mengirim data ke perangkat untuk mengontrol bilah lampu. Kode untuk mengatur bilah lampu 0xD8, yang didefinisikan untuk kenyamanan sebagai SET_BARGRAPH_DISPLAY. Untuk permintaan ini, perangkat memerlukan buffer data 1-byte yang menentukan elemen mana yang harus dinyalakan dengan mengatur bit yang sesuai.

Aplikasi dapat menyediakan satu set delapan kontrol kotak centang untuk menentukan elemen bilah lampu mana yang harus dinyalakan. Elemen yang ditentukan sesuai dengan bit yang sesuai dalam buffer. Untuk menghindari kode UI, kode contoh di bagian ini mengatur bit sehingga lampu alternatif menyala.

Untuk mengeluarkan permintaan kontrol

  1. Alokasikan buffer data 1-byte dan muat data ke dalam buffer yang menentukan elemen yang harus dinyalakan dengan mengatur bit yang sesuai.

  2. Buat paket penyiapan dalam struktur WINUSB_SETUP_PACKET yang dialokasikan pemanggil. Inisialisasi anggota untuk mewakili jenis permintaan dan data sebagai berikut:

    • Anggota RequestType menentukan arah permintaan. Ini diatur ke 0, yang menunjukkan transfer data host-ke-perangkat. Untuk transfer perangkat ke host, atur RequestType ke 1.
    • Anggota Permintaan diatur ke kode yang ditentukan vendor untuk permintaan ini, 0xD8. Ini didefinisikan untuk kenyamanan sebagai SET_BARGRAPH_DISPLAY.
    • Anggota Panjang diatur ke ukuran buffer data.
    • Anggota Indeks dan Nilai tidak diperlukan untuk permintaan ini, sehingga mereka diatur ke nol.
  3. Panggil WinUsb_ControlTransfer untuk mengirimkan permintaan ke titik akhir default dengan melewati handel antarmuka WinUSB perangkat, paket penyiapan, dan buffer data. Fungsi menerima jumlah byte yang ditransfer ke perangkat dalam parameter LengthTransferred .

Contoh kode berikut mengirimkan permintaan kontrol ke perangkat USB yang ditentukan untuk mengontrol lampu pada bilah lampu.

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;
}

Langkah 4: Terbitkan permintaan I/O

Selanjutnya, kirim data ke titik akhir massal dan massal perangkat yang masing-masing dapat digunakan untuk permintaan baca dan tulis. Pada perangkat OSR USB FX2, kedua titik akhir ini dikonfigurasi untuk loopback, sehingga perangkat memindahkan data dari titik akhir massal ke titik akhir massal. Ini tidak mengubah nilai data atau menambahkan data baru apa pun. Untuk konfigurasi loopback, permintaan baca membaca data yang dikirim oleh permintaan tulis terbaru. WinUSB menyediakan fungsi berikut untuk mengirim permintaan tulis dan baca:

Untuk mengirim permintaan tulis

  1. Alokasikan buffer dan isi dengan data yang ingin Anda tulis ke perangkat. Tidak ada batasan pada ukuran buffer jika aplikasi tidak mengatur RAW_IO sebagai jenis kebijakan pipa. WinUSB membagi buffer menjadi potongan berukuran tepat, jika perlu. Jika RAW_IO diatur, ukuran buffer dibatasi oleh ukuran transfer maksimum yang didukung oleh WinUSB.
  2. Panggil WinUsb_WritePipe untuk menulis buffer ke perangkat. Teruskan handel antarmuka WinUSB untuk perangkat, pengidentifikasi pipa untuk pipa massal (seperti yang dijelaskan di bagian Kueri Perangkat untuk Deskriptor USB di artikel ini), dan buffer. Fungsi mengembalikan jumlah byte yang ditulis ke perangkat dalam parameter bytesWritten . Parameter Tumpang tindih diatur ke NULL untuk meminta operasi sinkron. Untuk melakukan permintaan tulis asinkron, atur Tumpang tindih ke penunjuk ke struktur YANG TUMPANG TINDIH .

Permintaan tulis yang berisi data panjang nol diteruskan ke tumpukan USB. Jika panjang transfer lebih besar dari panjang transfer maksimum, WinUSB membagi permintaan menjadi permintaan yang lebih kecil dengan panjang transfer maksimum dan mengirimkannya secara serial. Contoh kode berikut mengalokasikan string dan mengirimkannya ke titik akhir massal perangkat.

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(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;
}

Untuk mengirim permintaan baca

  • Panggil WinUsb_ReadPipe untuk membaca data dari titik akhir perangkat secara massal. Lewati handel antarmuka WinUSB perangkat, pengidentifikasi pipa untuk titik akhir massal, dan buffer kosong berukuran tepat. Saat fungsi kembali, buffer berisi data yang dibaca dari perangkat. Jumlah byte yang dibaca dikembalikan dalam parameter bytesRead fungsi. Untuk permintaan baca, buffer harus berupa kelipatan ukuran paket maksimum.

Permintaan baca dengan panjang nol segera selesai dengan sukses dan tidak dikirimkan tumpukannya. Jika panjang transfer lebih besar dari panjang transfer maksimum, WinUSB membagi permintaan menjadi permintaan yang lebih kecil dengan panjang transfer maksimum dan mengirimkannya secara serial. Jika panjang transfer bukan kelipatan MaxPacketSize titik akhir, WinUSB meningkatkan ukuran transfer ke kelipatan MaxPacketSize berikutnya. Jika perangkat mengembalikan lebih banyak data daripada yang diminta, WinUSB menyimpan data berlebih. Jika data tetap dari permintaan baca sebelumnya, WinUSB menyalinnya ke awal permintaan baca berikutnya dan menyelesaikan permintaan, jika perlu. Contoh kode berikut membaca data dari titik akhir massal perangkat.

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;
}

Langkah 5: Lepaskan handel perangkat

Setelah Anda menyelesaikan semua panggilan yang diperlukan ke perangkat, lepaskan handel file dan handel antarmuka WinUSB untuk perangkat dengan memanggil fungsi berikut:

  • CloseHandle untuk merilis handel yang dibuat oleh CreateFile, seperti yang dijelaskan di langkah 1.
  • WinUsb_Free untuk merilis handel antarmuka WinUSB untuk perangkat, yang dikembalikan oleh **WinUsb_Initialize.

Langkah 6: Menerapkan fungsi utama

Contoh kode berikut menunjukkan fungsi utama aplikasi konsol Anda.

Misalnya kode yang mendapatkan handel perangkat dan membuka perangkat (GetDeviceHandle dan GetWinUSBHandle dalam contoh ini), lihat Diskusi kode templat.

int _tmain(int argc, _TCHAR* argv[])
{

  GUID guidDeviceInterface = OSR_DEVICE_INTERFACE; //in the INF file
  BOOL bResult = TRUE;
  PIPE_ID PipeID;
  HANDLE hDeviceHandle = INVALID_HANDLE_VALUE;
  WINUSB_INTERFACE_HANDLE hWinUSBHandle = INVALID_HANDLE_VALUE;
  UCHAR DeviceSpeed;
  ULONG cbSize = 0;

  bResult = GetDeviceHandle(guidDeviceInterface, &hDeviceHandle);

  if(!bResult)
  {
    goto done;
  }

  bResult = GetWinUSBHandle(hDeviceHandle, &hWinUSBHandle);

  if(!bResult)
  {
    goto done;
  }

  bResult = GetUSBDeviceSpeed(hWinUSBHandle, &DeviceSpeed);

  if(!bResult)
  {
    goto done;
  }

  bResult = QueryDeviceEndpoints(hWinUSBHandle, &PipeID);

  if(!bResult)
  {
    goto done;
  }

  bResult = SendDatatoDefaultEndpoint(hWinUSBHandle);

  if(!bResult)
  {
    goto done;
  }

  bResult = WriteToBulkEndpoint(hWinUSBHandle, &PipeID.PipeOutId, &cbSize);

  if(!bResult)
  {
    goto done;
  }

  bResult = ReadFromBulkEndpoint(hWinUSBHandle, &PipeID.PipeInId, cbSize);

  if(!bResult)
  {
    goto done;
  }

  system("PAUSE");

done:
  CloseHandle(hDeviceHandle);
  WinUsb_Free(hWinUSBHandle);

  return 0;
}

Langkah berikutnya

Jika perangkat Anda mendukung titik akhir isochronous, Anda dapat menggunakan fungsi WinUSB untuk mengirim transfer. Fitur ini hanya didukung di Windows 8.1. Untuk informasi selengkapnya, lihat Mengirim transfer isochronous USB dari aplikasi desktop WinUSB.

Lihat juga