Can't compile and upload ADC examples

ProfHuster

Active member
I've spent six hours trying to get the Teensy 3.2 and 4.0 ADC examples going.
I reinstalled Teensyduino 1.8.19, Teensy Loader = 1.56 on my MacBook Pro (14-inch, 2021), MacOS Monterey12.3.1

I need to do timed dma two channel ADC for a project with a sample rate of 320 kHz. I am using a Teensy 4.0, but when I couldn't get the examples to work, I tried a 3.2.

Tried the Examples -> Examples for Teensy 4.0 -> ADC -> various
and Examples for Teensy 3.2/3.2
Various included adc_timer, adc_test , adc_dma, adc_pdb, adc_timer_dma, and adc_timer_dma_oneshot.

Results (Y=worked, N=nope):
Program | 3.2 | 4.0 | Notes
adc_test | Y | Y. | 3.2 has much more output than 4.0!
adc_pdb | Y | N | "Valid for Teensy 3.0 and 3.1"
adc_dma | N | N | No ADC_USE_DMA
adc_dma | N | N | Define ADC_USE_DMA, then "unref AnalogBufferDMA::init" for both
adc_timer | N | N | No ADC_USE_TIMER
adc_timer | Y | Y | defined ADC_USE_TIMER
synchronizedMeasirements | N | N | both No ADC_DUAL_ADCS
synchronizedMeasirements | Y | Y | defined No ADC_DUAL_ADCS

None of the DMA examples worked. The preprocessor did not have ADC_USE_DMA defined. When I manually defined it, I got errors like
Code:
/Users/whoever/Documents/Arduino/my_adc_dma/my_adc_dma.ino:78: undefined reference to `AnalogBufferDMA::init(ADC*, signed char)'
/Users/whoever/Documents/Arduino/my_adc_dma/my_adc_dma.ino:84: undefined reference to `AnalogBufferDMA::init(ADC*, signed char)'
collect2
The same errors for both T4.0 and T3.2. I looked at @pedvide's github library and thought is might be unsigned char versus int8_t, so I tried casting ADC_0 and ADC_1 to int8_t. Got the same error.

Am I missing something very obvious? These are straight from the Examples. The pedvide examples also don't work.
 
OK. Got out my windows machine, Win 10, Teensyduino 1.55, Arduino 1.8.16.
EXACTLY the same results. Uploading Examples -> Examples for Teensy 4.0 (or 3.1/3.2) -> ADC -> adc_dma
Errors
Code:
C:\Users\mhust\AppData\Local\Temp\arduino_build_608341\sketch\adc_dma_my_3_2.ino.cpp.o: In function `setup':
C:\Users\mhust\Dropbox\My PC (LAPTOP-09ORC1LF)\Documents\Arduino\adc_dma_my_3_2/adc_dma_my_3_2.ino:78: undefined reference to `AnalogBufferDMA::init(ADC*, signed char)'
C:\Users\mhust\Dropbox\My PC (LAPTOP-09ORC1LF)\Documents\Arduino\adc_dma_my_3_2/adc_dma_my_3_2.ino:84: undefined reference to `AnalogBufferDMA::init(ADC*, signed char)'
collect2.exe: error: ld returned 1 exit status
Error compiling for board Teensy 3.2 / 3.1.

For reference, the code is attached. I added "#define ADC_USE_DMA" and got the errors. Without the define statement, it only complied the setup & loop at the end so the Teensy printed ""adc_dma \nno ADC_USE_DMA".
Code:
/* Example for using DMA with ADC
    This example uses DMA object to do the sampling.  It does not use a timer so it runs
    at whatever speed the ADC will run at with current settings.

    It should work for Teensy LC, 3.x and T4

  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.
*/
#define ADC_USE_DMA
#ifdef ADC_USE_DMA

#include <ADC.h>
#include <AnalogBufferDMA.h>


// This version uses both ADC1 and ADC2
#if defined(KINETISL)
const int readPin_adc_0 = A0;
#elif defined(KINETISK)
const int readPin_adc_0 = A0;
const int readPin_adc_1 = A2;
#else
const int readPin_adc_0 = A0;
const int readPin_adc_1 = 26;
#endif

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

// Going to try two buffers here  using 2 dmaSettings and a DMAChannel
#ifdef KINETISL
const uint32_t buffer_size = 500;
#else
const uint32_t buffer_size = 1600;
#endif

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);

#ifdef ADC_DUAL_ADCS
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);
#endif

void setup() {
    while (!Serial && millis() < 5000) ;

    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(readPin_adc_0, INPUT); //pin 23 single ended
#ifdef ADC_DUAL_ADCS
    pinMode(readPin_adc_1, INPUT);
#endif

    Serial.begin(9600);
    Serial.println("Setup ADC_0");
    // 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->adc0->setAveraging(8); // set number of averages
    adc->adc0->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
    Serial.println("before enableDMA"); Serial.flush();


    // setup a DMA Channel.
    // Now lets see the different things that RingbufferDMA setup for us before
    abdma1.init(adc, ADC_0);
    abdma1.userData(initial_average_value); // save away initial starting average
#ifdef ADC_DUAL_ADCS
    Serial.println("Setup ADC_1");
    adc->adc1->setAveraging(8); // set number of averages
    adc->adc1->setResolution(12); // set bits of resolution
    abdma2.init(adc, ADC_1);
    abdma2.userData(initial_average_value); // save away initial starting average
    adc->adc1->startContinuous(readPin_adc_1);
#endif

    // Start the dma operation..
    adc->adc0->startContinuous(readPin_adc_0);

    Serial.println("End Setup");
}

char c = 0;


void loop() {

    // Maybe only when both have triggered?
#ifdef ADC_DUAL_ADCS
    if ( abdma1.interrupted() && abdma2.interrupted()) {
        if (abdma1.interrupted()) ProcessAnalogData(&abdma1, 0);
        if (abdma2.interrupted()) ProcessAnalogData(&abdma2, 1);
        Serial.println();
    }
#else
    if ( abdma1.interrupted()) {
        ProcessAnalogData(&abdma1, 0);
        Serial.println();
    }
#endif

}

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);
}

#else // make sure the example can run for any boards (automated testing)
void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println("adc_dma \nno ADC_USE_DMA");
}
void loop() {}
#endif // ADC_USE_DMA
 
The problem is that ADC_USE_DMA is not defined until there is a #include <ADC.h>, and the #include <ADC.h> is INSIDE the #ifdef ADC_USE_DMA. This is a problem (bug) not only in the example INO files, but also in the ADC library source file AnalogBufferDMA.cpp, which does the same thing. That's why AnalogBufferDMA::init() is undefined. Here is a fix:

1) In adc_dma.ino, add #include <ADC.h> before the #ifdef ADC_USE_DMA.
2) In ADC/AnalogBufferDMA.cpp, add #include "ADC.h" before #ifdef ADC_USE_DMA

It's a complex library, and I'm not sure what is the correct fix, i.e. whether those #ifdef ADC_USE_DMA should just be removed. There must have been some changes to these examples and source files that were never tested.

EDIT: This code/comment at the bottom of adc_dma.ino may be a clue as to what happened and why it wasn't caught. The #ifdef ADC_USE_DMA logic was added to allow the sketch to compile for platforms without DMA, but the way it was done, the sketch would always compile, and do nothing, never using DMA, always passing the automated test.

Code:
#else // make sure the example can run for any boards (automated testing)
void setup() {}
void loop() {}
#endif // ADC_USE_DMA

MORE INFO: adc_dma.ino uses 8-sample averaging. I modified that to 1 sample, e.g. adc->adc0->setAveraging(1/*8*/). I don't know much about DMA, but the buffer size is 1600, and the time between interrupts (buffer full?) seems to be 4 ms, so that would imply 1600/0.004 = 400,000 samples/sec on each of the two channels.
 
Last edited:
Fixes to ADC library to use DMA and for examples to build and run correctly

1) For source files AnalogBufferDMA.h and AnalogBufferDMA.cpp, move #include "ADC.h" outside #ifdef ADC_USE_DMA.
2) For examples listed below, move #include <ADC.h> outside initial #ifdef statement

adc_dma.ino
adc_timer.ino
adc_timer_dma.ino
adc_timer_dma_oneshot.ino
internal_reference.ino

Without these changes, all of these examples build and run, but with "empty" setup() and loop() functions. I tested all of them with T4.1, and some of them with T3.5. Note that T4 does not support internal reference, so that example does nothing on T4, but works correctly on T3.
 
There is definitely some magic in the code, like in adc_dma
Code:
if ((uint32_t)pbuffer >= 0x20200000u)
    arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
This frees some memory, but pbuffer is a pointer returned by pabdma->bufferLastISRFilled();, so the allocation must be done by pabdma. So...
I'll have a hard time modifying this code.

But I have a compile and upload that works!
Thanks
 
There is definitely some magic in the code, like in adc_dma
Code:
if ((uint32_t)pbuffer >= 0x20200000u)
    arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
This frees some memory, but pbuffer is a pointer returned by pabdma->bufferLastISRFilled();, so the allocation must be done by pabdma. So...
I'll have a hard time modifying this code.

But I have a compile and upload that works!
Thanks

Yes, that code is rather opaque, but it's not freeing memory. 0x20200000 is the address of 2nd 512K block of RAM in T4.x, which includes DMAMEM. So, I think what this code means is "if the buffer is DMAMEM, clear the data cache". This assures that the subsequent code is reading from RAM, and not from the cache. See the info below from https://github.com/TeensyUser/doc/wiki/Memory-Mapping.

arm_dcache_delete(void *addr, uint32_t size) - Delete data from the cache, without touching memory - Normally arm_dcache_delete() is used before receiving data via DMA or from bus-master peripherals which write to memory. You want to delete anything the cache may have stored, so your next read is certain to access the physical memory.
 
Back
Top