
I made a small device to monitor my PC's CPU, GPU, and memory usages and temperatures. ESP8266 is fed serial data through USB. The data comes in as a string of numbers and is parsed and displayed on the connected LCD display. The device is housed inside the case of an old power brick and everything is held in place with nuts and bolts.
The TFT display used is Fermion: 2.0" 320x240 IPS TFT LCD Display Module, or DFR0644 by DFRobot. For the graphics I used DFRobot's gfx library.
The following pinout connections is a reference I wrote down while building the device:
[MCU] ➝ [Display]
3V3 ➝ V (VCC)
GND ➝ G (GND)
D1 ➝ SC (SDCS, SD Card Chip Select)
D2 ➝ DC (DC, Data/Command)
D3 ➝ RT (RES, Reset)
D5 ➝ CK (SCLK, Serial Clock)
D6 ➝ SO (MISO, Master-In-Slave-Out)
D7 ➝ SI (MOSI, Master-Out-Slave-In)
D8 ➝ CS (CS, Chip Select)
The microcontroller needs a program to read and display the received data. I also need a way to send data to the MCU via USB.
For sending the serial data I modified Arduino Hardware Monitor by TechTalkies, which itself uses OpenHardwareMonitor's libraries to read system resource data and send it as serial data to microcontroller.
I modified the TechTalkies program to send 6 values in a single string, separated by a comma.
It sends CPU temp, CPU load, GPU temp, GPU load, memory load, and memory used in GB.
I also modified the GUI of the TechTalkies HW monitor as it was very plain.
The MCU code includes a lot of testing stuff, as I was trying out and learning things.
I didn't bother to clean it up as it works as-is.
#include <DFRobot_GDL.h>
#define TFT_DC D2
#define TFT_CS D8
#define TFT_RST D3
#define PIN_SPI_CS D1
/**
* @brief Constructor Constructor of hardware SPI communication
* @param dc Command/data line pin for SPI communication
* @param cs Chip select pin for SPI communication
* @param rst reset pin of the screen
*/
DFRobot_ST7789_240x320_HW_SPI screen(/*dc=*/TFT_DC,/*cs=*/TFT_CS,/*rst=*/TFT_RST);
// 240x320 px
String dataStr = "";
bool demoMode = false;
int demoIndex = 0;
String demoData[5] = {
"32,18,45,10,48,7.7",
"41,17,45,7,49,7.9",
"31,6,47,1,48,7.7",
"30,10,46,20,51,8.2",
"32,16,47,5,50,8.0"
};
int barH = 20;
int barW = 320;
int celsiusSpace = 3; //space between temp num and celsius degree circle
int16_t paddingLeft = 10;
int16_t paddingRight = 10;
int borderW = 4;
uint16_t intelBlue = 0x033f;
uint16_t coolGreen = 0x3646;
uint16_t memOrange = 0xfb80; // 0xfb20 0xfb60
uint16_t crimsonRed = 0xf80b;
uint16_t cpuColorLight = intelBlue;
uint16_t gpuColorLight = crimsonRed;
uint16_t memColorLight = memOrange;
uint16_t cpuBgColor = 0x0022;
uint16_t gpuBgColor = 0x1801; // 0x00c0
uint16_t memBgColor = 0x1040; //0x2880
// CPU data
uint16_t cpuColorDark = cpuColorLight;
int cpuTempX = 90;
int cpuTempY = 0 + 12;
int cpuTempW = 75;
int cpuTempH = 30;
String cpuTempPrev = "0";
int cpuLoadX = 215;
int cpuLoadY = cpuTempY;
int cpuLoadW = 75;
int cpuLoadH = 30;
String cpuLoadPrev = "0";
int cpuBarX = 0;
int cpuBarY = cpuTempY+35;
int cpuBarW = 0;
// GPU data
uint16_t gpuColorDark = gpuColorLight;
int gpuTempX = cpuTempX;
int gpuTempY = 80 + 12;
int gpuTempW = cpuTempW;
int gpuTempH = cpuTempH;
String gpuTempPrev = "0";
int gpuLoadX = cpuLoadX;
int gpuLoadY = gpuTempY;
int gpuLoadW = cpuLoadW;
int gpuLoadH = cpuLoadH;
String gpuLoadPrev = "0";
int gpuBarX = cpuBarX;
int gpuBarY = gpuTempY+35;
int gpuBarW = 0;
// Memory data
uint16_t memColorDark = memColorLight;
int memUsedX = 90;
int memUsedY = 160 + 12;
int memUsedW = 93;
int memUsedH = cpuTempH;
String memUsedPrev = "00.0";
int memLoadX = cpuLoadX;
int memLoadY = memUsedY;
int memLoadW = cpuLoadW;
int memLoadH = cpuLoadH;
String memLoadPrev = "0";
int memBarX = cpuBarX;
int memBarY = memUsedY+35;
int memBarW = 0;
void setup() {
Serial.begin(115200);
screen.begin();
screen.setRotation(1); // Rotate 270 degrees clockwise
screen.fillScreen(COLOR_RGB565_BLACK);
screen.fillScreen(COLOR_RGB565_RED);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setTextSize(8);
screen.setTextColor(COLOR_RGB565_RED);
screen.setCursor(0, 0);
screen.println("Rogue");
delay(500);
screen.setTextColor(COLOR_RGB565_RED);
screen.setCursor(0, 75);
screen.println("Sensor");
delay(500);
screen.setTextColor(coolGreen);
screen.setCursor(0, 150);
screen.println("Ultra");
delay(500);
screen.setTextSize(3);
screen.setCursor(200, 220);
screen.setTextColor(COLOR_RGB565_RED);
screen.println("v1.0.1");
delay(1500);
screen.setTextColor(COLOR_RGB565_RED);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.setTextSize(4);
// Draw static stuff which doesn't change on updates
// CPU static draws
screen.fillRect(0, 0, 320, 80, cpuBgColor); // Draw CPU bg
//screen.drawRect(0, 0, 320-borderW, 80-borderW, cpuColorLight); // CPU border
drawBorderRect(0, 0, 320, 80, cpuColorLight, borderW);
screen.setTextColor(cpuColorLight);
screen.setCursor(paddingLeft, cpuTempY);
screen.print("CPU");
// Create fake Celsius Degree symbol with a small "o"
screen.setTextSize(2);
screen.setCursor(cpuTempX + cpuTempW + celsiusSpace, cpuTempY-4);
screen.print("o");
screen.setTextSize(4);
screen.setCursor(cpuTempX + cpuTempW + celsiusSpace + 15, cpuTempY);
screen.print("C");
screen.setCursor(cpuLoadX+cpuLoadW+0, cpuLoadY);
screen.print("%");
screen.drawRect(cpuBarX+paddingLeft, cpuBarY, barW-paddingRight-paddingLeft, barH, cpuColorLight);
//screen.fillRect(cpuBarX+1, cpuBarY+1, barW-2, barH-2, COLOR_RGB565_NAVY);
// GPU static draws
// Draw CPU bg
screen.fillRect(0, 80, 320, 80, gpuBgColor);
//screen.drawRect(borderW, 80+borderW, 320-borderW, 80-borderW, gpuColorLight); // GPU border
//drawBorderRect(x, y, w, h, color, borderWidth);
drawBorderRect(0, 80, 320, 80, gpuColorLight, borderW);
screen.setTextColor(gpuColorLight);
screen.setCursor(paddingLeft, gpuTempY);
screen.print("GPU");
screen.setTextSize(2);
screen.setCursor(gpuTempX + gpuTempW + celsiusSpace, gpuTempY-4);
screen.print("o");
screen.setTextSize(4);
screen.setCursor(gpuTempX + gpuTempW + celsiusSpace + 15, gpuTempY);
screen.print("C");
screen.setCursor(gpuLoadX+gpuLoadW+0, gpuLoadY);
screen.print("%");
screen.drawRect(gpuBarX+paddingLeft, gpuBarY, barW-paddingRight-paddingLeft, barH, gpuColorLight);
//screen.fillRect(gpuBarX+1, gpuBarY+1, barW-2, barH-2, COLOR_RGB565_OLIVE);
// Mem static draws
// Draw Mem bg
screen.fillRect(0, 160, 320, 80, memBgColor);
//screen.drawRect(borderW, 160+borderW, 320-borderW, 80-borderW, memColorLight); // Mem border
//drawBorderRect(x, y, w, h, color, borderWidth);
drawBorderRect(0, 160, 320, 80, memColorLight, borderW);
screen.setTextColor(memColorLight);
screen.setCursor(paddingLeft, memUsedY);
screen.print("Mem");
screen.setTextSize(2);
screen.setCursor(memUsedX+100, memUsedY);
screen.print("GB");
screen.setTextSize(4);
screen.setCursor(memLoadX+memLoadW+0, memLoadY);
screen.print("%");
screen.drawRect(memBarX+paddingLeft, memBarY, barW-paddingRight-paddingLeft, barH, memColorLight);
//dataStr = "99,99,99,99,99,99";
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop(){
if (Serial.available() > 0 || demoMode) {
dataStr = Serial.readString(); //read until timeout
dataStr.trim(); // remove any \r \n whitespace at the end of the String
if (dataStr.length() == 1) {
dataStr = "0,0,0,0,0,00.0";
}
//Serial.print("LOL");
Serial.println(dataStr);
if (dataStr == "SERIAL DISCONNECT") {
disconnectScreen();
dataStr = "0,0,0,0,0,00.0";
}
if (demoMode) {
dataStr = demoData[demoIndex];
}
// Get sensor values from data string
//String getValue(String data, char separator, int index)
char separator = ',';
String cpuTemp = getValue(dataStr, separator, 0);
String cpuLoad = getValue(dataStr, separator, 1);
String gpuTemp = getValue(dataStr, separator, 2);
String gpuLoad = getValue(dataStr, separator, 3);
String memLoad = getValue(dataStr, separator, 4);
String memUsed = getValue(dataStr, separator, 5);
if (cpuLoad.toInt() > 100) {
cpuLoad = "100";
}
if (gpuLoad.toInt() > 100) {
gpuLoad = "100";
}
if (memLoad.toInt() > 100) {
memLoad = "100";
}
String cpuTempStr = formatValueStr(cpuTemp);
String cpuLoadStr = formatValueStr(cpuLoad);
String gpuTempStr = formatValueStr(gpuTemp);
String gpuLoadStr = formatValueStr(gpuLoad);
String memLoadStr = formatValueStr(memLoad);
String memUsedStr = formatMemUsed(memUsed);
///////// Draw CPU values
screen.setTextColor(cpuColorLight);
// Update CPU temp
if (cpuTemp != cpuTempPrev) {
//updateNum(cpuTempStr, formatValueStr(cpuTempPrev), cpuTempX, cpuTempY, cpuTempW, cpuTempH, cpuBgColor);
screen.fillRect(cpuTempX, cpuTempY, cpuTempW, cpuTempH, cpuBgColor);
screen.setCursor(cpuTempX, cpuTempY);
screen.print(cpuTempStr);
cpuTempPrev = cpuTemp;
}
// Update CPU load
if (cpuLoad != cpuLoadPrev) {
//updateNum(cpuLoadStr, formatValueStr(cpuLoadPrev), cpuLoadX, cpuLoadY, cpuLoadW, cpuLoadH, cpuBgColor);
screen.fillRect(cpuLoadX, cpuLoadY, cpuLoadW, cpuLoadH, cpuBgColor);
screen.setCursor(cpuLoadX, cpuLoadY);
screen.print(cpuLoadStr);
cpuLoadPrev = cpuLoad;
}
// Update CPU bar width
int newCpuBarW = getBarWidthPx(cpuLoad);
rePaintBar(cpuBarW, newCpuBarW, cpuBarY, cpuColorDark, cpuBgColor);
cpuBarW = newCpuBarW;
///////// Draw GPU values
screen.setTextColor(gpuColorLight);
// Update GPU temp
if (gpuTemp != gpuTempPrev) {
//updateNum(gpuTempStr, formatValueStr(gpuTempPrev), gpuTempX, gpuTempY, gpuTempW, gpuTempH, gpuBgColor);
screen.fillRect(gpuTempX, gpuTempY, gpuTempW, gpuTempH, gpuBgColor);
screen.setCursor(gpuTempX, gpuTempY);
screen.print(gpuTempStr);
gpuTempPrev = gpuTemp;
}
// Update GPU load
if (gpuLoad != gpuLoadPrev) {
//updateNum(gpuLoadStr, formatValueStr(gpuLoadPrev), gpuLoadX, gpuLoadY, gpuLoadW, gpuLoadH, gpuBgColor);
screen.fillRect(gpuLoadX, gpuLoadY, gpuLoadW, gpuLoadH, gpuBgColor);
screen.setCursor(gpuLoadX, gpuLoadY);
screen.print(gpuLoadStr);
gpuLoadPrev = gpuLoad;
}
// Update GPU bar width
int newGpuBarW = getBarWidthPx(gpuLoad);
rePaintBar(gpuBarW, newGpuBarW, gpuBarY, gpuColorDark, gpuBgColor);
gpuBarW = newGpuBarW;
///////// Draw Mem values
screen.setTextColor(memColorLight);
//screen.setTextColor(gpuColorLight,COLOR_RGB565_GREEN);
// Update Mem used
if (memUsed != memUsedPrev) {
//updateNum(memUsedStr, formatMemUsed(memUsedPrev), memUsedX, memUsedY, memUsedW, memUsedH, memBgColor);
screen.fillRect(memUsedX, memUsedY, memUsedW, memUsedH, memBgColor);
screen.setCursor(memUsedX, memUsedY);
screen.print(memUsedStr);
memUsedPrev = memUsed;
}
// Update Mem load number
if (memLoad != memLoadPrev) {
//updateNum(memLoadStr, formatValueStr(memLoadPrev), memLoadX, memLoadY, memLoadW, memLoadH, memBgColor);
screen.fillRect(memLoadX, memLoadY, memLoadW, memLoadH, memBgColor);
screen.setCursor(memLoadX, memLoadY);
screen.print(memLoadStr);
memLoadPrev = memLoad;
}
// Update Mem bar width
int newMemBarW = getBarWidthPx(memLoad);
rePaintBar(memBarW, newMemBarW, memBarY, memColorDark, memBgColor);
memBarW = newMemBarW;
}
if (demoMode) {
demoIndex += 1;
if (demoIndex >= 5) {
demoIndex = 0;
}
delay(1000);
}
//delay(200);
}
// updateNum(cpuTempStr, formatValueStr(cpuTempPrev), cpuTempX, cpuTempY, cpuTempW, cpuTempH);
// Update only parts of number which have changed
void updateNum(String newValue, String oldValue, int x, int y, int w, int h, int bgColor) {
// Check each character on number string
//int inc = w/newValue.length();
int inc = 15;
int xPos = x;
//Serial.println(inc);
Serial.println(newValue);
Serial.println(oldValue);
String oldValueLen = String(oldValue.length());
String newValueLen = String(newValue.length());
Serial.println("oldValueLen: <" + oldValueLen + ">") ;
Serial.println("newValueLen: <" + newValueLen + ">") ;
//Serial.println("newValue.length(): " + String(newValue.length() );
String oldChar;
String newChar;
for (int i = 0; i < newValue.length(); i++) {
//from.charAt(idx)
oldChar = oldValue.substring(i, i+1);
newChar = newValue.substring(i, i+1);
Serial.println("oldChar: <" + oldChar + ">, newChar: <" + newChar + ">");
if (oldChar != newChar) {
// Clear old character
screen.fillRect(xPos, y, inc, h, COLOR_RGB565_BLACK); // bgColor
// Draw new character
screen.setCursor(xPos, y);
screen.print(newChar);
}
xPos += (inc * (i+1));
}
//updateNum(cpuTemp, cpuTempPrev, cpuTempX, cpuTempY, cpuTempW, cpuTempH);
//screen.fillRect(cpuTempX, cpuTempY, cpuTempW, cpuTempH, COLOR_RGB565_BLACK);
// Draw CPU temp
//screen.setCursor(cpuTempX, cpuTempY);
//screen.print(cpuTemp);
}
// Re-paint only parts needed, not the whole bar
void rePaintBar(int oldW, int newW, int barY, int color, int bgColor) {
if (newW > oldW) {
// Paint new parts of bar
screen.fillRect(paddingLeft+oldW, barY+1, newW-oldW, barH-2, color );
} else if(newW < oldW) {
// Clear old parts
screen.fillRect(paddingLeft+newW, barY+1, oldW-newW, barH-2, bgColor );
}
//
if (newW == 0) {
screen.fillRect(0+paddingLeft, barY+1, 1, barH-2, color);
}
}
int16_t getBarWidthPx(String loadStr)
{
int load = loadStr.toInt();
float px = (320.0 - (float)paddingLeft - (float)paddingRight - 1.0) * (load/100.0);
int16_t rounded;
rounded = int16_t(px + 0.5);
//if (rounded > 2) {
// rounded = rounded-2;
//}
//Serial.println(rounded);
return rounded;
}
String formatValueStr(String valueStr) {
valueStr.trim();
int load = valueStr.toInt();
String newStr = "";
if (load < 10) {
newStr = " " + valueStr;
} else if (load >= 10 && load < 100) {
newStr = " " + valueStr;
} else {
newStr = valueStr;
}
//newStr = newStr + "%";
return newStr;
}
String formatMemUsed(String memUsed) {
String newStr = "";
//Serial.println("memUsed: " + memUsed);
if (memUsed.length() <= 3) {
newStr = " " + memUsed;
} else {
newStr = memUsed.substring(0, 4);
}
return newStr;
}
// https://stackoverflow.com/questions/9072320/split-string-into-string-array
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length()-1;
for(int i=0; i<=maxIndex && found<=index; i++){
if(data.charAt(i)==separator || i==maxIndex){
found++;
strIndex[0] = strIndex[1]+1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}
// Draw 4 rectangles to create an empty rectange with border width
void drawBorderRect(int x, int y, int w, int h, int color, int bW) {
// drawRect(x, y, w, h, color)
//screen.fillRect(x, y, bW, h, color); // Left
//screen.fillRect(x+w-bW, y, bW, h, color); // Right
//screen.fillRect(x, y, w, bW, color); // Top
//screen.fillRect(x, y+h-bW, w, bW, color); // Bottom
// Square-size bottom-left border
//screen.fillRect(x, y, bW, h, color); // Left
//screen.fillRect(x, y+h-bW, h/2, bW, color); // Bottom, square sized
// Small bottom-left border
//screen.fillRect(x, y+h/2, bW, h, color); // Left
//screen.fillRect(x, y+h-bW, h/2, bW, color); // Bottom, square sized
// Draw small hooks to left and right border
int hookLen = 10;
//screen.fillRect(x, y, hookLen, bW, color); // Top-left
//screen.fillRect(x, y+h-bW, hookLen, bW, color); // Bottom-left
//screen.fillRect(w-hookLen, y, hookLen, bW, color); // Top-right
//screen.fillRect(w-hookLen, y+h-bW, hookLen, bW, color); // Bottom-right
// Centered left border
int margin = 20;
//screen.fillRect(x, y+margin, bW, h-margin*2, color); // Left
}
void disconnectScreen() {
screen.fillScreen(COLOR_RGB565_RED);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.fillScreen(COLOR_RGB565_RED);
screen.fillScreen(COLOR_RGB565_BLACK);
screen.fillScreen(intelBlue);
screen.setTextSize(5);
screen.setTextColor(intelBlue);
int shadowSize = 5;
int rowH = 60;
screen.setCursor(shadowSize, shadowSize);
screen.println("Serial");
screen.setCursor(shadowSize, rowH+shadowSize);
screen.println("disconnect");
screen.setCursor(shadowSize, rowH*2+30+shadowSize);
screen.println("Restarting...");
screen.setTextColor(intelBlue, COLOR_RGB565_WHITE);
screen.setCursor(0, 0);
screen.println("Serial");
screen.setCursor(0, rowH);
screen.println("disconnect");
screen.setTextColor(COLOR_RGB565_WHITE);
screen.setCursor(0, rowH*2+30);
screen.println("restarting");
delay(2000);
screen.fillScreen(COLOR_RGB565_BLACK);
setup();
}
I put a Disobey sticker on the box because it had a large empty area on the right side of the front.
I think it looks nice.
This was the second microcontroller project I finished.