Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Arduino Due: ADC → DMA → USB @ 1MSPS
#undef HID_ENABLED
// Arduino Due ADC->DMA->USB 1MSPS
// by stimmer
// from http://forum.arduino.cc/index.php?topic=137635.msg1136315#msg1136315
// Input: Analog in A0
// Output: Raw stream of uint16_t in range 0-4095 on Native USB Serial/ACM
// on linux, to stop the OS cooking your data:
// stty -F /dev/ttyACM0 raw -iexten -echo -echoe -echok -echoctl -echoke -onlcr
volatile int bufn,obufn;
uint16_t buf[4][256]; // 4 buffers of 256 readings
void ADC_Handler(){ // move DMA pointers to next buffer
int f=ADC->ADC_ISR;
if (f&(1<<27)){
bufn=(bufn+1)&3;
ADC->ADC_RNPR=(uint32_t)buf[bufn];
ADC->ADC_RNCR=256;
}
}
void setup(){
SerialUSB.begin(0);
while(!SerialUSB);
pmc_enable_periph_clk(ID_ADC);
adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
ADC->ADC_MR |=0x80; // free running
ADC->ADC_CHER=0x80;
NVIC_EnableIRQ(ADC_IRQn);
ADC->ADC_IDR=~(1<<27);
ADC->ADC_IER=1<<27;
ADC->ADC_RPR=(uint32_t)buf[0]; // DMA buffer
ADC->ADC_RCR=256;
ADC->ADC_RNPR=(uint32_t)buf[1]; // next DMA buffer
ADC->ADC_RNCR=256;
bufn=obufn=1;
ADC->ADC_PTCR=1;
ADC->ADC_CR=2;
}
void loop(){
while(obufn==bufn); // wait for buffer to be full
SerialUSB.write((uint8_t *)buf[obufn],512); // send it - 512 bytes = 256 uint16_t
obufn=(obufn+1)&3;
}
#!/usr/bin/env python
# from http://forum.arduino.cc/index.php?topic=137635.msg1270996#msg1270996
import pyqtgraph as pg
import time, threading, sys
import serial
import numpy as np
class SerialReader(threading.Thread):
""" Defines a thread for reading and buffering serial data.
By default, about 5MSamples are stored in the buffer.
Data can be retrieved from the buffer by calling get(N)"""
def __init__(self, port, chunkSize=1024, chunks=5000):
threading.Thread.__init__(self)
# circular buffer for storing serial data until it is
# fetched by the GUI
self.buffer = np.zeros(chunks*chunkSize, dtype=np.uint16)
self.chunks = chunks # number of chunks to store in the buffer
self.chunkSize = chunkSize # size of a single chunk (items, not bytes)
self.ptr = 0 # pointer to most (recently collected buffer index) + 1
self.port = port # serial port handle
self.sps = 0.0 # holds the average sample acquisition rate
self.exitFlag = False
self.exitMutex = threading.Lock()
self.dataMutex = threading.Lock()
def run(self):
exitMutex = self.exitMutex
dataMutex = self.dataMutex
buffer = self.buffer
port = self.port
count = 0
sps = None
lastUpdate = pg.ptime.time()
while True:
# see whether an exit was requested
with exitMutex:
if self.exitFlag:
break
# read one full chunk from the serial port
data = port.read(self.chunkSize*2)
# convert data to 16bit int numpy array
data = np.fromstring(data, dtype=np.uint16)
# keep track of the acquisition rate in samples-per-second
count += self.chunkSize
now = pg.ptime.time()
dt = now-lastUpdate
if dt > 1.0:
# sps is an exponential average of the running sample rate measurement
if sps is None:
sps = count / dt
else:
sps = sps * 0.9 + (count / dt) * 0.1
count = 0
lastUpdate = now
# write the new chunk into the circular buffer
# and update the buffer pointer
with dataMutex:
buffer[self.ptr:self.ptr+self.chunkSize] = data
self.ptr = (self.ptr + self.chunkSize) % buffer.shape[0]
if sps is not None:
self.sps = sps
def get(self, num, downsample=1):
""" Return a tuple (time_values, voltage_values, rate)
- voltage_values will contain the *num* most recently-collected samples
as a 32bit float array.
- time_values assumes samples are collected at 1MS/s
- rate is the running average sample rate.
If *downsample* is > 1, then the number of values returned will be
reduced by averaging that number of consecutive samples together. In
this case, the voltage array will be returned as 32bit float.
"""
with self.dataMutex: # lock the buffer and copy the requested data out
ptr = self.ptr
if ptr-num < 0:
data = np.empty(num, dtype=np.uint16)
data[:num-ptr] = self.buffer[ptr-num:]
data[num-ptr:] = self.buffer[:ptr]
else:
data = self.buffer[self.ptr-num:self.ptr].copy()
rate = self.sps
# Convert array to float and rescale to voltage.
# Assume 3.3V / 12bits
# (we need calibration data to do a better job on this)
data = data.astype(np.float32) * (3.3 / 2**12)
if downsample > 1: # if downsampling is requested, average N samples together
data = data.reshape(num/downsample,downsample).mean(axis=1)
num = data.shape[0]
return np.linspace(0, (num-1)*1e-6*downsample, num), data, rate
else:
return np.linspace(0, (num-1)*1e-6, num), data, rate
def exit(self):
""" Instruct the serial thread to exit."""
with self.exitMutex:
self.exitFlag = True
# Get handle to serial port
# (your port string may vary; windows users need 'COMn')
s = serial.Serial('/dev/ttyACM0')
# Create the GUI
app = pg.mkQApp()
plt = pg.plot()
plt.setLabels(left=('ADC Signal', 'V'), bottom=('Time', 's'))
plt.setYRange(0.0, 3.3)
# Create thread to read and buffer serial data.
thread = SerialReader(s)
thread.start()
# Calling update() will request a copy of the most recently-acquired
# samples and plot them.
def update():
global plt, thread
t,v,r = thread.get(1000*1024, downsample=100)
plt.plot(t, v, clear=True)
plt.setTitle('Sample Rate: %0.2f'%r)
if not plt.isVisible():
thread.exit()
timer.stop()
# Set up a timer with 0 interval so Qt will call update()
# as rapidly as it can handle.
timer = pg.QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
# Start Qt event loop.
if sys.flags.interactive == 0:
app.exec_()
@hamed

This comment has been minimized.

Copy link

hamed commented Sep 7, 2013

This sketch helped me a lot, I use it as a base to implement the recording part of my white noise spectrum analyzer.
https://github.com/hamed/snowWhiteNoise

@edziekon

This comment has been minimized.

Copy link

edziekon commented Apr 12, 2016

This code is excellent! I am experiencing one issue though.
About 7 min 40 sec into the code running, it just stops. I ran it 3 times and came up with the exact same time. I have not touched the computer, no errors are displayed, there is not a time limit in the code that I can see. In order to get the program to run again, I need to press the reset button on the Due and rebuild the python script.

What would cause the code to all of a sudden stop working? Is there a limit to the number of loop iterations in the Due? Is there a limit to the number of iterations in python? Is this an OS issue? (I am using Windows 10, 64-bit)

I have tried removing all code pertaining to exiting in the Python code, yet the issue persists.

Any advice is appreciated.
Thanks,
Eric

@direvius

This comment has been minimized.

Copy link

direvius commented Oct 18, 2016

It seems there is a bug in arduino part. See here for the explanation and a fix: http://forum.arduino.cc/index.php?topic=137635.msg2526475#msg2526475

@lafarigoule

This comment has been minimized.

Copy link

lafarigoule commented Dec 8, 2016

Nice job! I runt it, work very well. But I have a big problem because I am new with arduino DUE and the registers. I want to make 4 channels acquisition and It was very difficult for me. Do you have an exemple, or give me a direction to go. Thank's . Patrick
And I want to keep the plot of the 4 channels in Python, of course.

@abonellitoro

This comment has been minimized.

Copy link

abonellitoro commented Dec 21, 2016

Hi, very nice! I'm trying to make 2 channels acquisition but I don't fully understand your code. Could you explain it a little bit so I can modify to use 2 channels instead of one?

@borisbarbour

This comment has been minimized.

Copy link

borisbarbour commented Apr 1, 2017

I think there is an error in ADC_Handler where the buffer pointers are updated. By the time the handler is called, the DMA is already filling the "next" buffer = bufn+1. So not only must bufn be incremented, but the "next" buffer should then be bufn+2. I think the updates work better like this:

void ADC_Handler(){ // move DMA pointers to next buffer
int f=ADC->ADC_ISR;
if (f&(1<<27)){
bufn=(bufn+1)&3;
ADC->ADC_RNPR=(uint32_t)buf[(bufn+1)&3];
ADC->ADC_RNCR=bufsize;
}
}

@hoomanj

This comment has been minimized.

Copy link

hoomanj commented Sep 14, 2017

Hi, Awesome code.

can you help me write a python code that will just simply write the data in a text file. so a text file containing time values,voltage,rate

no need for graphing etc. just receive the data from the Due and store it in a text file.

thanks!

@dazza2017

This comment has been minimized.

Copy link

dazza2017 commented Dec 12, 2017

Hi
Im trying to run the python code on my pc using python3.5.4. and I get an error:
Exception: PyQtGraph requires one of PyQt4, PyQt5 or PySide; none of these packages could be imported.
I have already installed PyQtGraph so which one of these others mentioned above should I install with my version of python?
Thanks

@trycage

This comment has been minimized.

Copy link

trycage commented May 12, 2018

Hi all

In order to get the program run (I did amend the buffer increment in the ADC_Handler ), I need to open a serial when I use the native port.

More specifically, every time I close the serial from the PC side the Arduino DUE stops, I use a led blinking as a check. My led blinking is related to the actual transfer, hence I guess the process just hangs at the SerialUSB.write.
There are other posts in which users noted issue when a serial transfer is initiated on the native port without an actual connection. The weird thing is that is I re-open the port on the PC side the Arduino actually continues from the point it was interrupted.
.

Thanks.

Best

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.