Project showcase
Sampling Scope & Frequency Counter

Sampling Scope & Frequency Counter © LGPL

6-channel digital sampling oscilloscope with built-in frequency counter, maximum sampling rate 47kHz.

  • 4,234 views
  • 15 comments
  • 34 respects

Components and supplies

About this project

I wanted to be able to monitor electrical signals from the circuits I'm building, so I needed an oscilloscope. The solution I built uses an Arduino UNO in combination with Processing running on a PC.

The oscilloscope features a 6-channel selectable input (A0-A5 pins on Arduino), separate trigger input with rising or falling edge trigger setting, continuous or single sweep mode, adjustable time base (100us-200s/div), and adjusatable sensitivity and offset per channel. It also has a frequency counter mode that measures the frequency of the trigger input signal.

Note that I am using the timer and interrupt registers of my Arduino UNO directly, bypassing the Arduino API, so the code will not work on other Arduino versions without changes. Please make sure that you are familiar with timers and interrupts before you adapt the code.

Warning: do not connect signals outside the range of 0-5V directly to your Arduino inputs, because that may damage your device beyond repair!

The pictures below show the GUI of the oscilloscope on the computer. The buttons to the right control the settings of the scope. Pressing a number will change the active input channel, pressing a colored box will toggle the visibility of the respective channel trace on and off. To start/stop measuring, press "Start/Stop." In single sweep mode, you need to press "Start/Stop" every time you want to start a new sweep.

You can find schematics of NE555 wave generator circuit in many places on the internet, e.g. here: https://www.physicsforums.com/threads/555-timer-50-duty-cycle-astable-run-from-5v.923687/

Note: the number of samples in fast time bases is limited to achieve accurate high speed sampling. This explains the low resolution of the signal trace. On slower time bases the resolution is better (see below).

The time period for counting can be changed by pressing the "Time-" and "Time+" buttons. "Time+" will increase the number of periods (T) over which the frequency is averaged or change the clock division factor ("/1024", "/256" or "/64") which determines the resolution. "/64" gives the best resolution, but for lower frequency signals may cause timer overflows, leading to wrong readings. "Time-" does the opposite.

For testing, you can change the duty cycle of the Arduino PWM signal by pressing '"-" and "+" on your PC. If you connect an LED to the PW pin you will notice that the brightness of the LED changes.

Code

OscilloscopeA.inoArduino
This is the code for the oscilloscope project on the Arduino One
/*
   6-channel oscilloscope

  Operation mode:
    '#': frequency counter
    '*': oscilloscope
    '!': reset
    
   Trigger modes:
    'E': rising edge
    'F': falling edge

   Sweep mode:
    'C': continuous
    'D': single sweep

   Time base:
   100, 200, 500 us
   1, 2, 5 ms
   10, 20, 50 ms
   100, 200, 500 ms
   1, 2, 5 s
   10, 20, 50 s
   100, 200 s

   Timebase is identified by characters 'a'-'t'

   Channel:
   Channels are selected by '0'-'5'

   Counter time base
   'G': 1 period, clock divider 1024
   'H': 1 period, clock divider 256
   'I': 1 period, clock divider 64
   'J': 10 periods, clock divider 1024
   ...
   'U': 10000 periods, clock divider 64
*/

#define ScopeMode '*'
#define CounterMode '#'

#define InitialMode '#'

// Trigger input pin (only 2 or 3)
#define TriggerPin 2

#define MaxSamples 1000

// Pin to connect an LED showing counter measuring intervals
#define CounterPin 13
// Pin to generate a PWM signal (5 or 6 only)
#define PwmPin 6
#define PwmFreq65k 0x1
#define PwmFreq7k 0x2
#define PwmFreq976 0x3
#define PwmFreq244 0x4
#define PwmFreq061 0x5

// Timer and interrupt settings
#define INTBIT B00000001
#define TRIGCLR B00000001
#define TRIGRISE B00000011
#define TIMERCTCA B00000000
#define TIMERCTCB B10001000
#define TIMERCNTA B00000000
#define TIMERCNTB B00000000
#define TIMERNOCLK B11111000
#define TIMERPS0001 B00000001
#define TIMERPS0256 B00000100
#define TIMERPS1024 B00000101
#define ADCINIT B10000111
#define ADCSELECT B01100000
#define ADCSTART B01000000
#define ADCPSCLR B11111000
#define ADCPS016 B00000100
#define ADCPS032 B00000101
#define ADCPS064 B00000110
#define ADCPS128 B00000111
#define ADCREADY B00010000
#define CLEARADIF B10101111

boolean scope = true;

// The interrupt setting depends on the choice of the trigger pin
byte intBit = (INTBIT << (TriggerPin == 2 ? 0 : 1));

// Current mode variables
boolean continuousSweep = true;
byte currentChannel = 0;
char currentBase;

// Sample variables
volatile byte sample[MaxSamples];
byte timerPrescaler;
int samples;
volatile int index;
volatile int writeIndex;

// Frequencey counter current ode variables
int periodCount;
volatile int periods = 0;
volatile unsigned long count;
volatile byte counterDiv = 5;

// PWM output value
unsigned int pwm = 128;

/* Initialize the Analog-Digital Converter */
void initAdc()
{
  ADCSRA = ADCINIT;
  ADMUX  = ADCSELECT;
}

/* Read a sample from the ADC */
void readAdc()
{
  unsigned int result = 0;

  ADCSRA |= ADCSTART; // Start conversion
  while ((ADCSRA & ADCREADY) == 0);
  sample[index++] = ADCH;                       // 8-bit sample size for speed
  ADCSRA &= CLEARADIF;
}

/* Trigger Interrupt Service Routine */
#if (TriggerPin == 2)
ISR(INT0_vect)
#else
ISR(INT1_vect)
#endif
{
  if (scope)
  {
    EIMSK &= ~intBit;
    EIFR |= intBit;

    readAdc();                                    // Read first sample immediately
    TCNT1 = 0;                                    // Reset timer
    TCCR1B |= timerPrescaler;                     // Start timer now
  }
  else
  {
    int c = TCNT1;
    
    TCNT1 = 0;
    TCCR1B = counterDiv;                          // Start counter
    count += c;                                   // Add current timer to total count
    periods++;                                    // Another period counted
    if (periods > periodCount)                    // If all periods counted for a measurment...
    {
      TCCR1B = 0;                                 // ... Stop counter
      writeCount(count);                          // Report value to PC
      counterReset();                             // Reset counter for next measurement
    }
  }
}

/* Handle the end of a sweep */
void stopSweep()
{
  TCCR1B &= TIMERNOCLK;                           // Set clock select to 0 (no clock)
  index++;
  writeData();                                    // Write sampled data to serial connection
  if (continuousSweep)
  {
    scopeReset();                                 // Restart automatically in continuous sweep mode
  }
}

/* Reset the scope for a new sweep */
void scopeReset()
{
  TCCR1B &= TIMERNOCLK;                           // Stop the timer by setting clock select to 0 (no clock)

  Serial.print((char) 0xFF);                      // Mark end of sweep to console

  index = 0;                                      // Reset sweep data
  writeIndex = 0;

  EIFR |= intBit;                                 // Reset trigger interrupt flag
  EIMSK |= intBit;                                // Enable interrupts on trigger input

  // Wait for trigger signal interrupt
}

/* Reset the frequency counter to start another measurement */
void counterReset()
{
  digitalWrite(CounterPin, HIGH - digitalRead(CounterPin)); // Toggle indicator LED
  periods = 0;                                    // Reset counted periods
  count = 0UL;                                    // Reset total timer counts
  TCNT1 = 0;                                      // Reset timer
  EIFR != intBit;
}

/* Interrupt Service Routine for timer OCR compare match */
ISR(TIMER1_COMPA_vect)
{
  readAdc();                                      // Read next ADC sample and store it
//  if (currentBase >= 'g')                         // On slow time bases, write immediately
//  {
//    writeData();
//  }
  if (index >= samples)
  {
    stopSweep();                                  // Got all samples for this sweep, so end it
  }
}

/* Set the sample time for the selected time base.
 * The selection is done with a single character 'a'-'t'.
*/
void setSampleTime(char c)
{
  unsigned int cnt;

  currentBase = c;                                // Store the time base as current

  ADCSRA &= ADCPSCLR;                             // Clear prescaler
  // Set ADC prescaler
  switch (c)
  {
    case 'a':
    case 'b':
    case 'c':
    case 'd':
      ADCSRA |= ADCPS016;
      break;
    case 'e':
      ADCSRA |= ADCPS032;
      break;
    case 'f':
      ADCSRA |= ADCPS064;
      break;
    default:
      ADCSRA |= ADCPS128;
      break;
  }

  // Set #samples
  switch (c)
  {
    case 'a':
      samples = 48;
//      samples = 46;
      break;
    case 'b':
      samples = 95;
//      samples = 91;
      break;
    case 'c':
//      samples = (50 << (c - 'a'));
      samples = 238;
//      samples = 227;
      break;
      break;
    case 'd':
    case 'e':
      samples = 400;
      break;
    default:
      samples = (c >= 'o' ? 1000 : 500);
      break;
  }

  // Set timer prescaler
  timerPrescaler = (c <= 'j' ? TIMERPS0001 : (c <= 'r' ? TIMERPS0256 : TIMERPS1024));

  // Set counter max value
  switch (c)
  {
    case 'a':
    case 'b':
    case 'c':
//      cnt = 400;
      cnt = 336;
//      cnt = 352;
      break;
    case 'd':
    case 'e':
    case 'f':
    case 'g':
    case 'h':
      cnt = 400 << (c - 'd');
      break;
    case 'i':
      cnt = 16000;
      break;
    case 'j':
      cnt = 32000;
      break;
    case 'k':
      cnt = 250;
      break;
    case 'l':
    case 'm':
    case 'n':
      cnt = 625 << (c - 'l');
      break;
    case 'o':
    case 'p':
    case 'q':
      cnt = 3125 << (c - 'o');
      break;
    case 's':
      cnt = 15625;
      break;
    case 'r':
    case 't':
      cnt = 31250;
      break;
  }
  OCR1A = cnt;
}

/* Set trigger mode to falling or rising edge */
void setTriggerMode(char c)
{
  if (c == 'F')
  {
    EICRA &= ~(TRIGCLR << (TriggerPin == 2 ? 0 : 2));
  }
  else
  {
    EICRA |= TRIGRISE << (TriggerPin == 2 ? 0 : 2);
  }
}

/* Sweep mode (continuous or single) */
void setSweepMode(char c)
{
  continuousSweep = (c == 'C');                   // 'C' is continuous, 'S' is single
}

/* Set the channel '1'-'6' */
void setChannel(char c)
{
  currentChannel = (c - '1');                     // Internally, channels are 0-5
  ADMUX &= B11110000;
  ADMUX |= (currentChannel & 0x7);                // Switch the ADC multiplexer to the channel pin
}

/* Start oscilloscope mode */
void setScope()
{
  scope = true;
  digitalWrite(CounterPin, LOW);                  // Switch off counter indicator
  TCCR1A = TIMERCTCA;                             // Use Timer1 in 'match OCR' mode for sampling
  TCCR1B = TIMERCTCB;                             // No clock, so no interrupts yet
  TIMSK1 |= (1 << OCIE1A);                        // Enable timer1 compare interrupts
  execute(currentBase);                           // Set the time base to the last used
  scopeReset();                                   // Restart scope
}

/* Start frequency counter mode */
void setCounter()
{
  scope = false;
  periodCount = 1;
  digitalWrite(CounterPin, HIGH);                 // Switch on counter indicator
  TCCR1A = TIMERCNTA;                             // Use Timer1 in normal mode for counting
  TCCR1B = 0;                                     // Hold timer
  TIMSK1 &= ~(1 << OCIE1A);                       // Disable timer1 compare interrupts
  EIFR |= intBit;
  EIMSK |= intBit;                                // Enable external interrupt
  counterReset();                                 // Restart frequency counter
}

/* Set the number of periods to count for determining frequency */
void setPeriods(char c)
{
  int s = c - 'G';
  int p = s / 3;                                  // Period count 1, 10, 100, 1000 or 10000
  counterDiv = 5 - (s % 3);                       // Clock divider 64, 256 or 1024 for accuracy
  periodCount = 1;
  for (int per = 0; per < p; per++)
  {
    periodCount *= 10;
  }
}

/* Handle command characters sent from the console */
void execute(char c)
{
  switch (c)
  {
    case '!':
      break;
    case 'E':
    case 'F':
      setTriggerMode(c);
      break;
    case 'C':
    case 'D':
      setSweepMode(c);
      break;
    case '#':
      setCounter();
      break;
    case '*':
      setScope();
      break;
    case '-':
      if (pwm > 0)
      {
        pwm--;
      }
      analogWrite(PwmPin, pwm);
      break;
    case '+':
      if (pwm < 255)
      {
        pwm++;
      }
      analogWrite(PwmPin, pwm);
      break;
    default:
      if (c >= '1' && c <= '6')
      {
        setChannel(c);
      }
      else if (islower(c))
      {
        setSampleTime(c);
      }
      else
      {
        setPeriods(c);
      }
      break;
  }
  if (scope)
  {
    scopeReset();
  }
  else
  {
    counterReset();
  }
}

/* Send all available samples to the console */
void writeData()
{
  for (; writeIndex < index; writeIndex++)
  {
    Serial.print((char) sample[writeIndex]);
  }
}

/* Writes the count value for the defined number of periods in 4 bytes, LSB first */
void writeCount(unsigned long cnt)
{
  unsigned long c = cnt;
  
  for (int d = 0; d < 4; d++)
  {
    Serial.print((char) (c & 0xFF));
    c >>= 8;
  }
  Serial.print((char) (0xFF));                    // Send all ones to mark end of transmission
}

/* Standard set-up */
void setup()
{
  Serial.begin(115200);                           // Fast serial connection

  pinMode(TriggerPin, INPUT_PULLUP);              // The trigger input
  pinMode(CounterPin, OUTPUT);                    // The frequency counter indicator LED
  pinMode(PwmPin, OUTPUT);                        // A PWM source for testing

  TIMSK0 = 0;                                     // Disbable other timer interrupts
  TIMSK2 = 0;

//  TCCR0B = (TCCR0B & 0xF8) | PwmFreq7k;
  TCCR0B = (TCCR0B & 0xF8) | PwmFreq976;           // Set pin 5/6 PWM frequency
  
  // External interrupt for trigger signal
  EIMSK &= ~intBit;                               // Disable trigger interrupt first;
  EIFR |= intBit;                                 // Clear pending interrupts
  EICRA = TRIGRISE << (TriggerPin == 2 ? 0 : 2);  // Start with rising edge

  initAdc();                                      // Set up the analog inputs and the ADC

  // Set the default controls
  execute('E');                                   // Rising edge trigger
  execute('C');                                   // Continuous sweep
  execute('1');                                   // Channel A0
  execute('h');                                   // Time base at 10ms/div
  execute('G');                                   // Counter time base at 1x/1024

  execute(InitialMode);                           // Start in selected initial mode

  analogWrite(PwmPin, pwm);                       // Switch on PWM signal
}

/* Standard loop */
void loop()
{
  if (Serial.available())                         // If a command was sent from the console, ...
  {
    execute(Serial.read());                       // ...handle it here
  }
}
Oscilloscope.pdeProcessing
The code for the GUI of the oscilloscope built with Processing
static boolean Debug = false;

import processing.serial.*;

boolean scope = false;

static final String SerialPort ="COM3";            // Change this to match your Arduino port

static final float fclk = 16e6;                    // Arduinos clock frequency

///* Commands to the Arduino board */
//static final char Reset = 'X';
//static final char ScopeMode = 'Y';
//static final char CounterMode = 'Z';
//static final char Channel1 = 'A';
//static final char ChannelMax = 'F';
//static final char TrigRising = 'w';
//static final char TrigFalling = 'x';
//static final char ContSweep = 'y';
//static final char SingleSweep = 'z';
//static final char TimeBaseMin = 'a';
//static final char TimeBaseMax = 't';
//static final char CounterBaseMin = 'G';
//static final char CounterBaseMax = 'U';

/* Text definitions */
static final String Sampling = "Sampling...";
static final String SampleFreqFmt = "[%1.1f Hz]";
static final String FreqFmt = "%1.2f Hz";
static final String PeriodFmt = "T = %dx (/%d)";

/* Trace definitions */
static final int MaxSample = 1000;
//static final int MaxSample = 500;
static final int SampleSize = 8;
static final int SampleMax = (1 << SampleSize) - 1;
static final int Channels = 6; //ChannelMax - Channel1 + 1;

/* Screen size */
static final int MaxX = 1000;
static final int MaxY = 550;
/* Trace dimensions */
static final int Width = 800;
static final int Height = 500;

/* Time base parameters class */
class TimebaseSet
{
  TimebaseSet(int f, float pwr, int s, float st)
  {
    factor = f;
    p10 = pwr;
    samples = s;
    sampleTime = st;
  }

  int factor;
  float p10;
  int samples;
  float sampleTime;
};

/* Class to execute a button action, used in conjunction with Button class */
abstract class ButtonAction
{
  public abstract void execute();

  public void setButton(Button b)
  {
    _button = b;
  }
  protected Button _button;
};

/* Class to represent a button with an associated action */
public class Button
{
  public Button(int centerx, int centery, int w, int h, String name, long col, ButtonAction action)
  {
    _cx = centerx;
    _cy = centery;
    _buttonWidth = w;
    _buttonHeight = h;
    text = name;
    red = (int) (col >> 16);
    green = (int) ((col >> 8) & 0xFF);
    blue = (int) (col & 0xFF);
    _action = action;
    if (_action != null)
    {
      _action.setButton(this);
    }
  }

  void enable(boolean on)
  {
    enabled = on;
  }

  /* Shows the button on the screen */
  public void draw()
  {
    if (enabled)
    {
      rectMode(CENTER);
      fill(red, green, blue);
      stroke(192, 192, 192);
      rect(_cx, _cy, _buttonWidth, _buttonHeight);
      textSize(20);
      fill(255, 255, 255);
      textAlign(CENTER, CENTER);
      text(text, _cx, _cy - 3);
    }
  }

  /* Checks if the button was clicked, executes the action if so */
  public void isClicked(int x, int y)
  {
    int bw = _buttonWidth / 2;
    int bh = _buttonHeight / 2;

    boolean result = enabled && ((x >= _cx - bw && x <= _cx + bw && y >= _cy - bh && y <= _cy + bh));

    if (Debug && result)
    {
      println(text + " clicked!");
    }
    if (result)
    {
      _action.execute();
    }
  }

  protected int _cx;
  protected int _cy;
  protected int _buttonWidth;
  protected int _buttonHeight;
  public int red;
  public int green;
  public int blue;
  public String text;
  protected ButtonAction _action;
  protected boolean enabled = true;
};

/* Class for check box buttons */
class CheckButton extends Button
{
  class Toggle extends ButtonAction
  {
    public Toggle(ButtonAction action)
    {
      _taction = action;
    }

    public void execute()
    {
      if (Debug)
      {
        println(text + " toggled");
      }
      _state = !_state;
      _taction.execute();
    }

    protected ButtonAction _taction;
  };

  public CheckButton(int centerx, int centery, int w, int h, String name, long col, ButtonAction action)
  {
    super(centerx, centery, w, h, name, col, null);

    _action = new Toggle(action);
  }

  /* Shows the button on the screen */
  public void draw()
  {
    rectMode(CENTER);
    if (_state)
    {
      fill(255 - red, 255 - green, 255 - blue);
    } else
    {
      fill(red, green, blue);
    }
    stroke(192, 192, 192);
    rect(_cx, _cy, _buttonWidth, _buttonHeight);
    textSize(14);
    fill(255, 255, 255);
    textAlign(LEFT, CENTER);
    text(text, _cx + _buttonWidth / 2 + 5, _cy - 3);
  }

  /* Returns the current state of the check box */
  public boolean getState()
  {
    return _state;
  }

  protected boolean _state = false;
};

/* Class for channel selection actions */
class ChAction extends ButtonAction
{
  ChAction(int ch)
  {
    _ch = ch;
  }

  public void execute()
  {
    if (_button.red == 0 & _button.green == 0 && _button.blue == 0)
    {
      setChannel(_ch);
    } else
    {
      showChannel(_ch);
    }
  }

  protected int _ch;
};

int currentChannel = 0;

TimebaseSet[] timebase = {
  new TimebaseSet(1, 0.0001, 48, 0.000021), // 0
  new TimebaseSet(2, 0.0001, 95, 0.000021), 
  new TimebaseSet(5, 0.0001, 238, 0.000021), 
  new TimebaseSet(1, 0.001, 400, 0.000025), 
  new TimebaseSet(2, 0.001, 400, 0.000050), 
  new TimebaseSet(5, 0.001, 500, 0.000100), 
  new TimebaseSet(1, 0.01, 500, 0.000200), // 6
  new TimebaseSet(2, 0.01, 500, 0.000400), 
  new TimebaseSet(5, 0.01, 500, 0.001), 
  new TimebaseSet(1, 0.1, 500, 0.002), 
  new TimebaseSet(2, 0.1, 500, 0.004), 
  new TimebaseSet(5, 0.1, 500, 0.01), 
  new TimebaseSet(1, 1, 500, 0.02), 
  new TimebaseSet(2, 1, 500, 0.04), 
  new TimebaseSet(5, 1, 1000, 0.05), 
  new TimebaseSet(1, 10, 1000, 0.1), 
  new TimebaseSet(2, 10, 1000, 0.2), 
  new TimebaseSet(5, 10, 1000, 0.5), 
  new TimebaseSet(1, 100, 1000, 1.0), 
  new TimebaseSet(2, 100, 1000, 2.0)
};

int timebaseIndex = 7;
float timediv;
float sens;                     /* mV/div */

int samples;
int channelSamples[] = { 0, 0, 0, 0, 0, 0 };
float sampleTime;
float sample[][] = new float[Channels][MaxSample];

long  channelColor[] = { 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFF0000, 0x00FF00, 0x0000FF };
float  channelSampleTime[] = { 0, 0, 0, 0, 0, 0 };
boolean channelOn[] = { false, false, false, false, false, false };
boolean channelVisible[] = { false, false, false, false, false, false };

float periodCount;
float divider = 1024.0;
float count = 0.0;
float countDigit = 1.0;
float frequency = 0.0;
boolean countingInd = false;
char periodCountInd = 'G'; //CounterBaseMin;


Serial port;
PFont f;

int set = 0;
int index = 0;

int x0 = 0, x1 = Width;
int y0 = 25, y1 = Height + y0;
int divx = Width / 10;
int divy = Height / 10;

float scalex;
float scaley;
float xcenter = (x0 + x1) / 2;
float ycenter = (y0 + y1) / 2;
float offset[] = { 0, 0, 0, 0, 0, 0 };
int sensFact[] = { 5, 5, 5, 5, 5, 5 };
int sens10[] = { 100, 100, 100, 100, 100, 100 };

boolean measuring = false;
//int flushingCountData = 0;
//boolean getChannelCount = false;

ArrayList<Button> button = new ArrayList();
CheckButton sweepButton;
CheckButton triggerButton;

void scale()
{
  scalex = Width / timediv / 10.0;
  scaley = Height / (sens / 1000.0) / 10.0;
}

float plotX(float time)
{
  return scalex * time;
}

float plotY(int channel, float voltage)
{
  return y1 - scaley * (voltage + offset[channel]);
}

/* Switch between scope and frequency counter mode */
void toggleMode()
{
  scope = !scope;
  //getChannelCount = scope;    // Wait for the number of channels reported by Arduino
  //flushingCountData = (scope ? 3 : 0);
  port.write(scope ? '*' : '#');
//  port.write(scope ? ScopeMode : CounterMode);
  port.clear();
  if (scope)
  {
    updateTimebase();
  } else
  {
    //scope = false;
    updatePeriodCount();
  }
}

/* Switch active channel for receiving samples */
void setChannel(int ch)
{
  channelOn[currentChannel] = false;
  currentChannel = ch;
  for (int i = 0; i < MaxSample; i++)
  {
    sample[currentChannel][i] = 0;
  }
  channelSamples[currentChannel] = samples;
  channelOn[currentChannel] = true;
  channelVisible[currentChannel] = true;
  sens = sensFact[currentChannel] * sens10[currentChannel];
  port.write((char) currentChannel + '1');  // Send channel switch command to Arduino
  //port.write((char) (currentChannel + Channel1));  // Send channel switch command to Arduino
  index = 0;
}

/* Toggle a channel's trace visibility */
void showChannel(int ch)
{
  channelVisible[ch] = !channelVisible[ch];
}

/* Handle start/stop button press */
void startStop()
{
  measuring = !measuring;
  index = 0;
  if (measuring && sweepButton.getState())
  {
    if (!Debug)
    {
      port.write('!');    // Send a reset command to Arduino
      //port.write(Reset);    // Send a reset command to Arduino
    }
  }
}

/* Toggle sweep mode between single and continuous */
void setSweep()
{
  if (!Debug)
  {
    port.write(sweepButton.getState() ? 'D' : 'C');
    //port.write(sweepButton.getState() ? SingleSweep : ContSweep);
  }
  index = 0;
}

/* Toggle trigger mode between rising and falling edge */
void setTriggerMode()
{
  if (!Debug)
  {
    port.write(triggerButton.getState() ? 'F' : 'E');
    //port.write(triggerButton.getState() ? TrigFalling : TrigRising);
  }
  index = 0;
}

/* Increase sensitivity */
void sensUp()
{
  if (sens > 10.0)
  {
    sensFact[currentChannel] /= 2;
    if (sensFact[currentChannel] == 0)
    {
      sens10[currentChannel] /= 10;
      sensFact[currentChannel] = 5;
    }
  }
}

/* Decrease sensitivity */
void sensDn()
{
  if (sens < 5000.0)
  {
    sensFact[currentChannel] *= 2;
    if (sensFact[currentChannel] == 4)
    {
      sensFact[currentChannel] = 5;
    }
    if (sensFact[currentChannel] >= 10)
    {
      sens10[currentChannel] *= 10;
      sensFact[currentChannel] = 1;
    }
  }
}

/* Update time base based on the value of timebaseIndex */
void updateTimebase()
{
  timediv = (float) timebase[timebaseIndex].factor * timebase[timebaseIndex].p10;
  samples = timebase[timebaseIndex].samples;
  sampleTime = timebase[timebaseIndex].sampleTime;
  channelSamples[currentChannel] = samples;
  scale();
  if (!Debug)
  {
    port.write((char) (timebaseIndex + 'a'));    // Send command to Arduino
    //port.write((char) (timebaseIndex + TimeBaseMin));    // Send command to Arduino
  }
  index = 0;
}

/* Increase time base (slower scan) */
void timeUp()
{
  if (scope)
  {
    if (timebaseIndex < 19)
    //if (timebaseIndex < TimeBaseMax - TimeBaseMin)
    {
      timebaseIndex++;
      updateTimebase();
    }
  } else
  {
    if (periodCountInd < 'U')
    //if (periodCountInd < CounterBaseMax)
    {
      periodCountInd++;
      updatePeriodCount();
    }
  }
}

/* Decrease time base (faster scan) */
void timeDn()
{
  if (scope)
  {
    if (timebaseIndex > 0)
    {
      timebaseIndex--;
      updateTimebase();
    }
  } else
  {
    if (periodCountInd > 'G')
    //if (periodCountInd > CounterBaseMin)
    {
      periodCountInd--;
      updatePeriodCount();
    }
  }
}

/* Update periods */
void updatePeriodCount()
{
  periodCount = 1.0;
  divider = 64.0;
  int s = periodCountInd - 'G';
  //int s = periodCountInd - CounterBaseMin;
  int p = s / 3;
  int d = 2 - (s % 3);
  for (int div = 0; div < d; div++)
  {
    divider *= 4.0;
  }
  for (int per = 0; per < p; per++)
  {
    periodCount *= 10.0;
  }
  port.write(periodCountInd);
  count = 0.0;
  countDigit = 1.0;
}

/* Initiate */
void setup()
{
  if (!Debug)
  {
    port = new Serial(this, SerialPort, 115200);
  } else
  {
    // For testing simulate a sine wave
    for (index = 0; index < MaxSample; index++)
    {
      sample[0][index] = 2.5*sin(2.0*PI*20.0*((float) (index * sampleTime / 1000.0)))+2.5;
    }
  }

  // Screen
  size(1000, 550);
  frameRate(50);
  background(0);
  f = createFont("System", 16);

  // Define buttons
  button.add(new Button(900, 50, 190, 50, "Scope / Count", 0x404040, new ButtonAction() { 
    public void execute() {
      toggleMode();
    }
  }
  ));
  button.add(new Button(900, 110, 190, 50, "Start / Stop", 0x404040, new ButtonAction() {
    public void execute() {
      startStop();
    }
  }
  ));
  for (int ch = 0; ch < Channels; ch++)
  {
    button.add(new Button(825 + ch * 30, 205, 25, 25, "" + (char) ('1' + ch), 0, new ChAction(ch)));
  }
  for (int ch = 0; ch < Channels; ch++)
  {
    button.add(new Button(825 + ch * 30, 240, 25, 25, "", channelColor[ch], new ChAction(ch)));
  }
  button.add(sweepButton = new CheckButton(825, 280, 20, 20, "Single Sweep", 0x000000, new ButtonAction() {
    public void execute() {
      setSweep();
    }
  }
  ));
  button.add(triggerButton = new CheckButton(825, 315, 20, 20, "Trigger on falling edge", 0x000000, new ButtonAction() {
    public void execute() {
      setTriggerMode();
    }
  }
  ));
  button.add(new Button(850, 380, 90, 50, "Sens -", 0x404040, new ButtonAction() {
    public void execute() {
      sensDn();
    }
  }
  ));
  button.add(new Button(950, 380, 90, 50, "Sens +", 0x404040, new ButtonAction() {
    public void execute() {
      sensUp();
    }
  }
  ));
  button.add(new Button(850, 440, 90, 50, "Offset -", 0x404040, new ButtonAction() {
    public void execute()
    {
      if (offset[currentChannel] > -15.0)
      {
        offset[currentChannel] -= 0.5;
      }
    }
  }
  ));
  button.add(new Button(950, 440, 90, 50, "Offset +", 0x404040, new ButtonAction() {
    public void execute()
    {
      if (offset[currentChannel] < 15.0)
      {
        offset[currentChannel] += 0.5;
      }
    }
  }
  ));
  button.add(new Button(850, 500, 90, 50, "Time -", 0x404040, new ButtonAction() {
    public void execute() {
      timeDn();
    }
  }
  ));
  button.add(new Button(950, 500, 90, 50, "Time +", 0x404040, new ButtonAction() {
    public void execute() {
      timeUp();
    }
  }
  ));

  // Set initial configuration of the scope (matches Arduino defaults)
  updateTimebase();
  updatePeriodCount();
  setChannel(currentChannel);
  startStop();
}

void draw()
{
  clear();

  if (measuring & scope)
  {
    textSize(16);
    fill(255, 255, 255);
    text(Sampling, 900, 147);
    text(String.format(SampleFreqFmt, 1.0 / sampleTime), 900, 170);
  }

  /* Gridlines */
  stroke(0, 128, 0);

  /* Vertical */
  for (int x = 0; x <= x1; x += divx)
  {
    line(x, y1, x, y0);
  }
  /* Horizontal */
  for (int y = y0; y <= y1; y += divy)
  {
    line(x0, y, x1, y);
  }

  /* Emphasize horizontal and vertical center lines */
  stroke(0, 255, 0);
  line(xcenter, y0, xcenter, y1);
  line(x0, ycenter, x1, ycenter);

  /* Show all buttons */
  for (int b = 0; b < button.size(); b++)
  {
    button.get(b).draw();
  }

  /* Show active channel */
  stroke(255, 0, 0);
  noFill();
  rect(825 + currentChannel * 30, 205, 25, 25);

  /* Scaling info text */
  textSize(20);
  fill(255, 255, 255);
  textAlign(LEFT, CENTER);
  textFont(f);
  fill(0, 255, 0);
  sens = sensFact[currentChannel] * sens10[currentChannel];
  text(String.format("%d%cV/div", (int) (sens > 999 ? sens / 1000 : sens), 
    sens < 1000.0 ? 'm' : '\0'), x0 + 4, y0 + divy / 4);
  text(String.format("Offset: %+1.1fV", offset[currentChannel]), x0 + 4, y0 + 3 * divy / 4);

  int tb = (int) (timediv);
  char unit = ' ';

  if (timediv < 0.001)
  {
    tb = (int) (timediv * 1000000.0 + 0.5);
    unit = 'u';
  } else if (timediv < 1.0)
  {
    tb = (int) (timediv * 1000.0 + 0.5);
    unit = 'm';
  }

  textAlign(CENTER, CENTER);
  text(String.format("%d%cs/div", tb, unit), x0 + 19 * divx / 2, y0 + 19 * divy / 2);

  if (scope)
  {
    /* Display the sample traces */

    for (int ch = 0; ch < Channels; ch++)
    {
      if (channelVisible[ch])
      {
        sens = sensFact[ch] * sens10[ch];
        scale();
        float prevx = plotX(0.0);
        float prevy = plotY(ch, sample[ch][0]);
        long c = channelColor[ch];

        prevy = max(prevy, y0);
        prevy = min(prevy, y1);

        stroke(c >> 16, (c >> 8) & 0xFF, c & 0xFF);

        for (int i = 1; i < channelSamples[currentChannel] && prevx < x1; i++)
        {
          float x = plotX(i * sampleTime);
          float y = plotY(ch, sample[ch][i]);
          if (y <= y1 && y >= y0)
          {
            line(prevx, prevy, x, y);
            prevx = x;
            prevy = y;
          }
        }
      }
    }
  } else
  {
    rectMode(CENTER);
    stroke(255, 255, 255);
    fill(0, 0, 0);
    rect((x0 + x1) / 2, ycenter, 4 * divx, 2 * divy);

    if (countingInd)
    {
      fill(255, 0, 0);
      rect(xcenter - 7 * divx / 4, ycenter - 3 * divy / 4, 20, 20);
    }

    textAlign(CENTER, CENTER);
    textSize(30);
    fill(255, 255, 255);
    text(String.format(FreqFmt, frequency), xcenter, ycenter - divy / 4);
    textSize(18);
    text(String.format(PeriodFmt, (int) periodCount, (int) divider), xcenter, ycenter + divy / 2);
  }
  sens = sensFact[currentChannel] * sens10[currentChannel];
}

/* Handle button clicks */
void mouseClicked()
{
  int mx = mouseX; 
  int my = mouseY;

  for (int b = 0; b < button.size(); b++)
  {
    button.get(b).isClicked(mx, my);
  }
}

void keyPressed()
{
  if (!Character.isLetter(key))
  {
    port.write(key);
  }
}

/* Handle incoming sample data from Arduino */
void serialEvent(Serial port)
{
  int s;
  float v;

  try
  {
    if (port.available() != 0)
    {
      s = port.read();
      v = s;
      if (s == 0xFF)    // End-of-sweep indicator
      {
        if (scope)
        {
          //if (flushingCountData > 0)
          //{
          //  flushingCountData--;
          //}
          index = 0;
        } else
        {
          if (countDigit >= 4294967296.0)
          {
            if (count > 0)
            {
              frequency = fclk * periodCount / (count * divider) ;
            }
            count = 0.0;
            countDigit = 1;
            countingInd = !countingInd;
          } else
          {
            count += (v * countDigit);
            countDigit *= 256;
          }
        }
      } else
      {
        if (scope)
        {
          //if (getChannelCount)
          //{
          //  if (flushingCountData == 0)            // If 3 consecutive 0xFF found, number of channels follows 
          //  {
          //    getChannelCount = false;
          //    s -= '0';
          //    for (int i = 0; i < 6; i++)
          //    {
          //      button.get(2 + i).enable(i < s);
          //      button.get(8 + i).enable(i < s);
          //    }
          //  } else
          //  {
          //    flushingCountData = 3;
          //  }
          //}
          if (measuring)
          {
            sample[currentChannel][index++] = map(s, 0.0, 255.0, 0.0, 5.0);
            if (index >= channelSamples[currentChannel])
            {
              index = 0;
              if (sweepButton.getState())
              {
                measuring = false;
              }
            }
          }
        } else
        {
          count += (v * countDigit);
          countDigit *= 256;
        }
      }
    }
  }

  catch(RuntimeException e)
  {
    e.printStackTrace();
  }
}

Schematics

Oscilloscope.fzz
Fritzing file showing the connections for the oscilloscope
oscilloscope_CbRi0lv6ys.fzz

Comments

Similar projects you might like

Frequency Counter for PC

Project tutorial by moty

  • 13,211 views
  • 0 comments
  • 25 respects

Frequency Counter for Android

Project tutorial by moty

  • 3,047 views
  • 3 comments
  • 9 respects

Android Frequency Counter

Project tutorial by Projecter

  • 2,145 views
  • 0 comments
  • 5 respects

Arduino Frequency Counter with 16×2 LCD Display

Project in progress by jasir

  • 19,108 views
  • 6 comments
  • 17 respects

Counter

Project tutorial by moty

  • 4,671 views
  • 2 comments
  • 6 respects

Duty Cycle Calculator and Frequency Meter

Project tutorial by Boaz Lawnce

  • 1,649 views
  • 0 comments
  • 5 respects
Add projectSign up / Login