ADC library, with support for Teensy 4, 3.x, and LC

Hello ,

I am looking to use Teensy 3.2 with the ADC to acquire data, to then run some signal processing. I read the thread, and trid to find relevant information with the search engine, but I failed.

I need accurate sample timing, as for any signal processing data, and from what I read in the thread, it means using PDB. For this there are some good examples for this nice libraries.
Ideally, a DMA memory transfer would be nice to have.

I found an example with the DMA + ring buffer, but I didn't find examples with DMA+ring buffer + ADC triggered using PDB.
Does any of you do have such an example?

I did not fully understood how the ADC was trigerred in the DMA example . Free running ADC, and DMA stores as soon as a sample is available? Is this correct?

Thanks a lot, and thanks to PEDVIDE for his awsome work.

Olivier.
 
@Paul, @Pedvide - Not sure what version of sources are in current beta. Some of the test cases did not compile, I edited one and adc_pdb and when I compared my possible push up to github, it showed more differences... So don't think the current beta release matches github\pedvide master? - Code is using old way to specify speeds...

Also when I tried the ringbuffer DMA example it did not appear to do any conversions... I have not debugged...

As I mentioned in the Well monitoring thread, I am playing around with my own version (@hobi not sure if this one will work for you or not).

When I converted to try to use both ADC0 and ADC1 using DMA and PDB, it worked first time, hung second time. Figured out a fix that got it to work... Wonder if maybe should update ADC stopPDB method to zero out the appropriate(PDB0_CH1C1 or PDB0_CH0C1), doing that allowed it to work multiple times.

Not sure if anyone will find this version interesting or not... Again WIP...
Code:
#include <ADC.h>
#include <DMAChannel.h>
#define BUFFER_SIZE 100

ADC adc;

// Variables for ADC0
volatile DMAMEM uint16_t adc0_buf[BUFFER_SIZE]; // buffer 1...
volatile uint8_t adc0_busy = 0;
DMAChannel adc0_dma;

// Variables for ADC1
volatile DMAMEM uint16_t adc1_buf[BUFFER_SIZE];
volatile uint8_t adc1_busy = 0;
DMAChannel adc1_dma;

// References for ISRs...
extern void adc0_dma_isr(void);
extern void adc1_dma_isr(void);


void setup() {
  while (!Serial && millis() < 3000) ;
  Serial.begin(115200);
  Serial.println("Test DMA Analog Read");

  // Initialize the
  adc.setAveraging(4);
  adc.setResolution(12);
  adc.setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED, ADC_0); // change the conversion speed
  adc.setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED, ADC_0); // change the sampling speed


  adc.setAveraging(4, ADC_1);
  adc.setResolution(12, ADC_1);
  adc.setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED, ADC_1); // change the conversion speed
  adc.setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED, ADC_1); // change the sampling speed
  Serial.printf("First Read of A13: %d\n", adc.analogRead(A13));
  Serial.printf("First Read of A14: %d\n", adc.analogRead(A14));
  Serial.printf("First Read of A15: %d\n", adc.analogRead(A15));
  Serial.printf("First Read of A16: %d\n", adc.analogRead(A16));

  // Lets setup Analog 0 dma
  adc0_dma.source((volatile uint16_t&)ADC0_RA);
  adc0_dma.destinationBuffer(adc0_buf, sizeof(adc0_buf));
  adc0_dma.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC0);
  adc0_dma.interruptAtCompletion();
  adc0_dma.disableOnCompletion();
  adc0_dma.attachInterrupt(&adc0_dma_isr);
  NVIC_DISABLE_IRQ(IRQ_PDB); // we don't want or need the PDB interrupt


  // Lets setup Analog 1 dma
  adc1_dma.source((volatile uint16_t&)ADC1_RA);
  adc1_dma.destinationBuffer(adc1_buf, sizeof(adc1_buf));
  adc1_dma.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC1);
  adc1_dma.interruptAtCompletion();
  adc1_dma.disableOnCompletion();
  adc1_dma.attachInterrupt(&adc1_dma_isr);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("Enter any key to run sample");
  while (Serial.read() == -1) ;
  while (Serial.read() != -1) ;
  Serial.println("Start Sample");
  memset((void*)adc0_buf, 0, sizeof(adc0_buf));
  memset((void*)adc1_buf, 0, sizeof(adc1_buf));
  uint32_t start_time = millis();

  // Start 0
  adc0_busy = 1;
  adc1_busy = 1;
  adc.adc0->startSingleRead(A14);
  adc.adc1->startSingleRead(A13);
  adc0_dma.enable();
  adc1_dma.enable();
  adc.enableDMA(ADC_0);
  adc.enableDMA(ADC_1);
  adc0_dma.enable();
  adc1_dma.enable();
  adc.adc0->startPDB(60 * 50);
  adc.adc1->startPDB(60 * 50);

  dumpDMA_TCD("ADC_0", &adc0_dma);
  dumpDMA_TCD("ADC_1", &adc1_dma);

  while (adc0_busy || adc1_busy) {
    if ((millis() - start_time) > 1000) {
      Serial.printf("Timeout %d %d\n", adc0_busy, adc1_busy);
      break;
    }
  }
  
[COLOR="#FF0000"]  PDB0_CH1C1 = 0;
  PDB0_CH0C1 = 0;[/COLOR]

  adc.adc0->stopPDB();
  adc0_dma.disable();
  adc.disableDMA(ADC_0);
  adc0_dma.disable();

  adc.adc1->stopPDB();
  adc.adc1->stopPDB();
  adc1_dma.disable();
  adc.disableDMA(ADC_1);

  dumpDMA_TCD("ADC_0", &adc0_dma);
  dumpDMA_TCD("ADC_1", &adc1_dma);

  Serial.printf("Sample completed: %d\n", millis() - start_time);
  for (int i = 0; i < BUFFER_SIZE; i++) {
    Serial.printf("%4u ", adc0_buf[i]);
    if ((i % 20) == 19) Serial.println();
  }
  Serial.println("\n");

  for (int i = 0; i < BUFFER_SIZE; i++) {
    Serial.printf("%4u ", adc1_buf[i]);
    if ((i % 20) == 19) Serial.println();
  }
  Serial.println();


}


void adc0_dma_isr(void)
{
  adc0_busy = false;
  adc0_dma.clearInterrupt();
  adc0_dma.clearComplete();
}

void adc1_dma_isr(void)
{
  adc1_busy = false;
  adc1_dma.clearInterrupt();
  adc1_dma.clearComplete();
}



typedef struct  __attribute__((packed, aligned(4))) {
  uint32_t SADDR;
  int16_t SOFF;
  uint16_t ATTR;
  uint32_t NBYTES;
  int32_t SLAST;
  uint32_t DADDR;
  int16_t DOFF;
  uint16_t CITER;
  int32_t DLASTSGA;
  uint16_t CSR;
  uint16_t BITER;
} TCD_DEBUG;

void dumpDMA_TCD(const char *psz, DMABaseClass *dmabc)
{
  Serial.printf("%s %08x %08x:", psz, (uint32_t)dmabc, (uint32_t)dmabc->TCD);
  TCD_DEBUG *tcd = (TCD_DEBUG*)dmabc->TCD;
  Serial.printf("%08x %04x %04x %08x %08x ", tcd->SADDR, tcd->SOFF, tcd->ATTR, tcd->NBYTES, tcd->SLAST);
  Serial.printf("%08x %04x %04x %08x %04x %04x\n", tcd->DADDR, tcd->DOFF, tcd->CITER, tcd->DLASTSGA,
                tcd->CSR, tcd->BITER);

}
Adding the lines in RED was what got it to work multiple times... Now back to next part of what I mentioned in well monitor...
 
KurtE,

I will have a look, but I think it will help.

I have one basic question. For me the dma should work fully in the background without any cpu need. Do we really need to have an adc isr ?? As long as we have that Adc isr executing it removes the whole point of having a dma running in the background.... do I get it right?

Hobi
 
Hi, if you are talking about my two interrupts:
Code:
void adc0_dma_isr(void)
{
  adc0_busy = false;
  adc0_dma.clearInterrupt();
  adc0_dma.clearComplete();
}

void adc1_dma_isr(void)
{
  adc1_busy = false;
  adc1_dma.clearInterrupt();
  adc1_dma.clearComplete();
}
I asked for them. In particular I asked for 100 elements to be read. These ISRs are called when those 100 reads are done.. If you don't want the interrupt, you don't need to ask for them.

Example in the code that asks for them: adc0_dma.interruptAtCompletion();
 
OOOps, you are right,

I got confused with the naming. These are DMA interrupts, not ADC ones! Ok, very clear. Thanks for the reply!

Olivier.
 
Last edited:
You are welcome... And in this case, the code could have also enabled interrupts for the PDB as well... Some turn these on during debug, in order to be able to monitor when the ADC is being triggered... I do not have these enabled...
 
I also have a question regarding the interrupts. I need to sample both ADCs simultaneously at fixed intervals. I looked into the analogReadIntervalTimer example. This however uses only one ADC to multiplex two pin readings, which has me confused about how to trigger an interrupt when using both ADCs.

The examples uses a function called adc0_isr() to process the results when the conversion is done, but nowhere does the sketch indicate when this particular function should be called. Does the library assume you name the ISR function this way? And what if you only want to trigger the ISR when both ADC conversions are done? enableInterrupts(ADC_x) requires you to specifically declare which ADC you want to fire the interrupt.
 
Hi folks,

Just wanted to be sure the simple code i wrote does what it intends to do. Simple single ended ADC acquisition at 6000Hz, precisely timed, for signal processing.

Thank you for your help.

Here is the code :

Code:
#define BTSERIAL Serial1
#include <ADC.h>

const int readPin = A14; // ADC0
ADC *adc = new ADC(); // adc object;
boolean inc=true, led = false, nval=false;
int value, val=0;
uint32_t ifreq = 6000; // ADC sampling frequency 6000Hz


void setup() {
Serial.begin(230400);
BTSERIAL.begin(230400);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(readPin, INPUT);
Serial.println("Begin setup");

///// ADC0 only ////
adc->setAveraging(32); // set number of averages
adc->setResolution(16); // set bits of resolution
adc->setReference(ADC_REFERENCE::REF_3V3,ADC_0);
adc->setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); // change the conversion speed
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::MED_SPEED); // change the sampling speed

Serial.print("Start pdb 6000Hz ");
adc->adc0->stopPDB();
adc->adc0->startSingleRead(readPin); // call this to setup everything before the pdb starts, differential is also possible
adc->enableInterrupts(ADC_0);
adc->adc0->startPDB(ifreq); //frequency in Hz
   
 Serial.println("End setup");
}

void loop() 
{
    if (nval)
    {
      Serial.write((char)(0x00ff&&value));
      Serial.write(((char)(value>>8)));
      BTSERIAL.write((char)(0x00ff&&value));
      BTSERIAL.write(((char)(value>>8)));
      nval = false;
      digitalWrite(LED_BUILTIN,led);
      led = !led;    
    }   
}

void adc0_isr() {
    // Goal is to read very accurately at 6000Hz the ADC to get as accurate as possible the sample timing. Hence using PDB.
    // Serial outputs are likely to interfere as they are interrupt driven, and were mooved to the main loop.
    // readSingle as a result of PDB trigerring the conversion
    value = (uint16_t)adc->adc0->readSingle();
    nval = true;
}

void pdb_isr(void) 
{
        PDB0_SC &=~PDB_SC_PDBIF; // clear interrupt
}
 
Last edited by a moderator:
ADC bug or ...?

Hello to all.

I little modified previous code ,and put to ADC input A4 not DC coupled 10Hz sinus ~170mVpp with midpoint 1,65V offset.
Output sinus on DAC has strange distortion - it seems like jump adc value in two points !
Teensy 3.5 board.

Code:
#include <ADC.h>

const int readPin = A4; // ADC0=A14
ADC *adc = new ADC(); // adc object;
boolean inc=true, led = false, nval=false;
int value, val=0;
uint32_t ifreq = 60000; // ADC sampling frequency 6000Hz, MAX is 250khz


void setup() {

pinMode(readPin, INPUT);
analogWriteResolution(12);

///// ADC0 only ////
adc->setAveraging(0); // set number of averages
adc->setResolution(12); // set bits of resolution
adc->setReference(ADC_REFERENCE::REF_3V3,ADC_0);
adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change the conversion speed
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed

adc->adc0->stopPDB();
adc->adc0->startSingleRead(readPin); // call this to setup everything before the pdb starts, differential is also possible
adc->enableInterrupts(ADC_0);
adc->adc0->startPDB(ifreq); //frequency in Hz
   
}

void loop() 
{
    analogWrite(A21, value);
}

void adc0_isr() {
    // Goal is to read very accurately at 6000Hz the ADC to get as accurate as possible the sample timing. Hence using PDB.
    // Serial outputs are likely to interfere as they are interrupt driven, and were mooved to the main loop.
    // readSingle as a result of PDB trigerring the conversion
    value = (uint16_t)adc->adc0->readSingle();
    nval = true;
}

void pdb_isr(void) 
{
        PDB0_SC &=~PDB_SC_PDBIF; // clear interrupt
}
 
@KurtE Thank you very much for your simple fix of how to stop PDB by zeroing PDB0_CH0C1. My project using Teensy 3.2 finally!!!! works reliably without random hanging. It probably should be a part of stopPDB() ...

p.s. I also suggest @Pedvide to include your code from post #402 as an easy to understand and nice example of using ADC with PDB and DMA... I was struggling to find something like this for a long time...
 
Last edited:
Actually I have teensy 3.1 boards. I just had the setting for the ide on 3.0 for testing if it would compile.
For the actual project I will not need synchronous ADC.
Pedro should get the feedback. That's al
 
Hello

I have a question. I'm using teensy 3.6 and ADC, i would like to increase the speed of reading. I read a Voltage for one second and i get allwasy 13750-13760 Sample. It is 12 Bit.
My code
#include <ADC.h>

volatile int elapsed;
int readPin = A9; // ADC0
ADC *adc = new ADC();; // adc object
int value1 = 0;

void setup() {

//pinMode(LED_BUILTIN, OUTPUT);
pinMode(readPin, INPUT);
Serial.begin(9600);
adc->setAveraging(0, ADC_0); // set number of averages
adc->setResolution(12, ADC_0); // set bits of resolution
adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED, ADC_0); // , change the conversion speed
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::LOW_SPEED, ADC_0); // change the sampling speed
while(!Serial){};
}

void loop() {
elapsedMicros elapsed;
while (elapsed <= 1000000)
{
value1 = adc->adc0->analogRead(readPin);
Serial.println(value1);
}
exit(0);
}

I tried also

#include <ADC.h>

ADC *adc = new ADC();; // adc object
volatile int elapsed;
int readPin = A9; // ADC0
int value = 0;

void setup() {

//pinMode(LED_BUILTIN, OUTPUT);
pinMode(readPin, INPUT);
Serial.begin(9600);
adc->setAveraging(0); // set number of averages
adc->setResolution(12); // set bits of resolution
adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // , change the conversion speed
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed
adc->startContinuous(readPin, ADC_0);
while(!Serial){};
}

void loop() {
elapsedMicros elapsed;
while (elapsed <= 1000000)
{
value = (uint16_t)adc->analogReadContinuous(ADC_0); // the unsigned is necessary for 16 bits, otherwise values larger than 3.3/2 V are negative!
Serial.println(value, DEC);
}
exit(0);
}

But the result is exactly the same.
And it doesn't matter if i change Speed to VERY_HIGH_SPEED or to LOW_SPEED it is always 13.7 k
adc->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED, ADC_0);
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED, ADC_0);

Do you have any idea how can i speed up it?
 
Hello,

Looking at your code I see some strange "spaces" in the lines where you set the Conversion speed & Sampling speed.
Is this also in your actual code? Do you get compiler warnings/errors?

Regards, Otto
 
No, there is no spaces, i see it here too.

Thats how it look like:

adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // , change the conversion speed
adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed

edit

Ok i don't know what is happening but in code i don't have these spaces
 
Hi Kranvonkyln,

there are a few parts where I would search for problems:
1) When does elapsedMicros start to count? I prefer to use e.g. unsigned long duration; before code to be tested: duration = microes(); after code to be tested: duration = microes() - duration; to be sure, that you get the right elapsed time

2) You include Serial.println(value, DEC) in the loop and you measure it! It takes a hell of a lot more time that the analogRead statement - so whatever you try to improve whith that statement, you will never really see something happening!

Kind regards,
Stefan
 
Hello everyone!
I have a question. If I use continuous reading on both ADC 0 and 1 at A9 and A1 and use also simultaneously single reading on A2 all going smooth and working but if I enable interruptions for ADC’s then I cannot get to work simultaneously single reading anymore on any analog inputs.
Sorry for noobies question, it is normal?
I just learning how to use ADC library and interrupts 😃 in other ways.
I was tested it on original example analog continuous read, without modifications.
 
Hello,

Thanks for this library, it helped me use some of the more advanced ADC features without having to dig into register definitions, but I've run into a problem, adc->startSingleRead(A11) doesn't generate an interrupt on completion, whereas all the other pins I read from do. Any ideas on where I could be going wrong?

I am using the latest version of the library as checked out from github today 2019-06-04, on a teensy 3.6, using arduino 1.8.7 and teensyduino 1.44
 
Hello,

Thanks for this library, it helped me use some of the more advanced ADC features without having to dig into register definitions, but I've run into a problem, adc->startSingleRead(A11) doesn't generate an interrupt on completion, whereas all the other pins I read from do. Any ideas on where I could be going wrong?

I am using the latest version of the library as checked out from github today 2019-06-04, on a teensy 3.6, using arduino 1.8.7 and teensyduino 1.44

Figured it out: calling adc->enableInterrupts(); only enables interrupts for ADC0, you have to also call adc->enableInterrupts(1); to enable them for ADC1

EDIT: Just fyi, without any special optimizations, when running at HIGH_SPEED & using interrupts I get ~530k samples/s which is great
 
Last edited:
Will this library be updated for the Teensy 4.0? Or is there any other version that is currently compatible with it?
 
With the help of KurtE and mjs513, this library will be updated for Teensy 4 in a few days. I'll make an announcement and update the first post.
 
With the help of KurtE and mjs513, this library will be updated for Teensy 4 in a few days. I'll make an announcement and update the first post.

That is great work @Pedvide! Email Paul/Robin about a bump to Sr+ so you can edit old post #1. Or post the update and it can get migrated.
 
Teensy 4 support is official! I have updated the first page and the code is available on the github repo as usual.
 
Teensy 4 support is official! I have updated the first page and the code is available on the github repo as usual.

I downloaded the library this evening and I can compile it for Teensy 3.6 no problem but it does not compile for the Teensy 4.0

Code:
C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\ADC/ADC_Module.h:50:2: error: #error "Board not supported!"

 #error "Board not supported!"

  ^

Error compiling for board Teensy 4.0.
 
I downloaded the library this evening and I can compile it for Teensy 3.6 no problem but it does not compile for the Teensy 4.0

Code:
C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\ADC/ADC_Module.h:50:2: error: #error "Board not supported!"

 #error "Board not supported!"

  ^

Error compiling for board Teensy 4.0.

That shows the TeensyDuino install library location - is that were the latest Github Download was installed? The latest T_4 version isn't included in a TeensyDuino install yet AFAIK.
 
Back
Top