Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 10 of 10

Thread: Fastest digital/analog input sampling with T3.6

  1. #1

    Fastest digital/analog input sampling with T3.6

    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

  2. #2
    Senior Member
    Join Date
    Nov 2015
    Location
    Cold hollow VT
    Posts
    130
    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.

  3. #3
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    You wont resonably be able to use anything above 13 bits. The 3.6 can sample much faster then 100-150K, it depends on what settings your using.

    Bill Greiman getting 3Msps

  4. #4
    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)

  5. #5
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    2,551
    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.

  6. #6
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    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.

  7. #7
    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.

  8. #8
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    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?

  9. #9
    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)?

  10. #10
    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?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •