一:確認電腦有無硬體監測功能
1. 檢查主機板有無硬體監測晶片, 例如 winbond 的商標有無出現在主機板上.
2. 進入 BIOS 查看有無 CPU Temperature, Fan 之類的數據. 如果 BIOS 沒有出現這類的數據, 則這塊主機板8成沒有硬體監測功能 .
3. BIOS 有 CPU Temperature, Fan 之類的數據, 但你無法從主機板看出硬體監測晶片的型號, 可到 http://www.cpuid.com/hwmonitor.php 下載 HWMonitor, 執行此程式或許可得知硬體監測晶片的型號. 因為 HWMonitor 只能辨識一些常用的晶片, 例如 ITE, IT87, Winbond 晶片.
4. 查到晶片型號後, 到其製造商網站下載 datasheet. Winbond 晶片可在 http://www.winbond-usa.com/下載.

注意: 本站程式碼只適用 Winbond 晶片 W83627EHF, W83627EHG. 其他型號請根據其 datasheet 自行修改程式碼.
網站的程式碼使用的是全形空白, 要編譯從網站複製的程式碼, 請刪除全形空白.

二: Windows 硬體監測程式的原理
Windows 禁止應用程式存取硬體資源, 所以硬體監測程式要拆成2部分-驅動程式和應用程式. 驅動程式負責存取硬體資源, 應用程式以 DeviceIoControl 函式和驅動程式溝通. 以下列出的程式碼是驅動程式和應用程式溝通的介面.
public.h
DEFINE_GUID (GUID_DEVINTERFACE_Thermal,0x781EF630, 0x72B2, 0x11d2, 0xB8, 0x52, 0x00, 0xC0, 0x4F, 0xAD, 0x51, 0x71);
#define IOCTL_Thermal_Read_Monitor CTL_CODE(FILE_DEVICE_UNKNOWN,7,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_Thermal_Write_Monitor CTL_CODE(FILE_DEVICE_UNKNOWN,8,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_Thermal_LPC_Port CTL_CODE(FILE_DEVICE_UNKNOWN,9,METHOD_BUFFERED,FILE_ANY_ACCESS)
typedef struct _LPC_port_number{
 UCHAR LPC_IO_port;
 USHORT ChipID;
 USHORT MonitorIndexPort;
}LPC_Port_Number,*PLPC_Port_Number;

三: AddDevice
在 AddDevice 函式加入這一段
...
status = IoRegisterDeviceInterface(PhysicalDeviceObject,(LPGUID) &GUID_DEVINTERFACE_Thermal,NULL,&fdoData->InterfaceName);
if (!NT_SUCCESS (status)) {
 IoDetachDevice(fdoData->NextLowerDriver);
 IoDeleteDevice (deviceObject);
 return status;
}
...

IoRegisterDeviceInterface 將驅動程式註冊, 使應用程式能和驅動程式溝通. 註冊好的名字會放在 &fdoData->InterfaceName . fdoData 是 DEVICE_OBJECT 的 DeviceExtension .

四: IRP_MN_START_DEVICE
在 DispatchPnP 函式加入這一段
...
NTSTATUS status;
UCHAR num,ber;
...
case IRP_MN_START_DEVICE:
...
 FdoData->LPC_IO_port = get_LPC_IO_port(&FdoData->ChipID);
 if(FdoData->LPC_IO_port){
  enter_LPC_IO(FdoData);
  writeConfigureRegister(7,0xB,FdoData);//指定 logic device B: Hardware Monitor
  num = readConfigureRegister(0x60,FdoData);//取得 Hardware Monitor 的 port 位址, MSB
  ber = readConfigureRegister(0x61,FdoData);//取得 Hardware Monitor 的 port 位址, LSB
  _outp(FdoData->LPC_IO_port,0xAA);//要離開 configure 模式, 就寫入 0xAA
  FdoData->MonitorIndexPort = num << 8 | ber;
  if(FdoData->MonitorIndexPort) FdoData->MonitorIndexPort += 5;//port 位址 + 5 得到 index 位址
 }
 status = IoSetDeviceInterfaceState(&FdoData->InterfaceName, TRUE);
 if (!NT_SUCCESS (status)) {
  return status;
 }
...

get_LPC_IO_port 可以得到 LPC IO port 位址, 位址只有2個可能值 0x2E 或 0x4E. 得到 LPC IO port 位址後, 才能進入 Configuration Registers , 以取得 Logoc Device B: Hardware Monitor 的 port 位址.
請視情況修改 Chip_ID_21h , datasheet 是寫 6Xh, 但我實際的讀值卻是 54h, 比較簡單的寫法是不比較 Chip_ID_21h.
/* success , return 0x2E or 0x4E. Else return 0 */
UCHAR get_LPC_IO_port(PUSHORT chipID){
 UCHAR cr20,cr21,i;
 for(i=0;i<1;++i){
/* 先測試 port 是不是 0x2E */
  _outp(0x2E,0x87);
  _outp(0x2E,0x87);
/* WINBOND W83627EHF 要先在 port 寫入 0x87 2次, 才能進入 configure 模式 */
  _outp(0x2E,0x20);//設定要取得 Configuration Register 0x20 的值
  cr20 = (UCHAR)_inp(0x2F);//取得 Configuration Register 0x20 的值
  if(cr20 != Chip_ID_20h) break;
  _outp(0x2E,0x21);//設定要取得 Configuration Register 0x21 的值
  cr21 = (UCHAR)_inp(0x2F);//取得 Configuration Register 0x21 的值
  if((cr21 & 0xF0) == Chip_ID_21h){
/* 要離開 configure 模式, 就寫入 0xAA */
   _outp(0x2E,0xAA);
*chipID = cr20 << 8 | cr21;
   return 0x2E;
  }
 }/* 測試 port 是不是 0x4E */
 _outp(0x2E,0xAA);
 _outp(0x4E,0x87);
 _outp(0x4E,0x87);
 _outp(0x4E,0x20);
 cr20 = (UCHAR)_inp(0x4F);
 if(cr20 != Chip_ID_20h){
  _outp(0x4E,0xAA);
  return 0;
 }
 _outp(0x4E,0x21);
 cr21 = (UCHAR)_inp(0x4F);
 if((cr21 & 0xF0) == Chip_ID_21h){
  _outp(0x4E,0xAA);
  *chipID = cr20 << 8 | cr21;
  return 0x4E;
 }
 _outp(0x4E,0xAA);
 return 0;//失敗傳回 0
}

五: DispatchDeviceControl
在 DispatchDeviceControl 函式加入這一段
NTSTATUS status;
UCHAR length3;
PUCHAR pbyt;
ULONG outBufLength,inBufLength;
PVOID buffer;
PIO_STACK_LOCATION irpStack;
PFDO_DATA fdoData;
PLPC_Port_Number pPort;
status = STATUS_SUCCESS;
fdoData = (PFDO_DATA) DeviceObject->DeviceExtension;
irpStack = IoGetCurrentIrpStackLocation (Irp);
inBufLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;
outBufLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
buffer = Irp->AssociatedIrp.SystemBuffer;
switch (irpStack->Parameters.DeviceIoControl.IoControlCode){
 case IOCTL_Thermal_Read_Monitor:
  if(!fdoData->MonitorIndexPort){
   status = STATUS_NO_SUCH_DEVICE;
   break;
  }
  if(inBufLength < 1 || outBufLength < 1){
   status = STATUS_BUFFER_TOO_SMALL;
   break;
  }
  pbyt = buffer;
  length3 = *pbyt;
  _outp(fdoData->MonitorIndexPort,length3);//在 index port 寫入想要存取暫存器的號碼
  length3 = (UCHAR)_inp(fdoData->MonitorIndexPort + 1);//在 index port 的下1個 port 得到暫存器的值
  *pbyt = length3;
  Irp->IoStatus.Information = 1;// 傳給應用程式 1 byte 的資料
  break;
 case IOCTL_Thermal_Write_Monitor:
  if(!fdoData->MonitorIndexPort){
   status = STATUS_NO_SUCH_DEVICE;
   break;
  }
  if(inBufLength < 2){
   status = STATUS_BUFFER_TOO_SMALL;
   break;
  }
  pbyt = buffer;
  length3 = *pbyt;
  _outp(fdoData->MonitorIndexPort,length3);
  ++pbyt;
  length3 = *pbyt;
  _outp(fdoData->MonitorIndexPort + 1,length3);//對此暫存器寫入 length3
  break;
...