Fastest digital/analog input sampling with T3.6

Status
Not open for further replies.

128ITSH

Active member
Hello everyone!
I started thinking of making a simple logic analyzer/oscilloscope with the Teensy 3.6. (I know It will not be close to a real oscilloscope, but (for the things I'm working on) I don't need one.
My question is in which way can I get the fastest sampling rate with both the ADC and digital inputs? And also, Is there a way to send the samples to the computer via USB asynchronously?
I have searched about DMA but I'm not quiet sure what it does. what is a source/destination buffer? can the source be a digital/analog input? There isn't much information about DMA so its hard for me to guess.

Thanks in advance,
128ITSH
 
The sampling rate depends on the number of bits of resolution you are sampling with and what the speed of the processor chip is. 8-bits samples faster than 16-bits but loses resolution. Don't expect interleaving multiple channels to go any faster as there are limited number of ADC's available, TWO total. You will probably get about 100K to 150K samples per second native with a Teensy. I have not yet figured out DMA but suspect it would get you maybe 1million samples with an external SPI ADC. A logic analyser could probably read that fast with straight digital input for many channels. If you want FAST then look into FIFO's behind each of your ADC inputs in an off Teensy sampling circuit, upwards of 20-million samples / sec or better if done carefully.
 
Thanks for the responses.
- I would like to use the internal teensy ADC, not an external one.
- I don't care to have 8-10 bit resolution, If it improves the sample rate.
My main question is what kind of code instead of digtalRead() or analogRead() will get the teensy to its maximum capabilities, and also transmit the data without interrupting the samples? (by writing samples I mean both analog and digital)
 
For analog readings, I‘d mot probably go with the ADC library which allows continuous sampling and DMA transfer to a (ring) buffer. Most of this is then running semi-autonomous in the background without eating too much CPU cycles.
 
Some examples of DMA ADC operations can be found here. The speed at which you can send is largely limited by the PC. You will want to send packets of several readings at a time or the pc will choke the Teensy's serial. Also at higher rates the Arduino serial monitor has a bad habit of running out of memory and crashing at least for me.
 
Thanks for your replay.
I played with the DMA example in the ADC library, but can't find a way to sample faster than 200khz, as the library examples show continuous sampling but not continuous data sending to the PC. It's hard for me to figure out all of the DMA stuff, and using the buffer only gets the sampling slower (the higher the buffer size, the slower sampling rate).
This is the code I got so far:
Code:
//This sketch is a try to see how many samples can get transmitted from the teensy in one second
#include "ADC.h"
#include "RingBufferDMA.h"

const int readPin = A9;

ADC *adc = new ADC(); // adc object

const int buffer_size = 4;

DMAMEM static volatile int16_t __attribute__((aligned(buffer_size + 0))) buffer1[buffer_size];

// use dma with ADC0
RingBufferDMA *dmaBuffer = new RingBufferDMA(buffer1, buffer_size, ADC_0);

volatile int bufferPos = 0;
//samples are running for one second, then stopped
bool _start1 = false;
unsigned long beforeMillis = 0;
const unsigned long interval1 = 1000;
long nSamples = 0;

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(readPin, INPUT); //pin 23 single ended

  Serial.begin(115200);


  adc->setResolution(8); // set bits of resolution
  adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
  adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);

  // enable DMA and interrupts
  adc->enableDMA(ADC_0);

  // ADC interrupt enabled isn't mandatory for DMA to work.
  adc->enableInterrupts(ADC_0);
  
  delay(4000);
  adc->adc0->startContinuous(readPin);
  dmaBuffer->start(&dmaBuffer_isr);
  beforeMillis = millis();
  _start1 = true;
  
}



int i = 0;
void loop()
{

  if (_start1)
  {

    unsigned long _now = millis();
    if (_now - beforeMillis < interval1)
    {
      if (bufferPos >= buffer_size)
      {
        bufferPos = 0;
        Serial.println("new chunk");
        for (i = 0; i < buffer_size; i++)
        {
          Serial.println(buffer1[i]);
          nSamples++;
        }
      }
    }
    else
    {
      _start1 = false;
      Serial.println("ended");
      Serial.print("Samples: ");
      Serial.println(nSamples);
      adc->stopContinuous(ADC_0);
      
    }

  }

}

void dmaBuffer_isr() {

  // update the internal buffer positions
  bufferPos++;
  dmaBuffer->dmaChannel->clearInterrupt();
}


// it can be called everytime a new value is converted. The DMA isr is called first
void adc0_isr(void) {
  adc->adc0->readSingle(); // clear interrupt
}
(excuse me if the code is messy)
I'm quiet sure I'm missing the point of DMA here and using it incorrectly. In which way should I transmit the buffer from the memory? my try in the code was to cycle trough the buffer and print each output, when the buffer is full, but that is probably very inefficient.
 
Running 12 hours days at work right now so cant give actual code pieces but.
You did not change the number of samples per reading you are doing so by default 4 samples are taken per value output.
Your buffer is really small. The teensy has a massive amount of ram, no reason to bottleneck it and force it to such a small buffer.
Your trying to rapidly print that small buffer to the PC and apparently 1 value at a time. 64bytes I believe is the default minimum packet size for USB on the Teensy. So it waits until it has at least 64bytes if I remember correctly. There may also be a limit to how fast you can dump that data to the PC, OS dependent?
 
Thanks for your help!
I'm using windows 10, but anything between 500ksps-1000 will satisfy me and I'm pretty sure It is capable for it.
thanks for the explanation about the buffer but in the current code increasing buffer_size seams to slow the samples. what function can print the buffer in the most effiecient way?
I read the datasheet for the MCU and now I understand more about dma transfers and the adc.
Is it possible to transfer the result value from the ADC (ADC_RA register) directly to some usb registers and transmit it, i.e without using Serial.print() (or any other serial function)?
 
Got it! It turns out that the dmaBuffer_isr() function is being called every time the buffer is full, not when every conversion is done.
This is the coed that works now and gives me around 790kspS:
Code:
//This sketch is a try to see how many samples can get transmitted from the teensy in one second
//samples are running for one second, then stopped and number of samples is printed
#include "ADC.h"
#include "RingBufferDMA.h"

const int readPin = A9;

ADC *adc = new ADC(); // adc object

//dma buffer size. above this seem to have problems with the serial monitor.
const int buffer_size = 256;
//dma buffer
DMAMEM static volatile int16_t __attribute__((aligned(buffer_size + 0))) buffer1[buffer_size]; 

// use dma with ADC0
RingBufferDMA *dmaBuffer = new RingBufferDMA(buffer1, buffer_size, ADC_0); 

//timing variables
bool _start1 = false;
unsigned long beforeMillis = 0;
const unsigned long interval1 = 1000;
long nSamples = 0;

void setup() {

  pinMode(readPin, INPUT); //pin 23 single ended

  Serial.begin(2000000);

  adc->setAveraging(1); // default is 4, we want as fast as possible
  adc->setResolution(8); // set bits of resolution
  adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED);
  //library docs say ADC_CONVERSION_SPEED::VERY_HIGH_SPEED is out of specs. use HIGH_SPEED instead for safer operation
  adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); 
  
  // enable DMA
  adc->enableDMA(ADC_0);

  delay(4000);
  adc->adc0->startContinuous(readPin);
  dmaBuffer->start(&dmaBuffer_isr);
  beforeMillis = millis();
  _start1 = true;
  
}

//loop only checks if 1 second elapsed from the start, if yes, set _start1 false and stop the adc convertion
void loop()
{
  if (_start1)
  {
    unsigned long _now = millis();
    if (_now - beforeMillis > interval1)
    {
      adc->stopContinuous(ADC_0); //stop converting
      _start1 = false;
      Serial.println(""); //print results
      Serial.println("ended");
      Serial.print("Samples: ");
      Serial.println(nSamples);


    }
  }
}
void dmaBuffer_isr() //called everytime the buffer is full?
{
 
  Serial.write((uint8_t *)buffer1, buffer_size);
  nSamples= nSamples+buffer_size;
  dmaBuffer->dmaChannel->clearInterrupt();
}
Thanks for everyone who have been guiding me trough this!
I have more things to tweak though:
-The Serial.write() function outputs characters on the serial monitor instead of numbers, and also doesn't allow 16 bit int (thats why it is casted to uint8_t) so if I change the resolution to higher it gets messy. Any alternative for this function?
-Any ideas to make this even faster, simply?
 
Status
Not open for further replies.
Back
Top