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

Thread: Teensy 3.6 Triggered Signal Averaging and Buffered Acquisition

  1. #1
    Junior Member
    Join Date
    Feb 2019

    Teensy 3.6 Triggered Signal Averaging and Buffered Acquisition

    Before explaining the steps I've taken to date, I'd like to first describe the technical scope of my challenge. In short, I would like to acquire data (ADC) at a rate of 250,000 kSps that is synchronized with a trigger that occurs every 10-40 Hz. Additionally, it is necessary to perform signal averaging on the data as the actual signal is quite noisy (typically a couple of hundred averages is needed) so I would like to average the readings from the respective data bins as defined by the trigger.

    [TL : DR]

    How to sample multiple ADCs continuously while simultaneously stream data through the USB?

    From my experimentation using the links and discussions below, I gather that a single buffer collecting all of the data at 12-bit resolution is simply not possible and that a buffering approach may be best through a combination of PDB and DMA. However, this raises the question as to how to synchronize the buffer to the trigger especially if you need a circular/ring buffer to capture the data? Is there a way to tag/timestamp the values with respect to the trigger and still stream the information?

    My initial thought was to simply acquire data on two channels with ADC0 sampling the input trigger and ADC1 sample the actual signal, send the data back to the PC, and fold the data appropriately using some other script based upon the rising edges recorded on the ADC0 channel. Is this a rational approach or is there a better option?

    Digging through the forums the range of examples from tni have been extremely helpful: In this example I can get the sampling rates needed but it is unclear to me how to trigger the byte stream back to the PC. My guess is that I need to detect when the buffer is full and transfer the last half before it is overwritten. This seems to be where I'm running into a lack of knowledge regarding how to trigger an interrupt and use a circular/ring buffer appropriately.

    Other potential/partial solutions investigated:

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    There is an ADC library that does automated reads.

    Teensy 3.x, LC ADC library Copyright (c) 2017 Pedro Villanueva

    This included sample seems to have them on a DMA schedule: ...\hardware\teensy\avr\libraries\ADC\examples\rin gBufferDMA\ringBufferDMA.ino

  3. #3
    Junior Member
    Join Date
    Feb 2019
    When you say automated reads, do you mean with respect to the DMA? This surely looks like a key library to consider (the non-functioning example I'm working on uses it) but I'm not sure how exactly and when the ringBufferDMA calls its isr. Is it when it is full? If that's the case then I need to assume at least two things: 1.) that both buffers have been filled equally and 2.) that I need to transfer 1/2 the buffer via serial within the dma's isr?

    Finally, is it appropriate to use Serial in the isr or just set a flag that gets caught in the loop to then print out the values?

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    I meant automated by the ADC library - yes, in this case it seems it is by DMA.

    No, serial print from an _isr is not good practice. Just found that example by name as an example to start with. I see it only buffers 8 at a time - USB packets are 64 bytes of data - so working up to that to pass a full buffer would be most efficient.

    Not sure the next step to get continuous run and offloading data ahead of it being overwritten … but that should be possible which seems to be the goal here.

  5. #5
    Junior Member
    Join Date
    Feb 2019
    I'm still not entirely sure how to effectively send the binary data in chunks once the buffer is full. Should this be done in chunks or as soon as they become available?

    After banging my forehead for a while I saw that there is an issue associated with the RingBufferDMA and ADC_1 ( Basically, the correct DMA/ADC pair was not being assigned in the pedvide code. In case this serves someone else I'll pass it on:


    *   It doesn't work for Teensy LC yet!
    *   Added example for two pins on ADC_0 and ADC_1 
    *   Added a couple of lines to ensure the correct DMA setup on ADC_1
    #include "ADC_Module.h"
    #include "ADC.h"
    #include "RingBufferDMA.h"
    const uint8_t readPin = A9;
    const uint8_t readPin1 = A12; // digital pin 31, on ADC1
    ADC *adc = new ADC(); // adc object
    // Define the array that holds the conversions here.
    // buffer_size must be a power of two.
    // The buffer is stored with the correct alignment in the DMAMEM section
    // the +0 in the aligned attribute is necessary b/c of a bug in gcc.
    const uint8_t buffer_size = 8;
    DMAMEM static volatile int16_t __attribute__((aligned(buffer_size+0))) buffer[buffer_size];
    // use dma with ADC0
    RingBufferDMA *dmaBuffer = new RingBufferDMA(buffer, buffer_size, ADC_0);
    #if ADC_NUM_ADCS>1
    const int buffer_size1 = 8;
    DMAMEM static volatile int16_t __attribute__((aligned(buffer_size1+0))) buffer1[buffer_size1];
    // use dma with ADC1
    RingBufferDMA *dmaBuffer1 = new RingBufferDMA(buffer1, buffer_size1, ADC_1);
    #endif // defined
    void setup() {
        pinMode(LED_BUILTIN, OUTPUT);
        pinMode(readPin, INPUT); //pin 23 single ended
        pinMode(readPin1, INPUT); //pin on ADC_1
        // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REF_EXT.
        //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2
        adc->setAveraging(8); // set number of averages
        adc->setResolution(12); // set bits of resolution
        // always call the compare functions after changing the resolution!
        //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V
        //adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V
        // enable DMA and interrupts
        // ADC interrupt enabled isn't mandatory for DMA to work.
        #if ADC_NUM_ADCS>1
        adc->setAveraging(8, ADC_1); // set number of averages
        adc->setResolution(12, ADC_1); // set bits of resolution
    char c=0;
    void loop() {
         if (Serial.available()) {
          c =;
          if(c=='s') { // start dma
                Serial.println("Start DMA");
          } else if(c=='d') { // start dma
                Serial.println("Start DMA 1");
                dmaBuffer1->dmaChannel->disable();//added to get around the ring buffer issue:
                dmaBuffer1->dmaChannel->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1); // start DMA channel when ADC finishes a conversion
          } else if(c=='c') { // start conversion
              Serial.print("Conversion: ");
              adc->analogRead(readPin, ADC_0);
          } else if(c=='v') { // start conversion
              Serial.print("Conversion: ");
              adc->analogRead(readPin1, ADC_1);
          } else if(c=='p') { // print buffer
          } else if(c=='l') { // toggle led
              digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
          } else if(c=='r') { // read
              Serial.print("read(): ");
          } else if(c=='f') { // full?
              Serial.print("isFull(): ");
          } else if(c=='e') { // empty?
              Serial.print("isEmpty(): ");
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
    void dmaBuffer_isr() {
        digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
        // update the internal buffer positions
    void dmaBuffer1_isr() {
        digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
        // update the internal buffer positions
    // it can be called everytime a new value is converted. The DMA isr is called first
    void adc0_isr(void) {
        //int t = micros();
        Serial.println("ADC0_ISR"); //Serial.println(t);
        adc->adc0->readSingle(); // clear interrupt
    // it can be called everytime a new value is converted. The DMA isr is called first
    #if ADC_NUM_ADCS>1
    void adc1_isr() {
        //int t = micros();
        Serial.println("ADC1_ISR"); //Serial.println(t);
        //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) );
    void printBuffer() {
        Serial.println("Buffer: Address, Value");
        uint8_t i = 0;
        // we can get this info from the dmaBuffer object, even though we should have it already
        volatile int16_t* buffer = dmaBuffer->buffer();
        for (i = 0; i < dmaBuffer->size(); i++) {
            Serial.print(uint32_t(&buffer[i]), HEX);
            Serial.print(", ");

Posting Permissions

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