📜 ⬆️ ⬇️

Own implementation of the CPU and RAM load monitor from the one found in shukhlyad

One of the most beloved hubs in the Habré has always been DIY for me, I myself would like to do something with my own hands. But since I am more of a programmer, and less so an electronics engineer, the “prototypes” I've made are always unpresentable. This device is no exception. The code is also not combed, because This is more of a proof-of-concept than a commercial solution. Nevertheless, I think this post will be useful, and even there will be those who repeat this hack.
Inspired by the post about the switch Vu-meters showing CPU load and RAM usage, I decided to make my own version. Not having miniature voltmeters, but having a screen conquered by its price and simplicity of the 16x2 protocol, the screen based on the hd44780 controller decided to organize visualization on it. As the MK, the MSP430G2 lunchpad was chosen, of which I bought a zhmenka when they were at $ 4.30. Nothing prevents to implement it all on any arduino, you just need to change the names of the pins.
The scheme is very simple (taken from the Internet):
image

You can also connect the backlight and adjust the contrast, but I think it will not be difficult to deal with this, even for novice electronics engineers (judging by myself).
The software part consists of two parts, the first is a sketch for Energia / Arduino. Everything is very simple and clear.
Sketch
#include <LiquidCrystal.h> byte cpuByte; byte ramByte; unsigned long lastUpdateTime; unsigned long disconntime; int difer = 0; 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(); lcd.print("FPanel V1.2"); } 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);} for (int i=cpuByte; i < 16; i++) { lcd.write(32);} lcd.setCursor(0, 1); lcd.print("RAM "); lcd.setCursor(4, 1); for (int i=0; i < ramByte; i++) { lcd.write(255);} for (int i=ramByte; i < 16; i++) { lcd.write(32);} lastUpdateTime = millis(); } if (millis()-lastUpdateTime > 3000) { lcd.setCursor(0, 0); lcd.print(" CONNECTING "); lcd.setCursor(0, 1); difer = (millis()-disconntime-3000) / 250; if (difer > 16) {disconntime = millis()-3000;} for (int i=0; i < difer; i++) {lcd.write(255);} for (int i=difer; i < 16; i++) {lcd.write(32);} delay(10); } delay(50); } 


The logic is simple, we wait for two bytes and output their values ​​as a string of characters with the code 255. I erase the previous values ​​with spaces, because with lcd.clear () everything blinks. If more than 3 seconds data does not come - DISCONNECTED.

I program mainly under the 1Cx platforms, and of the compiled PLs, Delphi / Pascal is closest to me the most. On Delphi version XE2 and I decided to write the second part of the project. Since the planned operation of this device on the server operating system of the Windows family, the software is implemented as a service.
Delphi source
 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('\SOFTWARE\FPanel', 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. 


To install the service, the FP_service.exe file must be run with the / install option with administrative rights. After that, you need to create a ComNum string key in the registry with the value of the COM port number on which our lunchpad hangs along the path HKEY_LOCAL_MACHINE \ SOFTWARE \ FPanel, in my case the value is ComNum = "12". After this, it is enough to start the FPService service through the “Services” snap-in. Next time she will start herself. The service reconnects to the lunchpad automatically, does not interfere with other programs.
Video of work:

Archive Delphi project + ready.
Thanks for attention.
UPD: For 5 days of tests no problems were found.
UPD: Updated the sketch, improved the rendering algorithm, plus added animation of the connection wait.
UPD: Updated the link to the project archive.

')

Source: https://habr.com/ru/post/206392/


All Articles