-
-
Save pklaus/5921022 to your computer and use it in GitHub Desktop.
#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_() |
Hi!
I see that you use SeriaUSB together with Python. Did you do anything particular? In my case, when using the Native port, nothing is getting to the PC... (I use Linux, in case it matters)
Hi, I want use the Arduino due or teensy 4.0 like SPI-USB convwersion and I need 1Msps, I want use TCL or c++ in linux but I cant get more the 7ksps, with Arduino Serial console I get 1Msps but I want a TCL script,
what I doing wrong?
Can be something like Baudrate? Cant use more than 250000 in linux
What i can do?
Thanks
Hi @emanavas,
Hi, I want use the Arduino due or teensy 4.0 like SPI-USB convwersion and I need 1Msps, I want use TCL or c++ in linux but I cant get more the 7ksps, with Arduino Serial console I get 1Msps but I want a TCL script,
what I doing wrong?
First of all, could you confirm you're connecting to the Native USB Port, not the Programming USB Port? Are you running the sketch as in the original Gist on top of this page or a modified version?
Can be something like Baudrate? Cant use more than 250000 in linux
Baudrate only matters if there's a physical UART connection in your transmission line. (Such as in the case of the "Programming USB Port" where the transmission takes this route: PC <- USB -> ATmega16U2 <- UART (LV-TTL) -> Arduino Due
)
What i can do?
Show us your minimal code example of your TCL / C++ code so that we can help further.
Phil
Hi pklaus,
Finally I Decided use a C++ and I get around 3MB/s Using printng a FIX value from TEENSY 4.0, My goal its attach a external ADC 1-3Msps to teensy and send to PC.
ABout a DUE, I used a Native USB and I now I know this Baudrate its a virtual and depends of the driver og the host (Linux/PC).
Basically always work by the USB 2.0 (480Mbps) because we dont have a FTDI.
Minor correction: it should be (3.3/(2**12 - 1))
instead of (3.3/2**12)
on this line.
Hello
I use this code, it works fine
But the result shows there is only 666kHzSPS, not 1MSPS.
Do you give me a direction to go?
Thanks
ADC_FREQ_MAX is defined as 20000000. The datasheet of the SAM3X specifies a max ADC frequency of 22MHz.
Use the following call to adc_init to get full 1MSPS:
adc_init(ADC, SystemCoreClock, 22000000, ADC_STARTUP_FAST);
This is great code! Is there any possibility to add more than one channel to the ADC? I am interested in using up to 5 analog inputs
Hello, may I ask why after executing app.exec_()
will keep running interactively
Traceback (most recent call last):
t,v,r = thread.get(1000*1024, downsample=100)
data = data.reshape(num/downsample,downsample).mean(axis=1)
TypeError: 'float' object cannot be interpreted as an integer
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