Introduction

During the development of Arduino programs, I sometimes want to measure an analogue input signal together with a sketch-variable, whereby the program defines the trigger moment. To do this, I have built this oscilloscope library, which is in fact a circular buffer with oscilloscope functions. It has been kept as simple as possible to save memory space and optimize the speed.

The output data is simply printed to the serial port. The data can be shown in a graph by Excel: copy the data from the serial port window and paste it to Excel with "paste special". With the help of the trigger() we can catch very hard to find bugs:

Arduino debug oscilloscope
Arduino debug oscilloscope

Here is a sine wave of 2kHz measured with the fast 10-bit ADC, shown in Excel:

Oscilloscope base library for the Arduino
Oscilloscope base library for the Arduino

Advantages of the oscilloscope base library

  • It is a base-oscilloscope and measures all kinds of sketch-variables. An input voltage is the variable analogRead().
  • Measuring an analog input and a value: scope.sample(i, analogRead(pin));
  • The trigger signal can be generated by the sketch.
  • The sample rate is 37kS/s when using a 10-bit ADC.
  • It can be used with 1 or 2 channels.
  • We can use pre-trigger and post-trigger.
  • The sample rate is measured and printed automatically.

Fast 10-bit ADC

As part of the oscilloscope library, the fast 10-bit ADC is build too.

Future extensions

This library can in the future be used as parent class for extensive oscilloscope libraries.

Simple example with one channel

#include <Albert.h>
#include "Scope.h"
const byte ADCpin = 1;
Scope scope; 
void setup(void) 
{ Serial.begin(9600);
  scope.start(1); // 1 channel
  scope.trigger(); 
}
void loop(void) 
{ scope.sample(analogReadfast(ADCpin)); 
}

Example with two channels and pre-trigger

#include <Albert.h>
#include <Scope.h>
const byte ADCpin = 1;
Scope scope; 
void setup(void) 
{ Serial.begin(9600);
  scope.start(2, 5); // 2 channels, preSamples = 5
  for(int i=100; i<1000; i++)
  { scope.sample(i, analogReadfast(ADCpin)); // combination of a value i and ADC
    if(i==200) scope.trigger();
  }
  
}
void loop(void) 
{ 
}

Serial port output:

usPerDiv 26
ptr value
96 196 683
97 197 553
98 198 419
99 199 290
100 200 176
101 201 78 trigger
102 202 37
103 203 59
104 204 128
105 205 231
106 206 355
107 207 487
108 208 620
109 209 744

Oscilloscope library

/*                                                     
Scope.cpp

The library <Streaming.h> has to be installed too, download here: http://arduiniana.org/libraries/streaming/
The library <Albert.h> has to be installed too, download here: http://www.avdweb.nl/arduino/albert-library.html
                                                     
Copyright (C) 2016  Albert van Dalen http://www.avdweb.nl
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at http://www.gnu.org/licenses .

Version 1-1-2016
Version 2-1-2016

start           ___|____________________________________|________

sample          |_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_
                             
trigger         ____________|________________________________________
                             _
triggered       ____________| |______________________________________

samples                     1 2 3 4 5 6 
                                         ________
show            ________________________|        |______________
                    ____________________                 ___________
samplingOn      ___|                    |_______________|

*/

#include "Scope.h"
#include <Albert.h> // this library has to be installed too
#include <Streaming.h> // this library has to be installed too

void Scope::start(byte _channels, int _preSamples, unsigned int _recordLenght)  
{ ptr = samples = triggerPtr = preSamples = triggered = 0;
  stopPtr=32767;
  samplingOn=1;
  channels = _channels;
  recordLenght = maxBytes/(2*channels); 
  if(_recordLenght <= recordLenght) recordLenght = _recordLenght;
  if(abs(_preSamples) <= recordLenght) preSamples = _preSamples; 
}

void Scope::sample(int value) // 1 channel
{ if(samplingOn)
  { ringBuffer.chA[ptr] = value;
    sampleControl();
  }
}

void Scope::sample(int value1, int value2) // 2 channels
{ if(samplingOn)
  { ringBuffer.chAB[ptr][0] = value1;
    ringBuffer.chAB[ptr][1] = value2;
    sampleControl();
  }
}

void Scope::sampleControl()
{ if(triggered) 
  { samples=0;
    triggered_us = micros();  
  }  
  ptr = calcPtr(ptr+1);
  samples++;
  if(ptr==stopPtr) show();
}

unsigned int Scope::calcPtr(int _ptr)
{ if(_ptr>=recordLenght) return (_ptr - recordLenght); // happens most frequent
  if(_ptr<0) return (_ptr + recordLenght);
  return _ptr;
}

void Scope::trigger()
{ if(!triggered)
  { triggerPtr = ptr;
    stopPtr = calcPtr(triggerPtr-preSamples);
    triggered=1;
  }
}

void Scope::show() 
{ Serial << "\nusPerDiv " << (micros()-triggered_us)/samples;
  samplingOn=0;
  Serial << "\nptr value ";
  ptr=stopPtr;
  do 
  { if(channels==1) Serial << endl << ptr, ringBuffer.chA[ptr]; 
    else Serial << endl << ptr, ringBuffer.chAB[ptr][0], ringBuffer.chAB[ptr][1]; 
    if(ptr==triggerPtr) Serial << " trigger";
    ptr = calcPtr(ptr+1);  
  } 
  while (ptr!=stopPtr);
  stopPtr = 32767; 
}

void Scope::testBuffer()
{ start(1);
  for(int i=0; i<1000; i++)
  { if(i==0) trigger(); 
    sample(i);
  }  
}

 

/*
Scope.h
                                                     
Copyright (C) 2016  Albert van Dalen http://www.avdweb.nl
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License at http://www.gnu.org/licenses .

Version 1-1-2016
*/
 
#ifndef SCOPE_H
#define SCOPE_H

#include <Arduino.h>

const int maxBytes = 700; // max value without errors 780

class Scope
{
public:
  void start(byte _channels, int _preSamples=0, unsigned int _recordLenght=65535);
  void sample(int value);
  void sample(int value1, int value2);
  void trigger();
  void testBuffer();
   
protected:
  void sampleControl();
  unsigned int calcPtr(int ptr);
  void show();

  union{int chA[maxBytes/2]; int chAB[maxBytes/4][2];} ringBuffer; 

  unsigned long triggered_us;
  bool triggered, samplingOn; 
  byte channels; // 1 or 2 channels
  unsigned int recordLenght, ptr, samples, triggerPtr, stopPtr; 
  int preSamples;
};

#endif

Circular buffer size

I've figured out what the maximum allowed size is of the circular buffer: this is about 780 bytes. However, if the oscilloscope library is part of a large sketch, the circular buffer would have to be made smaller. Do this by reducing maxBytes below in the library:

const int maxBytes = 700;

If the buffer is made too large then we get an uncontrolled situation. While there are no compiler errors, the content at the end of the circular buffer may be overwritten by nonsense. The reason is that the Arduino works without operating system that keeps control.

Do you have any comments? Please let me know.
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.