最近在研究一款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);
- }
复制代码
参考:
|