前言
如果程序员配电脑要干点什么酷酷的事情的话,那就自己DIY一个机箱监控副屏吧。监控副屏在某宝最便宜也要上百块,本文介绍了怎么使用成本几十块的串口屏模块从0开始完成一个酷炫的机箱监控副屏。主要是软件编程较多,仅涉及到一点硬件知识,可放心食用。
先睹为快
环境准备
- 确保本地有C/C++编译环境, 可下载安装Cygwin或者MinGW。
- 确保有串口驱动,一般win10都自带。
- HF035串口屏和USB转TTL模块。
- 下载安装HF035串口屏的界面设计软件sGUI,和相关开发资料,点击此处下载(报毒请忽略),也可进入厂商官网进行下载:厂商官网。
界面设计
串口屏厂商一般都会提供一个界面设计的软件,在软件内可以拖拽式的进行界面布局设计,软件使用教程请参考屏幕厂商资料,设计过程此处不详述,与个人审美有关,这里简单的进行了一些组件的排列(又不是不能用)。建议将更多的美化设计下沉到背景底图上,使用PS等专业软件进行底图产出,只使用串口屏的文本、数字等组件用来实现功能。
在界面设计好后使用串口下载到屏幕中,此时屏幕上应该就可以显示出界面了。这里需要注意的是在下载过程时选择正确的COM端口,波特率使用默认的115200。TTL调试器的接线需要注意RX和TX是一一对应的,RX接板子的RX,TX接板子的TX,这块屏幕是厂商故意这么设计的,与正常的RX接TX,TX接RX不同。
串口屏语法
串口屏的程序是使用串口协议发送厂商规定格式的数据到屏幕上进行显示,这块板子使用的是字符串的方式进行数据组织,所以在写程序时比较简单,发送类似如下的字符串到指定COM口即可。
SET_NUM(0,10,2);SET_TXT(0,'KB');\r\n
其中SET_NUM函数的0代表组件的id,10代表具体的数值,2代表数值总共占两位。其中的SET_TXT函数的0代表组件的id,‘KB’代表具体显示的文本值。多个函数可以多次分开发送,也可连接一起发送,厂商使用‘\r\n’作为分隔符,所以一定需要在末尾带上分隔符,否则串口屏会一直等待接收。其余API可参考串口屏厂商文档。
程序设计
为了监控硬件和网络信息,我们可以使用非常好用的一款软件:TrafficMonitor,此软件的优点请自行搜索,绝对是小而美的精品软件,官方仓库地址,目前已有27.5k的star,鄙人已使用多年,墙裂推荐。
TrafficMonitor在v1.82版本新增了插件系统,意味着可以使用其提供的硬件监控数据进行自定义的显示,提供了无限的想象空间。我们可以使用它的插件系统获取硬件数据,然后通过串口将数据发送到屏幕进行显示。程序设计要点可以分为串口自动扫描连接、硬件数据获取与发送两个方面,下面分别进行介绍。
串口自动扫描连接
串口方面的操作使用的是C++标准库windows.h进行实现,具体代码也是借鉴了他人的一些代码,这里找不到仓库地址了,不过可以直接照搬我的实现,原理都是一样的。主要难点就是扫描串口,我是在注册表Hardware\\DeviceMap\\SerialComm中进行遍历查找串口的。在程序中编写了一个autoConnect方法,主要功能就是扫描注册表,然后逐个进行连接,返回第一个连接成功的串口,这种写法有一定局限,比如不能适应多串口的场景,对于有特殊需求的读者可以自行修改相关代码。
bool SerialPort::autoConnect()
{
HKEY hkey;
int result;
int i = 0;
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Hardware\\DeviceMap\\SerialComm", NULL, KEY_READ, &hkey);
if (ERROR_SUCCESS == result) //打开串口注册表
{
do
{
TCHAR portName[0x100] = {0}; //portName是注册表名称(不是设备管理器里的前缀),commName就是值 串口名字COMN
TCHAR commName[0x100] = {0};
DWORD dwSize = sizeof(portName) / sizeof(TCHAR);
DWORD dwLong = dwSize;
result = RegEnumValue(hkey, i, portName, &dwLong, NULL, NULL, (LPBYTE)commName, &dwSize);
if (ERROR_NO_MORE_ITEMS == result)
{
break;
} // 枚举串口
// printf("%d %s %s\n", i, portName, commName); //显示名称及值
char buff[30];
sprintf(buff, "\\\\.\\%s", commName);
// printf("try connect %s\n", buff);
bool state = this->openSerial(buff);
// printf("state = %d, connected = %d\n", state, this->connected);
if (state && this->connected)
{
break;
}
i++;
} while (TRUE);
RegCloseKey(hkey); //关闭注册表
}
return -1; //没找到特定串口
}
硬件数据获取与发送
TrafficMonitor的插件系统会定时回调OnMonitorInfo函数,会传递进来一个包含监控数据的对象,我们只需实现OnMonitorInfo函数,然后解析获取想要的监控数据即可,关于插件编写的细节具体可参考官方的插件开发指南。在获取到数据后按照串口的数据格式要求进行组装,这里我们使用sprintf将所有数据组合后一次性发送。在实测过程中串口屏的刷新率比较低,达到了200ms,所以有明显的刷屏视觉感知,我们在刷屏时将刷新的内容乱序发送,可以减少一些人眼的刷屏感知时间。
void HF035::OnMonitorInfo(const MonitorInfo& monitor_info)
{
// 日志调试
// FILE* fp = fopen("hf035.log", "a+");
char buf[255];
// 上行网速
this->formatSpeed(monitor_info.up_speed, this->speedStr1);
// 下行网速
this->formatSpeed(monitor_info.down_speed, this->speedStr2);
// 乱序格式化串口数据,注意末尾必须‘\r\n’
sprintf(buf, "SET_TXT(8,'%s');SET_TXT(9,'%s');SET_NUM(0,%d,2);SET_TXT(11,'%s');SET_NUM(1,%d,2);SET_TXT(10,'%s');SET_NUM(2,%d,2);SET_NUM(7,%d,2);SET_NUM(3,%d,2);SET_NUM(6,%d,2);SET_NUM(4,%d,2);SET_NUM(5,%d,2);\r\n",
//上行网速尾标
this->getSpeedPix(monitor_info.up_speed),
//下行网速尾标
this->getSpeedPix(monitor_info.down_speed),
// CPU使用率
this->formatUsage(monitor_info.cpu_usage),
this->speedStr2,
// CPU温度
this->formatUsage(monitor_info.cpu_temperature),
this->speedStr1,
// GPU使用率
this->formatUsage(monitor_info.gpu_usage),
// 硬盘温度
this->formatUsage(monitor_info.hdd_temperature),
// GPU温度
this->formatUsage(monitor_info.gpu_temperature),
// 硬盘使用率
this->formatUsage(monitor_info.hdd_usage),
// 内存使用率
this->formatUsage(monitor_info.memory_usage),
// 主板温度
this->formatUsage(monitor_info.main_board_temperature));
// 如果已连接则进行发送,否则进行自动重连
if(this->serialPort->isConnected()){
// 发送串口数据
this->serialPort->writeSerialPort(buf, strlen(buf));
}else{
// fwrite("autoConnect\n", 1, 13, fp);
// 自动扫描连接串口
this->serialPort->autoConnect();
}
// 关闭文件
// fclose(fp);
// printf("cpu: %d \n", monitor_info.cpu_usage);
}
/* 格式化使用率,保证数值长度不超过2位,避免界面超出 */
int HF035::formatUsage(int num)
{
return num < 0 ? 0 : (num > 99 ? 99 : num);
}
/* 格式化网速,最大显示99MB,最小显示0.1KB*/
void HF035::formatSpeed(unsigned long long speed, char * buf)
{
float temp = 0.1f;
if(speed > 103809024){
// 99 MB
temp = 99.0f;
}else if(speed > 104858){
// MB
temp = speed / 1048576.0f;
}else if(speed > 103){
// 0.1KB
temp = speed / 1024.0f;
}
// 数值大于2位时不显示小数,避免界面超出
if(temp > 9.9f){
sprintf(buf, "%d", (int)temp);
}else{
sprintf(buf, "%.1f", temp);
}
}
/* 获取网速后缀 */
char * HF035::getSpeedPix(unsigned long long speed)
{
if(speed > 1048576){
// MB
return "M";
}
return "K";
}
编译运行
TrafficMonitor要求编译为dll后放到插件目录,然后重启软件即可自动加载插件运行,如果一切正常就可以看到屏幕上的内容每两秒会更新一次。
编译命令:
g++ HF035.cpp SerialPort.cpp -lstdc++ -shared -o HF035.dll
将HF035.dll放到插件目录C:\Program Files\TrafficMonitor\plugins,重启软件即可看到插件加载成功:
完整项目地址:https://github.com/printlin/computerMonitorScreen