Introduction: Arduino High Speed Oscilloscope With PC Interface

Use your Arduino and PC as a fast Storage Oscilloscope.

The Arduino can reliably gather voltage readings at a frequency of between 141 and 153 KiloHertz.

1000 data readings can be taken in around 6.8ms .

Transfered to a PC, these points can be accurately plotted against time.
This Instructable will show you how the analogue input can be repeatedly added to a 1000 byte buffer and then transferred to a serial monitor. The data is collected using a high frequency interrupt, whose period can be accurately determined. The frequency can be altered to produce a range of possible periods.

I have written a PC interface to display the data and control the arduino. My PC program is presented as is - it would take a very long instructable to explain it!

The data output from the Arduino is not complex. I am sure others will write interfaces for the operating system of their choosing ....

I have written two slightly different versions for the Arduino data capture. One utilizes software triggering for when an accurate change in voltage is required, before the oscilloscope triggers. The second, uses hardware edge triggering based on an interrupt on Arduino pin 2. The hardware version runs a little faster at the highest frequency.

.

I did a minor rewrite today (31/8/2014). The PC interface now includes the option to set the voltage reference to accurately reflect the real value of the Arduino "5V" line. There are also small adjustments to the Arduino software.

.

As of 6/9/2014 I have developed a slightly modified version of the Software Triggered version which runs at up to 227.3 KHz on my Mega, using register commands to directly control single conversion reads. If there is interest, let me know.

The ADC Arduino Mega information is to be found in pages 242-260 of the Amtel atmega328p manual.

.

As of 29/9/2015 the PC and Arduino software have been updated.

The video is best viewed in High definition (720p), full screen:

Step 1: Requirements

Arduino Mega 2560 (Let me know if other arduino types work)

.
The following component works- alternatives may be viable (with program tweaking- I leave that to you!)

LCD Keypad Shield http://www.hobbytronics.co.uk/arduino-lcd-keypad-...

.

Simple Buffer box to accept analogue voltages: (Only required if you don't already have voltage buffers)

NE5534P op amp

22pf, 10nf capacitors

Two 100K variable resistors

22K, 4k7, 12K, two 470K resistors, 0.25W ok

10K precision resistor (1%)

Two 1K variable resistors

10uF electrolytic capacitor, 16V or more

Copper Stripboard, Plastic Box, Connectors and soldering equipment

A multimeter

Balanced +-9V supply (At least +-6V is needed to allow the NE5534P to produce 5V at the output)

.

A PC with a windows operating system. I have tested my interface on XP, Windows 7 and windows 8.

(Or make your own software interface.)

Step 2: The Specifications

The Arduino program:

Both versions sample on Analogue port 1.

.

The data can be captured with or without a trigger.

The sampling frequency is controllable, allowing different sample periods.

Some text information is sent to the serial port and lcd screen.

The lcd screen buttons control trigger slope, sample period and sampling.

The voltage data is output as bytes in a buffer. (This is for speed)

A program is needed to capture the raw data.

.

1) Oscilloscope.ino

Software trigger version.

The trigger level can be specified in mV. Sampling commences when the voltage climbs above (or falls below) by more than the trigger value, between two subsequent sampled readings.

The trig level is a best fit, limited by the resolution of the a/d port.

The reference voltage can be set. (Useful if you find the "5V" line is not at 5V, or you are using a device whose reference is entirely different.)

.

2) OscilloscopeExt.ino

Hardware trigger version.

The trigger is hardware controlled by digital port 2.

Simply connect your input to analogue port 1 and digital port 2.

It is activated by setting a non zero, positive, trigger level.

.

Common Specifications

Real time Oscilloscope using interrupts.

Serial, LCD and flash memory support.

Lcd button support. (The Lcd adc port is specified by the variable lcdport)
Serial Monitor set at 115200 baud.

SDCARD detected for future development.
If required, the following hardware has been tested:

.

Data is written to a 1000 byte buffer, which when full is written to the Serial port.

Serial output format:

Zero byte (handshake)

4 bytes with the integer value of sampling frequency in milli Hertz

1000 bytes of data

.

Sampling can be a single event or repetative (A fast run).

In a fast run the arduino will wait for a serial response of any character for 1500 milli seconds after outputting data. If a character is received (a handshake), the Arduino will immediately gather more data. If 1500 mS is up more data is recorded, regardless.

.

The buttons on the LCD Keypad Shield will produce the following result:

Select - initiates a new sample (or curtails a fast run)
Up - Positive edge trigger

Down - Negative edge trigger

Left - Decrease sampled period (increases Prescalar)

Right - Increase sampled period (decrease Prescalar)

Note that the LCD screen requires Analogue port 0 to allow the buttons to work.

After sampling basic information is displayed on the Lcd.

.

Serial Input commands:

'again' Initiate new data capture.

'test' Toggle a square wave on PM3 Square for testing.

'trig' Trigger level set. If 0 is sent triggering is cancelled. Non zero trigger levels produce triggering. The lcd screen buttons control whether the trigger is on a posive or negative slope.

'run' Repeated sampling- a fast run. No serial port text is sent during a run- only raw data.

During a fast run the sample period and edge select can be altered. A fast run is stopped using the lcd select key.

.

The PC Interface:

Capture raw com port oscilloscope data- from Arduino.
Send text to control Arduino.

Receive text from Arduino.

Set arduino trig level and sample frequency.

Initiate sampling and fast runs.

Set voltage reference "vref" to match Arduino. Send vref to Arduino. Note that only the software oscilloscope version uses this information. (To calculate the sofware trigger level).

Setting the voltage reference displays the voltage more accurately than working with the default 5V level.

.

Graph incoming data against time.

Available scales:

raw 0-255

0V to 5V

-2.5 to +2.5V

-5 to +5V

-9 to +9V

.

Graph time axis calculated from received sampling frequency.

Data can be sampled once or repeatedly.

.

During repeated sampling:

X axis scaling can be frozen to maintain comparison positions

The entire graph can be frozen.

.

Output readings to a data file (see scopedata.txt)

.

Calculate data frequency based on-

1) positive edges around the average of the data maximum and data minimum voltage

2) dsp method detailed at ' https://www.instructables.com/id/Reliable-Frequenc...

.

Clipboard copy of graph and data info-screen.

jpg image file of oscilloscope display.

Printer output of oscilloscope display.

Zoom option in graph.

.

Junk wait setting to trash spurious startup characters from Arduino.

Timeout setting to cease capture when comport stops receiving characters.

Set serial baud rate.

The application locks position when capturing-

this ensures that the interrupts that would occur can not slow down the sampling.

Last settings saved.

Step 3: Setting Up the Interrupt for the Software Triggered Version

There have been several excellent instructables on setting up fast data capture on the arduino.

I have developed the following from a variety of sources.

I use the variable prescalar to control the frequency of the sampling interrupt.

Essentially we set up an interrupt which grabs data automatically at a controllable frequency.

Once the interrupt starts, data is captured in an interrupt routine.

If triggering is on, data is not transferred to the buffer until the triggering condition is met.

The analogue port is specified by the variable adport. ( ADMUX |=adport )

The frequency is defined using a prescalar. (ADCSRA register: bits ADPS2, ADPS1 and ADPS0. )

These bits determine the division factor between the system clock frequency and the input clock to the ADC.

.

The Arduino 5V is used as a voltage reference. (The REFS0 bit)

Note that the Voltage on my USB port appears to drive my Arduino at 4.76V.

If I connect a power supply to the Arduino the '5V' line is at 5.06V

If you are seeking accuracy, supply external power!.

.

Set the number of bits used in the analogue port capture. For speed 8 bits are read. The ADLAR bit controls the presentation of the ADC conversion Write one to ADLAR to left adjust. Otherwise, the value is right adjusted. This has an Immediate effect on the ADC Data Register.

.

Set the bits in ADCSRA for the frequency prescalar. (ADPS0, ADPS1, ADPS2 bits)

.

Set the interrupt to repeatedly Auto Trigger when the analogue port is read. (The ADATE and ADIE bit)

Enable the ADC. (The ADEN bit)

Note the use of the sbi function. This and the cbi function are very useful for setting and clearing data register bits.

Start the ADC interrupt. (The ADSC bit)

.

// Setup continuous reading of the adc port 'adport' using an interrupt

//
cli(); // disable interrupts so that we can change registers without being interrupted

//clear ADCSRA and ADCSRB registers

ADCSRA = 0;

ADCSRB = 0;

ADMUX |= adport; //set up continuous sampling of analog pin adport

ADMUX |= (1 << REFS0); // set reference voltage to AVCC

ADMUX |= (1 << ADLAR); // left align the ADC value- so we can read highest 8 bits from ADCH register only

// Set the prescalar. 8 is the fastest workable frequency

// 8 prescalar 143Khz (after tolerable interrupt speed reduction)
if (prescalar==8) ADCSRA |= (1 << ADPS1) | (1 << ADPS0);

// 16 prescalar - 72 Khz sampling

if (prescalar==16) ADCSRA |= (1 << ADPS2);

// 32 prescaler - 16mHz/32=500kHz - produces 37 Khz sampling

if (prescalar==32) ADCSRA |= (1 << ADPS2) | (1 << ADPS0);

// 64 prescalar produces 19.2 Khz sampling

if(prescalar==64) ADCSRA |= (1 << ADPS2) | (1 << ADPS1);

// 128 prescalar - 9.4 Khz sampling if (prescalar==128) ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

ADCSRA |= (1 << ADATE); // enable auto trigger

ADCSRA |= (1 << ADIE); // Activate ADC Conversion Complete Interrupt

sbi(ADCSRA,ADEN); // enable ADC

ADCSRA |= (1 << ADSC); // start ADC measurements on interrupt

starttime=micros(); // record time so that we can calculate period and frequency

// enable interrupts and off we go!

sei();

.

The Interrupt code

The adc interrupt is serviced by the ISR(ADC_vect) routine.

Variable bufcount is the position in the buffer, whose size is BUF_SIZE (=1000).

Flag triggered is true when data can be captured.

When the buffer is full flag writeit is set true and the adc is disabled. ( cbi(ADCSRA,ADEN); )

To trigger the interrupt either triggered is set to true in setup and the data starts recoding immediately.

Or the newval data item is compared with the previous oldval.

If the difference is greater than the trigger, triggered is set to true and the data is stored in the buffer.

The variable trigcount is used to count the number of data items read during triggering.

This number is added to the buffer size during the frequency calculation to maintain the accuracy of the observed frequency.

.

// this is the key to the program!!
ISR(ADC_vect) {

if (triggered){

bufa[bufcount]=ADCH;

bufcount++; // increment buffer counter

if (bufcount==BUF_SIZE) {

cbi(ADCSRA,ADEN); // disable ADC

endtime=micros(); // record endtime for interval calculation

writeit=true; // flag that a write is needed

}

} else {

// look for a trigger

newval=ADCH;

trigcount++;

trip = newval-oldval;

if(!trigplus) trip = -trip;

if (trip > trigger) triggered=true; else oldval=newval;

}

}

Step 4: Setting Up the Interrupt for the Hardware Triggered Version

This method is very similar to the software version.

Triggering is handled differently.

Digital Port two must be connected to analogue input 1, for this to work.

Essentially if no triggering is selected, the adc interrupt is enabled and data is captured immediately. If triggering is selected an interrupt on digital port 2 is used to enable the interrupt on the adc port 1.

Digital port 2 can be configured to read analogue signals and generate an interrupt on rising or falling data edges. It is very fast.

My interrupt routine is called gotinterrupt and as I am using digital port 2 the interrupt id is 0.

const byte ExtInterrupt = 2;

pinMode(ExtInterrupt,INPUT);

attachInterrupt (0, gotinterrupt, RISING);

attachInterrupt (0, gotinterrupt, FALLING);

.

The flag triggered controls whether the digital port 2 interrupt starts the analogue port 1 interrupt . When triggered is false the interrupt starts the adc interrupt when it detects an edge in the analogue input.

.

// same as the software interrupt code up to here..

sbi(ADCSRA,ADEN); // enable ADC

// then ..

if (trigger==0) {

sbi(ADCSRA,ADSC); // start ADC measurements on interrupt

starttime=micros();

}else{

// allow pw2 interrupt to start adc interrupt

triggered=false;

}

sei(); // enable interrupts

.

The Interrupt code

1) The port 2 interrupt is serviced by gotinterrupt

This interrupt is activated by a positive or negative edge on port 2.

It does nothing until triggered is false.

void gotinterrupt() {
if (!triggered){

triggered=true;

sbi(ADCSRA,ADSC); // start ADC measurements on interrupt

starttime=micros(); // record starttime for interval calculation

}

}

2) The adc Interrupt is serviced by ISR(ADC_vect)

Variable bufcount stores the position in the buffer, whose size is BUF_SIZE (=1000)

Data is stored in locations bufa(0) to bufa(999)

When the buffer is full flag writeit is set true and the adc is disabled. ( cbi(ADCSRA,ADEN); )

.

// ADC Interrupt routine
// this is the key to the program!!

ISR(ADC_vect) {

bufa[bufcount]=ADCH;

bufcount++; // increment buffer counter

if (bufcount==BUF_SIZE) {

cbi(ADCSRA,ADEN); // disable ADC

endtime=micros(); // record endtime for interval calculation

writeit=true; // flag that a write is needed

}

}

Step 5: Out-Putting the Data

The Void loop routine performs the following functions

1) If writeit is true:

  • calculates the observed time period, correcting for micro time rollover
  • outputs the buffer bufa
  • calculates frequency in KiloHertz
  • outputs buffer bufinfo, with a handshke zero byte and the frequency in milliHertz
  • enables the lcd port so that the buttons can be read
  • Switches off the test pulse on pw3
  • Outputs text details to the serial port if showdetails is true (set false by a fast run)
  • sets flag writeit to false and hasdata to true

The data is sent in two concurrent sections

  • a) A 5 byte header buffer with zero, followed by 4 bytes which is the frequency in milliHz. Sending in milliHertz increases the accuracy of the conveyed frequency value.
  • b) 1000 bytes of raw data, each in the range 0 to 255.

2) If hasdata is true

  • check the lcd buttons
  • if a fast run is active, look for a handshake character from the pc (or 1.5s timeout) and get more data
  • check for serial input commands

.

LCD Buttons

The input is received by the subroutine buttoncheck.

The lcd buttons are read from analogue port 0.

Each button produces a voltage within a certain range of values

The left button, for instance lies in the range 400 to 599

  • Select - initiates a new sample (or curtails a fast run)
  • Up - Positive edge trigger
  • Down - Negative edge trigger
  • Left - Decrease sampled period (increases Prescalar)
  • Right - Increase sampled period (decrease Prescalar)

.

Serial Input Data

The input is received by the subroutine commandcheck.

'test' toggles a square wave on testpin (= digital port 3), using the flag pwtoggle.

pinMode(testpin,OUTPUT);
if (pwtoggle==false) analogWrite(testpin, 0);

if (pwtoggle) analogWrite(testpin,127);

This is very useful. Simply connect digital port 3 to analogue 1, directly.

'trig' sets the trigger level. In the software triggered version the trigger variable is used mathematically to set the triggering level. In the hardware version it simply toggles triggering.

'again' produces a one off sample

'run' produces a fast repeated sample

'vref' sets the voltage reference value. (Only used by the software triggered oscilloscope to interpret trigger level.)

Step 6: Setting Up the Windows PC Interface

Open winpcInterface.zip and extract to a folder of your choice.

You will find:

  • oscilloscope.exe the windows interface
  • MSCOMM32.OCX comport controller
  • RICHTX32.OCX text box controller
  • rm chart setup.exe graph drawing support
  • ocxWIN7_8.bat batch file to install ocx on windows 7 or 8
  • ocxXP.bat batch file to install ocx on windows xp

Install in this order

1) The graph support 'rm chart setup.exe' . I found this very useful package on the Internet. VB programmers will find this interesting!

2) For windows 7/8 copy the address of the folder in which you extracted the application. If you right click on the address in the bar at the top of windows file explorer you will find the option to copy the folder address.

3) The batch file for your windows operating system.

XP - 'ocxXP.bat'

win7 or win8 - 'ocxWIN7_8.bat'

For win 7/8 you will need to

  • Right click on the batch file and run as administrator
  • Paste in the address copied in stage 2 and then enter.

.

The ocx will be copied to the system directory and registered.

.

Ready to test!

Connect up your arduino.

Install one of the two arduino oscilloscope programs. (Hard or soft triggering version)

Exit any serial interface used to do this.

oscilloscope.exe can then be run.

Set the comport to the one used by your arduino.

Set the baudrate to 115200.

Click capture.

Connect digital port 3 to analogue 1.

Type test into the command box and return.

A square wave is available on the digital port. Select again and you will see it plotted by the oscilloscope.

Select frequency and you will get the square wave frequency. The first estimate is based on the rising edges at the midpoint of the voltage range. The second is based on a technique outlined in an excellent article at:

https://www.instructables.com/id/Reliable-Frequency...

The graph can be 'zoomed' using a mouse left click and drag, or un- zoomed with a right click.

Options are available to:

  • fast 'run'
  • freeze a fast run
  • freeze the x-scale during fast run for data comparison.
  • print or copy the graph
  • output the data as a text file
  • alter triggering levels
  • alter y scale
  • apply an offset for bi-polar y axis
  • alter voltage reference

Step 7: Building a Simple Buffer Box for Analogue Input

There are many possible ways to pre-condition the voltage

Here are three simple Input conditioners for your oscilloscope:

  1. Voltage follower
  2. Bipolar +-9V to 0-5V converter
  3. A.C. coupler

The follower needs to be

  • Fast
  • High bandwidth
  • Low noise
  • High Input impedance
  • Low Output impedance

The NE5534P fits the bill and is cheap. Datasheet: http://pdf.datasheetcatalog.com/datasheet2/d/0ji5w...

It has a simple dc offset circuit- make sure you trim the 100K pot to set the output voltage to zero for an input of zero.

I used a balanced +-9V supply. However +-6V would give enough headroom to provide an output of 5V. The absolute maximum is +- 22V.

.

The bipolar converter allows for a signal which is below zero to be measured by the analogue port, which can not be taken below zero.

This bipolar converter is interesting. In the past I have designed these with an op amp, precision voltage reference and lots of trim pots. This design was inspired by an article which was supported by Ronald Michallick of Linear Applications. He suggested using a three resistor bridge and supplied an excel spreadsheet to design it.

If you need a different range of input voltages use the spreadsheet to get your own resistor values.

Setup the two 1k trim pots with an accurate resistance meter so that the upper and lower resistors meet the design specification.

Some points to note:

a) The arduino "5V" level may not be accurate. Alter the spreadsheet b2-b3 values if you want strict accuracy.

b) Reducing +-9V to 0-5V is a voltage drop of 18 to 5.

one digit of our oscilloscope is 5*1000*1/255 = 19.6mV.

An input change of 18*19.6/5 will cause a change in the analogue port.

That is 70.6mV. So relatively coarse voltage changes are observed (with 71mV jumps).

.

The A.C. coupler is very straightforward. The DC bias is set at the midpoint of our analogue port voltage (5V).

No external DC will transfer across the 10uF capacitor. This circuit works well with a microphone pre-amp.

To setup this circuit connect to the arduino, Use the raw data scale on the pc software. Grab the port data and trim the 100K pot so that the input is 127. Or use a voltmeter and set to "5V"/2.

.

The circuits are all straight forward to build on one piece of Copper stripboard.

I suggest a socket for the op amp.

Putting the circuits in a box is useful, The separate outputs can be inter-connected as needs arise.

Note the 0.1inch connectors on the side of my box so that standard Arduino header leads can be used.

Check your circuits before testing. All responsibility for the use of this article rests with you.

.

Let me know when you get it working.

Comments

author
DavidPatterson (author)2015-01-24

My latest version runs at up to 238 Khz, has a touchscreen lcd and additional signal analysis. It can also log the the data to an SD card.

endview.jpginfo.jpginsidebox2.jpg
author
Pacman333 (author)DavidPatterson2015-10-13

Dear David,

can you give the code?

Regards

Pacman

author
DavidPatterson (author)Pacman3332016-04-11

Following limited interest I have placed a copy of my 238Khz oscilloscope software at:

http://www.davepatterson.me.uk/public/Oscilloscope...

The code has adequate annotation. I offer it without support.

The required hardware:

Arduino mega 2560

Sainsmart 3.2 inch lcd and shield: $24.99

http://www.sainsmart.com/sainsmart-3-2-tft-lcd-di...

A sd card to fit the sainsmart shield (less than 2GB)

A good quality potentiometer between 5V and ground with the wiper to analogue 4. Any value that will not pull a large current. (This sets the trig level)

Any signal conditioning circuit of your choice- connected to analogue 0.

David

author
JurisP1 (author)DavidPatterson2016-10-27

Thank You for sharing Your work! Recently I got big interest in simple DIY scopes, I was collecting resources and projects around the Net, and found this yours too. Can not try it right away, need to get display, but I am pretty sure I will. From Your point of view, will be there modifications, or is it okay "as is", and should be considered final? Thanks!

author
DavidPatterson (author)JurisP12016-10-27

Hi JurisP1,

my 238KHz code is the fastest internal ADC version I have written. It uses a mega and a colour touch screen display by sain.

I have written code to run at 1.2 MHz. using the parallel interface on the ADC TLV571 chip.

As a starting point, the code and details in this instructable work well.

Regards,

David

author
JurisP1 (author)DavidPatterson2016-11-04

Thanks for answer! Yes, I did see on YouTube Your video with external ADC, are details and code of that also public somewhere? Thank You!

author
DavidPatterson (author)JurisP12016-11-04

Sorry, no.

author
DavidPatterson (author)Pacman3332015-10-14

Hi Pacman,

should there be sufficient interest, I may write an instructable over the winter months.

You will find this a useful starting point on using the mega with a touch screen:

https://www.instructables.com/id/Make-an-Oscillosco...

.

Regards,

David

author

Dear David,

thank you for that very useful instruction.

However, I got the problem that your oscilloscope software on the PC does not connect properly to the serial port.

The Arduino serial monitor works well (on COM3), i.e. proper replies are comming. But when trying to capture in your oscilloscpe application, the message "The arduino timed out" comes. Nothing is displayed. If I leave the serial monitor open at the same time, I get the message "Data handling error 8005 Port already open". Means that connection is tried on the right port. What could be?

By the way: I changed the texts as sent by Arduino a bit. I hope this cannot cause the problem.

I am running the PC under Windows 8.

Best Regards

Albert

author
DavidPatterson made it! (author)albert88552015-09-19

Hi Albert,

A couple of things to establish:

What version of the arduino operating system are you using?

Did you install the chart support and ocx files successfully?

Using the original software triggering version:

What does the lcd show? (take a picture)

Have you set the visual basic oscilloscope software to a baud rate of 115200?

I suggest a junk wait of 0 and timeout Never. (Default installation timeout is 30 seconds)

What happens when you click on port 3 on the pc oscilloscope? ( Take a screen shot)

What happens when you select capture on the pc oscilloscope (Take a screen shot)

I have checked the zip files on a windows 8 pc a few minutes ago - everything installed and ran ok.

It would be useful if you could post the picture and screen shots

Remember there is a pwm square wave option-

send test from the pc oscilloscope and the connect port 3 to analogue 1

Cheers,

David

win8.jpg
author

Hello David,

thank you for your quick reply. You helped me already! It was the "junk wait" parameter which I had not understood and which I set to 3 just in the believe "the higher the better". Sorry!

By the way: How did you increase the sample rate to 238 Khz?

Regards

Albert

author

Hi Albert,

Glad you got it sorted.

To achieve the turbo rate requires taking control of analogue data capture completely. I might write a detailed instructable on this later this year. Here are the key points (good luck) :

// Defines for setting register bits
#ifndef mysbi
#define mysbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define BUF_SIZE 1000;

const byte check = 1<<ADSC;

boolean triggered;

boolean writeit;

byte oldval, newval;

cli(); // disable interrupts
ADCSRA = 0; // clear ADCSRA register
ADCSRB = 0; // free running - only has effect if ADATE in ADCSRA=1
ADMUX |= adport; //set up continuous sampling of analog pin adport
ADMUX |= (1 << REFS0); // set reference voltage to Vcc
ADMUX |= (1 << ADLAR); // left align the ADC value- so we can read highest 8 bits from ADCH register only

ADCSRA |= (1 << ADPS1); // prescalar 4
mysbi(ADCSRA,ADEN); // enable ADC
sei(); // enable interrupts


/* Fast read via registers
cf pages 242-260 of ATmega328P manual
"A single conversion is started by writing logical 1 to
the ADC Start conversion bit ADSC. This bit stays high
as long as the conversion is in progress and will be cleared
by hardware when the conversion is completed."

>= 237.2 KHz !!!!!
*/

// First conversion- initialises ADC
mysbi(ADCSRA,ADSC);
while((ADCSRA & check)== check); // wait for ADSC byte to go low
// New conversion and use current ADCSRA value for trigger
byte startit = ADCSRA | check;
ADCSRA = startit;
while (!triggered){
// wait for adc conversion
while((ADCSRA & check)== check);
newval = ADCH;
// New conversion
ADCSRA = startit;
trip = newval-oldval;
if(!trigplus) trip = -trip;
if (trip > trigger) triggered=true; else oldval=newval;
}
starttime=micros();
for(unsigned int i = 0; i < BUF_SIZE; i++){
// wait for conversion
while((ADCSRA & check)== check);
bufa[i]=ADCH;
// New conversion
ADCSRA = startit;
}
endtime=micros();
mycbi(ADCSRA,ADEN); // disable ADC
writeit=true;
}
}

Cheers,

David

author

Hi David,

thanks for your description. If I understand correctly, you make two things:

- You don't use the auto trigger mode. Instead you set the ADSC bit "manually" each time you have read the conversion value.

- You are waiting for the conversions completed in the main loop by polling the ADSC bit to go low. Means you don't use an interrupt service routine (O.k. but why is there the sei() command after setting the ADC?).

What of the two measures brings the main effect?

Best Regards

Albert

author

Hi Albert,

1) Yes to no auto trigger and initiate adc conversion directly.

2) Yes to waiting for end of conversion.

3) sei() required because cli() used prior to altering registers. It is not good practice to change registers with the possibility of a system interrupt affecting the change.

4) The speed is gained by not allowing repeated readings on a dedicated adc port to be interrupted. Both 1) and 2) are required to achieve this. Without 2) successive readings would be un-reliable at best.

I admit to having to study the atmega manual repeatedly before evolving this code.

There are easier ways to get an (even) higher frequency scope- The 1.2 Mhz TLV571 adc chip is relatively easy to use and has a parallel interface.

Regards,

David

author

Hi David,

thank you very much for the explanations.

I selected your example mainly to learn about optimal use of the ADC functions. As a by-product I may use the data sampling functions for "hardware debugging" in later projects. An external ADC chip seems to be not necessary for the moment.

Best Regards

Albert

author

Analogue to digital converter TLV571. 1179KHz.

View video in HD fullscreen:

.

.

author

Re the turbo code:

I missed including this definition-

// Defines for clearing register bits
// will not work with certain loaded libraries if called cbi (same applies to sbi)
#ifndef mycbi
#define mycbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

.

Open the video full screen:

author
hackerh (author)2017-08-24

Hello

Please allow me a lot of questions.

You have tried this code from the description 2015/09/20.

The maximum frequency of sampling is 30KHZ in signal sine wave.

Is there a faster sampling mode or code for me to speed up the faster regester for sampling.

Knowing I'm using my maximum prescalar value.

I could explain my steps to speed up my response to Arduino to speed up sampling.

This code I used.

Thank you

// Defines for setting register bits

#ifndef mysbi

#define mysbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#endif

#ifndef mycbi

#define mycbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#endif

#define BUF_SIZE 1000

uint8_t bufa[BUF_SIZE];

const byte check = 1 << ADSC;

boolean triggered;

boolean trigplus = true;

boolean writeit;

byte oldval, newval;

int trigger;

unsigned long fasttime, starttime;

unsigned long endtime;

int trip;

float frequency, period , myinterval;

byte p;

void setup() {

cli(); // disable interrupts

Serial.begin(115200); // start serial for output

ADCSRA = 0; // clear ADCSRA register

ADCSRB = 0; // free running - only has effect if ADATE in ADCSRA=1

ADMUX |= 0; //set up continuous sampling of analog pin adport

ADMUX |= (1 << REFS0); // set reference voltage to Vcc

ADMUX |= (1 << ADLAR); // left align the ADC value- so we can read highest 8 bits from ADCH register only

ADCSRA |= (1 << ADPS0); // prescalar 2

mysbi(ADCSRA, ADEN); // enable ADC

sei(); // enable interrupts

}

ISR(ADC_vect) {

}

void startad() {

}

void loop() {

/* Fast read via registers

cf pages 242-260 of ATmega328P manual

"A single conversion is started by writing logical 1 to

the ADC Start conversion bit ADSC. This bit stays high

as long as the conversion is in progress and will be cleared

by hardware when the conversion is completed."

>= 237.2 KHz !!!!!

*/

// First conversion- initialises ADC

mysbi(ADCSRA, ADSC);

while ((ADCSRA & check) == check); // wait for ADSC byte to go low

// New conversion and use current ADCSRA value for trigger

byte startit = ADCSRA | check;

ADCSRA = startit;

while (!triggered) {

// wait for adc conversion

while ((ADCSRA & check) == check);

newval = ADCH;

// New conversion

ADCSRA = startit;

trip = newval - oldval;

if (!trigplus) trip = -trip;

if (trip > trigger) triggered = true; else oldval = newval;

}

for (unsigned int i = 0; i < BUF_SIZE; i++) {

// wait for conversion

while ((ADCSRA & check) == check);

bufa[i] = ADCH;

// New conversion

ADCSRA = startit;

}

mycbi(ADCSRA, ADEN); // disable ADC

writeit = true;

for (unsigned int i = 0; i < BUF_SIZE; i++) {

Serial.println(bufa[i]);

}

}

author
DavidPatterson (author)hackerh2017-08-25

Working example detailed in response dated 2017-08-24.

Tested on a mega.

David

author
hackerh (author)DavidPatterson2017-08-25

Hello David
I have tried the program on mega I have boosted your image signal at 30KHZ.
The signal is somewhat distorted. The extra frequency at 30KHZ is more distorted.
If you can write me a program in which sampling shows me high frequency
Program without programming a LED screen or connecting with a PC means a program that deals with ADC fast only
Thank you David
I'm sorry to take your time.

30khz.bmp30KHZ-SR.bmp
author
DavidPatterson (author)hackerh2017-08-25

Hi,

I am happy to respond to questions regarding the article.

Without intending to cause offence I must point out that I am not a free software writing service.

David

author
hackerh (author)DavidPatterson2017-08-25

I mean the LCD screen is not LED
Thank

author
DavidPatterson (author)hackerh2017-08-24

Hi,

I presume you are using a mega or 3rd party mega?

1) cli() should follow serial.begin

2) the isr service subroutine is not required

3) use a prescalar of 4: ADCSRA |= (1 << ADPS1); // prescalar 4

4) get rid of the triggering for initial testing- delete:

while (!triggered) {

// wait for adc conversion

while ((ADCSRA & check) == check);

newval = ADCH;

// New conversion

ADCSRA = startit;

trip = newval - oldval;

if (!trigplus) trip = -trip;

if (trip > trigger) triggered = true; else oldval = newval;

}

5) As you disable the adc in the main loop at the end- you need to enable it at the beginning- mysbi(ADCSRA, ADEN); // enable ADC

Have you had a look at my software link in the reply to Pacman333 2016-04-11 ?

This operates with a Sainsmart 3.2" lcd for display and a potentiometer to set the trig level.

Cheers,

David

author

This test program runs at 239.234Khz on a mega:

Note- pin 10 is configured to produce a pwm square wave for testing..

// Defines for setting register bits
#ifndef mysbi
#define mysbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#ifndef mycbi
#define mycbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

const byte testpin = 10;
// connect pin10 to analogue 0 for testing
// defines for pwm output on testpin (pin 10 specific on mega!)
#ifndef fastpwm
#define fastpwm (TCCR2B = (TCCR2B & B11111000) | B00000010)
#endif

#ifndef slowpwm
#define slowpwm (TCCR2B = (TCCR2B & B11111000) | B00000100)
#endif

#define BUF_SIZE 1000
uint8_t bufa[BUF_SIZE];

const byte check = 1<<ADSC;
boolean writeit, startit;
float elapsed;
byte adport = 0;

void setup(){
Serial.begin(115200);
Serial.println("ADC test");

cli(); // disable interrupts
ADCSRA = 0; // clear ADCSRA register
ADCSRB = 0; // free running - only has effect if ADATE in ADCSRA=1
ADMUX |= adport; //set up continuous sampling of analog pin adport
ADMUX |= (1 << REFS0); // set reference voltage to Vcc
ADMUX |= (1 << ADLAR); // left align the ADC value- so we can read highest 8 bits from ADCH register only
ADCSRA |= (1 << ADPS1); // prescalar 4
sei(); // enable interrupts

// option to start pwm wave on port 10:
fastpwm;
// slowpwm;
analogWrite(testpin, 127);

writeit = false;
startit = true;
}

void loop(){
float frequency;

if (startit) startad();

if (writeit) {
for(unsigned int i = 0; i < BUF_SIZE; i++){
Serial.print(i);
Serial.print(" ");
Serial.println(bufa[i]);
}
frequency = 1000 *(BUF_SIZE / elapsed);
Serial.print("\nElapsed time = ");
Serial.print (elapsed);
Serial.print("uS Frequency ");
Serial.print(frequency,3);
Serial.println("KHz");
writeit = false;
startit = true;
delay(2000);
}
}

/* Fast read via registers
cf pages 242-260 of ATmega328P manual
"A single conversion is started by writing logical 1 to
the ADC Start conversion bit ADSC. This bit stays high
as long as the conversion is in progress and will be cleared
by hardware when the conversion is completed."

>= 237.2 KHz !!!!!
*/

void startad(){
unsigned long starttime, endtime;
startit = false;

cli(); // disable interrupts
mysbi(ADCSRA,ADEN); // enable ADC
sei(); // enable interrupts

// First conversion- initialises ADC
mysbi(ADCSRA,ADSC);
while((ADCSRA & check)== check); // wait for ADSC byte to go low
// New conversion and use current ADCSRA value for trigger
byte startit = ADCSRA | check;
ADCSRA = startit;
starttime = micros();
for (unsigned int i = 0; i < BUF_SIZE; i++){
// wait for conversion
while((ADCSRA & check)== check);
bufa[i] = ADCH;
// New conversion
ADCSRA = startit;
}
endtime = micros();
cli();
mycbi(ADCSRA,ADEN); // disable ADC
sei();
elapsed = endtime - starttime;
writeit = true;
}

author
hackerh (author)2017-08-23

Hello DavidPatterson

Is there a special library for my ADC TLV571 chip.

If there is no library upload me the Ka program is an example of fast ADC read via ADC TLV571 chip

Thank

author
DavidPatterson made it! (author)hackerh2017-08-23

Hi,

I am not aware of a TLV571 library and have not released my TLV571 source code to the public. Until I find the time to write an instructable for the TLV571 project I will limit my comments (and support) ....

I simply read the chip's output via 8 input lines, forming portA on the mega.

The chip was configured to use software start, internal clock and end of conversion line active.

To initiate a conversion I used PORTC to switch the necessary lines and polled the eoc line to detect a data ready condition.

cs on PC0

wr on PC1

rd on PC2

econ on PC3

// code starts here

#define BUF_SIZE 1000
byte bufa[BUF_SIZE];

const byte econ = 8; // PC3 8 end of conversion line

void setup(){

//setup dataline to output
DDRA = B11111111;

// control line setup 3 output, 1 input (and rest input for safety)
DDRC = B00000111;
// Set the 3 output handshake lines high
PORTC = B00000111;
delay(1);

// Configure the TLV571
// write cr1 1st to ensure values are set before software control starts
// INT OSC FAST,BINARY
byte cr1 = B01010000;
PORTA = cr1;
Serial.print(F("CR1 "));
writecycle();

// SOFTWARE START, EOC INTERNAL CLOCK, NORMAL
byte cr0 = B00110000;
PORTA = cr0;
Serial.print(F("CR0 "));
writecycle();

// set data lines to input
DDRA = B00000000;
delay(1);

}

void loop(){

byte delayer;

unsigned long starttime, endtime;

starttime = micros();
for (int il = 0; il < BUF_SIZE; il ++) {
sei();
delayMicroseconds(delayer); // only use if full rate not required!
cli();
PORTC = B00000010; // RD low, WR high, CS low
PORTC = B00000110; // RD high, WR high, CS low
while ((PINC & econ) == 0 ); // wait for econ to go high
bufa[il] = PINA;
}
endtime = micros();
sei();
}

// do something with data

}

void writecycle() {
cli();
PORTC = B00000110; // read high, WR high, CS low
__asm__("nop\n\t"); // wait one machine cycle (at 16 MHz)
// yielding a 62.5 ns (nanosecond) delay
PORTC = B00000100; // read high, WR low, CS low
__asm__("nop\n\t");
PORTC = B00000110; // read high, WR high, CS low - transfer data
__asm__("nop\n\t");
while ((PINC & econ) == 0 ); // wait for econ to go high
sei();
Serial.print(PORTA, BIN);
Serial.println(F(" write OK"));
Serial.flush();
}

Good luck!

David

TLVlead.jpgConnections.jpg
author
hackerh (author)2017-08-21

Hello

I have a query to make it possible to create Oscilloscope By Arduino UNO and have a fast analogRead can read the signal with high speed for 200KHZ.

If possible, it helps me to accomplish it

Is it possible to give me an example of this is a small program showing the action of ADC fast to take the sample of 200KHZ

This email is www.14laid@gmail.com

Thank

author
DavidPatterson (author)hackerh2017-08-21

Hi,

The uno has less memory:


  • Flash Memory: 32 KB of which 0.5 KB used by bootloader

  • SRAM: 2 KB (ATmega328)

  • EEPROM: 1 KB (ATmega328)

Mega:


  • Flash Memory 256 KB of which 8 KB used by bootloader

  • SRAM 8 KB

  • EEPROM 4 KB

As a result I have always used mega's for storage hungry applications (and do not have a single uno)

You might want to look at some of the 3rd party mega boards- they are often cheaper than a uno!

Regards,

David

author
hackerh (author)DavidPatterson2017-08-22

Hello
thank you my friend
But I'm for the purpose of understanding ADC fast even in Arduino Mega and understanding the sampling method of 200KHZ so that the display signal is clean, so you will be able to correct the errors that are in it
It will download you the program you created
It can be adjusted to take samples from 0-200Khz
Thank you
-This is the program:

#include "TimerOne.h"
#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

volatile int value[300]; // variable to store the value coming from the sensor
volatile int i;
volatile int p = 0;
void setup()
{
Serial.begin(9600) ;
#if FASTADC
// set prescale to 16
sbi(ADCSRA, ADPS2) ;
cbi(ADCSRA, ADPS1) ;
cbi(ADCSRA, ADPS0) ;
#endif
Timer1.initialize(10);
Timer1.attachInterrupt( timerIsr1 ); // attach the service routine here
}
void timerIsr1() {
if (p == 0) {
for ( int i = 0; i < 300; i ++) {
value[i] = analogRead(A0);
// delayMicroseconds(2);
};
p = 1;
}
}

void loop()
{

for (i = 0; i < 300; i++)
{
Serial.println(value[i]);
delayMicroseconds(2);
}
// delayMicroseconds(2);
p=0;


}

author
DavidPatterson (author)hackerh2017-08-22

Hi,

using analogRead is a non starter for high frequency sampling.

You need to read the whole article and then focus on my reponse..

to albert8855 dated 2015-09-20

Using 300 data values at 200Khz = 300*1/200000= 0.0015S

So a sample period of 1.5mS..

You need more data points to capture a meaningful section of the input wave structure.

1000 data points gives 5ms sample period

David

author
hackerh (author)2017-08-20

Hello

I have a query to make it possible to create Oscilloscope By Arduino UNO and have a fast analogRead can read the signal with high speed for 200KHZ.

If possible, it helps me to accomplish it

This email is www.14laid@gmail.com

Thank

author
mach1950 (author)2016-07-19

I only just came across this David. Very well put together intstructable, and thank you very much for sharing your knowledge. I'll be following any future work with interest.

author
carlos66ba (author)2014-08-21

Very nice! One option (for about the same cost) is to use the Teensy 3.1 (http://www.pjrc.com/teensy/teensy31.html) which is a lot faster, especially the A/D conversion (I think it can be done with DMA).

author

A useful link.

The teensy appears to be 3.3V based. So the Arduino and PC program would run incorrectly, without modification. I have no idea whether the same interrupts and register controls are available on the Teensy. The serial route out is also unclear to me. Not exactly a drop in solution?

author
Scargill (author)carlos66ba2014-10-19

Indeed, with the Teensy 3.1 running at 72 Meg and with 64k RAM it seems to me that this beautiful PC interface could be done justice!! We could be looking at a scope fast enough to debug normal Arduinos!!

author
KipsD (author)2014-09-10

please more info on how to utilise the ADC register. i really need to learn the details

plez!!!

author
DavidPatterson (author)KipsD2014-09-11

Hi KipsD,

The code is fully annotated.

Have a look at step 3 above and void startad() plus ISR(ADC_vect) in the code.

Refer to pages 242-260 of the amtel manual I attached to the first page.

There is also information in step 6 of my instructable on saving wav files:

https://www.instructables.com/id/Arduino-Mega-Audio...

Let me know if something fails to make sense.

author
KipsD (author)DavidPatterson2014-10-13

i'm very preciate your reply.

i'm very sorry to ask this question.

i have 1 question instead of doing 1000 reading. can i use the adc interupt to take a reading between 1 second which is control by timer 1 interupt and each time the interupt timer1 is flagged it will take realtimeclock calculation.

example:

timer 1 interupt each second to take the rtc value.

during interval of one second the adc will kick on reading and display on serial monitor with the rtc value(like timestamp).

will it work?

right now i experimenting on how fast the the data can be recorded using arduino , but i'm not so good on programing.

so far i only know how to manipulate the ADC clock and still learning ADC interupt.

author
DavidPatterson (author)KipsD2014-10-14

Hi,

If you only want to take 1 reading every second, your question is not really anything to do with rapid data analysis.

For 1 period of 1s I suggest polling the millis() function and look for the difference between the current millis() time and the loop start time being a multiple of 1000 mS. No need to set up an interrupt, manipulate the adc clock, or alter the number of bits the adc returns. The calls to the rtc device will be chip specific and are well documented for common devices.

With a 1 second interval you will have time to output to the serial monitor within the interval.

Note that, if you do set the code within an interrupt routine, you can not output to the serial monitor from within the interrupt service routine. Depending on the interrupt used millis() can be affected.

The millis() function resets to zero ("rolls over") after a number of days- not a problem if you run your program non continuously. However for a continuous logging situation you will need to allow for the reset in your code.

author
KipsD (author)DavidPatterson2014-10-14

I'm sorry for the misinformation about the reading
I want to take rapid reading between the the timer 1 interupt which occur every second..
My purpose is take 500 reading or more to plot a smooth sinewave for 1volt AC (50 herzt) input signal..

author
DavidPatterson (author)KipsD2014-10-14

I still would not use an interrupt to initiate a process which has a period of 1 s.

The 500 readings will require an interrupt service routine (Or code which starts a new adc reading directly) You will need to store the readings in memory and then output to the serial port.

If you study my code the continuous run option will do the work for you. Add a suitable delay to the run re-start section and you will be nearly there.

author
johnag (author)2014-10-06

A great and well written instructable very detailed and very interesting. thank you for sharing.

author
DavidPatterson (author)johnag2014-10-06

Thanks for that!

author
seamster (author)2014-08-21

Cool! Thanks for sharing this!

author
DavidPatterson (author)seamster2014-08-21

Thanks

About This Instructable

49,218views

101favorites

License:

More by DavidPatterson:Arduino Mega GPS with LCD and SD LoggingArduino High speed Oscilloscope with PC interfacePlaying audio sound files ( wav ) with an Arduino
Add instructable to: