Reading serial data and output on OLED display [solved]

So I've just gotten into Arduino programming as I bought myself one for testing and projects

I've been into programming before in automation (Ladder and structured text) and I currently work as an automation technician. However I'm struggling a bit here.

What I'm trying is to read CPU % and Memory % from my PC trough serial, and output them as basic numbers to the OLED display.

The datas are sending and being recieved, with the U8glib Console example I can recieve "M53" and "C4" characters. M stands for memory, and the number is the percent usage. C stands for CPU and the number is also the percent usage.

What I'm struggling with is creating two "static" numbers on left and right of the display, which updates according to the serial input. This is an easy task, but I don't know where to begin.

Here you can see that I've input static "CPU% MEM%" characters.

Under those I would like the number and a percent symbol in large characters.

The console example takes the serial input and displays them in rows upwards, here I've sent only two packets for the picture.

This is how the program looks like which sends the serial numbers according to the CPU/MEM usage.

Any help with this is greatly appreciated. The next step is creating visual bars or meters, but that's quite difficult.

/*

  Console.pde
  
  Read from serial monitor, output to display
  
  >>> Before compiling: Please remove comment from the constructor of the 
  >>> connected graphics display (see below).
  
  Universal 8bit Graphics Library, https://github.com/olikraus/u8glib/
  
  Copyright (c) 2011, olikraus@gmail.com
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification, 
  are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice, this list 
    of conditions and the following disclaimer.
    
  * Redistributions in binary form must reproduce the above copyright notice, this 
    list of conditions and the following disclaimer in the documentation and/or other 
    materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
  
*/


#include "U8glib.h"

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NONE);	// I2C / TWI 

// setup input buffer
#define LINE_MAX 30 
uint8_t line_buf[LINE_MAX] = "U8GLIB Console";
uint8_t line_pos = 0;

// setup a text screen to support scrolling
#define ROW_MAX 12


uint8_t screen[ROW_MAX][LINE_MAX];
uint8_t rows, cols;

// line height, which matches the selected font (5x7)
#define LINE_PIXEL_HEIGHT 7

// clear entire screen, called during setup
void clear_screen(void) {
  uint8_t i, j;
  for( i = 0; i < ROW_MAX; i++ )
    for( j = 0; j < LINE_MAX; j++ )
      screen[i][j] = 0;  
}

// append a line to the screen, scroll up
void add_line_to_screen(void) {
  uint8_t i, j;
  for( j = 0; j < LINE_MAX; j++ )
    for( i = 0; i < rows-1; i++ )
      screen[i][j] = screen[i+1][j];
  
  for( j = 0; j < LINE_MAX; j++ )
    screen[rows-1][j] = line_buf[j];
}

// U8GLIB draw procedure: output the screen
void draw(void) {
  uint8_t i, y;
  // graphic commands to redraw the complete screen are placed here    
  y = 0;       // reference is the top left -1 position of the string
  y--;           // correct the -1 position of the drawStr 
  for( i = 0; i < rows; i++ )
  {
    u8g.drawStr( 0, y, (char *)(screen[i]));
    y += u8g.getFontLineSpacing();
  }
  {
  // graphic commands to redraw the complete screen should be placed here  
  u8g.setFont(u8g_font_9x15);
  //u8g.setFont(u8g_font_osb21);
  u8g.drawStr( 0, 0, "CPU%  MEM%");
  }
}

void exec_line(void) {
  // echo line to the serial monitor
  Serial.println((const char *)line_buf);
  
  // add the line to the screen
  add_line_to_screen();
  
  // U8GLIB picture loop
  u8g.firstPage();  
  do {
    draw();
  } while( u8g.nextPage() );
}

// clear current input buffer
void reset_line(void) { 
      line_pos = 0;
      line_buf[line_pos] = '\0';  
}

// add a single character to the input buffer 
void char_to_line(uint8_t c) {
      line_buf[line_pos] = c;
      line_pos++;
      line_buf[line_pos] = '\0';  
}

// check serial in and handle the character
void read_line(void) {
  if ( Serial.available() )
  {
    uint8_t c;
    c = Serial.read();
    if ( line_pos >= cols-1 ) {
      exec_line();
      reset_line();
      char_to_line(c);
    } 
    else if ( c == '\n' ) {
      // ignore '\n' 
    }
    else if ( c == '\r' ) {
      exec_line();
      reset_line();
    }
    else {
      char_to_line(c);
    }
  }
}

// Arduino master setup
void setup(void) {
  // set font for the console window
  //u8g.setFont(u8g_font_5x7);
  u8g.setFont(u8g_font_9x15);
  
  // set upper left position for the string draw procedure
  u8g.setFontPosTop();
  
  // calculate the number of rows for the display
  rows = u8g.getHeight() / u8g.getFontLineSpacing();
  if ( rows > ROW_MAX )
    rows = ROW_MAX; 
  
  // estimate the number of columns for the display
  cols = u8g.getWidth() / u8g.getStrWidth("m");
  if ( cols > LINE_MAX-1 )
    cols = LINE_MAX-1; 
  
  clear_screen();               // clear screen
  delay(1000);                  // do some delay
  Serial.begin(9600);        // init serial
  exec_line();                    // place the input buffer into the screen
  reset_line();                   // clear input buffer
}

// Arduino main loop
void loop(void) {
  read_line();
}

Here's the original Arduino code for the program used on the PC. It's made for two analog meters and LEDs and reads the serial input and sends them amplified trough the analog outputs.

/*
    PC_Meter
    
    Drives PC Meter device.
    http://www.lungstruck.com/projects/pc-meter

    Written in 2013 by Scott W. Vincent
    http://www.lungstruck.com
    
    To the extent possible under law, the author has dedicated all copyright and related and neighboring rights to this
    software to the public domain worldwide. This software is distributed without any warranty. 

    You should have received a copy of the CC0 Public Domain Dedication along with this software.
    If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. 
*/

//Constants
const int METER_A_PIN = 11;        //Meter A pin
const int METER_B_PIN = 10;        //Meter B pin
const int METER_A_MAX = 246;       //100% location for meter A
const int METER_B_MAX = 248;       //100% location for meter B
const int LED_A_GREEN = 4;         //LED A Green Pin
const int LED_A_RED = 5;           //LED A Red Pin
const int LED_B_GREEN = 2;         //LED B Green Pin
const int LED_B_RED = 3;           //LED B Red Pin
const float RED_ZONE_PERC = .80;   //Percent at which LED goes from green to red
const long SERIAL_TIMEOUT = 3000;  //How long to wait until serial "times out"
const int CPU_READINGS = 4;        //Number of readings to avg. for CPU load%

//Variables
int meterARedZone = 0;                //Meter A red zone
int meterBRedZone = 0;                //Meter B red zone
unsigned long lastSerialRecd = 0;     //Time last serial recd
int cpuReadings[CPU_READINGS];        //array of cpu load% readings
int cpuTotal = 0;                     //CPU load running total
int cpuIndex = 0;                     //current position in array for cpu load%

void setup()
{
 Serial.begin(9600);
  
 //Setup pin modes
 pinMode(METER_A_PIN, OUTPUT);
 pinMode(METER_B_PIN, OUTPUT);
 pinMode(LED_A_GREEN, OUTPUT);
 pinMode(LED_A_RED, OUTPUT);
 pinMode(LED_B_GREEN, OUTPUT);
 pinMode(LED_B_RED, OUTPUT);
 
 //Calculate red zone and multiplier for both meters
 meterARedZone = METER_A_MAX * RED_ZONE_PERC;
 meterBRedZone = METER_B_MAX * RED_ZONE_PERC;
 
 initCpuValues();
 meterStartup();
 
 //Give meter some time to start receiving data
 lastSerialRecd = millis();
}


void loop()
{
  char buffer[5];                //buffer
  int perc = 0;                  //reading
  
  while (Serial.available() > 0)
  {
   Serial.readBytesUntil('\r', buffer, 5);
   
   //Thought this might be needed based on example I studied, seems okay without
   //buffer[4] = '\0';

   switch (buffer[0])
   {
    case 'C':
     //CPU - value is "smoothed".  See tutorial for more info:
     //http://arduino.cc/en/Tutorial/Smoothing
     cpuTotal = cpuTotal - cpuReadings[cpuIndex];
     cpuReadings[cpuIndex] = min(atoi(&buffer[1]), 100);
     cpuTotal = cpuTotal + cpuReadings[cpuIndex];
     perc = cpuTotal / CPU_READINGS;
     
     setMeterAndLED(METER_A_PIN, LED_A_GREEN, LED_A_RED, perc, meterARedZone, METER_A_MAX);
     
     //Advance index
     cpuIndex = cpuIndex + 1;
     if (cpuIndex >= CPU_READINGS)
       cpuIndex = 0;
     break;
    case 'M':
     //Memory
     perc = min(atoi(&buffer[1]), 100);
     setMeterAndLED(METER_B_PIN, LED_B_GREEN, LED_B_RED, perc, meterBRedZone, METER_B_MAX);
     break;
   }
   
   //Reset for next measurement
   perc = 0;
   memset(buffer, ' ', 5);
   
   //Update last serial received
   lastSerialRecd = millis();
  }
  
  //Check for timeout start "screen saver" if so
  unsigned long currentMillis = millis();
  if (currentMillis - lastSerialRecd > SERIAL_TIMEOUT)
    screenSaver();
}


void meterStartup()
{
 //Max both meters as test
 setMeterAndLED(METER_A_PIN, LED_A_GREEN, LED_A_RED, 100, meterARedZone, METER_A_MAX);
 setMeterAndLED(METER_B_PIN, LED_B_GREEN, LED_B_RED, 100, meterBRedZone, METER_B_MAX);
 delay(2000);
}

//Set Meter position and LED color.
void setMeterAndLED(int meterPin, int greenPin, int redPin, int perc, int redZone, int meterMax)
{
  //Map perc to proper meter position
  //int pos = perc * meterMultiplier;
  int pos = map(perc, 0, 100, 0, meterMax);
  
  //Set meter
  analogWrite(meterPin, pos);
  
  //Set LED
  int isGreen = (pos <  redZone);
  digitalWrite(greenPin, isGreen);
  digitalWrite(redPin, !isGreen);
}


//Move needles and blink LEDs when no serial rec'd for awhile.
//Stop once serial data rec'd again.
void screenSaver()
{
 analogWrite(METER_A_PIN, 0);
 analogWrite(METER_B_PIN, 0);
 int aPos = 0;
 int bPos = 0;
 int incAmt = 0;
 
 //Reset CPU readings
 initCpuValues();
 
 while (Serial.available() == 0)
 {
   //B meter position is opposite of A meter position
   bPos = 100 - aPos;
   
   //Move needles
   setMeterAndLED(METER_A_PIN, LED_A_GREEN, LED_A_RED, aPos, meterARedZone, METER_A_MAX);
   setMeterAndLED(METER_B_PIN, LED_B_GREEN, LED_B_RED, bPos, meterBRedZone, METER_B_MAX);
   
   //Change meter direction if needed.
   if (aPos == 100)
     incAmt = -1;
   else if (aPos == 0)
     incAmt = 1;
   
   //Increment position
   aPos = aPos + incAmt;
   
   delay(50);
 } 
}


//Init CPU array/total with zeroes
void initCpuValues()
{
  for (int counter = 0; counter < CPU_READINGS; counter++)
    cpuReadings[counter] = 0;
    cpuTotal = 0;
}

I confess I don't understand exactly what you want help with.

This is not wise

  while (Serial.available() > 0)
  {
   Serial.readBytesUntil('\r', buffer, 5);

at the very least change the WHILE to an IF

However it would be much better to use a more robust process to receive data. Have a look at the examples in Serial Input Basics

Also, I suggest you separate the receiving of the data from acting on the data. That way you have short functions that can be developed and tested independently of each other.

Sorry, but I don't know anything about the display you are using.

...R

Robin2:
I confess I don't understand exactly what you want help with.

This is not wise

  while (Serial.available() > 0)

{
Serial.readBytesUntil('\r', buffer, 5);



at the very least change the WHILE to an IF

However it would be much better to use a more robust process to receive data. Have a look at the examples in [Serial Input Basics](http://forum.arduino.cc/index.php?topic=396450.0)

Also, I suggest you separate the receiving of the data from acting on the data. That way you have short functions that can be developed and tested independently of each other.

Sorry, but I don't know anything about the display you are using.

...R

Yeah, that code isn't being used, however it's an example code from the maker of the CPU/MEM % program.

I wasn't specific enough, because I'm a little bit confused myself on how I should explain it.

What I'm trying to do is:

Read two forms of value from serial input. The values are integral numbers (0-100) with a character in front ("C" or "M") which says what the value is representing; CPU or memory.

The serial recieves the values two by two every refresh, one CPU and one memory value. The console looks like this, for example:

C12
M45
C15
M45
C5
M39
C18
M40

and so on.

Then I want to output those values onto the OLED-display as normal decimals, with regular update intervals every second or so.

This gives me an overview of the CPU/RAM load on my PC.

Layout will initially be like this:

So i want to somehow "pick out" all the 'Cxx' numbers and write them on the left, and 'Mxx' numbers on the right.

The display is a SH1106 or SSD1306 OLED with 128x64 resolution. They both work the same way, only different colors and size.

I use the U8GLIB library to run the display. I haven't written any of the code myself yet, only modified examples.

So i want to somehow "pick out" all the 'Cxx' numbers and write them on the left, and 'Mxx' numbers on the right.

How is a "Cxx" package delimited? That is, how do you know when you have a complete package?

What do you suppose happens if you change the C to a space, and pass the string to atoi()?

heres the display code--

void drawScreen(void) {u8g.firstPage();
do { u8g.setFont(u8g_font_unifont);
// u8g.setFont(u8g_font_osb21);
u8g.drawStr(6,38,"CPU MEM");
u8g.setPrintPos(5,52);
u8g.print(CPUstr);
u8g.setPrintPos(53,52);
u8g.print(String(MemoryUsage));

}while( u8g.nextPage() );

}

hawkiee552:
So i want to somehow "pick out" all the 'Cxx' numbers and write them on the left, and 'Mxx' numbers on the right.

Look at the parse example in Serial Input Basics

The simplest way to send the data would be "<12,42>" where the first value is always the CPU value and the second is the MEM value.

...R

YESS!

Thanks to my friend which knows programming a bit more than me, he managed to tweak the existing code into showing exactly what I wanted on the display, and in realtime every serial update.

Here it is:

 #include "U8glib.h"

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NONE);	// I2C / TWI 

int cpu = 0;
int mem = 0;

void setup() {
    Serial.begin(9600);
}

void draw()
{
    u8g.setFont(u8g_font_unifont);
    u8g.firstPage();
    do {
        u8g.drawStr( 0, 20,"CPU    MEM");
        u8g.setPrintPos(5,52);
        u8g.print(String(cpu));
        u8g.setPrintPos(53,52);
        u8g.print(String(mem));
    } while( u8g.nextPage() );
}

void loop()
{
    char buffer[5];                //buffer
    int perc = 0;                  //reading

    while (Serial.available() > 0)
    {
        Serial.readBytesUntil('\r', buffer, 5);

        //Thought this might be needed based on example I studied, seems okay without
        //buffer[4] = '\0';

        switch (buffer[0])
        {
            case 'C':
                //CPU - value is "smoothed".  See tutorial for more info:
                //http://arduino.cc/en/Tutorial/Smoothing
                // cpuTotal = cpuTotal - cpuReadings[cpuIndex];
                // cpuReadings[cpuIndex] = min(atoi(&buffer[1]), 100);
                // cpuTotal = cpuTotal + cpuReadings[cpuIndex];
                // perc = cpuTotal / CPU_READINGS;
                perc = min(atoi(&buffer[1]), 100);
                cpu = perc;

                // setMeterAndLED(METER_A_PIN, LED_A_GREEN, LED_A_RED, perc, meterARedZone, METER_A_MAX);

                //Advance index
                //cpuIndex = cpuIndex + 1;
                //if (cpuIndex >= CPU_READINGS)
                //    cpuIndex = 0;
                
                break;
            
            case 'M':
                //Memory
                perc = min(atoi(&buffer[1]), 100);
                mem = perc;
                // setMeterAndLED(METER_B_PIN, LED_B_GREEN, LED_B_RED, perc, meterBRedZone, METER_B_MAX);
                break;
        }

        //Reset for next measurement
        perc = 0;
         memset(buffer, ' ', 5);
        
    }
draw(); }

Thanks for the help everyone!