Arduino FIO LCD Oscilloscope

May 28, 2013

It has been 7 years (!) since I posted my PIC18F2550 KS0108 Graphical LCD Oscilloscope code and schematics. I have long since taken the circuit apart, sold my PIC microcontrollers, and moved on in my life (as one can surmise from my most recent posts detailing my graduate and postdoctoral work). However, I still get inquiries about the Microchip PIC oscilloscope, so I decided to recreate it using a simpler setup using my Arduino Fio.

IMG_4199

Here’s a short teaser video just to show that, yes, it works (going through a couple different sine wave frequencies, some random noise, etc. just to illustrate it working):

Click through the break to get more information on the setup.

I used an Arduino Fio board that I picked up from SparkFun.com (available at Amazon.com) and a small SPI graphical LCD board that I picked up for a few bucks at dx.com (SKU 153821, also apparently available at Amazon.com). Since I don’t have a soldering iron here, I had to improvise with some female to female cables, also purchased from dx.com (SKU 151650).

Dx.com describes the LCD as a 5V module, but the GLCD board designer’s page (mini12864) states otherwise (translated from Chinese via Google Translate):

Dimensions (L × W × H): 47mm × 38mm × 6mm (excluding pins)
LCD sight (L × W): 33.7mm × 33.5mm
LCD Active Display Area (L × W): 30.7mm × 23mm
Backlight: White LED backlight bracket
Operating voltage: 3.3V ~ 5.5V (built-in booster circuit, without pressure)
Control IC: UC1701
Display format: 128 × 64 rows
Display: Blue on White

(mini12864 datasheet)

So, I ordered one of the graphical LCDs, waited a few weeks for delivery (because dx.com is a notoriously slow shipper), received it, hooked it up, and tried out the following simple “Hello World” script to confirm that that the GLCD/FIO combo functioned:

#include "U8glib.h"

const byte lcdLED = 6;                   // LED Backlight
const byte lcdA0 = 7;                    // Data and command selections. L: command  H : data
const byte lcdRESET = 8;                 // Low reset
const byte lcdCS = 9;                    // SPI Chip Select (internally pulled up), active low
const byte lcdMOSI = 11;                 // SPI Data transmission
const byte lcdSCK = 13;                  // SPI Serial Clock

// SW SPI:
//U8GLIB_MINI12864 u8g(lcdSCK, lcdMOSI, lcdCS, lcdA0, lcdRESET);
// HW SPI:
U8GLIB_MINI12864 u8g(lcdSCK, lcdMOSI, lcdCS, lcdA0, lcdRESET);

void draw(void) {
  // graphic commands to redraw the complete screen should be placed here  
  u8g.setFont(u8g_font_micro);
  u8g.drawStr( 0, 20, "Hello World!");
}

void setup() {
  u8g.begin();
}

void loop() {
  // picture loop
  u8g.firstPage();  
  do {
    draw();
  } while( u8g.nextPage() );
  
  // rebuild the picture after some delay
  delay(1000);
}

And it worked!

Thankfully, the code I previously posted was written in C, so porting to the Arduino took only a few minutes. I took advantage of a great open source graphical LCD library (u8glib) to handle the brunt of the work and added a serial port menu for manipulating the various display parameters. One important difference between this project and the previous one: Since the Arduino Fio is a 3.3V device, it can only handle 0-3.3V inputs, limiting its utility as an “oscilloscope” without proper input protection/voltage scaling. However, the code is extremely portable, meaning that you should be able to program any other Arduino and have it up-and-running in no time.

IMG_4203

Here’s another 2 videos of the oscilloscope in action:

And finally, here’s the Arduino sketch for the oscilloscope:

#include "U8glib.h"
#include <EEPROM.h>

// Variables you might want to play with
byte useThreshold = 1;                  // 0 = Off, 1 = Rising, 2 = Falling
byte theThreshold = 128;                // 0-255, Multiplied by voltageConst
unsigned int timePeriod = 200;          // 0-65535, us or ms per measurement (max 0.065s or 65.535s)
byte voltageRange = 1;                  // 1 = 0-3.3V, 2 = 0-1.65V, 3 = 0-0.825V
byte ledBacklight = 100;

boolean autoHScale = true;             // Automatic horizontal (time) scaling
boolean linesNotDots = true;            // Draw lines between data points

// Variables that can probably be left alone
const byte vTextShift = 3;              // Vertical text shift (to vertically align info)
const byte numOfSamples = 100;          // Leave at 100 for 128x64 pixel display
unsigned int HQadcReadings[numOfSamples];
byte adcReadings[numOfSamples];
byte thresLocation = 0;                 // Threshold bar location
float voltageConst = 0.052381;          // Scaling factor for converting 0-63 to V
float avgV = 0.0;    
float maxV = 0.0;
float minV = 0.0;
float ptopV = 0.0;
float theFreq = 0;

const byte theAnalogPin = 7;             // Data read pin

const byte lcdLED = 6;                   // LED Backlight
const byte lcdA0 = 7;                    // Data and command selections. L: command  H : data
const byte lcdRESET = 8;                 // Low reset
const byte lcdCS = 9;                    // SPI Chip Select (internally pulled up), active low
const byte lcdMOSI = 11;                 // SPI Data transmission
const byte lcdSCK = 13;                  // SPI Serial Clock

// SW SPI:
//U8GLIB_MINI12864 u8g(lcdSCK, lcdMOSI, lcdCS, lcdA0, lcdRESET);
// HW SPI:
U8GLIB_MINI12864 u8g(lcdSCK, lcdMOSI, lcdCS, lcdA0, lcdRESET);

// High speed ADC code
// From: http://forum.arduino.cc/index.php?PHPSESSID=e21f9a71b887039092c91a516f9b0f36&topic=6549.15
#define FASTADC 1
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void collectData(void) {
  unsigned int tempThres = 0;
  unsigned int i = 0;

  if (autoHScale == true) {
    // With automatic horizontal (time) scaling enabled,
    // scale quickly if the threshold location is far, then slow down
    if (thresLocation > 5*numOfSamples/8) {
      timePeriod = timePeriod + 10;
    } else if (thresLocation < 3*numOfSamples/8) {
      timePeriod = timePeriod - 10;
    } else if (thresLocation > numOfSamples/2) {
      timePeriod = timePeriod + 2;
    } else if (thresLocation < numOfSamples/2) {
      timePeriod = timePeriod - 2;
    }
  }
  // Enforce minimum time periods
  if (timePeriod < 35) {
    timePeriod = 35;
  }
  
  // Adjust voltage contstant to fit the voltage range
  if (voltageRange == 1) {
    voltageConst = 0.0523810; // 0-3.30V
  } else if (voltageRange == 2) {
    voltageConst = 0.0261905; // 0-1.65V
  } else if (voltageRange == 3) {
    voltageConst = 0.0130952; //0-0.825V
  }
  
  // If using threshold, wait until it has been reached
  if (voltageRange == 1) tempThres = theThreshold << 2;
  else if (voltageRange == 2) tempThres = theThreshold << 1;
  else if (voltageRange == 3) tempThres = theThreshold;
  if (useThreshold == 1) {
     i = 0; while ((analogRead(theAnalogPin)>tempThres) && (i<32768)) i++;
     i = 0; while ((analogRead(theAnalogPin)<tempThres) && (i<32768)) i++;
  }
  else if (useThreshold == 2) {
     i = 0; while ((analogRead(theAnalogPin)<tempThres) && (i<32768)) i++;
     i = 0; while ((analogRead(theAnalogPin)>tempThres) && (i<32768)) i++;
  }

  // Collect ADC readings
  for (i=0; i<numOfSamples; i++) {
    // Takes 35 us with high speed ADC setting
    HQadcReadings[i] = analogRead(theAnalogPin);
    if (timePeriod > 35)
      delayMicroseconds(timePeriod-35);
  }
  for (i=0; i<numOfSamples; i++) {
    // Scale the readings to 0-63 and clip to 63 if they are out of range.
    if (voltageRange == 1) {
      if (HQadcReadings[i]>>4 < 0b111111) adcReadings[i] = HQadcReadings[i]>>4 & 0b111111;
      else adcReadings[i] = 0b111111;
    } else if (voltageRange == 2) {
      if (HQadcReadings[i]>>3 < 0b111111) adcReadings[i] = HQadcReadings[i]>>3 & 0b111111;
      else adcReadings[i] = 0b111111;
    } else if (voltageRange == 3) {
      if (HQadcReadings[i]>>2 < 0b111111) adcReadings[i] = HQadcReadings[i]>>2 & 0b111111;
      else adcReadings[i] = 0b111111;
    }
    // Invert for display
    adcReadings[i] = 63-adcReadings[i];
  }
  
  // Calculate and display frequency of signal using zero crossing
  if (useThreshold != 0) {
     if (useThreshold == 1) {
        thresLocation = 1;
        while ((adcReadings[thresLocation]<(63-(theThreshold>>2))) && (thresLocation<numOfSamples-1)) (thresLocation++);
        thresLocation++;
        while ((adcReadings[thresLocation]>(63-(theThreshold>>2))) && (thresLocation<numOfSamples-1)) (thresLocation++);
     }
     else if (useThreshold == 2) {
        thresLocation = 1;
        while ((adcReadings[thresLocation]>(63-(theThreshold>>2))) && (thresLocation<numOfSamples-1)) (thresLocation++);
        thresLocation++;
        while ((adcReadings[thresLocation]<(63-(theThreshold>>2))) && (thresLocation<numOfSamples-1)) (thresLocation++);
     }

     theFreq = (float) 1000/(thresLocation * timePeriod) * 1000;
  }
  
  // Average Voltage
  avgV = 0;
  for (i=0; i<numOfSamples; i++)
     avgV = avgV + adcReadings[i];
  avgV = (63-(avgV / numOfSamples)) * voltageConst;

  // Maximum Voltage
  maxV = 63;
  for (i=0; i<numOfSamples; i++)
     if (adcReadings[i]<maxV) maxV = adcReadings[i];
  maxV = (63-maxV) * voltageConst;

  // Minimum Voltage
  minV = 0;
  for (i=0; i<numOfSamples; i++)
     if (adcReadings[i]>minV) minV = adcReadings[i];
  minV = (63-minV) * voltageConst;

  // Peak-to-Peak Voltage
  ptopV = maxV - minV;
}

void handleSerial(void) {
  char inByte;
  char dataByte;
  boolean exitLoop = false;
  do {
    // Clear out buffer
    do {
      inByte = Serial.read();
    } while (Serial.available() > 0);
  
    Serial.print("\nArduino LCD Oscilloscope\n");
    Serial.print(" 1 - Change threshold usage (currently: ");
      if (useThreshold == 0) Serial.print("Off)\n");
      else if (useThreshold == 1) Serial.print("Rise)\n");
      else if (useThreshold == 2) Serial.print("Fall)\n");
    Serial.print(" 2 - Change threshold value (currently: ");
      Serial.print(theThreshold, DEC); Serial.print(")\n");
    Serial.print(" 3 - Change sampling period (currently: ");
      Serial.print(timePeriod, DEC); Serial.print(")\n");
    Serial.print(" 4 - Change voltage range (currently: ");
      if (voltageRange == 1) Serial.print("0-3.3V)\n");
      else if (voltageRange == 2) Serial.print("0-1.65V)\n");
      else if (voltageRange == 3) Serial.print("0-0.825V)\n");
    Serial.print(" 5 - Toggle auto horizontal (time) scaling (currently: ");
      if (autoHScale == true) Serial.print("On)\n");
      else if (autoHScale == false) Serial.print("Off)\n");
    Serial.print(" 6 - Toggle line/dot display (currently: ");
      if (linesNotDots == true) Serial.print("Lines)\n");
      else if (linesNotDots == false) Serial.print("Dots)\n");
    Serial.print(" 8 - Exit\n");
    
    // Wait for input/response, refresh display while in menu
    do {
      collectData();
      // Picture Display Loop
      u8g.firstPage();  
      do { draw(); } while( u8g.nextPage() );
    } while (Serial.available() == 0);
    inByte = Serial.read();
    
    if (inByte == '1') {
      Serial.print("Change threshold usage\n");
      Serial.print(" 0 - Off\n");
      Serial.print(" 1 - Rise\n");
      Serial.print(" 2 - Fall\n");
      do { } while (Serial.available() == 0);
      dataByte = Serial.read();
      if (dataByte == '0') useThreshold = 0;
      else if (dataByte == '1') useThreshold = 1;
      else if (dataByte == '2') useThreshold = 2;
    } else if (inByte == '2') {
      Serial.print("Change threshold value (thresholds for 0-3.3V,0-1.65V,0-0.825V ranges)\n");
      Serial.print(" 0 - 32 (0.41V, 0.21V, 0.10V)\n");
      Serial.print(" 1 - 80 (1.04V, 0.52V, 0.26V)\n");
      Serial.print(" 2 - 128 (1.66V, 0.83V, 0.41V)\n");
      Serial.print(" 3 - 176 (2.28V, 1.14V, 0.57V)\n");
      Serial.print(" 4 - 224 (2.90V, 1.45V, 0.72V)\n");
      do { } while (Serial.available() == 0);
      dataByte = Serial.read();
      if (dataByte == '0') theThreshold = 32;
      else if (dataByte == '1') theThreshold = 80;
      else if (dataByte == '2') theThreshold = 128;
      else if (dataByte == '3') theThreshold = 176;
      else if (dataByte == '4') theThreshold = 224;
    } else if (inByte == '3') {
      Serial.print("Change sampling frequency\n");
      Serial.print(" 0 - 28 kHz (35 us/sample)\n");
      Serial.print(" 1 - 20 kHz (50 us/sample)\n");
      Serial.print(" 2 - 10 kHz (100 us/sample)\n");
      Serial.print(" 3 - 5 kHz (200 us/sample)\n");
      Serial.print(" 4 - 2.5 kHz (400 us/sample)\n");
      do { } while (Serial.available() == 0);
      dataByte = Serial.read();
      if (dataByte == '0') timePeriod = 35;
      else if (dataByte == '1') timePeriod = 50;
      else if (dataByte == '2') timePeriod = 100;
      else if (dataByte == '3') timePeriod = 200;
      else if (dataByte == '4') timePeriod = 400;
    } else if (inByte == '4') {
      Serial.print("Change voltage range\n");
      Serial.print(" 1 - 0-3.3V\n");
      Serial.print(" 2 - 0-1.65V\n");
      Serial.print(" 3 - 0-0.825V\n");
      do { } while (Serial.available() == 0);
      dataByte = Serial.read();
      if (dataByte == '1') voltageRange = 1;
      else if (dataByte == '2') voltageRange = 2;
      else if (dataByte == '3') voltageRange = 3;
    } else if (inByte == '5') {
      Serial.print("Toggle auto horizontal (time) scaling\n");
      Serial.print(" 0 - Off\n");
      Serial.print(" 1 - On\n");
      do { } while (Serial.available() == 0);
      dataByte = Serial.read();
      if (dataByte == '0') autoHScale = false;
      else if (dataByte == '1') autoHScale = true;
    } else if (inByte == '6') {
      Serial.print("Toggle line/dot display\n");
      Serial.print(" 0 - Lines\n");
      Serial.print(" 1 - Dots\n");
      do { } while (Serial.available() == 0);
      dataByte = Serial.read();
      if (dataByte == '0') linesNotDots = true;
      else if (dataByte == '1') linesNotDots = false;
    } else if (inByte == '8') {
      Serial.print("Bye!\n");
      exitLoop = true;
    }
  } while (exitLoop == false);
}

void draw(void) {
  int i;
  char buffer[16];
  
  u8g.setFont(u8g_font_micro);
  
  // Draw static text
  u8g.drawStr(0, 5+vTextShift, "Av");
  u8g.drawStr(0, 11+vTextShift, "Mx");
  u8g.drawStr(0, 17+vTextShift, "Mn");
  u8g.drawStr(0, 23+vTextShift, "PP");
  u8g.drawStr(0, 29+vTextShift, "Th");
  u8g.drawStr(24, 35+vTextShift, "V");
  u8g.drawStr(0, 41+vTextShift, "Tm");
  u8g.drawStr(4, 47+vTextShift, "ms/div");
  u8g.drawStr(20, 53+vTextShift, "Hz");
  u8g.drawStr(0, 59+vTextShift, "R");
  
  // Draw dynamic text
  if (autoHScale == true) u8g.drawStr(124, 5, "A");
  dtostrf(avgV, 3, 2, buffer);
  u8g.drawStr(12, 5+vTextShift, buffer);
  dtostrf(maxV, 3, 2, buffer);
  u8g.drawStr(12, 11+vTextShift, buffer);
  dtostrf(minV, 3, 2, buffer);
  u8g.drawStr(12, 17+vTextShift, buffer);
  dtostrf(ptopV, 3, 2, buffer);
  u8g.drawStr(12, 23+vTextShift, buffer);
  dtostrf(theFreq, 5, 0, buffer);
  u8g.drawStr(0, 53+vTextShift, buffer);
  if (useThreshold == 0) {
    u8g.drawStr(12, 29+vTextShift, "Off");
  } else if (useThreshold == 1) {
    u8g.drawStr(12, 29+vTextShift, "Rise");
    dtostrf((float) (theThreshold>>2) * voltageConst, 3, 2, buffer);
  } else if (useThreshold == 2) {
    u8g.drawStr(12, 29+vTextShift, "Fall");
    dtostrf((float) (theThreshold>>2) * voltageConst, 3, 2, buffer);
  }
  u8g.drawStr(8, 35+vTextShift, buffer);
  // Correctly format the text so that there are always 4 characters
  if (timePeriod < 400) {
    dtostrf((float) timePeriod/1000 * 25, 3, 2, buffer);
  } else if (timePeriod < 4000) {
    dtostrf((float) timePeriod/1000 * 25, 3, 1, buffer);
  } else if (timePeriod < 40000) {
    dtostrf((float) timePeriod/1000 * 25, 3, 0, buffer);
  } else { // Out of range
    dtostrf((float) 0.00, 3, 2, buffer);
  }
  u8g.drawStr(12, 41+vTextShift, buffer);
  if (voltageRange == 1) {
    u8g.drawStr(4, 59+vTextShift, "0-3.30");
  } else if (voltageRange == 2) {
    u8g.drawStr(4, 59+vTextShift, "0-1.65");
  } else if (voltageRange == 3) {
    u8g.drawStr(4, 59+vTextShift, "0-0.83");
  }
  
  // Display graph lines
  u8g.drawLine((128-numOfSamples),0,(128-numOfSamples),63);
  if (useThreshold != 0)
     for (i=29; i<127; i+=3)
        u8g.drawPixel(i,63-(theThreshold>>2));
  for (i=0; i<63; i+=5) {
     u8g.drawPixel(53,i);
     u8g.drawPixel(78,i);
     u8g.drawPixel(103,i);
     u8g.drawPixel(127,i);
  }
  // Threshold bar
  for (i=0; i<63; i+=3)
     u8g.drawPixel(thresLocation+(128-numOfSamples),i);
  // Draw ADC readings
  if (linesNotDots == true) {
    for (i=1; i<numOfSamples; i++) // Draw using lines
      u8g.drawLine(i+(128-numOfSamples)-1,adcReadings[i-1],i+(128-numOfSamples),adcReadings[i]);
  } else {
    for (i=2; i<numOfSamples; i++) // Draw using points
      u8g.drawPixel(i+(128-numOfSamples),adcReadings[i]);
  }
}

void setup() {
  u8g.begin();
  Serial.begin(9600);
  
  // Turn on LED backlight
  analogWrite(lcdLED, ledBacklight);
  
  #if FASTADC
    // set prescale to 16
    sbi(ADCSRA,ADPS2) ;
    cbi(ADCSRA,ADPS1) ;
    cbi(ADCSRA,ADPS0) ;
  #endif
  delay(100);
}

void loop() {
  collectData();
  // Picture Display Loop
  u8g.firstPage();  
  do { draw(); } while( u8g.nextPage() );

  // If user sends any serial data, show menu
  if (Serial.available() > 0) {
    handleSerial();
  }

  // rebuild the picture after some delay
  delay(100);
}

Here’s the Arduino sketch for the square wave generator shown in the second video:

void setup() {
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
}

int i = 0;

void loop() {
  digitalWrite(2, bitRead(i, 0));
  digitalWrite(3, bitRead(i, 1));
  digitalWrite(4, bitRead(i, 2));
  digitalWrite(5, bitRead(i, 3));
  digitalWrite(6, bitRead(i, 4));
  digitalWrite(7, bitRead(i, 5));
  digitalWrite(8, bitRead(i, 6));
  digitalWrite(9, bitRead(i, 7));
  digitalWrite(10, bitRead(i, 8));
  digitalWrite(11, bitRead(i, 9));
  digitalWrite(12, bitRead(i, 10));
  digitalWrite(13, bitRead(i, 11));
  
  i++;
  if (i >= 4096) {
    i = 0;
  }
}
  1. May 29, 2013 at 12:08 AM | #1

    nice!

  2. June 10, 2013 at 8:18 PM | #2

    Great job!! The best part is “However, the code is extremely portable, meaning that you should be able to program any other Arduino and have it up-and-running in no time.”

    Maybe porting to an arduino mega/uno with a 128×64 display?

    • eric-jan
      July 15, 2013 at 3:02 PM | #3

      Arduino FIO > has same processor as the UNO > AtMega 328 ?
      FIO only has an socket to place a XBee on it, for wireless coms.
      Only, haven’t found any wiring schematics to hook things up…. yet.

  3. Govor
    June 22, 2013 at 6:21 AM | #4

    Better make it work faster and more usable then converting to stupid arduinos where every dumbass can connect wires without any soldering knowledge

    • peckerdunne
      July 4, 2013 at 3:18 PM | #5

      Piles give you the spasms again?

    • Adrian
      May 18, 2014 at 1:48 PM | #6

      So are you saying that ‘Electronic’s Pro’s’ are ‘dumb ass’s for using Breadboard? This is a great little project, for teaching or as a starting point for something more powerful.
      Thank you to the Arthur of this project…

  4. eric-jan
    July 15, 2013 at 3:06 PM | #7

    eric-jan :
    Arduino FIO > has same processor as the UNO > AtMega 328 ?
    FIO only has an socket to place a XBee on it, for wireless coms.
    Only, haven’t found any wiring schematics to hook things up…. yet.

    Oops, i now see lines 27 through 34 of the main code show the connections !

  5. LUIS
    July 23, 2013 at 6:40 PM | #8

    HELLO FRIEND, excuse, but you have many mistakes in the code, I wonder if you will be able to correct it, thank you.

  6. July 26, 2013 at 10:45 PM | #9

    We are Openiumper,an open source hardware promoter from China,I am very honored that you are interested in our product mini12864 LCD module and using it.If you need any help,we are glad to serve you,and you can contact us for technical support at any time.

  7. July 26, 2013 at 10:48 PM | #10

    My email:zhangming@openjumper.com

  8. October 7, 2013 at 3:58 AM | #11

    Great Project :)

    can i use the same code with arduino uno & GLCD 128×64 like this one
    http://n.mtng.org/ele/arduino/oscillo.html

    if there is changes – what i need to do to make the code compatibel with UNO & GLCD 128×64

  9. Gbureck
    January 27, 2014 at 11:13 PM | #12

    habe Probleme:

    Fehlernachricht:

    U8glib\U8glib.cpp.o: In function ‘U8GLIB::initRW8Bit(_u8g_dev_t*, unsgned char,….

    vielen Dank für Ihre Hilfe

  10. February 18, 2014 at 3:18 AM | #13

    Hi! Great project! How to alter the project under the display Nokia5110 (PCD8544)?

  11. Planer
    July 13, 2014 at 11:00 PM | #14

    code for arduino mini pro on atmega328 + OLED Display 128*64

    #include “U8glib.h”
    #include

    // Variables you might want to play with
    byte useThreshold = 1; // 0 = Off, 1 = Rising, 2 = Falling
    byte theThreshold = 128; // 0-255, Multiplied by voltageConst
    unsigned int timePeriod = 200;// 0-65535, us or ms per measurement (max 0.065s or 65.535s)
    byte voltageRange = 1; // 1 = 0-3.3V, 2 = 0-1.65V, 3 = 0-0.825V
    //byte ledBacklight = 100;

    boolean autoHScale = true; // Automatic horizontal (time) scaling
    boolean linesNotDots = true; // Draw lines between data points

    // Variables that can probably be left alone
    const byte vTextShift = 3; // Vertical text shift (to vertically align info)
    const byte numOfSamples = 100; // Leave at 100 for 128×64 pixel display
    unsigned int HQadcReadings[numOfSamples];
    byte adcReadings[numOfSamples];
    byte thresLocation = 0; // Threshold bar location
    float voltageConst = 0.0793650; // Scaling factor for converting 0-63 to V
    float avgV = 0.0;
    float maxV = 0.0;
    float minV = 0.0;
    float ptopV = 0.0;
    float theFreq = 0;

    const byte theAnalogPin = A0; // Data read pin

    //const byte lcdLED = 6; // LED Backlight
    //const byte lcdA0 = 7; // Data and command selections. L: command H : data
    //const byte lcdRESET = 8; // Low reset
    //const byte lcdCS = 9; // SPI Chip Select (internally pulled up), active low
    //const byte lcdMOSI = 11; // SPI Data transmission
    //const byte lcdSCK = 13; // SPI Serial Clock

    // SW SPI:
    //U8GLIB_MINI12864 u8g(lcdSCK, lcdMOSI, lcdCS, lcdA0, lcdRESET);
    // HW SPI:
    //U8GLIB_MINI12864 u8g(lcdSCK, lcdMOSI, lcdCS, lcdA0, lcdRESET);
    U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); // I2C / TWI

    // High speed ADC code
    // From: http://forum.arduino.cc/index.php?PHPSESSID=e21f9a71b887039092c91a516f9b
    #define FASTADC 1
    // defines for setting and clearing register bits
    #ifndef cbi
    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
    #endif
    #ifndef sbi
    #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
    #endif

    void collectData(void) {
    unsigned int tempThres = 0;
    unsigned int i = 0;

    if (autoHScale == true) {
    // With automatic horizontal (time) scaling enabled,
    // scale quickly if the threshold location is far, then slow down
    if (thresLocation > 5*numOfSamples/8) {
    timePeriod = timePeriod + 10;
    } else if (thresLocation numOfSamples/2) {
    timePeriod = timePeriod + 2;
    } else if (thresLocation < numOfSamples/2) {
    timePeriod = timePeriod - 2;
    }
    }
    // Enforce minimum time periods
    if (timePeriod < 35) {
    timePeriod = 35;
    }

    // Adjust voltage contstant to fit the voltage range
    if (voltageRange == 1) {
    voltageConst = 0.0793650; // 0-5V
    } else if (voltageRange == 2) {
    voltageConst = 0.0261905; // 0-1.65V
    } else if (voltageRange == 3) {
    voltageConst = 0.0130952; //0-0.825V
    }

    // If using threshold, wait until it has been reached
    if (voltageRange == 1) tempThres = theThreshold << 2;
    else if (voltageRange == 2) tempThres = theThreshold <tempThres) && (i<32768)) i++;
    i = 0; while ((analogRead(theAnalogPin)<tempThres) && (i<32768)) i++;
    }
    else if (useThreshold == 2) {
    i = 0; while ((analogRead(theAnalogPin)<tempThres) && (itempThres) && (i<32768)) i++;
    }

    // Collect ADC readings
    for (i=0; i 35)
    delayMicroseconds(timePeriod-35);
    }
    for (i=0; i>4 >4 & 0b111111;
    else adcReadings[i] = 0b111111;
    } else if (voltageRange == 2) {
    if (HQadcReadings[i]>>3 >3 & 0b111111;
    else adcReadings[i] = 0b111111;
    } else if (voltageRange == 3) {
    if (HQadcReadings[i]>>2 >2 & 0b111111;
    else adcReadings[i] = 0b111111;
    }
    // Invert for display
    adcReadings[i] = 63-adcReadings[i];
    }

    // Calculate and display frequency of signal using zero crossing
    if (useThreshold != 0) {
    if (useThreshold == 1) {
    thresLocation = 1;
    while ((adcReadings[thresLocation]>2))) && (thresLocation(63-(theThreshold>>2))) && (thresLocation(63-(theThreshold>>2))) && (thresLocation<numOfSamples-1)) (thresLocation++);
    thresLocation++;
    while ((adcReadings[thresLocation]>2))) && (thresLocation<numOfSamples-1)) (thresLocation++);
    }

    theFreq = (float) 1000/(thresLocation * timePeriod) * 1000;
    }

    // Average Voltage
    avgV = 0;
    for (i=0; i<numOfSamples; i++)
    avgV = avgV + adcReadings[i];
    avgV = (63-(avgV / numOfSamples)) * voltageConst;

    // Maximum Voltage
    maxV = 63;
    for (i=0; i<numOfSamples; i++)
    if (adcReadings[i]<maxV) maxV = adcReadings[i];
    maxV = (63-maxV) * voltageConst;

    // Minimum Voltage
    minV = 0;
    for (i=0; iminV) minV = adcReadings[i];
    minV = (63-minV) * voltageConst;

    // Peak-to-Peak Voltage
    ptopV = maxV - minV;

    }

    void handleSerial(void) {
    char inByte;
    char dataByte;
    boolean exitLoop = false;
    do {
    // Clear out buffer
    do {
    inByte = Serial.read();
    } while (Serial.available() > 0);

    Serial.print("\nArduino LCD Oscilloscope\n");
    Serial.print(" 1 - Change threshold usage (currently: ");
    if (useThreshold == 0) Serial.print("Off)\n");
    else if (useThreshold == 1) Serial.print("Rise)\n");
    else if (useThreshold == 2) Serial.print("Fall)\n");
    Serial.print(" 2 - Change threshold value (currently: ");
    Serial.print(theThreshold, DEC); Serial.print(")\n");
    Serial.print(" 3 - Change sampling period (currently: ");
    Serial.print(timePeriod, DEC); Serial.print(")\n");
    Serial.print(" 4 - Change voltage range (currently: ");
    if (voltageRange == 1) Serial.print("0-5V)\n");
    else if (voltageRange == 2) Serial.print("0-1.65V)\n");
    else if (voltageRange == 3) Serial.print("0-0.825V)\n");
    Serial.print(" 5 - Toggle auto horizontal (time) scaling (currently: ");
    if (autoHScale == true) Serial.print("On)\n");
    else if (autoHScale == false) Serial.print("Off)\n");
    Serial.print(" 6 - Toggle line/dot display (currently: ");
    if (linesNotDots == true) Serial.print("Lines)\n");
    else if (linesNotDots == false) Serial.print("Dots)\n");
    Serial.print(" 8 - Exit\n");

    // Wait for input/response, refresh display while in menu
    do {
    collectData();
    // Picture Display Loop
    u8g.firstPage();
    do { draw(); } while( u8g.nextPage() );
    } while (Serial.available() == 0);
    inByte = Serial.read();

    if (inByte == '1') {
    Serial.print("Change threshold usage\n");
    Serial.print(" 0 - Off\n");
    Serial.print(" 1 - Rise\n");
    Serial.print(" 2 - Fall\n");
    do { } while (Serial.available() == 0);
    dataByte = Serial.read();
    if (dataByte == '0') useThreshold = 0;
    else if (dataByte == '1') useThreshold = 1;
    else if (dataByte == '2') useThreshold = 2;
    } else if (inByte == '2') {
    Serial.print("Change threshold value (thresholds for 0-5V,0-1.65V,0-0.825V ranges)\n");
    Serial.print(" 0 - 32 (0.41V, 0.21V, 0.10V)\n");
    Serial.print(" 1 - 80 (1.04V, 0.52V, 0.26V)\n");
    Serial.print(" 2 - 128 (1.66V, 0.83V, 0.41V)\n");
    Serial.print(" 3 - 176 (2.28V, 1.14V, 0.57V)\n");
    Serial.print(" 4 - 224 (2.90V, 1.45V, 0.72V)\n");
    do { } while (Serial.available() == 0);
    dataByte = Serial.read();
    if (dataByte == '0') theThreshold = 32;
    else if (dataByte == '1') theThreshold = 80;
    else if (dataByte == '2') theThreshold = 128;
    else if (dataByte == '3') theThreshold = 176;
    else if (dataByte == '4') theThreshold = 224;
    } else if (inByte == '3') {
    Serial.print("Change sampling frequency\n");
    Serial.print(" 0 - 28 kHz (35 us/sample)\n");
    Serial.print(" 1 - 20 kHz (50 us/sample)\n");
    Serial.print(" 2 - 10 kHz (100 us/sample)\n");
    Serial.print(" 3 - 5 kHz (200 us/sample)\n");
    Serial.print(" 4 - 2.5 kHz (400 us/sample)\n");
    do { } while (Serial.available() == 0);
    dataByte = Serial.read();
    if (dataByte == '0') timePeriod = 35;
    else if (dataByte == '1') timePeriod = 50;
    else if (dataByte == '2') timePeriod = 100;
    else if (dataByte == '3') timePeriod = 200;
    else if (dataByte == '4') timePeriod = 400;
    } else if (inByte == '4') {
    Serial.print("Change voltage range\n");
    Serial.print(" 1 - 0-5V\n");
    Serial.print(" 2 - 0-1.65V\n");
    Serial.print(" 3 - 0-0.825V\n");
    do { } while (Serial.available() == 0);
    dataByte = Serial.read();
    if (dataByte == '1') voltageRange = 1;
    else if (dataByte == '2') voltageRange = 2;
    else if (dataByte == '3') voltageRange = 3;
    } else if (inByte == '5') {
    Serial.print("Toggle auto horizontal (time) scaling\n");
    Serial.print(" 0 - Off\n");
    Serial.print(" 1 - On\n");
    do { } while (Serial.available() == 0);
    dataByte = Serial.read();
    if (dataByte == '0') autoHScale = false;
    else if (dataByte == '1') autoHScale = true;
    } else if (inByte == '6') {
    Serial.print("Toggle line/dot display\n");
    Serial.print(" 0 - Lines\n");
    Serial.print(" 1 - Dots\n");
    do { } while (Serial.available() == 0);
    dataByte = Serial.read();
    if (dataByte == '0') linesNotDots = true;
    else if (dataByte == '1') linesNotDots = false;
    } else if (inByte == '8') {
    Serial.print("Bye!\n");
    exitLoop = true;
    }
    } while (exitLoop == false);

    }

    void draw(void) {
    int i;
    char buffer[16];

    u8g.setFont(u8g_font_micro);

    // Draw static text
    u8g.drawStr(0, 5+vTextShift, "Av");
    u8g.drawStr(0, 11+vTextShift, "Mx");
    u8g.drawStr(0, 17+vTextShift, "Mn");
    u8g.drawStr(0, 23+vTextShift, "PP");
    u8g.drawStr(0, 29+vTextShift, "Th");
    u8g.drawStr(24, 35+vTextShift, "V");
    u8g.drawStr(0, 41+vTextShift, "Tm");
    u8g.drawStr(4, 47+vTextShift, "ms/div");
    u8g.drawStr(20, 53+vTextShift, "Hz");
    u8g.drawStr(0, 59+vTextShift, "R");

    // Draw dynamic text
    if (autoHScale == true) u8g.drawStr(124, 5, "A");
    dtostrf(avgV, 3, 2, buffer);
    u8g.drawStr(12, 5+vTextShift, buffer);
    dtostrf(maxV, 3, 2, buffer);
    u8g.drawStr(12, 11+vTextShift, buffer);
    dtostrf(minV, 3, 2, buffer);
    u8g.drawStr(12, 17+vTextShift, buffer);
    dtostrf(ptopV, 3, 2, buffer);
    u8g.drawStr(12, 23+vTextShift, buffer);
    dtostrf(theFreq, 5, 0, buffer);
    u8g.drawStr(0, 53+vTextShift, buffer);
    if (useThreshold == 0) {
    u8g.drawStr(12, 29+vTextShift, "Off");
    } else if (useThreshold == 1) {
    u8g.drawStr(12, 29+vTextShift, "Rise");
    dtostrf((float) (theThreshold>>2) * voltageConst, 3, 2, buffer);
    } else if (useThreshold == 2) {
    u8g.drawStr(12, 29+vTextShift, "Fall");
    dtostrf((float) (theThreshold>>2) * voltageConst, 3, 2, buffer);
    }
    u8g.drawStr(8, 35+vTextShift, buffer);
    // Correctly format the text so that there are always 4 characters
    if (timePeriod < 400) {
    dtostrf((float) timePeriod/1000 * 25, 3, 2, buffer);
    } else if (timePeriod < 4000) {
    dtostrf((float) timePeriod/1000 * 25, 3, 1, buffer);
    } else if (timePeriod < 40000) {
    dtostrf((float) timePeriod/1000 * 25, 3, 0, buffer);
    } else { // Out of range
    dtostrf((float) 0.00, 3, 2, buffer);
    }
    u8g.drawStr(12, 41+vTextShift, buffer);
    if (voltageRange == 1) {
    u8g.drawStr(4, 59+vTextShift, "0-5");
    } else if (voltageRange == 2) {
    u8g.drawStr(4, 59+vTextShift, "0-1.65");
    } else if (voltageRange == 3) {
    u8g.drawStr(4, 59+vTextShift, "0-0.83");
    }

    // Display graph lines
    u8g.drawLine((128-numOfSamples),0,(128-numOfSamples),63);
    if (useThreshold != 0)
    for (i=29; i>2));
    for (i=0; i<63; i+=5) {
    u8g.drawPixel(53,i);
    u8g.drawPixel(78,i);
    u8g.drawPixel(103,i);
    u8g.drawPixel(127,i);
    }
    // Threshold bar
    for (i=0; i<63; i+=3)
    u8g.drawPixel(thresLocation+(128-numOfSamples),i);
    // Draw ADC readings
    if (linesNotDots == true) {
    for (i=1; i<numOfSamples; i++) // Draw using lines
    u8g.drawLine(i+(128-numOfSamples)-1,adcReadings[i-1],i+(128-numOfSamples),adcReadings[i]);
    } else {
    for (i=2; i 0) {
    handleSerial();
    }

    // rebuild the picture after some delay
    delay(100);

    }

  12. September 7, 2014 at 12:28 PM | #15

    Hi,

    Thanks for the very good article.

    I’m trying to add an extra interval for 0-5V, but I don’t understand how the folowing lines needs to be changed.


    if (voltageRange == 1) tempThres = theThreshold << 2;


    if (voltageRange == 1) {
    if (HQadcReadings[i]>>4 >4 & 0b111111;
    else adcReadings[i] = 0b111111;

  13. Dmitrii
    September 18, 2014 at 3:52 PM | #16

    Interesing idea. But unfortunaly very slow response of display. It can work more as registrator. But for working as oscilloscope it needs for increase speed of scanning signal and responce of display/.

  1. June 10, 2013 at 5:56 AM | #1
  2. June 20, 2013 at 11:56 AM | #2
  3. July 15, 2013 at 3:50 AM | #3
  4. July 15, 2013 at 3:50 AM | #4
  5. August 3, 2013 at 12:35 PM | #5
  6. August 29, 2013 at 2:33 AM | #6
  7. September 13, 2014 at 10:14 PM | #7

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>