Arduino‎ > ‎

CPU Meter with graphing and analog output

Ok, so I keep pushing the CPU monitoring to see what I can do with it.  Here's a new version!

This one has:
A green 10 segment LED for CPU use
A red 10 segment LED for Memory use
A red 2 digit 7 segment display for CPU use
A Red/Green (and orange) bi-color 8x8 Matrix for Graphed CPU use
An Analog meter for CPU use




Parts List:
Arduino Uno R3
Serial Cable for PC connection
Lots of Jumper wires (a mix of types)
20x 330ohm resistors (for the LED bars)
1x 100kohm resistor (for the analog meter)
1x Full size Breadboard
1x Half size Breadboard



The connections are a little complicated because of the shift registers, and I haven't done a new wiring diagram.  The diagram here should be fairly accurate for most of it, though.  For the 8x8 display it just needs power, ground, and a connection for I2C on Analog 4 & 5.  For the Analog meter, it just needs a ground connection, and a PWM connection (I'm using PIN 11) with a 100k ohm resistor.

Here's the Arduino Code - I know it needs some commenting and cleanup.  This has a much cleaner input method via serial than the last version!

/*
  PC CPU Meter by Kraegar 1/8/13
  Modified 1/16/2013 to add memory, run from shift registers
  Modified 1/17/2013 to add a 7 digit display showing CPU
  Mofiied 3/21/2013 to add a 8x8 matrix
  Modified 3/22/2013 to add a pwm controlled analog meter
  7 digit display code taken from the Arduino Cookbookf
  Requires 2 x 10 LED sets, can use 10 segment LEDs, and something running on the host to send data
  from 0 - 10.  Data needs to come in the form of 'CPU,MEM\n' Where CPU & MEM are values from 0 - 10
  Should probably add in some validation on the data we're getting
  The shift register code, including the great subroutines, are from DrLuke @ http://bildr.org/2011/02/74hc595/
  The new & improved serial input code from Nick Gammon, here: http://www.gammon.com.au/forum/?id=11425
  The new & improved data parsing from user dsnettleton, here: http://stackoverflow.com/questions/11068450/arduino-c-language-parsing-string-with-delimiter-input-through-serial-interfa
 */
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h" 

Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix();

int pwmPin=11;
const int ledCount = 10;    // the number of LEDs in the bar graph - has to be 10!

//The pins for the shift registers
int SER_Pin = 3;   //pin 14 on the 75HC595, pin3 on the Arduino
int RCLK_Pin = 4;  //pin 12 on the 75HC595, pin4 on the Arduino
int SRCLK_Pin = 5; //pin 11 on the 75HC595, pin5 on the Arduino


int LED7_SER = 8;
int LED7_RCLK = 9;
int LED7_SRCLK = 10;
#define LED7_595s 2
#define numOfLED7Pins LED7_595s * 8
boolean LED_regs[numOfLED7Pins];

//How many of the shift registers - change this
#define number_of_74hc595s 3

//do not touch - used to set our total array size, 8 pins per register
#define numOfRegisterPins number_of_74hc595s * 8
boolean registers[numOfRegisterPins];


int ledLevel;  //used for CPU level
int ledLevel2;  //used for memory level
const int NUMBER_OF_FIELDS = 4; // how many comma separated fields we expect
int fieldIndex = 0; // the current field being received
int values[NUMBER_OF_FIELDS]; // array holding values for all the fields


const int numeralO[11] = {
//ABCDEFG /dp
B11111100, // 0
B01100000, // 1
B11011010, // 2
B11110010, // 3
B01100110, // 4
B10110110, // 5
B00111110, // 6
B11100000, // 7
B11111111, // 8
B11100110, // 9
B00000010, // 10 = -
};

const int numeralT[11] = {
//ABCDEFG /dp
B00000000, // 0
B01100000, // 1
B11011010, // 2
B11110010, // 3
B01100110, // 4
B10110110, // 5
B00111110, // 6
B11100000, // 7
B11111111, // 8
B11100110, // 9
B00000010, // 10 = -
};

const int segmentOPins[8] = { 7,6,5,4,3,2,1,0 };
const int segmentTPins[8] = { 15,14,13,12,11,10,9,8 };

int columns [] = { 0,0,0,0,0,0,0,0 };  //array for holding out column values. start it at 0

void setup() {
  Serial.begin(9600);  //Begin serial communcation
  // Set the shift register pins to output
  pinMode(SER_Pin, OUTPUT);
  pinMode(RCLK_Pin, OUTPUT);
  pinMode(SRCLK_Pin, OUTPUT);
  pinMode(LED7_SER, OUTPUT);
  pinMode(LED7_RCLK, OUTPUT);
  pinMode(LED7_SRCLK, OUTPUT);
  //reset all register pins
  clearRegisters();
  writeRegisters();
  clearLED7Regs();
  writeLED7Regs();
  matrix.begin(0x71);  // pass in the address
}

// Got the serial read code from the arduino cookbook. Recipe 4.5 Everyone should have a copy.
void loop() {
  static char input_line [20];
  static unsigned int input_pos = 0;

  if (Serial.available () > 0) 
    {
    char inByte = Serial.read ();

    switch (inByte)
      {

      case '\n':   // end of text
        input_line [input_pos] = 0;  // terminating null byte
        // terminator reached! process input_line here ...
        displayOutput (input_line);
        
        
        // reset buffer for next time
        input_pos = 0;  
        break;
  
      case '\r':   // discard carriage return
        break;
  
      default:
        // keep adding if not full ... allow for terminating null byte
        if (input_pos < (20 - 1))
          input_line [input_pos++] = inByte;
        break;

      }  // end of switch
  }  // end of incoming data
}

void displayOutput(char * data){
  String input(data);
  input.replace("\n","");
  int commaIndex = input.indexOf(',');
  //  Search for the next comma just after the first
  int secondCommaIndex = input.indexOf(',', commaIndex+1);
  int thirdCommaIndex = input.indexOf(',', secondCommaIndex+1);
  
  String firstValue = input.substring(0, commaIndex);
  String secondValue = input.substring(commaIndex+1, secondCommaIndex);
  String thirdValue = input.substring(secondCommaIndex+1, thirdCommaIndex);
  String fourthValue = input.substring(thirdCommaIndex+1);
  
  values[0] = firstValue.toInt();
  values[1] = secondValue.toInt();
  values[2] = thirdValue.toInt();
  values[3] = fourthValue.toInt();
  Serial.println(input);
  Serial.print(values[0]);
  Serial.print(values[1]);
  Serial.print(values[2]);
  Serial.println(values[3]);

  if(values[0] > 0) {  //If the first value is our array is not 0, we have CPU data - consider 0 "null"
  
      ledLevel = values[0];  //Subtract one - this avoids our null array logic.
      for (int thisLed = 0; thisLed < ledCount; thisLed++) {  //loop over the LEDs, up to the level we got
        // if the array element's index is less than ledLevel,
        // turn the pin for this element on
        if (thisLed < ledLevel) {
          setRegisterPin(thisLed, HIGH);
        } else {
          // turn off all pins higher than the ledLevel:
          setRegisterPin(thisLed, LOW); 
        }
      }
      // Let's go ahead and graph that CPU data on our 8x8 as well!
      matrix.clear();      // clear display
      matrix.setRotation(2);
      int value=ledLevel; //get our new random value 
      for(int res=0; res<7; res++){     //This loop shifts everything left one in the columns array
        columns[res] = columns[res+1];
      }
      columns[7] = value;  //Set the last column to our new value
      for (int col=0; col<8; col++){  //Loop over the columns
        for(int row=0; row<8; row++){  //Loop over the value (rows)
          if(row<columns[col]){  //If this is true, we're drawing something
            if(row<=4){        //Light up green LEDs for 4 and down
              matrix.drawPixel(row, col, LED_GREEN);  //draw the green one
            }else if(row <=6 ){  // 6 and down, light up yellow LEDs
              matrix.drawPixel(row, col, LED_YELLOW);  //draw the yellows
            }else if(row==7){  //For 7, light up a red LED
              matrix.drawPixel(row, col, LED_RED);  //draw the red
            }
          }
        }
      }
      matrix.writeDisplay();  // write the changes we just made to the display
    }
    if(values[1] > 0) { //If the first value is our array is not 0, we have CPU data - consider 0 "null"
      ledLevel2 = values[1]; //Subtract one - this avoids our null array logic.
      for (int thisLed = 0; thisLed < ledCount; thisLed++) { //loop over the LEDs, up to the level we got
        // if the array element's index is less than ledLevel,
        // turn the pin for this element on
        if (thisLed < ledLevel2) {
          setRegisterPin(thisLed+10, HIGH);  //add 10, because the first 10 pins are for the CPU
        } else {
          // turn off all pins higher than the ledLevel:
          setRegisterPin(thisLed+10, LOW);  //add 10, because the first 10 pins are for the CPU
        }
      }
      if(values[2] > 0 && values[2] < 11) {
        showDigitT(values[2]);
      }else if(values[2] > 10){
        showDigitT(10);
      }
      if(values[3] > 0 && values[3] < 11) {
        showDigitO(values[3]);
      }else if(values[3] > 10){
        showDigitO(10);
      }
    }
    writeRegisters();
    writeLED7Regs(); 
    int pwmCpu;
    if(values[2] >= 10){
      pwmCpu = 100;
    }else{
      pwmCpu = ((values[2])*10)+(values[3]);
    }
    pwmCpu = (values[0])*10;
    
    //pwmCpu=random(0,100);
    if(pwmCpu > 100){
      pwmCpu=100;
    }
    
    Serial.println(pwmCpu);
    analogWrite(pwmPin, pwmCpu * 2.55);
    
    
    for(int i=0; i < min(NUMBER_OF_FIELDS, fieldIndex+1); i++){
      values[i] = 0; // set the values to zero, ready for the next message
      }
      fieldIndex = 0; // ready to start over
}


//set all register pins to LOW
void clearRegisters(){
  for(int i = numOfRegisterPins - 1; i >=  0; i--){
     registers[i] = LOW;
  }
}

//Set and display registers
//Only call AFTER all values are set how you would like (slow otherwise)
void writeRegisters(){

  digitalWrite(RCLK_Pin, LOW);
  for(int i = numOfRegisterPins - 1; i >=  0; i--){
    digitalWrite(SRCLK_Pin, LOW);
    int val = registers[i];
    digitalWrite(SER_Pin, val);
    digitalWrite(SRCLK_Pin, HIGH);
  }
  digitalWrite(RCLK_Pin, HIGH);
}

//set an individual pin HIGH or LOW
void setRegisterPin(int index, int value){
    registers[index] = value;
}


//set all register pins to LOW
void clearLED7Regs(){
  for(int i = numOfLED7Pins - 1; i >=  0; i--){
     LED_regs[i] = LOW;
  }
}

//Set and display registers
//Only call AFTER all values are set how you would like (slow otherwise)
void writeLED7Regs(){

  digitalWrite(LED7_RCLK, LOW);
  for(int i = numOfLED7Pins - 1; i >=  0; i--){
    digitalWrite(LED7_SRCLK, LOW);
    int val = LED_regs[i];
    digitalWrite(LED7_SER, val);
    digitalWrite(LED7_SRCLK, HIGH);
  }
  digitalWrite(LED7_RCLK, HIGH);
}

//set an individual pin HIGH or LOW
void setLED7Pin(int index, int value){
    LED_regs[index] = value;
}

void showDigitO( int number) {
  boolean isBitSet;

  for(int i = 1; i < 8; i++){
    setLED7Pin(i, LOW);
  }
  for(int segment =1; segment < 8; segment++){
    isBitSet = bitRead(numeralO[number], segment);
    setLED7Pin(segmentOPins[segment], isBitSet);
  }
  
}

void showDigitT( int number) {
  boolean isBitSet;
  for(int i = 1; i < 8; i++){
    setLED7Pin(i+7, LOW);
  }
  for(int segment =1; segment < 8; segment++){
    isBitSet = bitRead(numeralT[number], segment);
    setLED7Pin(segmentTPins[segment], isBitSet);
  }
  
}


And here's the current perl code - not guaranteed to work on all platforms, as I've discovered the WMI calls are touchy.  In the end, if you can spit out four comma separated values to Serial that's all you need.
It should be like this: 1,1,1,1

The first digit is 0-10, and is CPU use.  The second is 0-10, and is memory use.  The third is 0-10 and is the "tens" place for the exact cpu use, and the fourth is 0-10 and is the "ones" place for exact cpu use. (I know the last one can't be 10, but it's accepted as a valid value)

There's also an oddity where each time I want to run this, I need to first open the Arduino Serial console, and send one dummy set of data, say 1,1,1,1.  Once that's done I can close the console and run the script, and it works fine.  Can't figure out what's causing that.

#!/usr/bin/perl -w
# This code is almost 100% from http://www.perlmonks.org/?node_id=951420
# written by gulden
# Modified by Kraegar on 1/8/13 to send CPU use out the  COM3 port to an arduino
# Lots of cleanup needed, basically finished it where it was working, and haven't 
# reviewed it at all yet
# Not fully commented yet, since I didn't write most of it!
use strict;
use warnings;

use Win32::OLE;

# Init WMI
my $wmi = Win32::OLE->GetObject("winmgmts://./root/cimv2")
    or die "Failed getobject\n";


# get WMI values
sub get_wmi{
my $wmi = shift;
my $list, my $v;
my @properties = qw(PercentProcessorTime TimeStamp_Sys100NS);
my $class = 'Win32_PerfRawData_PerfOS_Processor';
my $key = 'Name';
$list = $wmi->InstancesOf("$class")
or die "Failed getobject\n";
my $hash;
foreach $v (in $list) {        
$hash->{$v->{$key}}->{$_}  = $v->{$_} for @properties;
}
$hash;
}


my $cpu;
my $hash_prev = get_wmi($wmi);

while(1){
#sleep(1);
select(undef, undef, undef, .25); #mimics sleeping for 1/2 a second
my $hash = get_wmi($wmi);
$cpu = sprintf("%.2f", 
(
1 - (
$hash->{'_Total'}->{'PercentProcessorTime'} - $hash_prev->{'_Total'}->{'PercentProcessorTime'}
) / 
(
$hash->{'_Total'}->{'TimeStamp_Sys100NS'} - $hash_prev->{'_Total'}->{'TimeStamp_Sys100NS'}
)* 100 );
my $nicecpu = sprintf("%.0f", $cpu);
my $rcpu = int($nicecpu/10+0.5)*10;
$rcpu = $rcpu/10;
my $list, my $v;

$list = $wmi->InstancesOf("Win32_OperatingSystem")
  or die "Failed getobject\n";

my $end_time = time;


my ($total_mem, $free_mem, $used_mem, $mem_percent, $free_percent, $rmem_percent);

foreach $v (in $list) {

  $total_mem = $v->{TotalVisibleMemorySize};
  $free_mem = $v->{FreePhysicalMemory};
  $used_mem = $total_mem - $free_mem;
  $mem_percent = sprintf("%.0f", $used_mem / $total_mem * 100);
  $rmem_percent = int($mem_percent/10+0.5)*10;
  $rmem_percent = $rmem_percent/10;
  $free_percent  =  sprintf("%.2f", $free_mem / $total_mem * 100);

  my $total_swap_mem = $v->{SizeStoredInPagingFiles};
  my $free_swap_mem = $v->{FreeSpaceInPagingFiles};
  my $used_swap_mem = $total_swap_mem - $free_swap_mem;
  my $used_swap_mem_perc = ($total_swap_mem - $free_swap_mem) / $total_swap_mem * 100;

}
my $cpu_a;
my $cpu_b;
if($nicecpu < 10){
$cpu_a = 0;
$cpu_b = $nicecpu;
}elsif($nicecpu==100){
$cpu_a=10;
$cpu_b=10;
}else{
$cpu_a = substr($nicecpu, 0, 1);
$cpu_b = substr($nicecpu, 1, 1);
}
print "$rcpu,$rmem_percent,$cpu_a,$cpu_b\n";
open( PORT, "+>COM3" ) or die "Can't open COM3: $!"; #I open the COM port each time. Seemed more reliable
print PORT "$rcpu,$rmem_percent,$cpu_a,$cpu_b\n";
close(PORT);  #Make sure to close the port, if you're going to open it each time especially!

$hash_prev = $hash;
}
Comments