CPU/RAM/FAN 监视器
我用过的最快的CPU是AMD 的K7。那时候我在一家主板厂上班,刚入行不久,上面要我测试AMDK7的问题。我跑去测试部门借到了一颗K7,小心翼翼的插在板子上(那时候CPU还有针脚),盖上风扇,上电发现风扇转动一下就停了,再次实验还是这样的现象。急忙找来老员工请教缘由,只见他推了推风扇,然后就诊断到:你没有锁扣,这个CPU烧掉了。听到这里,我大吃一惊,惊异于CPU如此脆弱,另外刚参加工作就出了这样的“篓子”心中也着实没底。不过去测试部门说明一下情况,把这个CPU挂账给我,后来也没有什么问题。只是从此之后,见到了AMD的CPU更加提心吊胆。从此,我也知道温度是关乎CPU生死的参数。因此,这次使用 Arduino尝试制作一个能够将CPU温度,CPU使用率,内存使用率和风扇转速显示出来的小装置。选择使用Leonrado来实现,因为它本身有原生的USB支持,方便实现和电脑的接口。通常的USB 设备在 Windows下都需要安装驱动,在实际使用中,相同的USB驱动在不同的Windows版本间有兼容性的问题。并且为了兼容32位和64位Windows,还需要准备2套驱动。此外,从Windows 7 X64开始,微软强制要求进行数字签名,给 USB 驱动的发行制造了很大困难。鉴于此,无需特别驱动的USB 设备需求强烈。总结下来主要有下面三种简单易行的方式:1. 直接使用 LibUSB 这样的第三方库。优点是可以实现跨平台,一套代码可以在Linux和Windows 下运行。缺点是对于开发者要求较高,仍然会遇到签名上的问题;2. 产品开发上使用 USB 串口芯片,借用芯片厂商提供的驱动。优点是开发简单,调试方便,对于产品来说,直接串口通讯即可,对于上位机来说也是串口编程。缺点是有时候芯片厂商提供的驱动也并不完美,客户在安装驱动时会有一些麻烦;3. 直接设计免驱动的固件。Windows中集成了很多USB设备的驱动,最常见的就是 Mass Storage ,我们插入 U盘之后,系统自动加载驱动,无需额外安装即可正常工作。对于第三种方法来说,最标准的就是声明自身为 HID 设备,Windows内置了对于 HID 设备驱动,时我们可以定义设备为 HID RAW 格式,这样可以很方便的使用。本文介绍如何使用Arduinio Leonrado制作一个能够显示当前CPU 温度和风扇转速的设备,而这个设备是通过 USBHID和上位机进行通讯的。Arduino代码使用NicoHood 的HID库来完成【参考1】。此外,还选用了RSCG12864B01的 12864 LCD模块,使用这个模块的原因是:接线少,相对于其他12864动辄十根以上的接线,这个模块只需要I2C的 SDA和SCL,外加一个BUSY即可。这样我们可以讲注意力集中在“显示什么”,而不是“如何显示”。同时,桃子老师提供了这个模块的库,用起来也十分方便【参考2】。硬件连接如下,可以看到硬件部分足够简单:接下来介绍软件部分,相比之下比硬件复杂多了。我们通过使用一个开源的程序 open Hardware Monitor 来完获得 CPU温度和风扇转速【参考2】。这里先介绍一下获得CPU温度的原理:很久之前的主板,是采用在CPUSlot 下面放置热敏二极管的方式来获得CPU温度,这种方法存在显而易见的缺点就是不准确。有一段时间,我更新BIOS给测试Team做验证,他们测试不出来其他问题就喜欢报告“当前CPU温度不准”。后来我也每次都根据他们给出来的偏差进行修改,过了一段大约他们也觉得太无聊就不再把这项列为必须需修改的项目。随着功耗的增长,CPU几乎是系统中功耗最高的部件(特别是只有集成显卡的主板),系统需要严格监视温度防止烧毁,当温度达到设定的阈值时,首先是增加风扇转速进行散热,其次会采取各种方法降低功耗,比如,自动降频,直至关机。后来以Intel为首的处理器公司将这样的温度传感器集成在CPU中。同时,引入了平台环境式控制接口(PlatformEnvironment Control Interface,英文缩写PECI), 这样使得外围的 Super IO(台式机主板)/ EmbeddedController(笔记本)/BMC (服务器)都有机会获得当前的CPU温度。除了CPU温度,还有系统温度和风扇转速之类的系统状态值。这些数值和主板的设计相关,BIOS中也没有统一的接口,例如:很多主机BIOS中有系统温度这个栏位,但是具体这个温度传感器放置在什么位置如何取值只有硬件工程师和BIOS工程师知道,不同主板差别很大。网上一直流传着从 WMI中可以获得当前CPU温度的说法。但是经过笔者研究,因为这个并非微软强制要求,因此绝大多数主板并不支持这个功能。也正是因为这一点,通过自己编程直接读取温度很难实现通用。试想一下,你在一台机器上编译调试后,想给女神露一手,结果确实无法抓取结果会是多么可悲的情景。具体代码如下:1. Arduino
#include "HID-Project.h"
#include "RSCG12864B.h"
//判断当前是否为 BUSY 的状态Pin
const int BUSYPIN = 7;
//初始化 LCD
RAYLIDLCD LCD12864(BUSYPIN);
//HID 的缓冲区
uint8_t rawhidData;
//接收到数据的结构体,总长度和缓冲区一样大
typedef struct sdata
{
unsignedintCPUTemperature; //CPU温度
unsigned intCPULoad; //CPU负载
unsigned intMemoryLoad; //内存负载
unsigned intFanSpeed; //风扇转速
unsigned char Reserved; //暂时无用
};
//String:"CPU 温度"
char cpuStr1[] = {'C','P','U',' ',0XCE, 0XC2, 0XB6, 0XC8, 0x00};
//String:"℃"
char cpuStr2[] = {0xA1,0xE6,0x00};
//String: "CPU负载"
char cpuStr3[] = {'C','P','U',' ',0xB8, 0xBA, 0xD4, 0xD8, 0x00,};
//String: "内存负载"
char RamStr1[] = {0xC4, 0xDA, 0xB4, 0xE6, 0xB8, 0xBA, 0xD4, 0xD8,0x00,};
//String: "N/A"
char NAStr[] = {'N','/','A', 0x00};
//String: "风扇转速"
char FanSpeedStr1[] = {0xB7, 0xE7, 0xC9, 0xC8, 0xD7, 0xAA, 0xCB, 0xD9,0x00};
//String: ""
char FanSpeedStr2[] = {'R','P','M',0x00};
void setup() {
LCD12864.begin();
LCD12864.setBrightness(200);
LCD12864.clear();
//初始化 HID 设备
RawHID.begin(rawhidData, sizeof(rawhidData));
}
//在 12864 上显示String
void showstring(int x, int y, String str)
{
char buffer;
str.toCharArray(buffer,str.length());
LCD12864.print(x,y, buffer, VLARGE);
}
//在字符串s末尾填充空格,保证总长度等于16个字符
String fillblank(String s)
{
intt=s.length();
for(int i=0;i<17-t;i++)
{
s=s+' ';
}
return s;
}
void loop() {
char tempstring;
float tempfloat;
sdata Data;//接收缓冲
char *p;
String s;
//检查是否有新数据
auto bytesAvailable = RawHID.available();
if(bytesAvailable)
{
//直接将收到的64Byte填充到结构体中
p=(char *)&Data;
while (bytesAvailable--) {
*p=RawHID.read();
p++;
}
Serial.print(Data.CPUTemperature);
Serial.print("");
Serial.print(Data.CPULoad);
Serial.print("");
Serial.print(Data.MemoryLoad);
Serial.print("");
Serial.println(Data.FanSpeed);
//处理CPU温度
s=cpuStr1;
if (0xFFFF!=Data.CPUTemperature) {
itoa(Data.CPUTemperature, tempstring, 10);
s=s+tempstring+cpuStr2;
}
else //如果收到为0xFFFF表示当前无此项
s=s+NAStr;
s=fillblank(s);
showstring(0, 0, s);
//处理CPU使用率
s=cpuStr3;
if (0xFFFF!=Data.CPULoad) {
//例如:当前系统上CPU 使用率为49.83%,
//这里将会收到十进制的4983
tempfloat=((float) Data.CPULoad) / 100;
dtostrf(tempfloat,2,2,tempstring);
s=s+tempstring+'%';
}
else //如果收到为0xFFFF表示当前无此项
s=s+NAStr;
s=fillblank(s);
showstring(0, 16, s);
//处理当前内存使用率
s=RamStr1;
if (0xFFFF!=Data.MemoryLoad) {
tempfloat=((float) Data.MemoryLoad) / 100;
dtostrf(tempfloat,2,2,tempstring);
s=s+tempstring+'%';
}
else //如果收到为0xFFFF表示当前无此项
s=s+NAStr;
s=fillblank(s);
showstring(0, 32, s);
//处理风扇转速
s=FanSpeedStr1;
if (0xFFFF!=Data.FanSpeed) {
itoa(Data.FanSpeed, tempstring, 10);
s=s+tempstring+FanSpeedStr2;
}
else //如果收到为0xFFFF表示当前无此项
s=s+NAStr;
s=fillblank(s);
showstring(0, 48, s);
}
delay(500);
}2.
上位机代码 VS2015 编译(为了更好的展示原理,这里选择 Console 方式编写程序,直接API调用,读者可以方便的迁移到其他语言)
#include "stdafx.h"
#define _WIN32_DCOM
#include <comdef.h>
#include <Wbemidl.h>
#include <conio.h>
#include <stdio.h>
#pragma comment(lib, "wbemuuid.lib")
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <setupapi.h>
#pragma comment( lib, "hid.lib" )
#pragma comment( lib, "setupapi.lib" )
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
);
}
float CPUTemperature;
float CPULoad;
float MemoryLoad;
float FanSpeed;
boolean SendData()
{
GUID HidGuid;
BOOL Result;
//获取HID设备的接口类GUDI
HidD_GetHidGuid(&HidGuid);
//根据获得的GUID枚举HID设备
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);
//这里打开的有可能是USB键盘鼠标这样比较特别的设备(只能查询)
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 ((0x2341 == strtAttrib.VendorID) &&
(0x8036 == 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_WRITE, FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
//发送报告的缓冲区,1字节报告ID+8字节报告数据。
UCHAR WriteReportBuffer;
DWORD lpNumberOfBytesWritten;
UINT LastError;
//装载实际数据
WriteReportBuffer = 0;
WriteReportBuffer = ((int)CPUTemperature) & 0xFF;
WriteReportBuffer = 0;
WriteReportBuffer = ((int)(CPULoad*100)) & 0xFF;
WriteReportBuffer = ((int)(CPULoad*100)>>8) & 0xFF;
WriteReportBuffer = ((int)(MemoryLoad * 100)) & 0xFF;
WriteReportBuffer = ((int)(MemoryLoad * 100) >> 8) & 0xFF;
if (((int)FanSpeed)!=100000) {
WriteReportBuffer = ((int)(FanSpeed)) & 0xFF;
WriteReportBuffer = ((int)(FanSpeed) >> 8) & 0xFF;
}
else
{
WriteReportBuffer = 0xFF;
WriteReportBuffer = 0xFF;
}
//调用WriteFile函数发送数据
Result = WriteFile(hUsb,
WriteReportBuffer,
65,
&lpNumberOfBytesWritten,
NULL
);
//如果函数返回失败,则可能是真的失败,也可能是IO挂起
if (Result == FALSE)
{
//获取最后错误代码
LastError = GetLastError();
//看是否是真的IO挂
if ((LastError == ERROR_IO_PENDING) || (LastError == ERROR_SUCCESS))
{
return TRUE;
}
//否则,是函数调用时发生错误,显示错误代码
else
{
printf("Sending error:%d \n", LastError);
//如果最后错误为1,说明该设备不支持该函数。
if (LastError == 1)
{
printf("This device doesn't support WriteFile function \n");
}
}
}
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 )
}
int main(int argc, char **argv)
{
HRESULT hres;
// Step 1: --------------------------------------------------
// Initialize COM. ------------------------------------------
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
printf("Failed to initialize COM library. Error code");
return 1; // Program has failed.
}
// Step 2: --------------------------------------------------
// Set general COM security levels --------------------------
hres = CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
if (FAILED(hres))
{
printf("Failed to initialize security. Error code = 0x");
CoUninitialize();
return 1; // Program has failed.
}
// Step 3: ---------------------------------------------------
// Obtain the initial locator to WMI -------------------------
IWbemLocator *pLoc = NULL;
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&pLoc);
if (FAILED(hres))
{
printf("Failed to create IWbemLocator object.");
CoUninitialize();
return 1; // Program has failed.
}
// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method
IWbemServices *pSvc = NULL;
// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\OpenHardwareMonitor"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
0, // Context object
&pSvc // pointer to IWbemServices proxy
);
if (FAILED(hres))
{
printf("Could not connect. Error code = 0x");
pLoc->Release();
CoUninitialize();
return 1; // Program has failed.
}
printf("Connected to ROOT\\OpenHardwareMonitor WMI namespace\n");
// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------
hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres))
{
printf("Could not set proxy blanket. Error code = 0x");
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1; // Program has failed.
}
VARIANT vtProp;
IWbemClassObject *pclsObj = NULL;
IEnumWbemClassObject* pEnumerator = NULL;
ULONG uReturn = 0;
HRESULT hr;
while (!_kbhit())
{
//Get CPU Package temperature from WMI
{
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Sensor WHERE Name LIKE \"CPU Package\" AND SensorType = \"Temperature\""),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
printf("Query for CPU temperature fail.");
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1; // Program has failed.
}
// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
// Get the value of the Name property
hr = pclsObj->Get(L"value", 0, &vtProp, 0, 0);
CPUTemperature = vtProp.fltVal;
printf("CPU Package Temperature : %f\n", CPUTemperature);
if (pclsObj!=NULL) {
pclsObj->Release();
}
if (pEnumerator!=NULL) {
pEnumerator->Release();
}
}
//Get CPU Load from WMI
{
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Sensor WHERE Name LIKE \"CPU Total\" AND SensorType = \"Load\""),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
printf("Query for CPU Load fail.");
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1; // Program has failed.
}
// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
// Get the value of the Name property
hr = pclsObj->Get(L"value", 0, &vtProp, 0, 0);
CPULoad = vtProp.fltVal;
printf("CPU Load : %f\n", CPULoad);
if (pclsObj != NULL) {
pclsObj->Release();
}
if (pEnumerator != NULL) {
pEnumerator->Release();
}
}
//Get Memory Load from WMI
{
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Sensor WHERE Name LIKE \"Memory\" AND SensorType = \"Load\""),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
printf("Query for Memory Load fail.");
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1; // Program has failed.
}
// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
// Get the value of the Name property
hr = pclsObj->Get(L"value", 0, &vtProp, 0, 0);
MemoryLoad = vtProp.fltVal;
printf("Memory Load : %f\n", MemoryLoad);
if (pclsObj != NULL) {
pclsObj->Release();
}
if (pEnumerator != NULL) {
pEnumerator->Release();
}
}
//Get Fan Speed from WMI
{
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Sensor WHERE Name LIKE \"Fan #1\" AND SensorType = \"Fan\""),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
printf("Query for fan speed fail.");
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1; // Program has failed.
}
// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if (hr != S_OK) {
FanSpeed = 100000.0;
printf("Fan Speed N/A\n");
}
else
{
// Get the value of the Name property
hr = pclsObj->Get(L"value", 0, &vtProp, 0, 0);
FanSpeed = vtProp.fltVal;
printf("Fan Speed : %f\n", FanSpeed);
if (pclsObj != NULL) {
pclsObj->Release();
}
}
if (pEnumerator != NULL) {
pEnumerator->Release();
}
}
printf("%X ", ((int)CPUTemperature) & 0xFF);
printf("%X ", ((int)CPULoad * 100) & 0xFF);
printf("%X ", ((int)CPULoad * 100 >> 8) & 0xFF);
printf("%X ", ((int)MemoryLoad * 100) & 0xFF);
printf("%X ", ((int)MemoryLoad * 100 >> 8) & 0xFF);
printf("%X ", ((int)FanSpeed) & 0xFF);
printf("%X \n", ((int)FanSpeed * 100) & 0xFF);
SendData();
Sleep(3000);
}
VariantClear(&vtProp);
// Cleanup
// ========
pSvc->Release();
pLoc->Release();
CoUninitialize();
system("PAUSE");
return 0; // Program successfully completed.
}
我在64位Windows7和64位Windows 10 上实验过,同时我在 Windows XP 上实验过, Application 有问题,这应该是因为我使用的编译器是VS2015导致的兼容性问题,在应用程序的代码上并没有使用特别的API(甚至也没有MFC),相信如果使用 VS2008之类重新编译一次即可在XP上运行。
Windows7 下运行的结果,测试的机器是台式,所以有CPU 风扇
在 Windows10 下运行的结果:
工作时的照片:
特别需要注意的是HID 的传输速度上面的问题,低速时在800Byte/S,全速时可以达到64000Bytes/S,高速时传输速度为24.576MB/S,因此 HID 的设计只在低速设备上使用,典型设备是键盘鼠标。
参考:
1. https://github.com/NicoHood/HID/wiki/RawHID-API
2. http://www.arduino.cn/thread-46577-1-1.html
3. http://openhardwaremonitor.org
不错不错!支持! 1.感谢对社区比赛的支持,开发者积分和贡献值已发放,请点击以下链接领取纪念衫并参与抽奖~
http://www.arduino.cn/thread-48132-1-1.html
2.比赛结果会在11.15号前公布,请耐心等待。 本帖最后由 Zoologist 于 2017-10-22 11:06 编辑
https://imgcache.qq.com/tencentvideo_v1/playerv3/TPout.swf?max_age=86400&v=20161117&vid=i05644epouh&auto=0 CPU温度是通过获取windows系统里面的? suoma 发表于 2017-10-22 11:43
CPU温度是通过获取windows系统里面的?
是的啊通过软件获得的 胸弟 能把上位机的程序分享一下吗/??不懂VS怎么用 362442340 发表于 2019-8-15 05:05
胸弟 能把上位机的程序分享一下吗/??不懂VS怎么用
帖子里面有的啊 我只看到上位机的代码没有编译好的程序。。
页:
[1]
2