2018年1月5日 星期五

USB HID Host on Windows : Based on SiLab C8051 Example Firmware, USBHIDtoUART


      C8051 (F34X/F32X and F38X) series released by Silicon Labs is very famous and popular microcontroller for its bargain price and multipurpose for the applications : the chips are based on 8051 with various interfaces, ADC, I2C master/slave, SPI master/slave...etc. And the chips support USB signal and interrupt with 8 endpoints, they satisfy most USB 2.0 use case.

     C8051 (and EFM8 Universal Bee) development boards are the excellent entry points for USB firmware beginner. But it is pitiful that the HIDtoUART host side code is not full opensource(or it is difficult to be download), there is no SLABHIDDEVICE.dll sourcecode in the the Silicon Labs's example packet, there is only GUI code could be found.
     This post wants to remove this pain : forget the SLABHIDDEVICE.dll, it is redundant.

 
 零. Create a Visual Studio project, named as C8051HIDtoUARTExampleHost, the project you need include/depended in DDK (Driver Development Kit):

  If you do not download DDK, my code depends on DDK only a small piece, you could down the necessary headers and library from here. I assume the decompressed folder you downloaded in the  C8051HIDtoUARTExampleHost folder :




   And set the project setting include and library directory in the ddk_hid, the additional dependencies on hid.lib and setupapi.lib:







一.   Add a main.c file in the project, the code be :


#include <stdio.h>
#include <stdlib.h>

#include <tchar.h>

#include <windows.h>

#ifdef _cplusplus
extern "C" {
#endif

#include "hidsdi.h"
#include "setupapi.h"
#include "dbt.h"

#ifdef _cplusplus
}
#endif

#define DEVICE_VENDOR_ID    (0x10C4)
#define DEVICE_PRODUCT_ID    (0x8468)
#define DEVICE_VERSION_NUMER   (0x0000)



BOOL SeekCustomedHIdDdvice(DWORD deviceVendorId, DWORD deviceProductId, 
         DWORD deviceVersionNumber, 
         TCHAR *pDevicePath, int pathBufferSize)
{ 

 BOOL isFoundDevice;

 GUID hidGuid;
 HDEVINFO hDevInfoSet;
 DWORD memberIndex;

 PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData;

 pDevDetailData = NULL;


 HidD_GetHidGuid(&hidGuid);
 hDevInfoSet = SetupDiGetClassDevs(&hidGuid, NULL, NULL, 
  DIGCF_DEVICEINTERFACE| DIGCF_PRESENT);

 printf(TEXT("seeking device\r\n"));

 memberIndex = 0;
 isFoundDevice = FALSE;

 while(1)
 {
  BOOL result;
  SP_DEVICE_INTERFACE_DATA devInterfaceData;
  
  DWORD requiredSize;
  TCHAR devicePath[MAX_PATH];

  HANDLE hDevHandle;
  HIDD_ATTRIBUTES devAttributes;

  devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
 

  result = SetupDiEnumDeviceInterfaces(hDevInfoSet, NULL, &hidGuid, 
   memberIndex, &devInterfaceData);

  if(FALSE == result) 
   break;
  memberIndex++;


  result = SetupDiGetDeviceInterfaceDetail(hDevInfoSet, &devInterfaceData,
   NULL, NULL, &requiredSize, NULL);

  pDevDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize);
  if(NULL == pDevDetailData)
  {
   _tprintf(TEXT("malloc pDevDetailData fail!\r\n"));
   break;
  }/*if NULL*/

  pDevDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  
  result = SetupDiGetDeviceInterfaceDetail(hDevInfoSet, &devInterfaceData,
   pDevDetailData, requiredSize, NULL, NULL);

  _tcscpy_s(&devicePath[0], MAX_PATH, pDevDetailData->DevicePath);
  free(pDevDetailData); pDevDetailData = NULL;

  if(FALSE == result)
   continue;

  hDevHandle = CreateFile(&devicePath[0], GENERIC_READ|GENERIC_WRITE, 
   FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  if(INVALID_HANDLE_VALUE == hDevHandle)
   continue;


  devAttributes.Size = sizeof(HIDD_ATTRIBUTES);
  result = HidD_GetAttributes(hDevHandle, 
   &devAttributes);

  CloseHandle(hDevHandle);

  if(FALSE == result)
   continue;

  if(deviceVendorId == devAttributes.VendorID 
   && deviceProductId == devAttributes.ProductID 
   && deviceVersionNumber == devAttributes.VersionNumber)
  {
   _tprintf(TEXT("hid device found\r\n"));
   _tcscpy_s(pDevicePath, pathBufferSize, &devicePath[0]);
   isFoundDevice = TRUE;
   break;
  }/**/
 }/*while*/

 if(NULL != pDevDetailData)
  free(pDevDetailData); 
 pDevDetailData = NULL;

 SetupDiDestroyDeviceInfoList(hDevInfoSet);

 return isFoundDevice;

}/*SeekCustomedHIdDdvice*/


BOOL gIsLeaveThread = FALSE;

#define HID_BUFFER_SIZE     (64)

DWORD WINAPI ReadCustomedHIDThread(LPVOID lpParameter)
{
 TCHAR *pDevicePath;
 HANDLE hDevHandle;
 OVERLAPPED overlapped;

 pDevicePath = (TCHAR*)lpParameter;
 ZeroMemory(&overlapped, sizeof(OVERLAPPED));

 hDevHandle = CreateFile(pDevicePath, GENERIC_READ, 
  FILE_SHARE_READ|FILE_SHARE_WRITE, 
  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, NULL);


 while(1)
 {
  int i;
  BYTE readBuffer[HID_BUFFER_SIZE];
  DWORD readSize;
  DWORD dwRes;

  if(FALSE != gIsLeaveThread)
   break;

  ZeroMemory(&readBuffer[0], sizeof(readBuffer));


  if(FALSE == ReadFile(hDevHandle, &readBuffer[0], HID_BUFFER_SIZE, 
   &readSize, &overlapped) && ERROR_IO_PENDING == GetLastError())
  {

#define TIMOUT_IN_MILLI_SEC    (2*1000)

   dwRes = WaitForSingleObject(hDevHandle, TIMOUT_IN_MILLI_SEC);

   switch(dwRes)
   {
    case WAIT_OBJECT_0:
     if(FALSE == GetOverlappedResult(hDevHandle, &overlapped, 
      &readSize, TRUE))
     {
      continue;
     }
     break;

    case WAIT_TIMEOUT:
    default:
     continue;
   }/*switch*/

  }/*if ReadFile*/

  
#define IN_DATA_SIZE   (60 + 1)
#define IN_DATA     (0x01)

  if( IN_DATA_SIZE != readSize)
   continue;

  if(IN_DATA != readBuffer[0])
   continue;

 
  for(i = 0; i < readBuffer[1]; i++){
   _tprintf(TEXT("%c"), 
    (int)readBuffer[2 + i]);
  }/*for i*/

 }/*while 1*/

 CloseHandle(hDevHandle);

 ExitThread((DWORD)0);
}/*ReadCustomedHIDThread*/

#define OUT_DATA_SIZE    (60 + 1)

DWORD WINAPI WriteCustomedHIDThread(LPVOID lpParameter)
{
 TCHAR *pDevicePath;
 HANDLE hDevHandle;
 BYTE writeBuffer[HID_BUFFER_SIZE];
 DWORD writtenSize;
 TCHAR strForSending[HID_BUFFER_SIZE];

 pDevicePath = (TCHAR*)lpParameter;

 ZeroMemory(&writeBuffer[0], sizeof(writeBuffer));

 _stprintf_s(&strForSending[0], sizeof(strForSending)/sizeof(TCHAR), 
  TEXT("%s"), TEXT("家常貓肉 Home cooking cat meat\r\n"));

 hDevHandle = CreateFile(pDevicePath, GENERIC_WRITE, 
  FILE_SHARE_READ|FILE_SHARE_WRITE, 
  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

 while(1)
 {
  if(FALSE != gIsLeaveThread)
   break;

#define OUT_DATA     (0x02)
  writeBuffer[0] = OUT_DATA;
  writeBuffer[1] = _tcslen(strForSending)*sizeof(TCHAR);

  memcpy(&writeBuffer[2], &strForSending[0], sizeof(writeBuffer) - 2);

  if(FALSE == WriteFile(hDevHandle, &writeBuffer[0], 
   OUT_DATA_SIZE, &writtenSize, NULL))
  {
   _tprintf(TEXT("GetLastError = %d\r\n"), GetLastError());
  }/*if */
 
  Sleep(2500);
 }/*while*/

 CloseHandle(hDevHandle);

}/*WriteCustomedHIDThread*/


BOOL CtrlHandler( DWORD fdwCtrlType )
{
 switch( fdwCtrlType ) 
 {
 case CTRL_C_EVENT:

  Beep(830, 200);
  Beep(932, 200);
  Beep(988, 200);
  Beep(830, 300);

  Beep(622, 200);
  Beep(494, 300);
  gIsLeaveThread = TRUE;
  Beep(622, 350);
  Beep(392, 700);
  Beep(415, 2000);

  return TRUE;
  break;

 default:
  return FALSE;
 }/*switch*/

 return FALSE;
}/*CtrlHandler*/


int _tmain(int argc, TCHAR *argv[])
{
 int k;
 TCHAR devicePath[MAX_PATH];
 HANDLE hReadThread, hWriteThread;
 unsigned long baudrate;

 baudrate = ULONG_MAX;
 ZeroMemory(&devicePath[0], MAX_PATH*sizeof(TCHAR)); 

 SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE);

 k = 0;
 while(k < argc)
 {
  if(0 == _tcscmp(argv[k], TEXT("-baudrate")) )
  {
   if(k + 1 < argc)
   {
    TCHAR *p_end;
    baudrate = _tcstol(argv[k + 1], &p_end, 10);
   }
   else
   {
    _tprintf(TEXT("-baudrate should be followed baud rate value!\r\n"));
    return 1;
   }/*if -braudrate*/
  }//*if */
  k++;
 }/*while argc*/

 if(ULONG_MAX != baudrate)
  _tprintf(TEXT("baud rate will be set as %u\r\n"), baudrate);

 if(FALSE == SeekCustomedHIdDdvice(DEVICE_VENDOR_ID, DEVICE_PRODUCT_ID, 
  DEVICE_VERSION_NUMER, &devicePath[0], MAX_PATH))
 {
  _tprintf(TEXT("no customed HID device has been found\r\n"));
  _tprintf(TEXT("maybe the pid, vid or vsn is not correct\r\n"));
  return 0;
 }/*if */

 _tprintf(TEXT("device path = %s\r\n"), &devicePath[0]);

 
 if(ULONG_MAX != baudrate)
 {
  HANDLE hDevHandle;
  BYTE writeBuffer[HID_BUFFER_SIZE];
  DWORD writtenSize;
  DWORD dwRes;

  ZeroMemory(&writeBuffer[0], HID_BUFFER_SIZE);

  hDevHandle = CreateFile(&devicePath[0], GENERIC_WRITE, 
   FILE_SHARE_READ|FILE_SHARE_WRITE, 
   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  
#define OUT_CONTROL     (0xFD)

  writeBuffer[0] = OUT_CONTROL;
  baudrate = _byteswap_ulong(baudrate);
  memcpy(&writeBuffer[1], &baudrate, sizeof(unsigned long));

  if(FALSE == WriteFile(hDevHandle, &writeBuffer[0], OUT_DATA_SIZE,
   &writtenSize, NULL))
  {
   _tprintf(TEXT("GetLastError = %d\r\n"), GetLastError());
  }/*if */

  CloseHandle(hDevHandle);
 }/*set baud rate*/

 hReadThread = hWriteThread = INVALID_HANDLE_VALUE;

 hReadThread = (HANDLE)_beginthreadex(NULL, 0, ReadCustomedHIDThread, 
   (void*)&devicePath[0], 0/*or CREATE_SUSPENDED*/, NULL);

 if(0 >= hReadThread)
 {
  _tprintf(TEXT("Create read thead fail\r\n"));
  return -1;
 }/*if*/


 hWriteThread = (HANDLE)_beginthreadex(NULL, 0, WriteCustomedHIDThread, 
   (void*)&devicePath[0], 0/*or CREATE_SUSPENDED*/, NULL);

 if(0 >= hWriteThread)
 {
  _tprintf(TEXT("Create write thead fail\r\n"));
  gIsLeaveThread = TRUE;
  WaitForSingleObject(hReadThread, INFINITE);
  return -1;
 }/*if*/
 

 WaitForSingleObject(hWriteThread, INFINITE);
 WaitForSingleObject(hReadThread, INFINITE);
 
 CloseHandle(hWriteThread);
 CloseHandle(hReadThread);

 return 0;
}/*main*/ 

二. Build and run the code,

You will find in the C8051's UART terminal, the string "家常貓肉 Home cooking cat meat" keeping output in brautrate 9600:

And if you send some string via the terminal to the C8051, the string will display in the windows console:




 If you want to modify the braudrate of UART, you could input the argument -baudrate baudrate_number:



And the output would be in the baudrate:



If you are interested in how to implement the same work in Linux, please refer to this post.

Reference :

    圈圈教你玩USB, 第二版 (Circle Teach You How to Play USB).




沒有留言:

張貼留言