Introduction: PC Hardware Monitor

Hi everyone. I started this project for two reasons: I built a watercooling loop in my pc recently and needed something to visually fill some space in the case AND i wanted to be able to have temperatures and other stats checked with a fast peek without OSD shenanigans filling the corner of the screen. Of course there are ready made solutions for that, but most of them just wouldn't fit my feng shui. So, instead of putting an HDMI 7" display in my case with a cable sticking out the case and the windows task bar always on, I decided to build my own toy.

As I'm not an engineer nor a programmer, but just a guy with a soldering iron and some self-taught knowledge, this won't be just a step-by-step instructable, I'll also try to focus on the problem solving and research aspects that led me to this build.




  • Arduino Nano (or UNO if you want)
  • TFT Display. In my case it is a ILI9486 / ILI9488L compatible 3.5" display.
  • Temperature Senso. In mu case an analog TMP36 temp sensor.
  • Cables, wires, dupont connectors (more on that later)
  • (optional) Breadboard for testing
  • (optional but recommended) a small perfboard

Step 1: Feasibility Study (sorta Of)

As I said, I didn't wanted and HDMI display stuck in my PC case so, cloaked in my own ingenuity, I started to search for similar ideas on the internet. And this is tip number one: Google is your friend (well, any decent search engine...). We live in a world where nothing is really original anymore, but instead of looking at this phrase with a negative meaning, we could use this for our advantage: whatever you want to create, probably someone somewhere had already done something similar, so if you don't know how to implement an idea, good chances are you'll find useful informations out there. When searching the internet, it is often useful to mind two rules:

  1. don't bother go after page 3 or 4 of any search, it's almost always a waste of time. Instead
  2. change the search terms, just rephrase the question from another point of view (ie:"arduino temperature sensor" -> "read temperature with arduino").

Actually it's full of good projects out there, and I admit I spent the first days studying most of these projects. But none of them was ready to go for me, as I wanted something that would fit with my needs.

As I had to make something custom, I decided to focus on the right hardware to use and leave the software side for later, because the software can always be create and adapted to needs, on the other hand hardware side I'm bound to availability and features.

I wanted something based on Arduino, because I already had it, it is well documented and it's community is flourishing. No problem here, as I said before plentiful of informations.

I wanted a display big enough to be seen clearly from a couple meters away and that would fit the look'n'feel of my build, this excluded any nokia and LCD character display. OLED too are out of question, as they are small. So I opted for a TFT colour display. No need for touch screen, as it will be inside the PC. I found a 3.5" one, already designed for Arduino, ~15€ on Amazon. Good enough.

Now, after the hardware has been pinpointed, I focused on the software.

Almost all the projects, Arduino side, are quite similar. I just need to adapt the code for the display and for the communication protocol to gather data from the server app. Computer side, most projects were based on C, C++, C#, python and most projects offered just a CLI interface or a Windows service-like server. I wanted a GUI, instead. I never used any C-like language in Windows, leave alone the GUI building. But I learned some Visual Basic 15 years ago, so I gave it a try and downloaded the free Visual Studio version from Microsoft.

After studying a lot of similar projects, I settled on using OpenHardwareMonitor to get all the hardware info and RivaTuner for FPS, because these are free and enough documented.

Step 2: Hardware Testing

Before turning on the soldering iron and fixing forever in time and space any electronic component, is good practice to build a test prototype (tip number two). Luckily enough, it is not 1995 anymore. Nowadays is quite easy to arrange fairly complex prototypes even on small breadboards. In my case, the TFT display had a drop in pinout for Arduino Uno, so I dropped it on my Arduino uno and started to play with the example libraries and read the reference manuals to understand its operating principles and limitations.

At this point I figured out how to draw lines and bitmaps and write text, so i started fiddling with software coding, leaving all the secondary stuff for later, but I'll include here the temperature sensor.

At some point down the line, I had an empty spot on the display but none of the data from the PC sensors was really useful, so I decided to put a temperature sensor inside the case for ambient temperature. The display eats up almost all the Arduino pins, luckily analog pin A5 is unused, so I tied up a TMP36. I even tested a DHT22 but it's way overkill for this application.

There is plenty of examples for the TMP36, I just copied one of these in a function. The TMP35 has 3 pins, Vin goes to 5V, GND goes to ground and Out goes to pin A5. I placed a 0.1uF ceramic capacitor between Vin and GND. They say it's needed. Probably it's useless in this case, but... I even set the Arduino analog reference voltage to the 3.3v pin for better temperature reading. Still useless in this case, but...

Step 3: Arduino Code

Please download and open the included Arduino code to follow the explanation in this step. I tried to leave enough comments in the code to be clear without flooding it.

You will definitively need the MCUFRIEND_kbv and the Adafruit GFX libraries. Both easily installable from the Arduino IDE.

The program can be subdivided in sections like this:

  1. define and declare all the global variables and other needed stuff
  2. initialize the display, set the external reference and draw the UI (all this in contained in the setup() function, as it must run only once)
  3. read data from serial connection and allocate it in the array (loop() function)
  4. read external temp sensor data (readExtTemp() function)
  5. print data on the display (printData() function)
  6. back to the loop

SECTION 1: Declarations and definitions

In the code's initial section, I used lots of pointers and arrays, so I've been able to squeeze lots of repetitive lines of code in shorter to write FOR cycles. Yes, I'm lazy. As you can see I declared a pointer array and populated it with all the pictures from the pics.h file. This made possible to do the FOR cycle trick to draw all the icons.

SECTION 2: setup(), mostly UI drawing

I settled with the default font as it doesn't haves transparent background, so it allows to write a new line of text over an old one without the need for deleting it. Using another font would have meant to draw a black square over the old text before writing a new line, leading to an unpleasant flickering effect.

After some testing, I got to a good compromise between readability and shown informations. I divided the display in two columns and 5 rows. The left column goes for CPU and motherboard data, including from top to bottom CPU name, temperature, load, RAM usage and motherboard temperature. The right one dedicated to GPU and includes GPU name, temperature, load, Frames Per Second counter and external temperature sensor.

As you can see in the code, I decided to avoid using pictures on the SD card, as it is really slow to load. I decided to include all the icons in the PROGMEM memory and drawing the lines with the dedicated drawLine() command. this is also useful for small UI corrections.

In the feeble attempt to give the UI a semblance of depth, I drew two of everything (lines, rectangles, pictures) with different colours and with a small offset. Sadly it is not the result I hoped for, but it will do the trick.

The last lines of this function are for printing placeholders on the TFT, until the Arduino will receive the data.

SECTION 3: main loop(), data fetching and formatting

Here the magic happens: data is receieved thru serial, assigned to the correct variable and then printed. To achieve all this in the least number of lines, I used a switch case command and a for cycle.

The communication protocol I came with is divided in two parts: an initial execute once handshake and the actual data part.

The handshake is needed for implement the autoconnection feature when the PC program starts. It goes like this:

  • PC sends the handshake string (in this case is just "*****;")
  • Arduino sends back a response

Easy peasy.

The data part looks like this: "i:xxx,yyy,zzz,aaa,;" the meaning is:

"i" is the index, i called it componentSelector in the code. "i" values are:

  • i=0 - NAMES. The following values are the names shown in the firs row on the display. This will be send and printed on the display only once, as of today is quite difficult to hotswap CPU and GPU...
  • i=1 - 1st COLUMN DATA - the following values are shown in the left half of the display from top to bottom. In my case: CPU temp, CPU load, RAM usage, Motherboard temp.
  • i=2 - 2nd COLUMN DATA - as above, but for the right half of the display
  • i=3 - PRINT COMMAND. In this case the raw serial string will be just "3:;" as other data is not needed.

"xxx, yyy, zzz, aaa" are the actual values. those are read as strings by the arduino and the whole formatting is made by the PC program. For i=0 these values are 14 characters each for the hardware names. For i=1 or 2 these will be just three character each, enough for showing temperatures and frames per seconds. Of course the ":", "," and";"characters are forbidden in these fields.

The ":" is the separator between componentSelector and values, the "," is the values separator and the ";" is the end of line

When receiving the data, the Arduino will save it as a string until the";" symbol is recived, then it will look for the ":" symbol and will use it to get the componentSelecor value. This will be used for the switch case function to select the correct procedure to follow. It is also used to select the correct index in the allData array.

After this the Arduino will look for the "," symbol and will proceed to put the values in the allData array.

If the componentSelector is 0, the printName flag will be set to true. If componentSelector is 3, readExtTemp() and printData() functions are called.

Section 4: readExtTemp() function

Not much to say here, it reads 32 times from pin A5 and outputs the temperature value as a string. I'm with the Rebels, so I use Celsius. Anything more than 100°C is incorrect so it will be shown as "---" on the display. For anything less than 100°C will be formatted to have enough spaces to cover 3 characters space on the display. It is possible to remove and reinsert the sensor and no weird value will be shown.

Section 5: printData() function

As always I used for cycles to sequentially print stuff on the display. If the flag printNames is true, it will print the names, set the flag to false, and continue.

Section 6: back to the loop

Self explaining enough, I'd say...

pics.h file

Here I stored all the icons for the UI. It is possible to use the SD card reader included in the display, but I had enough memory left in the Arduino for my black and white icons.

I designed them with Junior Icon Editor as it's free and quite good for pixel painting small icons. I had to convert the icon files (saved as PNG) with the SKAARHOJ online tool.

Step 4: Visual Basic Code

Here is the VB code

NOTICE: this is the first time I share a Visual Studio project. I just copied the project folders and zipped them. If this doesn't works, please let me know a better way to share this kind of projects. Thank you.

As I said earlier, I am unable to create a GUI in C# or other languages, but I had some experiences in Visual Basic a long time ago. I downloaded the Visual Studio Community edition (it's free of course) with the Visual Basic environment. Well, I had to figure out a lot of stuff, as the last time I used VB it was version 2005 or such... But the internet is full of good hints, as usual.

After figuring out some interface stuff, the newer version is actually easier and more flexible than the old one.

For this program I wanted something with a windows form but fully manageable from a system tray icon. I actually used the form almost only for debugging purposes, as I like to put textboxes and lists to read the output values of the functions and some command buttons to test them.

The "final" program is just a tray icon with a popup menu that shows the various controls and a main form with two listboxes that show the data sent to the Arduino.

I implemented an autoconnect function and a "start at boot" function. More on that later.

The main program is just an adaptation of various examples and snippets of code using the OpenHardwareMonitor library and the RivaTuner Shared Memory library.

The program goes like this:

  • get the data from OpenHardwareMonitor and RTSSSm libraries
  • prepare and format all the data for the communication protocol
  • send the data to the Arduino
  • rinse and repeat

of course the hardware names are read at start and sent only once.

The FPS counter activates only when a compatible app is used (eg a game, a 3D modelling program, and so on), else the "---" placeholder will be sent to the display.

i won't go deep explaining how to get the values from the libraries, as it's well documented on the internet and somewhat comprehensible from the code. Just want to tell about the problems of getting the motherboard temperature to show thru the OpenHardwareMonitor (from now on OHMonitor, because life is too short) library. I have an Asus Maximus VIII Gene MoBo, that is equipped with a fu**ton temperature sensors on the motherboard, but OHMonitor names them as Temperature sensor #1, #2 ... #n AND nowhere is specified the temperature location. So i had to install the awful Asus AI suite software, where the sensors have at least NAMES and compare the various temperatures between the two programs. It looks like my motherboard generic temp sensor is #2 for OHMonitor, so as you can see in the Timer1_tick sub under the MoBo stuff I had to look for a sensor name containing the string "#2" to get the correct reading.

TL;DR: you'll have to look after the correct motherboard temperature sensors for yourself. The rest is probably good to go.

However this is just Version 1, I'm planning to install this gadget my other PC, so I'll probably implement a way to select the sensors and maybe even redesign the interface on the Arduino on the go.

The Autoconnect Function

This function is actually simple: if the PC is not connected with an Arduino, every x milliseconds (based on the Timer1) this function is called. It tries to connect with every COM port on the PC, if successful it sends the handshake string "*****;". If the answer is "R", then the correct device is connected and the normal procedure is followed. Else, it tries the next COM port.

As you can see, there are a lot of exceptions in this function. This is because I wanted it fully plug and play, with no error output. Handling the exceptions, I've been able to make it ignore the complete absence of the external device and I can even hotplug and hotunplug the device whenever I want, without generating a breaking error for the program.

The Start At Boot function

I wanted the program to start at boot. Pretty easy, you say. Put a link in the appropriate folder, you say. But no. Due to the OHMonitor and RTSS libraries, we need administrator execution level to gather informations. This means the utterly annoying UAC screen every time this app is launched. No way. So I adapted the script made by Matthew Wai (link here) to achieve a silent start at boot. I just copied the script in the Resources1 file, splitted in several parts, then implemented a subroutine that creates (or removes) a windows task file, customized with the current program executable location and such things.

The System Tray Icon

Thanks to the NotifyIcon and ContextMenu objects, I've been able to implement an easy and fats way to control the app. Just right click on the tray icon and the menu appears. There are these oprions:

  • Start at boot: you can check and uncheck it to enable or disable the start at boot function
  • Autoconnect: same as above, but handles the autoconnect function
  • Connect/Disconnect: it handles the connection. Does not works with the Autoconnection enabled
  • Refresh time: gives a drop down submenu, you can choose the refresh time from 1 to ten seconds
  • Maximize: opens the main window. Same as double clicking on the icon
  • Exit: self explanatory
Compiling the software

To compile the software you will probably need to download and add a reference to the libraries not incuded in the code.

You can find the OpenHardwareMonitor library here. You must download the software, open the zip file and copy the OpenHardwareMonitorLib.DLL file in the project folder.

Here is the link for the RTSSharedMemoryNET library, you must download and compile for your architecture, then copy the RTSS[TL;DR]moryNET.DLL in yout project folder.

Now you need to add a reference in your code, instructions here

Just be sure to compile both the RTSS[TL;DR]moryNET and PCHwMon server projects for the same architecture.

I included a ready made setup program, so you can install the whole thing without fiddling with Visual Basic. It is compiled for x86, will work on both x86 and x64 architectures. It requites the .NET framework 4.7.2 to run.

In any case, you will need to install RivaTuner. You can find it here as a standalone app or you can install Msi Afterburner, which should include the RTServer.

Step 5: Final Hardware Implementation

My TFT display is a shield for Arduino Uno. I'm using and Arduino Nano v3, that is just a shrunk UNO. Following the pinout ad adapring it is quite easy. The schematic I included is pretty staightforward and build friendly. Just follow it.

Just remember to set the appropriate analog reference in the arduino code, based on the AREF pin configuration. Speaking of wich, there are three options:

  1. use the DEFAULT analogReference value, the 5V form the power. It is the least precise, but with the temperatures range we use it's OK. You will need to leave the AREF pin disconnected.
  2. use the INTERNAL analogReference value, it uses an ~1.1v source internal to the Arduino. I don't really know if it is usable with the output voltages from the sensor.You will need to leave the AREF pin disconnected this time too.
  3. connect the 3v3 pin to AREF and set th eanalogReference value to "3.3". I used this option in my code and in my circuit.BUT you should read the 3v3 pin with a good multimeter onche the circuit is built and running, possibly when already connected to the PC, and set analogReference to the exact value (or a good approximation) of this voltage in the Arduino code.

You can see from the pictures, I used a small perfboard to connect the lower pins of the TFT and to solder the Arduino, but I used two pieces of a flat cable with dupont connectors to connect the upper TFT pins. This is because the Arduino UNO pin spacing is not compatible with standard spacing perfboards on that side.

I also soldered the Arduino connectors upside down, so I've been able to follow my schematic without crossing and twisting all the data wires.

However, this is the simplest step. Just lay down the components as lined up as possible following the schematic and you're good to go.

I used an internal motherboard USB2.0 header to connect the thing. you colud either connect it externally if you want or modify and old (working) mini USB cable. Just cut a lenght of cable, saving the mini USB connector, and solder the 4 USB wires according to the figure here.

This will occupy only one port on the USB header. Usually, all the commercial devices occupy both parts of the connector, even if only one port is used. I had to modify my bluetooth connector to be able to fit both devices on a single header.


Step 6: Finale

Well, it's been an entertaining experience to write my first instructable.

I hope it will be useful, clear enough and not too boring to read.

I wrote it after the building of the monitor, so I don't really have any good shots of the work I've done.

In the future I think I'll put an hardware monitor in my other PC, probably I will improve something ad update the instructable. Maybe I'll 3d print an enclosure, and I'll include it here. For now I'm too busy with work after the pandemic lockdown.

I'd like to thank all the people who share their knowledge over the internet, without them most of the stuff I like to do couldn't be possible.

First Time Author Contest

Participated in the
First Time Author Contest