IoT Power PC 端技术总结 – 2 与设备通信

系列文章合集:IoT Power PC 端技术总结

传输数据的方式

IoT Power向电脑传输的数据,是原始的10KHz采样下来的12Bit ADC值,不经过处理直接发送到电脑上,然后电脑再进行计算处理成实际的电压电流值。
因为每个点有效位只有12Bit,实际使用了2Byte来存储一个点,电流档位信息就放到了每个值的空闲位。当PC解析数据时,就能知道这个ADC是哪个档位的,可以使用对应的换算算法。

在新设备与老设备的通信协议上,有着不同的区别:
– 较老的V1版本使用的是串口,波特率921600,并且因为这个原因,只能使用CP2102
– 后期设备全部使用的是USB FS,驱动使用了WinUSB,Bulk传输
– TCP/IP通信,基于TCP的数据传输,可提供远程查看波形的功能

两种方式都可以完成上面10KHz数据的要求(数据为40KByte/s)
实际效果上,使用串口传输延迟会更加不稳定,并且资源占用也更大(粘包与截断更严重)。

每个包都会携带一个功能code,用于区分数据包的不同功能、传递数据类型。
可以用于下发计算真实值使用的校准参数,每个设备校准值不同,按不同设备使用不同的校准参数,可以让实际求得的数据更准确。
也可以用来下发控制信息,直接控制设备的输出状态或配置参数。

为什么使用Rust处理数据

首先一点其实和性能无关。

项目初期,稀饭放姜要求对通信协议保密(目前仍然未公开),那显然是不能使用C#的,因为反编译相当容易,无法实现保密目标。在了解了C++的相关库后,我决定还是用更加现代的Rust来实现这个功能,更省心。

后期测试发现使用Rust的CPU占用只有C#写的处理逻辑的10%左右(当然很大程度上是因为用了System.Array,没有使用Span<T>导致的)。
更进一步来说,.Net平台与硬件处理相关的这些库,始终都有这样或那样的bug,同时这些bug基本都是框架自身的问题,难以修复。

最终是使用Rust构建了一套通信+数据处理库,提供给C#进行调用。

不同的通信方式

串口通信

由于需要尽可能地缩小Dll的体积,所以整个项目都在避免直接引用类似tokio之类的异步库,串口库也不例外。
这里直接使用的是阻塞式的serialport库,没有使用异步库。
在C#调用连接接口后,Rust层会直接开启一个新线程,死循环读取串口数据。

当数据到来之后,将数据存储在一个全局的Mutex<Vec<u8>>中,然后走处理函数进行分析。如果发现数据包不完整,会等待下一个数据包到来,下一包数据到来时拼接在缓存数据的末尾。当数据包完整时,会将数据包的功能码与数据部分分离,然后通知C#来读取对应的数据。

串口发送数据也是提前预存在一个全局变量中,等待接收数据空闲时发送出去。

WinUSB通信

为了最大限度地实现C#代码的复用,WinUSB通信的部分的Rust接口与串口通信的接口保持一致,只是在底层实现上有所不同。
WinUSB通信的部分,使用了rusb库,这个库是对libusb的封装,同样是阻塞式的,目前还没有提供异步的接口。

WinUSB的通信部份逻辑与串口通信的逻辑基本一致,只是在初始化时需要传入设备的VID与PID,以及WinUSB的接口号。Rust代码中会开启一个新线程,死循环读取WinUSB数据,然后进行处理。
与串口稍微有些不同的是,因为USB的特性,这套协议一次性传输的数据扩大了100倍,并且极少出现粘包与截断的情况,所以在处理数据包时,占用的资源比串口处理要少很多。

另外有一些不同的是,USB的设备名是一串字符串,所以需要一个单独的函数来获取当前设备列表,然后根据设备名来选择对应的设备。

TCP/IP通信

TCP/IP通信的部分,同样为了最大限度地实现C#代码的复用,Rust接口与串口通信的接口保持一致。
因为原始数据传输速率需求高达40KByte/s,所以传输的数据包在发送之前,使用了LZ4压缩算法进行压缩,收到的数据包要先进行解压。
压缩后的数据大约只有原始数据的1/10,大大减少了数据传输的宽带需求。

与C#的交互

提供Dll接口

这方面我已经在博客中有例子,这里就不再赘述了。

获取状态

提供给外部的接口,基本是相当于被Rust当作多线程的代码来检查了。所以如果想使用提供给外部的接口,返回全局变量的一些状态,需要加锁。
可以看到上面的例子中,我使用了Mutex<Vec<u8>>来存储缓存数据。实际上其他所有状态数据信息都需要使用Mutex来保护,防止Rust编译器生气。

结合起来使用

分两个独立的工程,调试起来会有一些麻烦。但好在Rust可以直接使用测试模块,直接测试函数的可行性,不需要依赖C#来调用测试,极大提升了开发效率。

我为了实现自动化更新,我在VS的编译生成步骤中加入了Rust编译的操作,这样每次运行前都可以确保Rust的DLL是最新的。
同时为了减小软件体积,我使用UPX对Rust编译出来的DLL进行压缩,又可以节省出来不少空间。

Rust编译也推荐打包一下静态库,因为早期发现很多人的电脑没有安装vc++运行库,导致无法运行。静态库可以避免这个问题。
如何让RUST编译生成出不依赖VC++库的文件?(打包静态库)

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注