Одним из самых любимых хабов на хабре всегда был для меня DIY, я и сам не прочь что-то сделать своими руками. Но так, как я в большей степени программист, а в меньшей — электронщик, сделанные мной «прототипы» всегда непрезентабельны. Данный девайс не исключение. Код тоже не причесан, т.к. это больше proof-of-concept чем коммерческое решение. Тем не менее, думаю данных пост будет полезен, и даже найдутся те, кто повторит эту поделку.
Вдохновившись постом о стрелочных Vu-метрах, показывающих загрузку CPU и использование RAM, решил сделать свой вариант. Не имея миниатюрных вольтметров, но имея покоривший своей ценой и простотой протокола 16х2 экран на базе контроллера hd44780 решил организовать визуализацию на нем. Как МК был выбран ланчпад MSP430G2, коих я купил жменьку, когда они были по $4.30. Ничего не мешает реализовать это все на любой arduino, нужно лишь поменять названия пинов.
Схема очень проста(взята из интернета):
Можно еще подключить подсветку и регулировку контраста, но думаю не составит труда разобраться с этим даже начинающим электронщикам(сужу по себе).
Программная часть состоит из двух частей, первая — скетч для Energia/Arduino. Все очень просто и понятно.
#include <LiquidCrystal.h>
byte cpuByte;
byte ramByte;
unsigned long lastUpdateTime;
LiquidCrystal lcd(P2_3, P2_4, P1_5, P2_0, P2_1, P2_2); // У меня подключение не такое как в схеме выше
void setup()
{
Serial.begin(9600);
lcd.begin(16, 2);
lcd.clear();
}
void loop()
{
if (Serial.available() == 2) {
cpuByte = Serial.read();
ramByte = Serial.read();
lcd.setCursor(0, 0);
lcd.print("CPU ");
lcd.setCursor(4, 0);
for (int i=0; i < cpuByte; i++) { lcd.write(255);}
lcd.setCursor(0, 1);
lcd.print("RAM ");
lcd.setCursor(4, 1);
for (int i=0; i < ramByte; i++) { lcd.write(255);}
lastUpdateTime = millis();
}
if (millis()-lastUpdateTime > 3000) {
lcd.setCursor(0, 0);
lcd.print(" DISCONNECTED ");
lcd.setCursor(0, 1);
lcd.print(" ");
}
delay(50);
}
Логика проста, ждем два байта и выводим их значения в виде строки из символов с кодом 255. Прошлые значения затираю пробелами, ибо с lcd.clear() все мигает. Если более 3х секунд данные не приходят — DISCONNECTED.
Программирую я в основном под платформы 1Cх, а из компилируемых ЯП близок мне Delphi/Pascal больше всего. На Delphi версии XE2 и решил я писать вторую часть проекта. Так как планируется работа данного девайса на серверных ОС семейства Windows, ПО реализовано как служба.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs, Registry;
type
TFPService = class(TService)
procedure ServiceExecute(Sender: TService);
procedure ServiceCreate(Sender: TObject);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
FPService: TFPService;
const
SystemBasicInformation = 0;
SystemPerformanceInformation = 2;
SystemTimeInformation = 3;
type
TPDWord = ^DWORD;
TSystem_Basic_Information = packed record
dwUnknown1: DWORD;
uKeMaximumIncrement: ULONG;
uPageSize: ULONG;
uMmNumberOfPhysicalPages: ULONG;
uMmLowestPhysicalPage: ULONG;
uMmHighestPhysicalPage: ULONG;
uAllocationGranularity: ULONG;
pLowestUserAddress: Pointer;
pMmHighestUserAddress: Pointer;
uKeActiveProcessors: ULONG;
bKeNumberProcessors: byte;
bUnknown2: byte;
wUnknown3: word;
end;
type
TSystem_Time_Information = packed record
liKeBootTime: LARGE_INTEGER;
liKeSystemTime: LARGE_INTEGER;
liExpTimeZoneBias: LARGE_INTEGER;
uCurrentTimeZoneId: ULONG;
dwReserved: DWORD;
end;
type
TSystem_Performance_Information = packed record
liIdleTime: LARGE_INTEGER; {LARGE_INTEGER}
dwSpare: array[0..750] of DWORD;
end;
var
NtQuerySystemInformation: function(infoClass: DWORD; buffer: Pointer; bufSize: DWORD; returnSize: TPDword): DWORD; stdcall = nil;
SysBaseInfo: TSystem_Basic_Information;
SysPerfInfo: TSystem_Performance_Information;
SysTimeInfo: TSystem_Time_Information;
status: Longint; {long}
liOldIdleTime, liOldSystemTime: LARGE_INTEGER;
dbSystemTime, dbIdleTime, dbIdleTimePercent: Double;
hCom: THandle;
DCB:TDCB;
Errors, Bytes : Cardinal;
TheStruct:TCOMSTAT;
Timeouts: TCommTimeOuts;
ComNum:string;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
FPService.Controller(CtrlCode);
end;
function TFPService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
Procedure getComNum;
var Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('SOFTWAREFPanel', false) then
begin
ComNum := Reg.ReadString('ComNum');
Reg.CloseKey;
end;
finally
Reg.Free;
end;
end;
Procedure InitCPUUsage;
begin
if @NtQuerySystemInformation = nil then NtQuerySystemInformation := GetProcAddress(GetModuleHandle('ntdll.dll'), 'NtQuerySystemInformation');
status := NtQuerySystemInformation(SystemBasicInformation, @SysBaseInfo, SizeOf(SysBaseInfo), nil);
if status <> 0 then exit;
end;
function CPUUsed: integer;
function Li2Double(x: LARGE_INTEGER): Double;
begin
Result := x.HighPart * 4.294967296E9 + x.LowPart
end;
begin
result := 0;
status := NtQuerySystemInformation(SystemTimeInformation, @SysTimeInfo, SizeOf(SysTimeInfo), nil);
if status <> 0 then Exit;
status := NtQuerySystemInformation(SystemPerformanceInformation, @SysPerfInfo, SizeOf(SysPerfInfo), nil);
if status <> 0 then Exit;
dbIdleTime := Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime);
dbSystemTime := Li2Double(SysTimeInfo.liKeSystemTime) - Li2Double(liOldSystemTime);
dbIdleTimePercent := dbIdleTime / dbSystemTime * 100;
liOldIdleTime := SysPerfInfo.liIdleTime;
liOldSystemTime := SysTimeInfo.liKeSystemTime;
if (dbIdleTimePercent / SysBaseInfo.bKeNumberProcessors) < 0 then result := 0 else
result := round(abs(100-(dbIdleTimePercent / SysBaseInfo.bKeNumberProcessors)));
end;
function RAMUsed: byte;
var RamStats: TMemoryStatus;
begin
GlobalMemoryStatus(RamStats);
result := ramStats.dwMemoryLoad;
end;
procedure con2com;
var ComFN:string;
begin
CloseHandle(hCom);
ComFN := '\.COM' + comNum;
hCom := CreateFile(PWidechar(ComFN), GENERIC_WRITE or GENERIC_READ, 0, nil, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, 0);
if hCom = INVALID_HANDLE_VALUE then Exit;
SetupComm(hCom,100,100);
GetCommState(hCom,DCB);
with DCB do begin
BaudRate:=9600;
ByteSize:=8;
Parity:=NoParity;
StopBits:=OneStopBit;
end;
SetCommState(hCom,DCB);
GetCommTimeouts(hCom,Timeouts);
with TimeOuts do begin
ReadIntervalTimeout := 1;
ReadTotalTimeoutMultiplier := 0;
ReadTotalTimeoutConstant := 1;
WriteTotalTimeoutMultiplier := 2;
WriteTotalTimeoutConstant := 2;
end;
SetCommTimeouts(hCom,Timeouts);
end;
procedure TFPService.ServiceExecute(Sender: TService);
var NumberWritten:LongWord;
twoBytes:array[0..1] of byte;
begin
while not Terminated do
begin
twoBytes[0] := round(CPUUsed / 8.33);
twoBytes[1] := round(RAMUsed / 8.33);
if WriteFile(hCom, twoBytes, 2, NumberWritten, nil) = False then con2com;
Sleep(250);
ServiceThread.ProcessRequests(False);
end;
end;
procedure TFPService.ServiceCreate(Sender: TObject);
begin
initCPUUsage;
getComNum;
con2com;
end;
end.
Для установки службы файл FP_service.ехе нужно запустить с параметром /install с правами администратора. После этого нужно создать строковый ключ ComNum в реестре, со значением номера COM-порта, на котором «висит» наш ланчпад по пути HKEY_LOCAL_MACHINESOFTWAREFPanel, в моем случае значение ComNum = «12». После этого достаточно запустить службу FPService через оснастку «Службы». В следующий раз она будет стартовать сама.
Видео работы:
Архив Delphi проекта+готовый.ехе
Спасибо за внимание.
Автор: phatok