1494浏览
查看: 1494|回复: 5

[ESP8266/ESP32] Firebeetle控制一个USB 接口的排插

[复制链接]
最近在研究一款USB控制的排插:BellWin出品的USBPower Spillter 型号是:UP520US。它带有5个插孔,一个用于控制插孔供电的USB接口和一个供电开关。这款排插不是给家用设计的,上面的的插孔和插头都是专门为机房这种特别场合设计的,在家用环境中必须使用转接头才能工作。
Firebeetle控制一个USB 接口的排插图1
Firebeetle控制一个USB 接口的排插图2

电器特性如下【参考1:
输入:220V20A
输入频率:50/60Hz
功耗:5W
每一路最高可支持15A,五组最大支持输出20A
工作温度:0-25
接口:IEC60320C13
认证:RoHS,IPCCUS
首先,测试一下它自带的控制程序。
Firebeetle控制一个USB 接口的排插图3
用户可以通过下面的按钮控制每一路的开关。
接下来,使用 USB Package Viewer 设备抓取USB数据包。USBPackage Viewer(简称UPV)是一款国产的USB数据逻辑分析仪,能够抓取从 Low Speed High Speed的数据包。有兴趣的朋友可以在【参考2】看到之前我的开箱视频。这个仪器有一个比较大的优点是能够实时显示,因为实际上大多数设备传输的有效数据并不多,实时显示能够帮助判断是否抓取到了需要的数据。
Firebeetle控制一个USB 接口的排插图4
从设备管理器上也可以看出,这个设备使用USB HID 协议。
Firebeetle控制一个USB 接口的排插图5
按下试验软件上的开关两次之后,在UPV中可以看到抓到了2个数据包:
Firebeetle控制一个USB 接口的排插图6
经过多次试验,总结数据如下:
  
操作
  
抓到的数据,总长度64Bytes
Port1  设置为 On
0B  00 00 00 00 00 00 5A……5A
Port2  设置为 On
0B  00 00 00 00 01 00 5A……5A
Port3  设置为 On
0B  00 00 00 00 02 00 5A……5A
Port4  设置为 On
0B  00 00 00 00 03 00 5A……5A
Port5  设置为 On
0B  00 00 00 00 04 00 5A……5A
Port1  设置为 Off
0B  00 00 00 00 00 01 5A……5A
Port2  设置为Off
0B  00 00 00 00 01 01 5A……5A
Port3  设置为Off
0B  00 00 00 00 02 01 5A……5A
Port4  设置为Off
0B  00 00 00 00 03 01 5A……5A
Port5  设置为Off
0B  00 00 00 00 04 01 5A……5A
根据上述表格,很容易编写一个控制这个排插开关的代码(VS2019:


  1. #include <stdio.h>
  2. #include <tchar.h>
  3. #include <windows.h>
  4. #include <setupapi.h>
  5. extern "C" {
  6.     void __stdcall
  7.         HidD_GetHidGuid(
  8.             OUT   LPGUID   HidGuid
  9.         );
  10.     typedef struct _HIDD_ATTRIBUTES {
  11.         ULONG   Size; // = sizeof (struct _HIDD_ATTRIBUTES)
  12.                       //
  13.         // Vendor ids of this hid device
  14.         //
  15.         USHORT  VendorID;
  16.         USHORT  ProductID;
  17.         USHORT  VersionNumber;
  18.         //
  19.         // Additional fields will be added to the end of this structure.
  20.         //
  21.     } HIDD_ATTRIBUTES, * PHIDD_ATTRIBUTES;
  22.     BOOLEAN __stdcall
  23.         HidD_GetAttributes(
  24.             IN  HANDLE              HidDeviceObject,
  25.             OUT PHIDD_ATTRIBUTES    Attributes
  26.         );
  27.     BOOLEAN __stdcall
  28.         HidD_SetFeature(
  29.             _In_    HANDLE   HidDeviceObject,
  30.             _In_reads_bytes_(ReportBufferLength) PVOID ReportBuffer,
  31.             _In_    ULONG    ReportBufferLength
  32.         );
  33. }
  34. #pragma comment( lib, "hid.lib" )
  35. #pragma comment( lib, "setupapi.lib" )
  36. void SetAll(HANDLE hUsb, bool AllOn)
  37. {
  38.     BOOL Result;
  39.     UCHAR WriteReportBuffer[65];
  40.     for (int i = 0; i < 8; i++) {
  41.         WriteReportBuffer[i] = 0x00;
  42.     }
  43.     for (int i = 8; i < 65; i++) {
  44.         WriteReportBuffer[i] = 0x5a;
  45.     }
  46.     WriteReportBuffer[1] = 0x0b;
  47.     for (byte i = 0; i < 5; i++) {
  48.         WriteReportBuffer[6] = i;
  49.         if (AllOn) {
  50.             WriteReportBuffer[7] = 0x01;
  51.         }
  52.         DWORD lpNumberOfBytesWritten;
  53.         Result = WriteFile(hUsb,
  54.             WriteReportBuffer,
  55.             65,
  56.             &lpNumberOfBytesWritten,
  57.             NULL);
  58.         //printf("Written %d bytes Result [%d]\n", lpNumberOfBytesWritten, Result);
  59.     }
  60. }
  61. int main()
  62. {
  63.     GUID HidGuid;
  64.     BOOL Result;
  65.     int  counter = -1;
  66.     HidD_GetHidGuid(&HidGuid);
  67.     printf("HID GUID: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n"
  68.         , HidGuid.Data1, HidGuid.Data2, HidGuid.Data3
  69.         , HidGuid.Data4[0], HidGuid.Data4[1], HidGuid.Data4[2], HidGuid.Data4[3], HidGuid.Data4[4]
  70.         , HidGuid.Data4[5], HidGuid.Data4[6], HidGuid.Data4[7]);
  71.     HDEVINFO hDevInfo = SetupDiGetClassDevs(&HidGuid, NULL, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
  72.     if (INVALID_HANDLE_VALUE != hDevInfo)
  73.     {
  74.         SP_DEVICE_INTERFACE_DATA strtInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) };
  75.         for (DWORD index = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &HidGuid, index, &strtInterfaceData); ++index)
  76.         {
  77.             char buf[1000];
  78.             SP_DEVICE_INTERFACE_DETAIL_DATA& strtDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA&)buf[0];
  79.             strtDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  80.             if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &strtInterfaceData, &strtDetailData, _countof(buf), NULL, NULL))
  81.             {
  82.                 printf("[%d] path: %ls\n", index, strtDetailData.DevicePath);
  83.                 HANDLE hUsb = CreateFile(strtDetailData.DevicePath,
  84.                     NULL, FILE_SHARE_WRITE,
  85.                     NULL, OPEN_EXISTING,
  86.                     FILE_ATTRIBUTE_NORMAL,
  87.                     NULL);
  88.                 HIDD_ATTRIBUTES strtAttrib = { sizeof(HIDD_ATTRIBUTES) };
  89.                 Result = HidD_GetAttributes(hUsb, &strtAttrib);
  90.                 CloseHandle(hUsb);
  91.                 if (TRUE == Result)
  92.                 {
  93.                     if ((0x04D8 == strtAttrib.VendorID) &&
  94.                         (0xFEDC == strtAttrib.ProductID))       //ÕÒµ½ÎÒÃÇ×Ô¼ºµÄÉ豸
  95.                     {
  96.                         printf("VendorID : %hX\n", strtAttrib.VendorID);
  97.                         printf("ProductID: %hX\n", strtAttrib.ProductID);
  98.                         printf("VerNumber: %hX\n", strtAttrib.VersionNumber);
  99.                         hUsb = CreateFile(strtDetailData.DevicePath,
  100.                             GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
  101.                             NULL, OPEN_EXISTING,
  102.                             FILE_ATTRIBUTE_NORMAL, NULL);
  103.                         //Switch(hUsb); GetData(hUsb);
  104.                         SetAll(hUsb,TRUE);
  105.                         Sleep(3000);
  106.                         SetAll(hUsb, FALSE);
  107.                         CloseHandle(hUsb);
  108.                     }//if ((0x8888==strtAttrib.VendorID) &&
  109.                 }            //if(TRUE==Result)
  110.             } // if( SetupDiGetDeviceInterfaceDetail(hDevInfo,&strtInterfaceData,&strtDetailData,_countof(buf),NULL,NULL) )
  111.         }    //for( DWORD index=0;
  112.         if (GetLastError() != ERROR_NO_MORE_ITEMS)
  113.         {
  114.             printf("No more items!\n");
  115.         }
  116.         SetupDiDestroyDeviceInfoList(hDevInfo);
  117.     }  //if( INVALID_HANDLE_VALUE != hDevInfo )
  118.     system("PAUSE");
  119.     return 0;
  120. }
复制代码
这个代码没有使用厂家提供的API(厂家提供了 DLL)而是直接对 HID设备编程,这样做的好处是无需外挂文件,一个 EXE 都可以搞定。坏处是,没有实现厂家API 提供的诸如取得序列号这样的功能,如果有需求还需要另外研究编写。但是无论如何,能够实现控制端口开关已经足够了。
接下来,使用DFRobot FireBeetle ESP32 + USB Host Shield 来实现控制。很早之前,设计过一款 Firebeetle USBHost Shield【参考3】,但是对应的这种封装的 Max3421e 市面上比较少见了,于是重新进行了设计,选用了另外一种封装(32TQFN-EP)重新进行了设计。具体的设计可以在【参考4】看到。
接下来就可以专注于代码的设计:
  1. #include "DFRobot_OLED12864.h"
  2. #include <hidboot.h>
  3. #include <usbhub.h>
  4. #include <SPI.h>
  5. #include "UP520US.h"
  6. #define ADC_SECTION 5
  7. USB     Usb;
  8. UP520US  up520us(&Usb);
  9. const uint8_t I2C_addr = 0x3c;
  10. const uint8_t pin_SPI_cs = D2;
  11. const uint8_t pin_analogKey = A0;
  12. #define ADC_BIT  4096
  13. DFRobot_OLED12864 OLED(I2C_addr, pin_SPI_cs);
  14. enum enum_key_analog {
  15.   key_analog_no,
  16.   key_analog_right,
  17.   key_analog_center,
  18.   key_analog_up,
  19.   key_analog_left,
  20.   key_analog_down,
  21. } eKey_analog;
  22. uint8_t PowerStatus = 0x1F;
  23. byte CurrentPort = 0;
  24. // 返回当前按键信息
  25. enum_key_analog read_key_analog(void)
  26. {
  27.   int adValue = analogRead(pin_analogKey);
  28.   if (adValue > ADC_BIT * (ADC_SECTION * 2 - 1) / (ADC_SECTION * 2)) {
  29.     return key_analog_no;
  30.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 3) / (ADC_SECTION * 2)) {
  31.     return key_analog_right;
  32.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 5) / (ADC_SECTION * 2)) {
  33.     return key_analog_center;
  34.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 7) / (ADC_SECTION * 2)) {
  35.     return key_analog_up;
  36.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 9) / (ADC_SECTION * 2)) {
  37.     return key_analog_left;
  38.   } else {
  39.     return key_analog_down;
  40.   }
  41. }
  42. // 在 OLED 上绘制菜单
  43. void ShowMainMenu(uint8_t status, uint8_t Current)
  44. {
  45.   char Buffer[15];
  46.   OLED.clear();
  47.   //                 0123456789ABCDEF
  48.   OLED.disStr(0, 0, "控制 USB Power ");
  49.   memset(Buffer, ' ', sizeof(Buffer));
  50.   for (uint8_t i = 0; i < 5; i++) {
  51.     // 1 表示当前 ON, 0 表示 OFF
  52.     if ((status & (1 << i)) == 0) {
  53.       Buffer[i * 2] = 'X';
  54.     } else {
  55.       Buffer[i * 2] = 'O';
  56.     }
  57.     Buffer[i * 2 + 1] = ' ';
  58.   }
  59.   OLED.disStr(0, 16, Buffer);
  60.   OLED.disStr(0, 32, "1 2 3 4 5");
  61.   memset(Buffer, ' ', sizeof(Buffer));
  62.   // 指示当前操作 Port
  63.   Buffer[Current * 2] = '^';
  64.   OLED.disStr(0, 48, Buffer);
  65.   OLED.display();
  66. }
  67. void setup(void)
  68. {
  69.   Serial.begin(115200);
  70.   OLED.init();
  71.   OLED.flipScreenVertically();
  72.   if (Usb.Init() == -1) {
  73.     Serial.print(F("\r\nOSC did not start"));
  74.     while (1); // Halt
  75.   }
  76.   Serial.println(F("\r\nUP520US Started"));
  77.   delay( 200 );
  78.   // 绘制初始屏幕
  79.   ShowMainMenu(PowerStatus, CurrentPort);
  80. }
  81. // 发送 USB 控制命令
  82. void SetPort(uint8_t Port, boolean SetOn)
  83. {
  84.   char WriteReportBuffer[64];
  85.   memset(WriteReportBuffer, 0x5a, sizeof(WriteReportBuffer));
  86.   WriteReportBuffer[0] = 0x0b;
  87.   for (int i = 1; i < 7; i++) {
  88.     WriteReportBuffer[i] = 0x00;
  89.   }
  90.   WriteReportBuffer[5] = Port;
  91.   // 设置为 OFF
  92.   if (SetOn == false) {
  93.     WriteReportBuffer[6] = 0x01;
  94.   }
  95.   Usb.outTransfer(up520us.GetAddress(), 1, sizeof(WriteReportBuffer), (uint8_t*)WriteReportBuffer);
  96. }
  97. void loop(void)
  98. {
  99.   enum_key_analog button;
  100.   Usb.Task();
  101.   button = read_key_analog();
  102.   if ( button != key_analog_no) {
  103.     // 左键移动指示光标
  104.     if (button == key_analog_left) {
  105.       CurrentPort = (CurrentPort - 1) % 5;
  106.     }
  107.     // 右键移动指示光标
  108.     if (button == key_analog_right) {
  109.       CurrentPort = (CurrentPort + 1) % 5;
  110.     }
  111.     // 中键确认
  112.     if (button == key_analog_center) {
  113.       if ((PowerStatus & (1 << CurrentPort)) == 0) {
  114.         // 如果选中的 Port 当前是 ON ,那么设置为 OFF
  115.         PowerStatus = PowerStatus | (1 << CurrentPort);
  116.         if (up520us.connected()) {
  117.           SetPort(CurrentPort, false);
  118.         }
  119.       } else {
  120.         // 如果选中的 Port 当前是 OFF ,那么设置为 ON
  121.         PowerStatus = PowerStatus & (~(1 << CurrentPort));
  122.         if (up520us.connected()) {
  123.           SetPort(CurrentPort, true);
  124.         }
  125.       }
  126.     }
  127.     ShowMainMenu(PowerStatus, CurrentPort);
  128.     Serial.print(PowerStatus, HEX);
  129.     Serial.print("  ");
  130.     Serial.println(CurrentPort, HEX);
  131.     delay(300);
  132.   }
  133. }
复制代码

简单介绍要点:
1.     为了OLED 和USBHost Shield 的兼容,需要更改OLED模块默认的CSPin, 从D5修改为D2;
2.     使用OLED提供的5方向按键,通过下面的函数返回按键信息

  1. // 返回当前按键信息
  2. enum_key_analog read_key_analog(void)
  3. {
  4.   int adValue = analogRead(pin_analogKey);
  5.   if (adValue > ADC_BIT * (ADC_SECTION * 2 - 1) / (ADC_SECTION * 2)) {
  6.     return key_analog_no;
  7.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 3) / (ADC_SECTION * 2)) {
  8.     return key_analog_right;
  9.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 5) / (ADC_SECTION * 2)) {
  10.     return key_analog_center;
  11.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 7) / (ADC_SECTION * 2)) {
  12.     return key_analog_up;
  13.   } else if (adValue > ADC_BIT * (ADC_SECTION * 2 - 9) / (ADC_SECTION * 2)) {
  14.     return key_analog_left;
  15.   } else {
  16.     return key_analog_down;
  17.   }
  18. }
复制代码

1.     为了实现人机交互,设计了一个简单的菜单。用于指示当前的开关状态:

  1. // 在 OLED 上绘制菜单
  2. void ShowMainMenu(uint8_t status, uint8_t Current)
  3. {
  4.   char Buffer[15];
  5.   OLED.clear();
  6.   //                 0123456789ABCDEF
  7.   OLED.disStr(0, 0, "控制 USB Power ");
  8.   memset(Buffer, ' ', sizeof(Buffer));
  9.   for (uint8_t i = 0; i < 5; i++) {
  10.     // 1 表示当前 ON, 0 表示 OFF
  11.     if ((status & (1 << i)) == 0) {
  12.       Buffer[i * 2] = 'X';
  13.     } else {
  14.       Buffer[i * 2] = 'O';
  15.     }
  16.     Buffer[i * 2 + 1] = ' ';
  17.   }
  18.   OLED.disStr(0, 16, Buffer);
  19.   OLED.disStr(0, 32, "1 2 3 4 5");
  20.   memset(Buffer, ' ', sizeof(Buffer));
  21.   // 指示当前操作 Port
  22.   Buffer[Current * 2] = '^';
  23.   OLED.disStr(0, 48, Buffer);
  24.   OLED.display();
  25. }
复制代码

1.     USB 控制方面和 Windows 下面的编程非常类似,直接发送指定格式的数据包即可:



  1. // 发送 USB 控制命令
  2. void SetPort(uint8_t Port, boolean SetOn)
  3. {
  4.   char WriteReportBuffer[64];
  5.   memset(WriteReportBuffer, 0x5a, sizeof(WriteReportBuffer));
  6.   WriteReportBuffer[0] = 0x0b;
  7.   for (int i = 1; i < 7; i++) {
  8.     WriteReportBuffer[i] = 0x00;
  9.   }
  10.   WriteReportBuffer[5] = Port;
  11.   // 设置为 OFF
  12.   if (SetOn == false) {
  13.     WriteReportBuffer[6] = 0x01;
  14.   }
  15.   Usb.outTransfer(up520us.GetAddress(), 1, sizeof(WriteReportBuffer), (uint8_t*)WriteReportBuffer);
  16. }
复制代码

参考:





zoologist  高级技匠
 楼主|

发表于 2023-1-26 12:31:01

工作的视频:


回复

使用道具 举报

三春牛-创客  初级技神

发表于 2023-1-26 23:22:05

厉害厉害
回复

使用道具 举报

三春牛-创客  初级技神

发表于 2023-1-26 23:24:20

赞赞赞赞
回复

使用道具 举报

花生编程  中级技匠

发表于 2023-1-26 23:24:24

厉害厉害!!
回复

使用道具 举报

花生编程  中级技匠

发表于 2023-1-26 23:25:32

不错,赞!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail