Satus of Teensy 4.0 ADC?

Garug

Well-known member
What is the status of Teensy 4.0 ADC support, using Teensyuino1.53 (or other version if better)

I find the information very scattered, what is supported and not for Teensy 4.0 and how to use it correctly

There is formal documentation for ADC.h but find it easier to find the information reading the actual files, but even with that it is difficult as they speak of many Teensy versions.

The DMA examples I do not get running, and find them difficult too, they are trying to do too many things with too many Teensy versions.

I thought it would be possible to set up continious reading so that it is reading many channels, but seems not It seems it can read only one pin per ADC.

I understood that Teensy 4.0 has only one ADC, but seems to have two, I got this working and providing correct values.


Code:
#include <ADC.h>
ADC *adc = new ADC();

void setup() {
    adc->adc0->setAveraging( 32 ); 
    adc->adc0->setResolution( 12 );      
    adc->adc0->setConversionSpeed( ADC_CONVERSION_SPEED::VERY_LOW_SPEED );
    adc->adc0->setSamplingSpeed( ADC_SAMPLING_SPEED::VERY_LOW_SPEED );
    delay(100);
    adc->adc0->recalibrate();
    delay(100);
    adc->adc1->setAveraging( 32 ); 
    adc->adc1->setResolution( 12 );      
    adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::VERY_LOW_SPEED );
    adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::VERY_LOW_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);

    pinMode(14, INPUT_DISABLE); //Potentiometer 1 (iris)
    pinMode(15, INPUT_DISABLE); //Potentiometer 2 (focus)
    pinMode(16, INPUT_DISABLE); //Joystick X
    pinMode(17, INPUT_DISABLE); //Joystick Y

    
    adc->adc0->startContinuous(14);
    adc->adc1->startContinuous(15);


    delay(1000);
    uint16_t value14 = (uint16_t)adc->adc0->analogReadContinuous()/4;
    Serial.println(value14);
    uint16_t value15 = (uint16_t)adc->adc1->analogReadContinuous()/4;
    Serial.println(value15);
}

But would need to read 4 inputs at relatively fast rate.

Reading them all with analogRead(pin);works, but the documentation says

"It waits until the value is read and then returns the result. " so not nice when trying to read all channels 10 kHz, and need to do many other things also.

Is it possible to mix analogReadContinuous(); and analogRead(pin);? I will try.

What I really would like to have is all 4 channels read with DMA at least 100 samples, 150 times per second, possible? how?

The 100 or more samples should distribute about evenly during the 6,6 ms sampling time, but it is ok if there is some missed samples between the 150 samplings per second as these readings would be used for weighted averaging and speed calculation, 150 times per second, it does not need to be sliding.

While testing DMA so far I got strange hysteresis on pin 15 readings, potentiometer moves couple of degrees before changing the value. The pins are set as suggested by some who had the same problem

pinMode(15, INPUT_DISABLE); //Potentiometer 2 (focus)

But this does not help

Code:
    Serial.println(IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02, BIN);//pin14
    Serial.println(IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03, BIN);//pin15
    Serial.println(IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_07, BIN);//pin16
    Serial.println(IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_06, BIN);//pin17

Gives the same for all pins

10000000000111000
10000000000111000
10000000000111000
10000000000111000


I find that a bit strange as this indicates the hysteresis is active and output driver is set to strong strength, but changing these does not help to the problem. It is high quality potentiometer, but problem could be also that it is broken, but do not think so.

about the

adc->adc1->recalibrate();

I added that because without that the range was 0 to 1021, now it is correctly 0 to 1023 (I do filtering with 12 bits and divide result by 4)
 
Last edited:
See :: <TD Install>\hardware\teensy\avr\libraries\ADC\index.html

It is included with TeensyDuino. Not sure of last updates that may be in TD 1.54 Beta 9 or on github.

Any problems or issues try github first.
 
But what exactly am I looking here

file:///Users/.../Desktop/Teensyduino%201.53.app/Contents/Java/hardware/teensy/avr/libraries/ADC/index.html

or here

https://github.com/pedvide/ADC/tree/master/examples

The guestions I have are:

- what are the known problems with Teensy 4.0 and ACD.h
- does Teensy 4.0 support DMA ADC with this library, where is a working example that shows how to do this. Is it possible to what is reguested above, 100 samples, 150 times per second, on 4 channels
- Is there way the setup analogReadContinuous(); to work on 4 pins simultaneously
- If not is there problems foreseen if configuring it for two ADC like above and reading the other two with analogRead();
- Over all what is the best way to read 4 channels 10kHz+ each, noise vs. MCU time consumption.
 
The DMA examples compile but never enter setup, or at least do not print Serial.print("START"); that is right after the while (!Serial && millis() < 5000) ; when cleaning them up of all the #if defined that makes it totally unreadable for me and assuming Teensy 4.0 has dual ADC as it seems I get

undefined reference to `AnalogBufferDMA::init(ADC*, signed char)'

cleaned it up for one ADC but still same error and does not compile

Code:
#include <ADC.h>
#include <DMAChannel.h>
#include <AnalogBufferDMA.h>


const int readPin_adc_0 = 14;
const int readPin_adc_1 = 15;

ADC *adc = new ADC(); // adc object
const uint32_t initial_average_value = 2048;

extern void dumpDMA_structures(DMABaseClass *dmabc);
elapsedMillis elapsed_sinc_last_display;

// Going to try two buffers here  using 2 dmaSettings and a DMAChannel

const uint32_t buffer_size = 1600;
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);




void setup() {
  Serial.begin(9600);
delay(1000);
Serial.print("START");

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(readPin_adc_0, INPUT); // Not sure this does anything for us
 
  pinMode(readPin_adc_1, INPUT);
 
  
  Serial.println("Setup both ADCs");

  adc->adc0->setAveraging(8); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  


  abdma1.init(adc, ADC_0/*, DMAMUX_SOURCE_ADC_ETC*/);
  abdma1.userData(initial_average_value); // save away initial starting average


  // Start the dma operation..
  adc->adc0->startSingleRead(readPin_adc_0); // call this to setup everything before the Timer starts, differential is also possible
  adc->adc0->startTimer(3000); //frequency in Hz


  Serial.println("End Setup");
  elapsed_sinc_last_display = 0;
}

void loop() {

  // Maybe only when both have triggered?

  if ( abdma1.interrupted() ) {
    if ( abdma1.interrupted()) {
      ProcessAnalogData(&abdma1, 0);
    }
  
    Serial.println();
    elapsed_sinc_last_display = 0;
  }

}

void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;
    int delta_from_center = (int) * pbuffer - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;

    pbuffer++;
  }

  int rms = sqrt(sum_delta_sq / buffer_size);
  average_value = sum_values / buffer_size;
  Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val,
                average_value, max_val, rms);
  pabdma->clearInterrupt();

  pabdma->userData(average_value);
}
 
Ok, so the problem was the old libraries on Teensyuino 1.53. I deleted the /ADC contents and copied the Ginthub contents there and now get it compiling and this seems to work

Code:
#include <ADC.h>
#include <DMAChannel.h>
#include <AnalogBufferDMA.h>


const int readPin_adc_0 = 14;//define your pin here

ADC *adc = new ADC(); // adc object
const uint32_t initial_average_value = 2048;

extern void dumpDMA_structures(DMABaseClass *dmabc);
elapsedMillis elapsed_sinc_last_display;

// Going to try two buffers here  using 2 dmaSettings and a DMAChannel

const uint32_t buffer_size = 1600;
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);




void setup() {

delay(2000);
Serial.println("START");

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(readPin_adc_0, INPUT); // Not sure this does anything for us
 
  pinMode(readPin_adc_1, INPUT);
 
  
  Serial.println("Setup ADC");

  adc->adc0->setAveraging(8); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  


  abdma1.init(adc, ADC_0);
  abdma1.userData(initial_average_value); // save away initial starting average


  // Start the dma operation..
  adc->adc0->startSingleRead(readPin_adc_0); // call this to setup everything before the Timer starts, differential is also possible
  adc->adc0->startTimer(3000); //frequency in Hz


  Serial.println("End Setup");
  elapsed_sinc_last_display = 0;
}

void loop() {

  
    if ( abdma1.interrupted()) {
      ProcessAnalogData(&abdma1, 0);
    
  
    Serial.println();
    elapsed_sinc_last_display = 0;
  }

}

void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;
    int delta_from_center = (int) * pbuffer - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;

    pbuffer++;
  }

  int rms = sqrt(sum_delta_sq / buffer_size);
  average_value = sum_values / buffer_size;
  Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val,
                average_value, max_val, rms);
  pabdma->clearInterrupt();

  pabdma->userData(average_value);
}

Now the question is can it be modified to read 4 channels 6.6 ms intervals and provide good amount of samples.
 
I got this working surprisingly well with quick and thirty method, modifying it from the exambles. There is currently at least the problem that

pabdma->userData(average_value);

does not get correct average_value.

To run this the latest version of ADC needs to be installed on Teensyuino 1.53, tested with Teensy 4.0.

Code:
/*4xADC with DMA
    
    using AnalogBufferDMA with two buffers, this runs in continuous mode and when one buffer fills
    an interrupt is signaled, which sets flag saying it has data, which this test application
    scans the data, and computes things like a minimum, maximum, average values and an RMS value.
    For the RMS it keeps the average from the previous set of data.

    This alternates the 2 available ADC for X, Y Joystick or potentiometers, so the readings are 
    not continious, but ok for reading this kind of controls
*/

#include <ADC.h>
#include <AnalogBufferDMA.h>
ADC *adc = new ADC(); // adc object

//Define your pins here
const int readPin_adc_0 = 16;
const int readPin_adc_1 = 17;
const int readPin_adc_2 = 14;
const int readPin_adc_3 = 15;

const uint32_t buffer_size = 100; //how many samples per read

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff2[buffer_size];
AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size, dma_adc2_buff2, buffer_size);



bool SwapUpper = true; 
void setup() {
    
    delay(1000);

    pinMode(readPin_adc_0, INPUT_DISABLE); 
    pinMode(readPin_adc_1, INPUT_DISABLE);
    pinMode(readPin_adc_2, INPUT_DISABLE); 
    pinMode(readPin_adc_3, INPUT_DISABLE);
 
    Serial.println("Setup ADC_0");
    adc->adc0->setAveraging(32); 
    adc->adc0->setResolution(12); 
    adc->adc0->setConversionSpeed( ADC_CONVERSION_SPEED::MED_SPEED );
    adc->adc0->setSamplingSpeed( ADC_SAMPLING_SPEED::MED_SPEED );

    abdma1.init(adc, ADC_0);
    abdma1.userData(2048); // initial starting average

    adc->adc0->startContinuous(readPin_adc_0);   

    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(32); 
    adc->adc1->setResolution(12); 
    adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::MED_SPEED );
    adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::MED_SPEED );
    
    abdma2.init(adc, ADC_1);
    abdma2.userData(2048); 
    
    adc->adc1->startContinuous(readPin_adc_1);

    Serial.println("End Setup");
}



void loop() {

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == true) {
        adc->adc0->startContinuous(readPin_adc_0);  
        adc->adc1->startContinuous(readPin_adc_1);  
        ProcessAnalogData(&abdma1, 0);
        ProcessAnalogData(&abdma2, 1);
        SwapUpper = false;
         }
         

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == false) {
        adc->adc0->startContinuous(readPin_adc_2);  
        adc->adc1->startContinuous(readPin_adc_3);  
        ProcessAnalogData(&abdma1, 2);
        ProcessAnalogData(&abdma2, 3);
        SwapUpper = true;
        Serial.println(); }
         }


void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;
    int delta_from_center = (int) * pbuffer - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;

    pbuffer++;
  }

  int rms = sqrt(sum_delta_sq / buffer_size);
  average_value = sum_values / buffer_size;
  Serial.printf(" %u <= %u <= %u  ",  min_val, average_value, max_val);
                
  pabdma->clearInterrupt();

  pabdma->userData(average_value);
}

This reads 100 values for Joystick X and Y axis and then changes the pins and reads 100 values for potentiometers 1 and 2. So it is not continuous 4 channel reading but for the purpose ok.

The averages have already good stability and it will improve with some advanced calculation. Currently it calculates in addition also min and max but from the data it is easy to calculate speed also etc.

The question I have before starting implementing this to the actual aplication, is there better way to do this?

Also I am a bit worried doing the pin change like this. It would be good if the DMA transfer stops after the set 100 samples, pin change is done correctly and then new DMA sampling for next 100 samples is started. How to do this?
 
Great to see you made headway.

Time was short here and the link to the installed web page of documentation provided has links to the github where known issues will be posted as well.

Glad the updated library was found - it is a work in progress but is generally functional with the Teensy 4.x having just two ADC units.

Some samples have been done here - using 'lists' of pins to read in turn. That and other examples are posted on the forum.
 
This works now pretty nicely, but update rates are much lower than above.

Value up to 10 000 Hz can be put to startTimer(4000); //frequency in Hz but it provides no better update-rates than 4000 Hz.

the

adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::LOW_SPEED );
adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::LOW_SPEED );

seem not to do anything

and small error remain despite adc->adc1->recalibrate();

is there way to use adc->adc0->startSingleRead(readPin_adc_0); so that sampling happens at natural frequency like with adc->adc1->startContinuous(readPin_adc_1);, or better the adc->adc1->startContinuous(readPin_adc_1); so that it stops after buffer is full and waits new start command?

Code:
/*
    reads 4 analogue channels with DMA using Teensy 4.0 also computes minimum, maximum and average
    values and stores them in MinAvgMax[4][3];.
   

    This alternates the 2 available ADC for X, Y Joystick or potentiometers, so the readings are 
    not continious, but ok for reading this kind of controls
*/

#include <ADC.h>
#include <AnalogBufferDMA.h>
ADC *adc = new ADC(); // adc object

//Define your pins here
const int readPin_adc_0 = 16;
const int readPin_adc_1 = 17;
const int readPin_adc_2 = 14;
const int readPin_adc_3 = 15;

const uint32_t buffer_size = 10; //how many samples per read

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size);

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size];
AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size);


bool SwapUpper = true; 
uint16_t  MinAvgMax[4][3];
uint32_t  synchTime;
uint16_t  SPS;


void setup() {
    
    delay(1000);

    pinMode(readPin_adc_0, INPUT_DISABLE); 
    pinMode(readPin_adc_1, INPUT_DISABLE);
    pinMode(readPin_adc_2, INPUT_DISABLE); 
    pinMode(readPin_adc_3, INPUT_DISABLE);
 
    Serial.println("Setup ADC_0");
    adc->adc0->setAveraging(32); 
    adc->adc0->setResolution(12); 
    adc->adc0->setConversionSpeed( ADC_CONVERSION_SPEED::LOW_SPEED );//do not seem to make any effect?
    adc->adc0->setSamplingSpeed( ADC_SAMPLING_SPEED::LOW_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);

    abdma1.init(adc, ADC_0);
    abdma1.userData(2048); // initial starting average
   
    adc->adc0->startSingleRead(readPin_adc_0);
    adc->adc0->startTimer(3000); //frequency in Hz   

    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(32); 
    adc->adc1->setResolution(12); 
    adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::LOW_SPEED );
    adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::LOW_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);
    
    abdma2.init(adc, ADC_1);
    abdma2.userData(2048); 
    
    adc->adc1->startSingleRead(readPin_adc_1);
    adc->adc1->startTimer(4000); //frequency in Hz

    Serial.println("End Setup");
}



void loop() {

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == true) {

        SPS = (1000000/(micros()-synchTime)); 
        Serial.printf("SPS: %d   ", SPS);
        synchTime = micros();

        ProcessAnalogData(&abdma1, 0);
        ProcessAnalogData(&abdma2, 1);
        abdma1.clearCompletion();
        abdma2.clearCompletion();
        adc->adc0->startSingleRead(readPin_adc_2);
        adc->adc1->startSingleRead(readPin_adc_3);
        SwapUpper = false;
         }
         

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == false) {


        ProcessAnalogData(&abdma1, 2);
        ProcessAnalogData(&abdma2, 3);
        abdma1.clearCompletion();
        abdma2.clearCompletion();
        adc->adc0->startSingleRead(readPin_adc_0);
        adc->adc1->startSingleRead(readPin_adc_1);
        SwapUpper = true;
        Serial.println(); }
         }



void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;

    pbuffer++;
  }

  average_value = sum_values / buffer_size;
  
  MinAvgMax[adc_num][0] = min_val;
  MinAvgMax[adc_num][1] = average_value;
  MinAvgMax[adc_num][2] = max_val;

 
  Serial.printf(" %u:  %u < %u < %u    ",  adc_num, min_val, average_value, max_val);
                
  pabdma->clearInterrupt();
  pabdma->userData(average_value);
}
 
The _SPEED chosen does have a direct effect on the ADC hardware setup to return at chosen rates. LOW_SPEED will be more limited.

A high speed sample alternating pins on both ADC units was posted and the only in depth example I did - but not finding the code now from a year or so back - it is on the forum but not sure of search terms ...

The usage for this case would be two pins for ADC0 and two for ADC1 - the code will read and swap continuously providing the observed values. Perhaps that would give better results in this case?
 
It is totally ok to read Joystick X and Y for 6 ms and then Potentiometer 1 and 2 for 6 seconds and continue like that. But I would like to have as many good samples during the 6 ms as possible so that that can be then further processed. It actually is probably better to read in burst so that the analogue pin is not switched for every read.

The above example using the startContinuous() gives 154 samples per second and each sample has 50 readings. The data looks like this

Screenshot 2021-05-24 at 21.24.21.jpg

The example using the startSingleRead() gives 150 SPS but only with 10 readings per sample. The setConversionSpeed(), setSamplingSpeed () and not even setAveraging(32) do not seem to affect the sampling speed, only the startTimer(4000); //frequency in Hz but only in few steps, 4000 provides same result as 10000. The data looks like this.

Screenshot 2021-05-24 at 21.28.15.jpg

so the startContinuous() would be better to use, but I do not trust that is, by my changes, coded correctly. However both provide very good result, at 12 bit only the last bit is changing, on first picture only few times. Note though that though the average is more stabile in startContinuous() the min and max have occasional bigger jumps that indicates something went maybe wrong.(probably because the channels are changed, in middle it has possibly already started filling the other buffer.)

Here is still both codes as there has been some changes

Code:
/*  Uses startContinuous();
 
    4x analogue read with DMA 
    
    using AnalogBufferDMA with two buffers, this runs in continuous mode and when one buffer fills
    an interrupt is signaled, which sets flag saying it has data, which this test application
    scans the data, and computes things like a minimum, maximum, average values and an RMS value.
    For the RMS it keeps the average from the previous set of data.

    This alternates the 2 available ADC for X, Y Joystick or potentiometers, so the readings are 
    not continious, but ok for reading this kind of controls
*/

#include <ADC.h>
#include <AnalogBufferDMA.h>
ADC *adc = new ADC(); // adc object

//Define your pins here
const int readPin_adc_0 = 16;
const int readPin_adc_1 = 17;
const int readPin_adc_2 = 14;
const int readPin_adc_3 = 15;

const uint32_t buffer_size = 50; //how many samples per read

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff2[buffer_size];
AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size, dma_adc2_buff2, buffer_size);



bool SwapUpper = true; 
uint16_t  MinAvgMax[4][3];
uint32_t  synchTime;
uint16_t  SPS;


void setup() {
    
    delay(1000);

    pinMode(readPin_adc_0, INPUT_DISABLE); 
    pinMode(readPin_adc_1, INPUT_DISABLE);
    pinMode(readPin_adc_2, INPUT_DISABLE); 
    pinMode(readPin_adc_3, INPUT_DISABLE);
 
    Serial.println("Setup ADC_0");
    adc->adc0->setAveraging(32); 
    adc->adc0->setResolution(12); 
    adc->adc0->setConversionSpeed( ADC_CONVERSION_SPEED::MED_SPEED );
    adc->adc0->setSamplingSpeed( ADC_SAMPLING_SPEED::MED_SPEED );

    abdma1.init(adc, ADC_0);
    abdma1.userData(2048); // initial starting average

    adc->adc0->startContinuous(readPin_adc_0);   

    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(32); 
    adc->adc1->setResolution(12); 
    adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::MED_SPEED );
    adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::MED_SPEED );
    
    abdma2.init(adc, ADC_1);
    abdma2.userData(2048); 
    
    adc->adc1->startContinuous(readPin_adc_1);

    Serial.println("End Setup");
}



void loop() {

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == true) {

        SPS = (1000000/(micros()-synchTime)); 
        Serial.printf("SPS: %d   ", SPS);
        synchTime = micros();
        adc->adc0->startContinuous(readPin_adc_0);  
        adc->adc1->startContinuous(readPin_adc_1);  
        ProcessAnalogData(&abdma1, 0);
        ProcessAnalogData(&abdma2, 1);
        SwapUpper = false;
         }
         

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == false) {
        adc->adc0->startContinuous(readPin_adc_2);  
        adc->adc1->startContinuous(readPin_adc_3);  
        ProcessAnalogData(&abdma1, 2);
        ProcessAnalogData(&abdma2, 3);
        SwapUpper = true;
        Serial.println(); }
         }





void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;

    pbuffer++;
  }

  average_value = sum_values / buffer_size;
  
  MinAvgMax[adc_num][0] = min_val;
  MinAvgMax[adc_num][1] = average_value;
  MinAvgMax[adc_num][2] = max_val;

  Serial.printf(" %u:  %u < %u < %u    ",  adc_num, min_val, average_value, max_val);
                
  pabdma->clearInterrupt();
  pabdma->userData(average_value);
}

void ProcessAnalogData2(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;
    int delta_from_center = (int) * pbuffer - average_value;
    sum_delta_sq += delta_from_center * delta_from_center;

    pbuffer++;
  }

  int rms = sqrt(sum_delta_sq / buffer_size);
  average_value = sum_values / buffer_size;
  Serial.printf(" %u <= %u <= %u  ",  min_val, average_value, max_val);
                
  pabdma->clearInterrupt();

  pabdma->userData(average_value);
}


Code:
/*  uses startSingleRead()
 
    Reads 4 analogue channels with DMA using Teensy 4.0 also computes minimum, maximum and average
    values and stores them in MinAvgMax[4][3];.
   
    This alternates the 2 available ADC for X, Y Joystick or potentiometers, so the readings are 
    not continious, but ok for reading this kind of controls
*/

#include <ADC.h>
#include <AnalogBufferDMA.h>
ADC *adc = new ADC(); // adc object

//Define your pins here
const int readPin_adc_0 = 16;
const int readPin_adc_1 = 17;
const int readPin_adc_2 = 14;
const int readPin_adc_3 = 15;

const uint32_t buffer_size = 10; //how many samples per read

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size);

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size];
AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size);


bool SwapUpper = true; 
uint16_t  MinAvgMax[4][3];
uint32_t  synchTime;
uint16_t  SPS;


void setup() {
    
    delay(1000);

    pinMode(readPin_adc_0, INPUT_DISABLE); 
    pinMode(readPin_adc_1, INPUT_DISABLE);
    pinMode(readPin_adc_2, INPUT_DISABLE); 
    pinMode(readPin_adc_3, INPUT_DISABLE);
 
    Serial.println("Setup ADC_0");
    adc->adc0->setAveraging(32); 
    adc->adc0->setResolution(12); 
    adc->adc0->setConversionSpeed( ADC_CONVERSION_SPEED::LOW_SPEED );//do not seem to make any effect?
    adc->adc0->setSamplingSpeed( ADC_SAMPLING_SPEED::LOW_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);

    abdma1.init(adc, ADC_0);
    abdma1.userData(2048); // initial starting average
   
    adc->adc0->startSingleRead(readPin_adc_0);
    adc->adc0->startTimer(3000); //frequency in Hz   

    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(32); 
    adc->adc1->setResolution(12); 
    adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::LOW_SPEED );
    adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::LOW_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);
    
    abdma2.init(adc, ADC_1);
    abdma2.userData(2048); 
    
    adc->adc1->startSingleRead(readPin_adc_1);
    adc->adc1->startTimer(4000); //frequency in Hz

    Serial.println("End Setup");
}



void loop() {

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == true) {

        SPS = (1000000/(micros()-synchTime)); 
        Serial.printf("SPS: %d   ", SPS);
        synchTime = micros();

        ProcessAnalogData(&abdma1, 0);
        ProcessAnalogData(&abdma2, 1);
        abdma1.clearCompletion();
        abdma2.clearCompletion();
        adc->adc0->startSingleRead(readPin_adc_2);
        adc->adc1->startSingleRead(readPin_adc_3);
        SwapUpper = false;
         }
         

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == false) {


        ProcessAnalogData(&abdma1, 2);
        ProcessAnalogData(&abdma2, 3);
        abdma1.clearCompletion();
        abdma2.clearCompletion();
        adc->adc0->startSingleRead(readPin_adc_0);
        adc->adc1->startSingleRead(readPin_adc_1);
        SwapUpper = true;
        Serial.println(); }
         }


void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;

    pbuffer++;
  }

  average_value = sum_values / buffer_size;
  
  MinAvgMax[adc_num][0] = min_val;
  MinAvgMax[adc_num][1] = average_value;
  MinAvgMax[adc_num][2] = max_val;

  Serial.printf(" %u:  %u < %u < %u    ",  adc_num, min_val, average_value, max_val);
                
  pabdma->clearInterrupt();
  pabdma->userData(average_value);
}
 
Last edited:
I am not sure if I did read long enough but looks like that uses analogueRead() that waits for the reading to be ready.

The reason I want to use DMA is to free processor resources and keep reading even when busy doing other tasks. The reason for the about 6 ms sycle time for reading the samples with DMA is that the same MCU controls a display that runs 140 fps. The display update is now with DMA, but before update all pixels need to be calculated and that takes a lot of resources.

I tried interrupt timer to keep consistent ADC sampling ant that works, but do not like that approach for high speed sampling with blocking analogueRead(). analogReadContinuous(); would be better, but seems to be only for one ADC at the time. Using analogReadContinuous(); with interupt timer and swapping analogue input for every read could be a solution, but really prefer if getting it to work reliably with DMA, reading 2 ADC for 3 ms as many good samples as possible and then switching the analogue channels and doing it again.

I have not tested yet how it works with the display code, but think the above methods should work and not consume much MCU time. Should still get swapping the channels with interrupt and so that ADC clocks run at native speed, but the DMA stops after buffer is full and waits for a new start.
 
... as far as the status of ADC - it is good and solid - just takes finding examples and reading the docs and code to see the features.

That example at one point had a print ADC errors commented out - that will tell if the chosen ADC channel cannot access the indicated pin or other problems occur.

Putting all 4 pins on one channel might be an option versus two each on two channels - that and speed, resolution and averaging values will help tailor the desired speed and accuracy to keep an accurate view of the reported values.
 
The startSynchronizedSingleRead() sounds interesting but should get that working with DMA. Maybe that is what is happening with the above /* uses startSingleRead() example, should just find how to set it so that after buffer is full there is interrupt, then I could change the analogue pins to read and start it again, in well controlled manner.

What I would like to have is read 2 analogue inputs for 3 ms, change the inputs and read 3ms again. with this I would have the 4 channels updated every 6 ms, and hopefully around 50 to 100 samples for each channel + the 32 samples smoothing. This should happen with help of the DMA to keep the reading speed constant and reduce the MCU load.

Then I could then synchronise the analogue reads and processing with the about 140 fps display update.

This is how it looks now https://vimeo.com/554612698


The 30 fps video does not quite show the smoothness of 140 fps display update :)

This video is just testing the primitives. the UI will be much different, though using these elements. The changes I am now trying to do with the analogue reads is more affecting how it all works under the hood and freeing up some resources.
 
Last edited:
Have fun. It doesn't do DMA auto reading in that example, just does the read asynchronously, it starts when called and then can be checked for completion.

Doing one on each channel then - advancing to the next pair of channel reads.

It may not suit desired use case - but it does return many 10K's of reads per second with lots of time in between to 'cipher' the results.

There may be other options to explore in the index.html linked - that user wanted to read 10 drum head piezos at the speed of sound to coordinate midi output.

The someWork in that code burned up 3.5ns each cycle to simulate the 'ciphering' he might be doing on the values received.
 
Currently I get around 10k speeds when the MCU is not busy doing anything else. the problem is, when there is movement on the screen, there is a lot of pixels to calculate and the analogue read speeds get more to the frame rate 140 Hz and that causes problems with filtering...

I tried the interrupt timer and that works, but not good to make that many interrupts.

There is may ways around the problems, I hope the DMA is the solution.
 
Now, but in practise how would that be different by doing it with interrupt timer?

Could the teensythreading use the CPU time for something else while the analogueRead() waits the conversion to be ready and how well would it work when that happens 40 000 per second?
 
TeensyThreads : <your install>\hardware\teensy\avr\libraries\TeensyThreads\readme.md

Included in TeensyDuino from github indicated in that ReadMe

Allows timeslice in Milli or Micro second.

The linked code code likely put that code in loop() in a thread and read prior values and start the next async read and then thread.yield() to return exit that thread. On next entry prior data should be complete.
> That .yield() would be done in place of :: someWork in that code burned up 3.5ns

Using DMA - some number of reads will pile up and it seems only the last would have value.
 
interrupt timers.... use interrupts. teensythreads shares timeslice of cpu to run multiple tasks without using interrupts. if your timer uses slow code call function everything has to wait for it... on a thread however it can take it's sweet time and allow other interrupts to work too, while sharing time slices with different tasks/loop. I've used it for 2 UART LCDs. the speed was visually faster than running in a single loop.
 
There was stubit mistake I had, forgot the adc->adc0->startTimer(18000); //frequency in Hz is for both separatelly and one of them was at 3000 so it was limiting also the other one.

This is working good, it provides 155 loops in a second, each loop has 50 readings for each 4 channels. it provides good stability on readings.

Code:
/*  uses startSingleRead()
 
    Reads 4 analogue channels with DMA using Teensy 4.0 also computes minimum, maximum and average
    values and stores them in MinAvgMax[4][3];.
   
    This alternates the 2 available ADC for X, Y Joystick or potentiometers, so the readings are 
    not continious, but ok for reading this kind of controls
*/

#include <ADC.h>
#include <AnalogBufferDMA.h>
ADC *adc = new ADC(); // adc object

//Define your pins here
const int readPin_adc_0 = 16;
const int readPin_adc_1 = 17;
const int readPin_adc_2 = 14;
const int readPin_adc_3 = 15;

const uint32_t buffer_size = 50; //how many samples per read

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size);

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size];
AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size);


bool SwapUpper = true; 
uint16_t  MinAvgMax[4][3];
uint32_t  synchTime;
uint16_t  SPS;


void setup() {
    
    delay(1000);

    pinMode(readPin_adc_0, INPUT_DISABLE); 
    pinMode(readPin_adc_1, INPUT_DISABLE);
    pinMode(readPin_adc_2, INPUT_DISABLE); 
    pinMode(readPin_adc_3, INPUT_DISABLE);
 
    Serial.println("Setup ADC_0");
    adc->adc0->setAveraging(32); 
    adc->adc0->setResolution(12); 
    adc->adc0->setConversionSpeed( ADC_CONVERSION_SPEED::MED_SPEED );//do not seem to make any effect?
    adc->adc0->setSamplingSpeed( ADC_SAMPLING_SPEED::HIGH_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);

    abdma1.init(adc, ADC_0);
    abdma1.userData(2048); // initial starting average
   
    adc->adc0->startSingleRead(readPin_adc_0);
    adc->adc0->startTimer(18000); //frequency in Hz   

    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(32); 
    adc->adc1->setResolution(12); 
    adc->adc1->setConversionSpeed( ADC_CONVERSION_SPEED::MED_SPEED );
    adc->adc1->setSamplingSpeed( ADC_SAMPLING_SPEED::HIGH_SPEED );
    delay(100);
    adc->adc1->recalibrate();
    delay(100);
    
    abdma2.init(adc, ADC_1);
    abdma2.userData(2048); 
    
    adc->adc1->startSingleRead(readPin_adc_1);
    adc->adc1->startTimer(18000); //frequency in Hz

    Serial.println("End Setup");
}



void loop() {

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == true) {

        SPS = (1000000/(micros()-synchTime)); 
        Serial.printf("SPS: %d   ", SPS);
        synchTime = micros();

        ProcessAnalogData(&abdma1, 0);
        ProcessAnalogData(&abdma2, 1);
        abdma1.clearCompletion();
        abdma2.clearCompletion();
        adc->adc0->startSingleRead(readPin_adc_2);
        adc->adc1->startSingleRead(readPin_adc_3);
        SwapUpper = false;
         }
         

    if ( abdma1.interrupted() && abdma2.interrupted() && SwapUpper == false) {


        ProcessAnalogData(&abdma1, 2);
        ProcessAnalogData(&abdma2, 3);
        abdma1.clearCompletion();
        abdma2.clearCompletion();
        adc->adc0->startSingleRead(readPin_adc_0);
        adc->adc1->startSingleRead(readPin_adc_1);
        SwapUpper = true;
        Serial.println(); }
         }


void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
  uint32_t sum_values = 0;
  uint16_t min_val = 0xffff;
  uint16_t max_val = 0;

  uint32_t average_value = pabdma->userData();

  volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
  volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

  float sum_delta_sq = 0.0;
  if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
  while (pbuffer < end_pbuffer) {
    if (*pbuffer < min_val) min_val = *pbuffer;
    if (*pbuffer > max_val) max_val = *pbuffer;
    sum_values += *pbuffer;

    pbuffer++;
  }

  average_value = sum_values / buffer_size;
  
  MinAvgMax[adc_num][0] = min_val;
  MinAvgMax[adc_num][1] = average_value;
  MinAvgMax[adc_num][2] = max_val;

  Serial.printf(" %u:  %u < %u < %u    ",  adc_num, min_val, average_value, max_val);
                
  pabdma->clearInterrupt();
  pabdma->userData(average_value);
}
 
The above was developed with Teensy(uino) 1.53, and still compiles fine with that, but with 1.56 I get "undefined reference to `AnalogBufferDMA::clearCompletion()'"

What has changed?
 
The above was developed with Teensy(uino) 1.53, and still compiles fine with that, but with 1.56 I get "undefined reference to `AnalogBufferDMA::clearCompletion()'"

What has changed?

Pedvide - version (9.1), has been updated from the version that shipped in Teensyduino (8.0)

I expect that Paul will Paul in the updated version for the next Teensyduino release.
 
Back
Top