Firebeetle控制一个USB 接口的排插
最近在研究一款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个数据包:经过多次试验,总结数据如下:
操作抓到的数据,总长度64Bytes
Port1设置为 On0B00 00 00 00 00 00 5A……5A
Port2设置为 On0B00 00 00 00 01 00 5A……5A
Port3设置为 On0B00 00 00 00 02 00 5A……5A
Port4设置为 On0B00 00 00 00 03 00 5A……5A
Port5设置为 On0B00 00 00 00 04 00 5A……5A
Port1设置为 Off0B00 00 00 00 00 01 5A……5A
Port2设置为Off0B00 00 00 00 01 01 5A……5A
Port3设置为Off0B00 00 00 00 02 01 5A……5A
Port4设置为Off0B00 00 00 00 03 01 5A……5A
Port5设置为Off0B00 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
//
USHORTVendorID;
USHORTProductID;
USHORTVersionNumber;
//
// Additional fields will be added to the end of this structure.
//
} HIDD_ATTRIBUTES, * PHIDD_ATTRIBUTES;
BOOLEAN __stdcall
HidD_GetAttributes(
INHANDLE 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;
for (int i = 0; i < 8; i++) {
WriteReportBuffer = 0x00;
}
for (int i = 8; i < 65; i++) {
WriteReportBuffer = 0x5a;
}
WriteReportBuffer = 0x0b;
for (byte i = 0; i < 5; i++) {
WriteReportBuffer = i;
if (AllOn) {
WriteReportBuffer = 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;
intcounter = -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, HidGuid.Data4, HidGuid.Data4, HidGuid.Data4, HidGuid.Data4
, HidGuid.Data4, HidGuid.Data4, HidGuid.Data4);
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;
SP_DEVICE_INTERFACE_DETAIL_DATA& strtDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA&)buf;
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;
UP520USup520us(&Usb);
const uint8_t I2C_addr = 0x3c;
const uint8_t pin_SPI_cs = D2;
const uint8_t pin_analogKey = A0;
#define ADC_BIT4096
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;
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 = 'X';
} else {
Buffer = 'O';
}
Buffer = ' ';
}
OLED.disStr(0, 16, Buffer);
OLED.disStr(0, 32, "1 2 3 4 5");
memset(Buffer, ' ', sizeof(Buffer));
// 指示当前操作 Port
Buffer = '^';
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;
memset(WriteReportBuffer, 0x5a, sizeof(WriteReportBuffer));
WriteReportBuffer = 0x0b;
for (int i = 1; i < 7; i++) {
WriteReportBuffer = 0x00;
}
WriteReportBuffer = Port;
// 设置为 OFF
if (SetOn == false) {
WriteReportBuffer = 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;
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 = 'X';
} else {
Buffer = 'O';
}
Buffer = ' ';
}
OLED.disStr(0, 16, Buffer);
OLED.disStr(0, 32, "1 2 3 4 5");
memset(Buffer, ' ', sizeof(Buffer));
// 指示当前操作 Port
Buffer = '^';
OLED.disStr(0, 48, Buffer);
OLED.display();
}
1. USB 控制方面和 Windows 下面的编程非常类似,直接发送指定格式的数据包即可:
// 发送 USB 控制命令
void SetPort(uint8_t Port, boolean SetOn)
{
char WriteReportBuffer;
memset(WriteReportBuffer, 0x5a, sizeof(WriteReportBuffer));
WriteReportBuffer = 0x0b;
for (int i = 1; i < 7; i++) {
WriteReportBuffer = 0x00;
}
WriteReportBuffer = Port;
// 设置为 OFF
if (SetOn == false) {
WriteReportBuffer = 0x01;
}
Usb.outTransfer(up520us.GetAddress(), 1, sizeof(WriteReportBuffer), (uint8_t*)WriteReportBuffer);
}
参考:1. http://powersplitter.bellwin.com.tw/UP520US.html2. https://www.bilibili.com/video/BV1CG4y1H7Xk/USBPacketViewer3. https://mc.dfrobot.com.cn/thread-309553-1-1.html4. https://mc.dfrobot.com.cn/thread-315217-1-1.html
工作的视频:
https://www.bilibili.com/video/BV1RD4y1n7te/
厉害厉害 赞赞赞赞 厉害厉害!! 不错,赞!
页:
[1]