前言:我为什么会想这么做
本就听闻rdp支持USB设备的重定向,又因为个人认为硬件开发环境部署比较繁琐。所以我在想:“如果能支持RDP远程烧录——实现本地客户端作为仅连接开发板的瘦客户端,通过连接远程服务器进行编译调试烧录”就好了。这么做不仅保证开发环境的统一,便于配置,而且一些复杂的大型的软件完全不用安装在本地,甚至可能能多用户使用。
下文的客户端指代RDP客户端,服务器指代RDP服务端
默认已开启RemoteFX USB重定向
Arduino IDE
目前唯一一个支持RDP桌面远程烧录的IDE
配置需要以下步骤
服务端识别COM设备
- 在客户端安装arduino开发板的驱动
- 在服务端端安装arduino开发板的驱动
尚未测试不同驱动版本之间兼容性,但是推荐安装相同驱动程序
连接Arduino开发板到客户端
确保本地能正常识别开发板
在远程桌面连接程序中配置开发板连接端口
启动RDP,在其他支持的Remote FX USB设备
中勾选对应设备,显示如下
选择确定,然后连接服务端。
安装并启动Arduino IDE并烧录程序
- 修改烧录端口时,会发现可以选择
COM3
端口,此端口不存在于设备管理器中。
- 找一个程序烧录,发现可以远程烧录,且单片机行为符合预料。
换句话说,Arduino IDE支持基于RDP的远程单片机烧录。
STC-ISP
完全不支持烧录到RDP提供的COM设备,即使能自选COM端口。
jlink+keil烧录
完全不支持,似乎设备插入电脑后会被jlink驱动注册为通用串行总线控制器
类型的设备,不作为单独的COM设备出现,因此我想不出来支持远程烧录的方法。
自己尝试
列出RDP远程COM设备
考虑到arduino IDE能扫描到RDP的远程端口,而服务端的设备管理器不能正常显示RDP的远程COM设备。因此一定存在一种比较小众的方式能够扫描到包括RDP远程COM端口在内的所有端口。
简单和gpt4o交流中得到两版代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void ListComPorts() { HDEVINFO hDevInfo = SetupDiGetClassDevs( NULL, _T("USB"), NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES );
if (hDevInfo == INVALID_HANDLE_VALUE) { std::cerr << "Failed to get device information set." << std::endl; return; }
SP_DEVINFO_DATA DeviceInfoData; DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++) { TCHAR deviceName[256]; DWORD dataSize;
if (SetupDiGetDeviceRegistryProperty( hDevInfo, &DeviceInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)deviceName, sizeof(deviceName), &dataSize)) {
_tprintf(_T("Found device: %s\n"), deviceName); } }
SetupDiDestroyDeviceInfoList(hDevInfo); }
|
测试后发现这个方法不能列出RDP远程COM设备。
第二种
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| void ListComPortsReg() { HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hKey) != ERROR_SUCCESS) { std::cerr << "Failed to open registry key." << std::endl; return; }
TCHAR valueName[256]; BYTE valueData[256]; DWORD valueNameSize, valueDataSize, index = 0; DWORD type;
while (true) { valueNameSize = sizeof(valueName) / sizeof(valueName[0]); valueDataSize = sizeof(valueData);
if (RegEnumValue(hKey, index, valueName, &valueNameSize, NULL, &type, valueData, &valueDataSize) != ERROR_SUCCESS) { break; }
if (type == REG_SZ) { std::wcout << L"Port name: " << reinterpret_cast<TCHAR*>(valueData) << std::endl; }
index++; }
RegCloseKey(hKey); }
|
这个方式通过直接读取注册表来获取所有COM设备,测试后发现可以读取RDP提供远程COM设备。
如何访问远程COM设备
经过对arduino的有意操作,在烧录过程中主动断开客户端连接的开发板,发现报错类似于\\.\COM3 xxx
。听此猜想对于由RDP提供的COM设备,虽然不能使用SetupDiEnumDeviceInfo
枚举出来,但是与一般的COM设备操作没有什么区别!太厉害了Windows
所以又借助gpt4o(是的我不会windows开发,我们AI太厉害了)生成了一个设备访问的示例(打开设备后就关闭)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void OpenDevice() { HANDLE hDevice = CreateFile( _T("\\\\.\\COM3"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); if (hDevice == INVALID_HANDLE_VALUE) { std::cerr << "Failed to open device." << std::endl; return; }
std::cout<<"Device opened"<<std::endl; CloseHandle(hDevice);
}
|
其中,为了图省事,写死了COM3
,这是我的开发板连接的客户端分配的COM端口,似乎在经过远程桌面连接设置后会被直接映射到服务端的相同端口上。
将这个应用release编译后扔到服务端运行
1 2 3 4 5
| PS C:\Users\djh\Desktop> .\testcom.exe ListComPorts ListComPortsReg Port name: COM3 Device opened
|
发现可以列出RDP提供的远程COM设备的端口号,而且可以成功打开这个设备。
我的开发板是Arduino Uno,USB连接。只打开随后关闭设备似乎会触发复位?这个不太懂。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #include <windows.h> #include <setupapi.h> #include <iostream> #include <tchar.h>
#pragma comment(lib, "setupapi.lib")
void OpenDevice() { HANDLE hDevice = CreateFile( _T("\\\\.\\COM3"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); if (hDevice == INVALID_HANDLE_VALUE) { std::cerr << "Failed to open device." << std::endl; return; }
std::cout<<"Device opened"<<std::endl; CloseHandle(hDevice);
} void ListComPorts() { HDEVINFO hDevInfo = SetupDiGetClassDevs( NULL, _T("USB"), NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES );
if (hDevInfo == INVALID_HANDLE_VALUE) { std::cerr << "Failed to get device information set." << std::endl; return; }
SP_DEVINFO_DATA DeviceInfoData; DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++) { TCHAR deviceName[256]; DWORD dataSize;
if (SetupDiGetDeviceRegistryProperty( hDevInfo, &DeviceInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)deviceName, sizeof(deviceName), &dataSize)) {
_tprintf(_T("Found device: %s\n"), deviceName); } }
SetupDiDestroyDeviceInfoList(hDevInfo); }
void ListComPortsReg() { HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hKey) != ERROR_SUCCESS) { std::cerr << "Failed to open registry key." << std::endl; return; }
TCHAR valueName[256]; BYTE valueData[256]; DWORD valueNameSize, valueDataSize, index = 0; DWORD type;
while (true) { valueNameSize = sizeof(valueName) / sizeof(valueName[0]); valueDataSize = sizeof(valueData);
if (RegEnumValue(hKey, index, valueName, &valueNameSize, NULL, &type, valueData, &valueDataSize) != ERROR_SUCCESS) { break; }
if (type == REG_SZ) { std::wcout << L"Port name: " << reinterpret_cast<TCHAR*>(valueData) << std::endl; }
index++; }
RegCloseKey(hKey); }
int main() { std::cout<<"ListComPorts"<<std::endl; ListComPorts(); std::cout << "ListComPortsReg" << std::endl; ListComPortsReg(); OpenDevice();
return 0; }
|
未来的想法
对于stc
研究了一下,有开源项目stcgal.py
可以进行烧录。我没有测试它是否原生支持烧录到远程com设备,如果不支持,可能可以经过比较简单的修改后,支持在rdp上进行远程烧录。
对于jlink+keil
开源项目想要用jlink烧录都需要安装jlink驱动和相关软件包,而驱动不会把设备注册为COM设备,所以也不知道如何操作才能烧录。因此这个彻底方式放弃了。
但是stm32本身似乎支持用ttl方式烧录,那么采用这种方式并找到一个能识别远程COM口,或者直接写入远程COM口的烧录软件或许可以成功烧录。但是我硬件相关的知识不多,特别是stm32相关的,所以不多赘述,放着未来我变强了再尝试解决吧。
总结
根据目前的情况来看,实现rdp远程烧录需要以下条件
- 开发板可以被识别为一般的COM设备进行烧录
- 烧录软件支持列出所有设备,或者不进行COM端口存在检查
测试下来真正能实现这个目标的就是Arduino了。