| 
 最近在研究一款USB控制的排插:BellWin出品的USBPower Spillter 型号是:UP520US。它带有5个插孔,一个用于控制插孔供电的USB接口和一个供电开关。这款排插不是给家用设计的,上面的的插孔和插头都是专门为机房这种特别场合设计的,在家用环境中必须使用转接头才能工作。    
电器特性如下【参考1】: 输入:220V,20A 输入频率:50/60Hz 功耗:5W 每一路最高可支持15A,五组最大支持输出20A 工作温度:0-25℃ 接口:IEC60320C13 认证:RoHS,IPC,CUS 首先,测试一下它自带的控制程序。 用户可以通过下面的按钮控制每一路的开关。 接下来,使用 USB Package Viewer 设备抓取USB数据包。USBPackage Viewer(简称UPV)是一款国产的USB数据逻辑分析仪,能够抓取从 Low Speed 到 High Speed的数据包。有兴趣的朋友可以在【参考2】看到之前我的开箱视频。这个仪器有一个比较大的优点是能够实时显示,因为实际上大多数设备传输的有效数据并不多,实时显示能够帮助判断是否抓取到了需要的数据。 从设备管理器上也可以看出,这个设备使用USB HID 协议。 按下试验软件上的开关两次之后,在UPV中可以看到抓到了2个数据包: 经过多次试验,总结数据如下: |    操作    |  |   | 0B  00 00 00 00 00 00 5A……5A   |   | 0B  00 00 00 00 01 00 5A……5A   |   | 0B  00 00 00 00 02 00 5A……5A  |   | 0B  00 00 00 00 03 00 5A……5A  |   | 0B  00 00 00 00 04 00 5A……5A  |   | 0B  00 00 00 00 00 01 5A……5A  |   | 0B  00 00 00 00 01 01 5A……5A  |   | 0B  00 00 00 00 02 01 5A……5A  |   | 0B  00 00 00 00 03 01 5A……5A  |   | 0B  00 00 00 00 04 01 5A……5A  |  
   根据上述表格,很容易编写一个控制这个排插开关的代码(VS2019):  
  
 
			
			
			- #include <stdio.h>
 - #include <tchar.h>
 - #include <windows.h>
 - #include <setupapi.h>
 - 
 - extern "C" {
 - 
 -     void __stdcall
 -         HidD_GetHidGuid(
 -             OUT   LPGUID   HidGuid
 -         );
 - 
 -     typedef struct _HIDD_ATTRIBUTES {
 -         ULONG   Size; // = sizeof (struct _HIDD_ATTRIBUTES)
 - 
 -                       //
 -         // Vendor ids of this hid device
 -         //
 -         USHORT  VendorID;
 -         USHORT  ProductID;
 -         USHORT  VersionNumber;
 - 
 -         //
 -         // Additional fields will be added to the end of this structure.
 -         //
 -     } HIDD_ATTRIBUTES, * PHIDD_ATTRIBUTES;
 - 
 -     BOOLEAN __stdcall
 -         HidD_GetAttributes(
 -             IN  HANDLE              HidDeviceObject,
 -             OUT PHIDD_ATTRIBUTES    Attributes
 -         );
 - 
 -     BOOLEAN __stdcall
 -         HidD_SetFeature(
 -             _In_    HANDLE   HidDeviceObject,
 -             _In_reads_bytes_(ReportBufferLength) PVOID ReportBuffer,
 -             _In_    ULONG    ReportBufferLength
 -         );
 - }
 - 
 - #pragma comment( lib, "hid.lib" )
 - #pragma comment( lib, "setupapi.lib" )
 - 
 - void SetAll(HANDLE hUsb, bool AllOn)
 - {
 -     BOOL Result;
 -     UCHAR WriteReportBuffer[65];
 - 
 -     for (int i = 0; i < 8; i++) {
 -         WriteReportBuffer[i] = 0x00;
 -     }
 -     for (int i = 8; i < 65; i++) {
 -         WriteReportBuffer[i] = 0x5a;
 -     }
 - 
 -     WriteReportBuffer[1] = 0x0b;
 -     for (byte i = 0; i < 5; i++) {
 -         WriteReportBuffer[6] = i;
 -         if (AllOn) {
 -             WriteReportBuffer[7] = 0x01;
 -         }
 - 
 -         DWORD lpNumberOfBytesWritten;
 - 
 -         Result = WriteFile(hUsb,
 -             WriteReportBuffer,
 -             65,
 -             &lpNumberOfBytesWritten,
 -             NULL);
 -         //printf("Written %d bytes Result [%d]\n", lpNumberOfBytesWritten, Result);
 -     }
 - 
 - 
 - }
 - 
 - int main()
 - {
 -     GUID HidGuid;
 -     BOOL Result;
 -     int  counter = -1;
 - 
 - 
 -     HidD_GetHidGuid(&HidGuid);
 -     printf("HID GUID: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n"
 -         , HidGuid.Data1, HidGuid.Data2, HidGuid.Data3
 -         , HidGuid.Data4[0], HidGuid.Data4[1], HidGuid.Data4[2], HidGuid.Data4[3], HidGuid.Data4[4]
 -         , HidGuid.Data4[5], HidGuid.Data4[6], HidGuid.Data4[7]);
 - 
 -     HDEVINFO hDevInfo = SetupDiGetClassDevs(&HidGuid, NULL, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
 -     if (INVALID_HANDLE_VALUE != hDevInfo)
 -     {
 -         SP_DEVICE_INTERFACE_DATA strtInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) };
 -         for (DWORD index = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &HidGuid, index, &strtInterfaceData); ++index)
 -         {
 - 
 -             char buf[1000];
 -             SP_DEVICE_INTERFACE_DETAIL_DATA& strtDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA&)buf[0];
 -             strtDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
 -             if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &strtInterfaceData, &strtDetailData, _countof(buf), NULL, NULL))
 -             {
 -                 printf("[%d] path: %ls\n", index, strtDetailData.DevicePath);
 - 
 -                 HANDLE hUsb = CreateFile(strtDetailData.DevicePath,
 -                     NULL, FILE_SHARE_WRITE,
 -                     NULL, OPEN_EXISTING,
 -                     FILE_ATTRIBUTE_NORMAL,
 -                     NULL);
 - 
 -                 HIDD_ATTRIBUTES strtAttrib = { sizeof(HIDD_ATTRIBUTES) };
 -                 Result = HidD_GetAttributes(hUsb, &strtAttrib);
 -                 CloseHandle(hUsb);
 - 
 -                 if (TRUE == Result)
 -                 {
 -                     if ((0x04D8 == strtAttrib.VendorID) &&
 -                         (0xFEDC == strtAttrib.ProductID))       //ÕÒµ½ÎÒÃÇ×Ô¼ºµÄÉ豸
 -                     {
 -                         printf("VendorID : %hX\n", strtAttrib.VendorID);
 -                         printf("ProductID: %hX\n", strtAttrib.ProductID);
 -                         printf("VerNumber: %hX\n", strtAttrib.VersionNumber);
 - 
 -                         hUsb = CreateFile(strtDetailData.DevicePath,
 -                             GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
 -                             NULL, OPEN_EXISTING,
 -                             FILE_ATTRIBUTE_NORMAL, NULL);
 - 
 -                         //Switch(hUsb); GetData(hUsb);
 -                         SetAll(hUsb,TRUE);
 -                         Sleep(3000);
 -                         SetAll(hUsb, FALSE);
 - 
 -                         CloseHandle(hUsb);
 - 
 -                     }//if ((0x8888==strtAttrib.VendorID) &&
 -                 }            //if(TRUE==Result)
 -             } // if( SetupDiGetDeviceInterfaceDetail(hDevInfo,&strtInterfaceData,&strtDetailData,_countof(buf),NULL,NULL) )
 -         }    //for( DWORD index=0;
 - 
 -         if (GetLastError() != ERROR_NO_MORE_ITEMS)
 -         {
 -             printf("No more items!\n");
 -         }
 - 
 -         SetupDiDestroyDeviceInfoList(hDevInfo);
 - 
 -     }  //if( INVALID_HANDLE_VALUE != hDevInfo )
 -     system("PAUSE");
 -     return 0;
 - }
 
  复制代码
 这个代码没有使用厂家提供的API(厂家提供了 DLL)而是直接对 HID设备编程,这样做的好处是无需外挂文件,一个 EXE 都可以搞定。坏处是,没有实现厂家API 提供的诸如取得序列号这样的功能,如果有需求还需要另外研究编写。但是无论如何,能够实现控制端口开关已经足够了。 接下来,使用DFRobot 的 FireBeetle ESP32 + USB Host Shield 来实现控制。很早之前,设计过一款 Firebeetle 的USBHost Shield【参考3】,但是对应的这种封装的 Max3421e 市面上比较少见了,于是重新进行了设计,选用了另外一种封装(32TQFN-EP)重新进行了设计。具体的设计可以在【参考4】看到。 接下来就可以专注于代码的设计: - #include "DFRobot_OLED12864.h"
 - #include <hidboot.h>
 - #include <usbhub.h>
 - #include <SPI.h>
 - #include "UP520US.h"
 - 
 - #define ADC_SECTION 5
 - 
 - USB     Usb;
 - UP520US  up520us(&Usb);
 - 
 - const uint8_t I2C_addr = 0x3c;
 - const uint8_t pin_SPI_cs = D2;
 - const uint8_t pin_analogKey = A0;
 - #define ADC_BIT  4096
 - 
 - DFRobot_OLED12864 OLED(I2C_addr, pin_SPI_cs);
 - 
 - enum enum_key_analog {
 -   key_analog_no,
 -   key_analog_right,
 -   key_analog_center,
 -   key_analog_up,
 -   key_analog_left,
 -   key_analog_down,
 - } eKey_analog;
 - 
 - uint8_t PowerStatus = 0x1F;
 - byte CurrentPort = 0;
 - 
 - // 返回当前按键信息
 - enum_key_analog read_key_analog(void)
 - {
 -   int adValue = analogRead(pin_analogKey);
 -   if (adValue > ADC_BIT * (ADC_SECTION * 2 - 1) / (ADC_SECTION * 2)) {
 -     return key_analog_no;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 3) / (ADC_SECTION * 2)) {
 -     return key_analog_right;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 5) / (ADC_SECTION * 2)) {
 -     return key_analog_center;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 7) / (ADC_SECTION * 2)) {
 -     return key_analog_up;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 9) / (ADC_SECTION * 2)) {
 -     return key_analog_left;
 -   } else {
 -     return key_analog_down;
 -   }
 - }
 - 
 - // 在 OLED 上绘制菜单
 - void ShowMainMenu(uint8_t status, uint8_t Current)
 - {
 -   char Buffer[15];
 - 
 -   OLED.clear();
 -   //                 0123456789ABCDEF
 -   OLED.disStr(0, 0, "控制 USB Power ");
 -   memset(Buffer, ' ', sizeof(Buffer));
 -   for (uint8_t i = 0; i < 5; i++) {
 - 
 -     // 1 表示当前 ON, 0 表示 OFF
 -     if ((status & (1 << i)) == 0) {
 -       Buffer[i * 2] = 'X';
 -     } else {
 -       Buffer[i * 2] = 'O';
 -     }
 -     Buffer[i * 2 + 1] = ' ';
 -   }
 -   OLED.disStr(0, 16, Buffer);
 -   OLED.disStr(0, 32, "1 2 3 4 5");
 -   memset(Buffer, ' ', sizeof(Buffer));
 -   // 指示当前操作 Port
 -   Buffer[Current * 2] = '^';
 -   OLED.disStr(0, 48, Buffer);
 -   OLED.display();
 - 
 - }
 - void setup(void)
 - {
 -   Serial.begin(115200);
 -   OLED.init();
 -   OLED.flipScreenVertically();
 - 
 -   if (Usb.Init() == -1) {
 -     Serial.print(F("\r\nOSC did not start"));
 -     while (1); // Halt
 -   }
 -   Serial.println(F("\r\nUP520US Started"));
 -   delay( 200 );
 -   // 绘制初始屏幕
 -   ShowMainMenu(PowerStatus, CurrentPort);
 - }
 - 
 - // 发送 USB 控制命令
 - void SetPort(uint8_t Port, boolean SetOn)
 - {
 -   char WriteReportBuffer[64];
 -   memset(WriteReportBuffer, 0x5a, sizeof(WriteReportBuffer));
 -   WriteReportBuffer[0] = 0x0b;
 -   for (int i = 1; i < 7; i++) {
 -     WriteReportBuffer[i] = 0x00;
 -   }
 -   WriteReportBuffer[5] = Port;
 -   // 设置为 OFF
 -   if (SetOn == false) {
 -     WriteReportBuffer[6] = 0x01;
 -   }
 -   Usb.outTransfer(up520us.GetAddress(), 1, sizeof(WriteReportBuffer), (uint8_t*)WriteReportBuffer);
 - }
 - 
 - void loop(void)
 - {
 -   enum_key_analog button;
 -   Usb.Task();
 - 
 -   button = read_key_analog();
 -   if ( button != key_analog_no) {
 -     // 左键移动指示光标
 -     if (button == key_analog_left) {
 -       CurrentPort = (CurrentPort - 1) % 5;
 -     }
 -     // 右键移动指示光标
 -     if (button == key_analog_right) {
 -       CurrentPort = (CurrentPort + 1) % 5;
 -     }
 -     // 中键确认
 -     if (button == key_analog_center) {
 -       if ((PowerStatus & (1 << CurrentPort)) == 0) {
 -         // 如果选中的 Port 当前是 ON ,那么设置为 OFF
 -         PowerStatus = PowerStatus | (1 << CurrentPort);
 -         if (up520us.connected()) {
 -           SetPort(CurrentPort, false);
 -         }
 -       } else {
 -         // 如果选中的 Port 当前是 OFF ,那么设置为 ON
 -         PowerStatus = PowerStatus & (~(1 << CurrentPort));
 -         if (up520us.connected()) {
 -           SetPort(CurrentPort, true);
 -         }
 -       }
 -     }
 -     ShowMainMenu(PowerStatus, CurrentPort);
 -     Serial.print(PowerStatus, HEX);
 -     Serial.print("  ");
 -     Serial.println(CurrentPort, HEX);
 -     delay(300);
 -   }
 - }
 
  复制代码
  
 简单介绍要点: 1.     为了OLED 和USBHost Shield 的兼容,需要更改OLED模块默认的CSPin, 从D5修改为D2; 2.     使用OLED提供的5方向按键,通过下面的函数返回按键信息  
 - // 返回当前按键信息
 - enum_key_analog read_key_analog(void)
 - {
 -   int adValue = analogRead(pin_analogKey);
 -   if (adValue > ADC_BIT * (ADC_SECTION * 2 - 1) / (ADC_SECTION * 2)) {
 -     return key_analog_no;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 3) / (ADC_SECTION * 2)) {
 -     return key_analog_right;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 5) / (ADC_SECTION * 2)) {
 -     return key_analog_center;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 7) / (ADC_SECTION * 2)) {
 -     return key_analog_up;
 -   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 9) / (ADC_SECTION * 2)) {
 -     return key_analog_left;
 -   } else {
 -     return key_analog_down;
 -   }
 - }
 
  复制代码
  
 1.     为了实现人机交互,设计了一个简单的菜单。用于指示当前的开关状态:  
 - // 在 OLED 上绘制菜单
 - void ShowMainMenu(uint8_t status, uint8_t Current)
 - {
 -   char Buffer[15];
 - 
 -   OLED.clear();
 -   //                 0123456789ABCDEF
 -   OLED.disStr(0, 0, "控制 USB Power ");
 -   memset(Buffer, ' ', sizeof(Buffer));
 -   for (uint8_t i = 0; i < 5; i++) {
 - 
 -     // 1 表示当前 ON, 0 表示 OFF
 -     if ((status & (1 << i)) == 0) {
 -       Buffer[i * 2] = 'X';
 -     } else {
 -       Buffer[i * 2] = 'O';
 -     }
 -     Buffer[i * 2 + 1] = ' ';
 -   }
 -   OLED.disStr(0, 16, Buffer);
 -   OLED.disStr(0, 32, "1 2 3 4 5");
 -   memset(Buffer, ' ', sizeof(Buffer));
 -   // 指示当前操作 Port
 -   Buffer[Current * 2] = '^';
 -   OLED.disStr(0, 48, Buffer);
 -   OLED.display();
 - 
 - }
 
  复制代码
  
 1.     USB 控制方面和 Windows 下面的编程非常类似,直接发送指定格式的数据包即可:  
  
  
 - // 发送 USB 控制命令
 - void SetPort(uint8_t Port, boolean SetOn)
 - {
 -   char WriteReportBuffer[64];
 -   memset(WriteReportBuffer, 0x5a, sizeof(WriteReportBuffer));
 -   WriteReportBuffer[0] = 0x0b;
 -   for (int i = 1; i < 7; i++) {
 -     WriteReportBuffer[i] = 0x00;
 -   }
 -   WriteReportBuffer[5] = Port;
 -   // 设置为 OFF
 -   if (SetOn == false) {
 -     WriteReportBuffer[6] = 0x01;
 -   }
 -   Usb.outTransfer(up520us.GetAddress(), 1, sizeof(WriteReportBuffer), (uint8_t*)WriteReportBuffer);
 - }
 
  复制代码
  
 参考:  
  
  
  
  
 |