Arduino FIO LCD Oscilloscope

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:

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:

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

32 thoughts to “Arduino FIO LCD Oscilloscope”

  1. 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?

    1. 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.

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

    1. 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…

  3. Pingback: Arduino FIO LCD Oscilloscope - Inspired by Nature
  4. Pingback: Arduino FIO LCD Oscilloscope - Inspired by Nature
  5. 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 !

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

  7. 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.

  8. habe Probleme:

    Fehlernachricht:

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

    vielen Dank für Ihre Hilfe

  9. 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);

    }

  10. 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;

  11. 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/.

  12. Hello Guys,

    I currently having problems to compile this code to my arduino, result with following errors:
    oscilloscope.ino:1:10: error: #include expects “FILENAME” or
    oscilloscope:34: error: ‘U8GLIB_SSD1306_128X64’ does not name a type
    oscilloscope.ino: In function ‘void collectData()’:
    oscilloscope:51: error: ‘gt’ was not declared in this scope
    oscilloscope:51: error: expected )' before ';' token
    oscilloscope:51: error: expected
    ;’ before ‘)’ token
    oscilloscope:53: error: expected }' before 'else'
    oscilloscope:53: error: expected
    )’ before ‘numOfSamples’
    oscilloscope:55: error: ‘lt’ was not declared in this scope
    oscilloscope:55: error: expected )' before ';' token
    oscilloscope:55: error: expected
    ;’ before ‘)’ token
    oscilloscope.ino: At global scope:
    oscilloscope:60: error: expected unqualified-id before ‘if’
    oscilloscope:60: error: expected unqualified-id before numeric constant
    oscilloscope:64: error: expected unqualified-id before ‘if’
    oscilloscope:66: error: expected unqualified-id before ‘else’
    oscilloscope:68: error: expected unqualified-id before ‘else’
    oscilloscope:72: error: expected unqualified-id before ‘if’
    oscilloscope:72: error: expected constructor, destructor, or type conversion before ‘;’ token
    oscilloscope:72: error: expected unqualified-id before numeric constant
    oscilloscope:73: error: expected unqualified-id before ‘else’
    oscilloscope:73: error: expected constructor, destructor, or type conversion before ‘)’ token
    oscilloscope:73: error: expected constructor, destructor, or type conversion before ‘;’ token
    oscilloscope:73: error: expected )' before '&amp;' token
    oscilloscope:73: error: expected unqualified-id before numeric constant
    oscilloscope:74: error: expected constructor, destructor, or type conversion before '=' token
    oscilloscope:74: error: expected unqualified-id before 'while'
    oscilloscope:74: error: expected constructor, destructor, or type conversion before ')' token
    oscilloscope:74: error: expected constructor, destructor, or type conversion before ';' token
    oscilloscope:74: error: expected
    )’ before ‘&’ token
    oscilloscope:74: error: expected unqualified-id before numeric constant
    oscilloscope:75: error: expected declaration before ‘}’ token

  13. I assembled this oscilloscope, there are several problems. Prompt with their decision.

    1) When the test device, the graphics are cut in half (bottom). I had the idea that the scheme is not enough for the amplifier bias voltage. Someone did it? Can you give the scheme?

    2) When summing up his hands to the analog input of Arduino, is responsible for receiving the signal, the oscilloscope shows the sine wave cheerfully 50-52Hz (full, without clipping). Indications on the screen is updated several times per second. If you hear any waveform, the oscilloscope begins to slow down (readings and schedule may hang for a few seconds, up to 7-10 seconds). After changing picture hangs again. If the disable generator and bring again hand, in 50% of cases, skokrost display returns. Can you suggest a solution to this problem?

    Thank you

    1. I’ve transitioned my blog to another host and there may be some broken elements, but I just double checked and it appears as though the github gists are correctly displayed in Safari and Chrome on a Mac?

  14. Hello, how are you!
    I know this post is from the year 2013, but would like to enteder as is done the part that makes the trigger oscilloscope, had as you explain to me, for I am with a small oscilloscope project but do not want to copy and mount, I I want to like the fact enteder program works.
    I await a response.
    att,
    Evanuel Ribeiro

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.