Arduino‎ > ‎

LED CPU & Memory Meter

My Earlier CPU Meter was good, but I knew I could do better.  So here's v2.0.

Changes:
  • It uses Shift Registers instead of direct output
  • It tracks both CPU and Memory
  • The arduino code had to be adjusted to take two variables as input, and can handle more
  • Timing was shortened to .5 seconds instead of every second for updates
Here's the wiring diagram (Made in Fritzing):




And a photo of it working (I used 10 segment LEDs, not individual ones.  Looks much cleaner!)




Here's the Arduino Code 

/*
  PC CPU Meter by Kraegar 1/8/13
  Modified 1/16/2013 to add memory, run from shift registers
  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/
 */
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

//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 = 2; // 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

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);
  //reset all register pins
  clearRegisters();
  writeRegisters();
}

// Got the serial read code from the arduino cookbook. Recipe 4.5 Everyone should have a copy.
void loop() {

  if( Serial.available()){  
    char ch = Serial.read();
    if(ch >= '0' && ch <= '9'){ // is this an ascii digit between 0 and 9?
                                // yes, accumulate the value if the fieldIndex is within range
                                // additional fields are not stored
      if(fieldIndex < NUMBER_OF_FIELDS) {
        values[fieldIndex] = (values[fieldIndex] * 10) + (ch - '0')+1;  //Convert from ASCII to decimal, add one so we can differentiate from a "null" array
      }
  }else if (ch == ','){ // comma is our separator, so move on to the next field
    fieldIndex++; // increment field index
  }else{
    // any character not a digit or comma ends the acquisition of fields
    // in this example it's the newline character sent by the Serial Monitor
    
    if(values[0] > 0) {  //If the first value is our array is not 0, we have CPU data - consider 0 "null"
      ledLevel = values[0] - 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 < ledLevel) {
          setRegisterPin(thisLed, HIGH);
        } else {
          // turn off all pins higher than the ledLevel:
          setRegisterPin(thisLed, LOW); 
        }
      }
    }
    if(values[1] > 0) { //If the first value is our array is not 0, we have CPU data - consider 0 "null"
      ledLevel2 = values[1] - 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
        }
      }
    }
    writeRegisters();
    for(int i=0; i < min(NUMBER_OF_FIELDS, fieldIndex+1); i++){
    //Serial.println(values[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;
}

And here's the perl code.  Adjust the COM port to match your arduino.  Again - hardly commented and rough.  I'll update it sometime.



#!/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 for CPU
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){
    select(undef, undef, undef, 0.5); #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;

    #The portion of the code to pull the memory values is from http://stackoverflow.com/questions/4726968/get-wmi-memory-values-using-perl
    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;
    }

    open( PORT, "+>COM3" ) or die "Can't open COM3: $!"; #I open the COM port each time. Seemed more reliable
    print PORT "$rcpu,$rmem_percent\n"; # Enable for debugging
    print "$rcpu,$rmem_percent\n"; #Enable for debugging
    close(PORT); #Make sure to close the port, if you're going to open it each time especially!
    $hash_prev = $hash;
}
Comments