Reconfigure ADC via a DMA transfer to allow multiple Channel Acquisition

Status
Not open for further replies.

SaileNav

Active member
I am trying to sample 4 to 8 Analog input signals coming from microphones on the Teensy 3.1 simultaneously.
Unfortunately there are only two ADC's available on the Teensy 3.1. So to measure 4-8 signals I want to sequentially measure all the target pins and then start again with the first pin. To achieve high sample rates I use DMA channels to lighten the load of the CPU.

The first DMA channel is configured to copy the measured samples from the ADC's output register to a buffer. I tested a sketch in which I only sampled one signal and It seemed to work.
To measure multiple channels I added a second DMA channel. The second DMA channel is configured to rewrite the ADC's configuration register with different values each time the first DMA channel is activated. The target pin numbers are kept in static array and the second DMA channel is supposed to copy the pin number to the ADC's register and thereby switching to a different target pin and starting a new configuration.

Unfortunately this does not seem to work. Any idea what I am doing wrong here?

Thanks in advance

Code:
#include <string.h>
#include "ADC.h" 
#include <DMAChannel.h>

#define BUF_SIZE 256
#define NO_BUFFERS 4

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer[NO_BUFFERS][BUF_SIZE];

volatile int ibuf;
volatile int obuf;

ADC *adc = new ADC();

DMAChannel* dma1 = new DMAChannel(false);
DMAChannel* dma2 = new DMAChannel(false);

const uint32_t ChannelsCfg [] =  { 6, 7, 15, 4 };  //pin 20-23

void setup() {

  Serial.begin(9600); // USB is always 12 Mbit/sec
  delay(1000);
  Serial.println("Ready");
  delay(1000);
  
}

void loop() {
  
  // clear buffer
  for (int i = 0; i < NO_BUFFERS; ++i){
    for (int j = 0; j < BUF_SIZE; ++j){
      adcbuffer[i][j] = 50000;
    }
  }

  ibuf = 0;
  obuf = 0;

  setup_dma();
  setup_adc();
  
  for (int i = 0; i < BUF_SIZE; ++i){
    while(obuf==ibuf);
    Serial.write((uint8_t *)adcbuffer[obuf],512);
    obuf=(obuf+1)&3;
  }
  
  for (;;) {}

} 

void setup_dma() {

  dma1->begin(true);              // allocate the DMA channel first
  dma1->TCD->SADDR = &ADC0_RA;    // where to read from
  dma1->TCD->SOFF = 0;            // source increment each transfer
  dma1->TCD->ATTR = DMA_TCD_ATTR_SSIZE(1);
  dma1->TCD->NBYTES_MLNO = 2;     // bytes per transfer
  dma1->TCD->SLAST = 0;
  dma1->destinationBuffer(adcbuffer[0], 512);   // destinaton sizeof(adcbuffer[0])
  dma1->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma1->disableOnCompletion();    // require restart in code
  dma1->interruptAtCompletion();
  dma1->attachInterrupt(dma1_isr);
  dma1->enable();

  dma2->begin(true);              // allocate the DMA channel first
  dma2->sourceCircular(ChannelsCfg, 4);
  dma2->destination(ADC0_SC1A);  
  //dma2->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0); 
  //read somewhere that DMAMUX_SOURCE_ADC0 is only able to trigger one DMA channel
  dma2->triggerAtTransfersOf(*dma1);
  dma2->enable();
  
} 

void setup_adc() {

  adc->setAveraging(32); // set number of averages
  adc->setResolution(16); // set bits of resolution
  adc->setReference(INTERNAL);
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  ADC0_SC1A = ChannelsCfg[0]; //write target to register to start first conversion

} 


void dma1_isr(void) {

    ibuf=(ibuf+1)&3;
    dma1->destinationBuffer(adcbuffer[ibuf],512); //sizeof(adcbuffer[ibuf])
    dma1->enable();
    dma1->clearInterrupt();

}
 
Did you have a look onto Freescale's application note AN 4590? There, the multi-channel acquisition by using DMA reconfiguration of one single ADC is described in every detail.
 
Have you by chance looked at the ADC Library on the forums?

Yes, I am using the ADC library. I include ADC.h on line 2.
I instantiated an ADC object on line 13 and I use the ADC object in the function setup_adc() on line 78-84 to set different properties of the ADC0.

I can't use analogRead() because it is a blocking method, meaning I can't use the processor in the meantime. It also does a calibration each time it is called making it slow.

If I had a single analog pin to measure I would use startContinuous() from the ADC library. I tested this and it worked fine but since I have to measure multiple signals at a time I need to switch between the pins after each ADC conversion.
 
Last edited:
Did you have a look onto Freescale's application note AN 4590? There, the multi-channel acquisition by using DMA reconfiguration of one single ADC is described in every detail.

I based my sketch on a document on the freescale website: Measuring all ADC inputs on FRDM-KL25 using DMA.

I am looking at your suggestion, Using DMA to Emulate ADC Flexible Scan Mode on Kinetis K Series, and it is very similar to what I am doing, because I am using triggerAtTransfersOf function of the DMA channel library.

In DMAChannel.h line number 441-449:
Code:
// Use another DMA channel as the trigger, causing this
// channel to trigger after each transfer is makes, except
// the its last transfer.  This effectively makes the 2
// channels run in parallel until the last transfer
void triggerAtTransfersOf(DMABaseClass &ch) {
	ch.TCD->BITER = (ch.TCD->BITER & ~DMA_TCD_BITER_ELINKYES_LINKCH_MASK)
	  | DMA_TCD_BITER_ELINKYES_LINKCH(channel) | DMA_TCD_BITER_ELINKYES_ELINK;
	ch.TCD->CITER = ch.TCD->BITER ;
}

Probably the devil will be in the details. Let me do a detailed comparison and get back to you.
 
Yes, I am using the ADC library. I include ADC.h on line 2.
I instantiated an ADC object on line 13 and I use the ADC object in the function setup_adc() on line 78-84 to set different properties of the ADC0.

I can't use analogRead() because it is a blocking method, meaning I can't use the processor in the meantime. It also does a calibration each time it is called making it slow.

If I had a single analog pin to measure I would use startContinuous() from the ADC library. I tested this and it worked fine but since I have to measure multiple signals at a time I need to switch between the pins after each ADC conversion.

How fast do you need to read the ADC's and how often?

p.s.
The reason I pointed out the ADC Library is I was pretty sure DMA was already supported.
 
Last edited:
How fast do you need to read the ADC's and how often?

I would like to sample 4 to 8 microphones simultaneously for 5 secs. Each microphone signal should be sampled at at least 12 KHz.
After the 5 secs there is a possibility to wait until data transfers to my laptop are complete. I want to do these 5 sec. measurements a few 100 times.
 
Code:
#include <string.h>
#include "DMAChannel.h"
#include "ADC.h" 

#define BUF_SIZE 256
#define NO_BUFFERS 4


ADC *adc = new ADC(); // adc object

DMAChannel* dma0 = new DMAChannel(false);
DMAChannel* dma1 = new DMAChannel(false);

const uint8_t ChannelsCfg [] =  { 6, 7, 15, 4 };

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer[NO_BUFFERS][BUF_SIZE];
volatile int ibuf;
volatile int obuf;

void setup() {
  char password[] = "start";
  uint8_t index = 0;
  char c = '0';
  
  Serial.begin(9600); // USB is always 12 Mbit/sec
  delay(100);
  // data acquisition will start with a synchronisation step:
  while(index!=5){
    while(Serial.available() == 0){
      delay(100);// polls whether anything is ready on the read buffer - nothing happens until there's something there
    }
    c = Serial.read();
    if(password[index]==c){
      index++;
    }else{
      index = 0;
    }
    delay(1);
  }
  Serial.println("Ready");
  delay(2000);
  

}

void loop() {
  
  // clear buffer
  for (int i = 0; i < NO_BUFFERS; ++i){
    for (int j = 0; j < BUF_SIZE; ++j){
      adcbuffer[i][j] = 50000;
    }
  }
 
  Serial.println("setup");
  delay(2000); 
  ibuf = 0;
  obuf = 0;

   
  setup_adc(23);
  setup_dma(); 
  
  Serial.println("dma0 Channel");
  print_config(dma0);

  Serial.println("dma1 Channel");
  print_config(dma1);
  
  for (int i = 0; i < BUF_SIZE; ++i){
    while(obuf==ibuf);
    //Serial.write((uint8_t *)adcbuffer[obuf],512); 
    Serial.println("read buffer and send data");
    obuf=(obuf+1)&3;
  }
  
  for (;;) {}

}

void setup_dma() {

  dma0->begin(true);              // allocate the DMA channel first
  dma0->TCD->SADDR = &ADC0_RA;    // where to read from
  dma0->TCD->SOFF = 0;            // source increment each transfer
  dma0->TCD->ATTR = 0x101;
  dma0->TCD->NBYTES = 2;     // bytes per transfer
  dma0->TCD->SLAST = 0;
  dma0->TCD->DADDR = &adcbuffer[0][0];// where to read from
  dma0->TCD->DOFF = 2; 
  dma0->TCD->BITER = 256;
  dma0->TCD->CITER = 256;    
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma0->disableOnCompletion();    // require restart in code
  dma0->interruptAtCompletion();
  dma0->attachInterrupt(dma0_isr);
  
  dma1->begin(true);              // allocate the DMA channel first
  dma1->TCD->SADDR = &ChannelsCfg[0];
  dma1->TCD->SOFF = 1;            // source increment each transfer
  dma1->TCD->ATTR = 0;
  dma1->TCD->SLAST = -4;
  dma1->TCD->BITER = 4;
  dma1->TCD->CITER = 4;
  dma1->TCD->DADDR = &ADC0_SC1A;
  dma1->TCD->NBYTES = 1;
  dma1->TCD->DOFF = 0;
  dma1->TCD->CITER = 0x01;
  dma1->TCD->BITER = 0x01;
  dma1->triggerAtTransfersOf(*dma0);
  dma1->triggerAtCompletionOf(*dma0);
  dma1->interruptAtCompletion();
  dma1->attachInterrupt(dma1_isr);

  dma0->enable();
  dma1->enable();
  
} 

void print_config(DMAChannel* dma) {
    Serial.print("channel\t");
    Serial.println(dma->channel);
    Serial.print("SADDR\t");
    Serial.println((uint32_t)dma->TCD->SADDR);
    Serial.print("SOFF\t");
    Serial.println((uint32_t)dma->TCD->SOFF);
    Serial.print("ATTR\t");
    Serial.println((uint32_t)dma->TCD->ATTR);
    Serial.print("NBYTES\t");
    Serial.println((uint32_t)dma->TCD->NBYTES);
    Serial.print("SLAST\t");
    Serial.println((uint32_t)dma->TCD->SLAST);
    Serial.print("DADDR\t");
    Serial.println((uint32_t)dma->TCD->DADDR);
    Serial.print("DOFF\t");
    Serial.println((uint32_t)dma->TCD->DOFF);
    Serial.print("CITER\t");
    Serial.println((uint32_t)dma->TCD->CITER);
    Serial.print("DLASTSGA\t");
    Serial.println((uint32_t)dma->TCD->DLASTSGA);
    Serial.print("CSR\t");
    Serial.println((uint32_t)dma->TCD->CSR);
    Serial.print("BITER\t");
    Serial.println((uint32_t)dma->TCD->BITER);
    Serial.println();
}

void setup_adc(int pin) {
  adc->setAveraging(32); // set number of averages
  adc->setResolution(16); // set bits of resolution
  adc->setReference(INTERNAL);
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  adc->adc0->startContinuous(pin);//  ADC0_SC3 |= ADC_SC3_ADCO;  ADC0_SC1A = get_pin(pin);   // set to hardware input channel
  
  
} 


void dma0_isr(void) {
    Serial.println("call dma0 isr");
    ibuf=(ibuf+1)&3;
    dma0->destinationBuffer(adcbuffer[ibuf],512); //sizeof(adcbuffer[ibuf])
    dma0->enable();
    dma0->clearInterrupt();
}

void dma1_isr(void) {
    dma1->clearInterrupt();
    Serial.println("call dma1 isr");
}


// convert pin name to hardware pin
// teensy 3.1 only

#if defined(__MK20DX256__)

int 
get_pin(int pin)
{
 
static const uint8_t channel2sc1a[] = {
  5, 14, 8, 9, 13, 12, 6, 7, 15, 4,
  0, 19, 3, 19+128, 26, 18+128, 23,
  5+192, 5+128, 4+128, 6+128, 7+128, 4+192
// A15  26   E1   ADC1_SE5a  5+64
// A16  27   C9   ADC1_SE5b  5
// A17  28   C8   ADC1_SE4b  4
// A18  29   C10  ADC1_SE6b  6
// A19  30   C11  ADC1_SE7b  7
// A20  31   E0   ADC1_SE4a  4+64
};

int index;

  if (pin <= 13) {
    index = pin;      // 0-13 refer to A0-A13
  } else if (pin <= 23) {
    index = pin - 14; // 14-23 are A0-A9
  } else if (pin >= 26 && pin <= 31) {
    index = pin - 9;  // 26-31 are A15-A20
  } else if (pin >= 34 && pin <= 40) {
    index = pin - 24; // 34-37 are A10-A13, 38 is temp sensor,
                // 39 is vref, 40 is A14
  } else {
    return 5;
  }

return channel2sc1a[index];

}   // get_pin()

#endif

I made some changes since my last post. I replaced some higher level functions of the DMAChannel library with low level register assignations in setup_dma() function. I looked at the manual and the example provided by Theremingenieur and set the register to the best of my knowledge, but no success yet.

To make it easier to debug I added a print statement to the interrupt service routines (ISR), called dma0_isr and dma1_isr.
I now get the following output on my laptop via minicom

Code:
Ready
setup

dma0 Channel
channel 0
SADDR   1073983504
SOFF    0
ATTR    257
NBYTES  2
SLAST   0
DADDR   536838656
DOFF    2
CITER   33536
DLASTSGA        0
CSR     298
BITER   33536

dma1 Channel
channel 1
SADDR   16392
SOFF    1
ATTR    0
NBYTES  1
SLAST   4294967292
DADDR    1073983488
DOFF    0
CITER   1
DLASTSGA        0
CSR     130
BITER   1

call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr
call dma1 isr

The print statement of the dma1's ISR is repeated 47 times and then minicom does not receive any output anymore. So i think the sketch crashes. The print statement of the dma0's ISR is never printed.

On a positive note, the fact that the ISR of dma1 is called means that the channel linking works.
ADC0--(end-of-conversion)-->dma0--(transfer,completion)-->dma1

Anybody any idea what I am doing wrong here?
Thanks in advance.
 
Last edited:
Yes, I have two projects i'm working on that would benefit greatly from being able to offload the ADC's from the CPU. It would also allow me to increase sample rates without affecting the rest of the project.

Im actually amazed its not a more commonly used feature of the Teensy.
 
That looks interesting, it will take me a while to get the gist of it, C is not my best language lol. Much better with hardware vs software.
Below is questions after browsing.

Not to familiar with string.h is that for serial comms?
I'm going to assume the D(n) ISR's are for external inputs. Not sure what the purpose of more then 1 is.. :confused:

This looks to be the ADC channels? I take it the values are from the datasheet. edit. never mind I see your using both ADC's, still trying to figure out how those values translate to actual pins, AD4 and AD5 on ADC1 have 2 pins each(ADC1_SE4a and ADC1_SE4b, same for 5, both sets are brought out on the T3.1/3.2), so how is the pin selected.... If im reading this poorly written 1400 page document right....
Code:
const uint16_t ChannelsCfg_0 [] =  { 0x46, 0x47, 0x4F, 0x44 };  //ADC0: ad6, ad7, ad15, ad4
const uint16_t ChannelsCfg_1 [] =  { 0x44, 0x45, 0x46, 0x47 };  //ADC1: ad4, ad5, ad6, ad7


This looks to be moving the internal buffer to an external one for use, but I have no clue how that syntax works lol.
Code:
obuf_0=(ibuf_0+2) % NO_BUFFERS;

Thought you were using 16 bit values?
Code:
Serial.write((uint8_t *)adcbuffer_0[obuf_0],2*BUF_SIZE);

Are you calibrating the ADC's? From what I understand they need to be calibrated at least once after startup.
 
Last edited:
Thought you were using 16 bit values?
Code:
Serial.write((uint8_t *)adcbuffer_0[obuf_0],2*BUF_SIZE);
This question is an easy one.
We just send it in chunks of 8bit. I think it is required if you use Serial.write, I am not sure.
At the receiver(PC)'s side the 16 bit values are reconstructed.

I'm going to assume the D(n) ISR's are for external inputs. Not sure what the purpose of more then 1 is.. :confused:
Yes ISR's are for external inputs. We needed multiple external input signals to tag the data.
The inputs are both triggers and taggers. We recorded data for different gestures, and we wanted to now for each record that is sent to the computer, which gesture invoked it.


This looks to be the ADC channels? I take it the values are from the datasheet. edit. never mind I see your using both ADC's, still trying to figure out how those values translate to actual pins, AD4 and AD5 on ADC1 have 2 pins each(ADC1_SE4a and ADC1_SE4b, same for 5, both sets are brought out on the T3.1/3.2), so how is the pin selected.... If im reading this poorly written 1400 page document right....
Code:
const uint16_t ChannelsCfg_0 [] =  { 0x46, 0x47, 0x4F, 0x44 };  //ADC0: ad6, ad7, ad15, ad4
const uint16_t ChannelsCfg_1 [] =  { 0x44, 0x45, 0x46, 0x47 };  //ADC1: ad4, ad5, ad6, ad7
Yeah the documentation really sucks. At the time we figured it out, but now I can't recall the details, but you can easily check it which pins I choose. Just use my values and apply a high signal to those inputs.


This looks to be moving the internal buffer to an external one for use, but I have no clue how that syntax works lol.
Code:
obuf_0=(ibuf_0+2) % NO_BUFFERS;
It is a circular buffer that is constantly being overwritten by the converted values of the ADC. Once one of the digital inputs of pin 2,4,6,8 is triggered, then a certain number of values is sent to the computer. We wanted to start with what was already in the buffer, so that we have a bit of what happened before the trigger, that is why we start at 2 positions after the ibuf pointer. You have to do % NO_BUFFERS, becuase it is circular.

Are you calibrating the ADC's? From what I understand they need to be calibrated at least once after startup.
I don't know exactly, maybe it happens in adc->adc0->enableDMA()

Hope that helps you further,
Sailenav
 
Every little bit helps, I should have a chance this weekend to drag out some gear and start playing with it on some hardware. Im going to contact Pedvide at some point once I understand this a little more and see if he wants to try adding any of this to the ADC Library. He already has a DMA ring buffer example that uses just 1 pin per ADC, so it may be possible to add this in without to much fuss.
 
Last edited:
Well, it sort of works.

Copy/paste is only pulling a small portion of it.
Code:
Ready
Start
1
WO«=SkXÄMä`m²dUf»wûa¯LàEªdž@Â:\ëmsW‰LKT2X‘]¼RÊNC?ÁS>K¿S”J–E-:µTngŠ=ÓA:HšQ^Bh> 4”GwGEk;iTSåJãQ?H3FêDëGc_�L’K�\{M–G/V+M�HøZ*PiÌIÄOTV“GJPÎLÕX*lh[R=
c[ü\Tb�nˆWãl~f$c&WÙZ„QejÐ[C4m
vú_YUeøZMH:S8SžX8hi‡kqäT‡dõaichZìcðhxiÄVÝlàc¨YAÄK)C¾.}1[LØFÔW!GPQ#\íZÿ>ùS@UÕiÝVº^ò]IVÃJE¹Q§WZ…ižqQyÙsl·Va*]�^4oxTþU‘|%Z¸PùgÀ\þd»Y°JÑ`§yêgj_®b6kÁe9¿[‘ašLµAÐ^UawZmJŸ`fR>kßT:K_’k'\NµP9WM`c»NEDll–gíHGJDMVL=BÃ9ÑA¯RQW	KìNâL²Z€8µF7QtXN=R\T_^XN�Z–jÉg&]wwm‰o%Vg¦QM•FÒOªCÅ<ØF8:œ?PTÞbEbU>tBLg@®\|SxNä<ÔZIAG\OìSVG1[óe´vF[ŽzŠrXôZñv��LýZ£sÅ[Loy,[¡vb€BýZ�nhƒ•õZor{k[$anj\)[’s¸…g[Øc)sd[îeŸ‚/ [èzé–üþZu¶}AõZ`g5Š.ìZ±e"ys[\a…uêöZœk«‚æZÈwq[;rÄwU[.gss9[ägÁzTüZxe_üZf[t8ýZ¶l–„h$[üb€fùZh!}O$[âkd'*Z×_j`=ýZgÁsS"[ËwZz8[’s	‚z[CqEw^F[BixYéZÞlp1îZ†} ÑZÁxÐg,ÚZlÎ}GÿZ>nMoK	[òqWmSáZîp1pBóZ"ymw
 
Got it working, had to change Serial.write to Serial.print, for some reason it was just sending garbage with serial.write.
I also noticed one strange result from the data. The last ADC pin from both lists(ChannelsCfg) is actually sent first. So instead of 0->1->2->3 you get 3->0->1->2.
I had to add a delay timer to the transmits also since using a wire to jumper the ISR pin was causing several transmits at a time. I only did it to D2 ISR since I was mainly interested in the output data and not what pin.
I will play around with the settings and see what I can find, here is my modifications to your code SaileNav.


Code:
#include <string.h>
#include "DMAChannel.h"
#include "ADC.h" 

#define BUF_SIZE 256
#define NO_BUFFERS 30
//#define BITS_NO_BUF 5

ADC *adc = new ADC(); // adc object

DMAChannel* dma0 = new DMAChannel(false);
DMAChannel* dma1 = new DMAChannel(false);
DMAChannel* dma2 = new DMAChannel(false);
DMAChannel* dma3 = new DMAChannel(false);


const uint16_t ChannelsCfg_0 [] =  { 0x46, 0x47, 0x4F, 0x44 };  //ADC0: ad6(A6), ad7(A7), ad15(A8), ad4(A9)
const uint16_t ChannelsCfg_1 [] =  { 0x44, 0x45, 0x46, 0x47 };  //ADC1: ad4(A17), ad5(A16), ad6(A18), ad7(A19)
//45
const int ledPin = 13;

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_0[NO_BUFFERS][BUF_SIZE];
DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_1[NO_BUFFERS][BUF_SIZE];

volatile int ibuf_0;
volatile int ibuf_1;

volatile int obuf_0;
volatile int obuf_1;

volatile int d2_active;
volatile int d4_active;
volatile int d6_active;
volatile int d8_active;

void setup() {
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  delay(500); 
  
  ibuf_0 = 0;
  ibuf_1 = 0;
  obuf_0 = 0;
  obuf_1 = 0;

  d2_active = 0;
  d4_active = 0;
  d6_active = 0;
  d8_active = 0;
  
  pinMode(2, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);

  attachInterrupt(2, d2_isr, FALLING);
  attachInterrupt(4, d4_isr, FALLING);
  attachInterrupt(6, d6_isr, FALLING);
  attachInterrupt(8, d8_isr, FALLING);

  // clear buffer
  for (int i = 0; i < NO_BUFFERS; ++i){
    for (int j = 0; j < BUF_SIZE; ++j){
      adcbuffer_0[i][j] = 50000;
      adcbuffer_1[i][j] = 50000;
    }
  }
   
  setup_adc();
  setup_dma(); 
  

}
elapsedMillis debounce;


void loop() {

  char password[] = "start";
  uint8_t index = 0;
  char c = '0';
  
  Serial.begin(9600);
  delay(100);
  // data acquisition will start with a synchronisation step:
  while(index!=5){
    while(Serial.available() == 0){
      delay(100);// polls whether anything is ready on the read buffer - nothing happens until there's something there
    }
    c = Serial.read();
    if(password[index]==c){
      index++;
    }else{
      index = 0;
    }
    delay(1);
  }
  Serial.println("Ready");

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(500);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(500); 
  
  Serial.println("Start");

  while(1){
    uint8_t pin = 0;
    if(d2_active) pin=1;
    if(d4_active) pin=2;
    if(d6_active) pin=3;
    if(d8_active) pin=4;

    if(pin>0){

      Serial.println(pin);
      
      obuf_0=(ibuf_0+2) % NO_BUFFERS;
      obuf_1=(ibuf_1+2) % NO_BUFFERS;
      for (int i = 0; i < 100; ++i){
        int a = adcbuffer_0[obuf_0][i];
        Serial.print(a);
        Serial.print(" , ");
        int b = adcbuffer_1[obuf_1][i];
        Serial.println(b);
       // Serial.write((uint8_t *)adcbuffer_0[obuf_0],2*BUF_SIZE); 
       // Serial.write((uint8_t *)adcbuffer_1[obuf_1],2*BUF_SIZE); 
        //Serial.println("read buffer and send data");
        obuf_0=(obuf_0+1) % NO_BUFFERS;
        obuf_1=(obuf_1+1) % NO_BUFFERS;

        while(obuf_0==ibuf_0);
        while(obuf_1==ibuf_1);
      }

      digitalWrite(ledPin, HIGH);   // set the LED on
      delay(50);                  // wait for a second
      digitalWrite(ledPin, LOW);    // set the LED off

      d2_active = 0;
      d4_active = 0;
      d6_active = 0;
      d8_active = 0;
      
    }
  }

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(100);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(100);   
}

void setup_dma() {
  dma0->begin(true);              // allocate the DMA channel 
  dma0->TCD->SADDR = &ADC0_RA;    // where to read from
  dma0->TCD->SOFF = 0;            // source increment each transfer
  dma0->TCD->ATTR = 0x101;
  dma0->TCD->NBYTES = 2;     // bytes per transfer
  dma0->TCD->SLAST = 0;
  dma0->TCD->DADDR = &adcbuffer_0[0][0];// where to write to
  dma0->TCD->DOFF = 2; 
  dma0->TCD->DLASTSGA = -2*BUF_SIZE;
  dma0->TCD->BITER = BUF_SIZE;
  dma0->TCD->CITER = BUF_SIZE;    
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma0->disableOnCompletion();    // require restart in code
  dma0->interruptAtCompletion();
  dma0->attachInterrupt(dma0_isr);
  
  dma1->begin(true);              // allocate the DMA channel 
  dma1->TCD->SADDR = &ChannelsCfg_0[0];
  dma1->TCD->SOFF = 2;            // source increment each transfer
  dma1->TCD->ATTR = 0x101;
  dma1->TCD->SLAST = -8;
  dma1->TCD->BITER = 4;
  dma1->TCD->CITER = 4;
  dma1->TCD->DADDR = &ADC0_SC1A;
  dma1->TCD->DLASTSGA = 0;
  dma1->TCD->NBYTES = 2;
  dma1->TCD->DOFF = 0;
  dma1->triggerAtTransfersOf(*dma0);
  dma1->triggerAtCompletionOf(*dma0);

  dma2->begin(true);              // allocate the DMA channel 
  dma2->TCD->SADDR = &ADC1_RA;    // where to read from
  dma2->TCD->SOFF = 0;            // source increment each transfer
  dma2->TCD->ATTR = 0x101;
  dma2->TCD->NBYTES = 2;     // bytes per transfer
  dma2->TCD->SLAST = 0;
  dma2->TCD->DADDR = &adcbuffer_1[0][0];// where to write to
  dma2->TCD->DOFF = 2; 
  dma2->TCD->DLASTSGA = -2*BUF_SIZE;
  dma2->TCD->BITER = BUF_SIZE;
  dma2->TCD->CITER = BUF_SIZE;    
  dma2->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dma2->disableOnCompletion();    // require restart in code
  dma2->interruptAtCompletion();
  dma2->attachInterrupt(dma2_isr);
  
  dma3->begin(true);              // allocate the DMA channel 
  dma3->TCD->SADDR = &ChannelsCfg_1[0];
  dma3->TCD->SOFF = 2;            // source increment each transfer
  dma3->TCD->ATTR = 0x101;
  dma3->TCD->SLAST = -8;
  dma3->TCD->BITER = 4;
  dma3->TCD->CITER = 4;
  dma3->TCD->DADDR = &ADC1_SC1A;
  dma3->TCD->DLASTSGA = 0;
  dma3->TCD->NBYTES = 2;
  dma3->TCD->DOFF = 0;
  dma3->triggerAtTransfersOf(*dma2);
  dma3->triggerAtCompletionOf(*dma2);

  dma0->enable();
  dma1->enable();
  
  dma2->enable();
  dma3->enable();
  
} 

void setup_adc() {
  adc->adc0->setResolution(16); // set bits of resolution
  //adc->adc0->setReference(ADC_REF_1V2);
  adc->adc1->setResolution(16); // set bits of resolution
  //adc->adc1->setReference(ADC_REF_1V2);`
  
  ADC1_CFG2 |= ADC_CFG2_MUXSEL;
  
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  adc->adc1->enableDMA();
  
  ADC0_SC1A = ChannelsCfg_0[3];
  ADC1_SC1A = ChannelsCfg_1[3];
} 

void d2_isr(void) {
  if(debounce > 1000){
    d2_active = 1;
    debounce = 0;
    }
    else{return;}
}

void d4_isr(void) {
    d4_active = 1;
}
void d6_isr(void) {
    d6_active = 1;
}

void d8_isr(void) {
    d8_active = 1;
}

void dma0_isr(void) {
    ibuf_0=(ibuf_0+1) % NO_BUFFERS;
    dma0->TCD->DADDR = &adcbuffer_0[ibuf_0][0];
    dma0->clearInterrupt();
    dma0->enable();
}

void dma2_isr(void) {
    ibuf_1=(ibuf_1+1) % NO_BUFFERS;
    dma2->TCD->DADDR = &adcbuffer_1[ibuf_1][0];
    dma2->clearInterrupt();
    dma2->enable();
}
 
Last edited:
Had some time to play around with this, still not sure why the adc values are shifted out of order by one. But here is a breakdown table of the available pins.
I did not dig into the Bandgap or Vrefout pins yet. I did look at the A/B channel for ADC1 and found that an ADCx_CFG2[MUXSEL] setting change is required to access the A channels.

View attachment ADC Channel Breakdowns.pdf
I will update the sheet as I get time to verify the unconfirmed pins.
 
Last edited:
Had time to do some poking around with a scope, found some interesting things and possible bugs.
I have attached a breakdown so far of the times needed to complete a run from the DMA controlled ADC's.
I performed the tests with a buffer size of 256 and 128, all values are number of filled buffers per second. I drive a pin high and then low when the DMA ISR fires.

A few interesting things to note.
10, 12, and 13 bit readings have the same exact time for most of the tests I ran.
Conversion Low Speed at 10bit seems to be broken and not inline with the other settings at the same conversion speed.
The number of averages seems to default to > 1 if not set in the code.
Averages of 2 and 4 took the same amount of time. :confused:

Teensy 3.1, Arduino 1.6.12, Teensyloader 1.32, I did not look at the ADC data just how long it was taking so I cant say if the data was better or worse per setting.
Default settings means that only the Bit resolution was set, everything else was commented out.
ADC Channel Breakdowns pg2.jpg

Attached my test code based on SaileNav's example. I have tried to simplify it to make it easier for others, hope I succeeded.

Code:
// Converted to only 1 buffer per ADC, reduced to a small example.
//Based on Example by SaileNav


#include "DMAChannel.h"
#include "ADC.h" 

#define BUF_SIZE 256

ADC *adc = new ADC(); // adc object

DMAChannel* dma0 = new DMAChannel(false);
DMAChannel* dma1 = new DMAChannel(false);
DMAChannel* dma2 = new DMAChannel(false);
DMAChannel* dma3 = new DMAChannel(false);


const uint16_t ChannelsCfg_0 [] =  { 0x46, 0x47, 0x4F, 0x44 };  //ADC0: ad6(A6), ad7(A7), ad15(A8), ad4(A9)
const uint16_t ChannelsCfg_1 [] =  { 0x44, 0x45, 0x46, 0x47 };  //ADC1: ad4(A17), ad5(A16), ad6(A18), ad7(A19)

const int ledPin = 13;

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_0[BUF_SIZE];
DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_1[BUF_SIZE];

volatile int d2_active;

void setup() {
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  delay(500); 

  d2_active = 0;
  
  pinMode(2, INPUT_PULLUP);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);

  attachInterrupt(2, d2_isr, FALLING);

  // clear buffer
  for (int i = 0; i < BUF_SIZE; ++i){
      adcbuffer_0[i] = 50000;
      adcbuffer_1[i] = 50000;
    
  }
   
  setup_adc();
  setup_dma(); 
  
}
elapsedMillis debounce;


void loop() {

  char password[] = "start";
  uint8_t index = 0;
  char c = '0';
  
  Serial.begin(9600);
  delay(100);
  // data acquisition will start with a synchronisation step:
  while(index!=5){
    while(Serial.available() == 0){
      delay(100);// polls whether anything is ready on the read buffer - nothing happens until there's something there
    }
    c = Serial.read();
    if(password[index]==c){
      index++;
    }else{
      index = 0;
    }
    delay(1);
  }
  Serial.println("Ready");

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(500);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(500); 
  
  Serial.println("Start");

  while(1){
    uint8_t pin = 0;
    if(d2_active) pin=1;

    if(pin>0){
      
      for (int i = 0; i < BUF_SIZE; i = i + 4){
        int a = adcbuffer_0[i];
        Serial.print(a);
        Serial.print(".");
        int b = adcbuffer_0[i+1];
        Serial.print(b);
        Serial.print(".");
        int c = adcbuffer_0[i+2];
        Serial.print(c);
        Serial.print(".");
        int d = adcbuffer_0[i+3];
        Serial.print(d);
        Serial.print("...");                
        int e = adcbuffer_1[i];
        Serial.print(e);        
        Serial.print(".");
        int f = adcbuffer_1[i+1];
        Serial.print(f);        
        Serial.print(".");
        int g = adcbuffer_1[i+2];
        Serial.print(g);        
        Serial.print(".");                        
        int h = adcbuffer_1[i+3];
        Serial.println(h);

      }

      digitalWrite(ledPin, HIGH);   // set the LED on
      delay(50);                  // wait for a second
      digitalWrite(ledPin, LOW);    // set the LED off

      d2_active = 0;     
    }
  }

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(100);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(100);   
}

void setup_dma() {
  dma0->begin(true);              // allocate the DMA channel 
  dma0->TCD->SADDR = &ADC0_RA;    // where to read from
  dma0->TCD->SOFF = 0;            // source increment each transfer
  dma0->TCD->ATTR = 0x101;
  dma0->TCD->NBYTES = 2;     // bytes per transfer
  dma0->TCD->SLAST = 0;
  dma0->TCD->DADDR = &adcbuffer_0[0];// where to write to
  dma0->TCD->DOFF = 2; 
  dma0->TCD->DLASTSGA = -2*BUF_SIZE;
  dma0->TCD->BITER = BUF_SIZE;
  dma0->TCD->CITER = BUF_SIZE;    
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma0->disableOnCompletion();    // require restart in code
  dma0->interruptAtCompletion();
  dma0->attachInterrupt(dma0_isr);
  
  dma1->begin(true);              // allocate the DMA channel 
  dma1->TCD->SADDR = &ChannelsCfg_0[0];
  dma1->TCD->SOFF = 2;            // source increment each transfer
  dma1->TCD->ATTR = 0x101;
  dma1->TCD->SLAST = -8;          // num ADC0 samples * 2
  dma1->TCD->BITER = 4;
  dma1->TCD->CITER = 4;
  dma1->TCD->DADDR = &ADC0_SC1A;
  dma1->TCD->DLASTSGA = 0;
  dma1->TCD->NBYTES = 2;
  dma1->TCD->DOFF = 0;
  dma1->triggerAtTransfersOf(*dma0);
  dma1->triggerAtCompletionOf(*dma0);

  dma2->begin(true);              // allocate the DMA channel 
  dma2->TCD->SADDR = &ADC1_RA;    // where to read from
  dma2->TCD->SOFF = 0;            // source increment each transfer
  dma2->TCD->ATTR = 0x101;
  dma2->TCD->NBYTES = 2;     // bytes per transfer
  dma2->TCD->SLAST = 0;
  dma2->TCD->DADDR = &adcbuffer_1[0];// where to write to
  dma2->TCD->DOFF = 2; 
  dma2->TCD->DLASTSGA = -2*BUF_SIZE;
  dma2->TCD->BITER = BUF_SIZE;
  dma2->TCD->CITER = BUF_SIZE;    
  dma2->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dma2->disableOnCompletion();    // require restart in code
  dma2->interruptAtCompletion();
  dma2->attachInterrupt(dma2_isr);
  
  dma3->begin(true);              // allocate the DMA channel 
  dma3->TCD->SADDR = &ChannelsCfg_1[0];
  dma3->TCD->SOFF = 2;            // source increment each transfer
  dma3->TCD->ATTR = 0x101;
  dma3->TCD->SLAST = -8;          // num ADC1 samples * 2
  dma3->TCD->BITER = 4;
  dma3->TCD->CITER = 4;
  dma3->TCD->DADDR = &ADC1_SC1A;
  dma3->TCD->DLASTSGA = 0;
  dma3->TCD->NBYTES = 2;
  dma3->TCD->DOFF = 0;
  dma3->triggerAtTransfersOf(*dma2);
  dma3->triggerAtCompletionOf(*dma2);

  dma0->enable();
  dma1->enable();
  
  dma2->enable();
  dma3->enable();
  
} 

void setup_adc() {
  //ADC0
  //adc->setAveraging(16); // set number of averages
  adc->adc0->setResolution(16); // set bits of resolution
  //adc->setConversionSpeed(ADC_VERY_LOW_SPEED); // change the conversion speed
  //adc->setSamplingSpeed(ADC_HIGH_SPEED); // change the sampling speed
  //adc->adc0->setReference(ADC_REF_1V2);
  
  //ADC1
  //adc->setAveraging(16, ADC_1); // set number of averages
  adc->adc1->setResolution(16); // set bits of resolution
  //adc->setConversionSpeed(ADC_VERY_LOW_SPEED, ADC_1); // change the conversion speed
  //adc->setSamplingSpeed(ADC_HIGH_SPEED, ADC_1); // change the sampling speed
  //adc->adc1->setReference(ADC_REF_1V2);`
  
  ADC1_CFG2 |= ADC_CFG2_MUXSEL;
  
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  adc->adc1->enableDMA();
  
  ADC0_SC1A = ChannelsCfg_0[3];
  ADC1_SC1A = ChannelsCfg_1[3];
} 

void d2_isr(void) {
  if(debounce > 200){
    d2_active = 1;
    debounce = 0;
    }
    else{return;}
}

void dma0_isr(void) {
    dma0->TCD->DADDR = &adcbuffer_0[0];
    dma0->clearInterrupt();
    dma0->enable();
    digitalWriteFast(4, HIGH);
    digitalWriteFast(4, LOW);
}

void dma2_isr(void) {
    dma2->TCD->DADDR = &adcbuffer_1[0];
    dma2->clearInterrupt();
    dma2->enable();
    digitalWriteFast(6, HIGH);
    digitalWriteFast(6, LOW);
}
 
Last edited:
Still trying to figure out why the values are out of order. Unless I make progress I may just live with the bug and code around it :/

Here is an 8 channel ADC0/4 channel ADC1 example. I did not change the serial print for the extra channels so first 4 are first line and then second 4 on second line etc.


Code:
// Converted to only 1 buffer per ADC, reduced to a small example.
//Based on Example by SaileNav


#include "DMAChannel.h"
#include "ADC.h" 

#define BUF_SIZE 256

ADC *adc = new ADC(); // adc object

DMAChannel* dma0 = new DMAChannel(false);
DMAChannel* dma1 = new DMAChannel(false);
DMAChannel* dma2 = new DMAChannel(false);
DMAChannel* dma3 = new DMAChannel(false);


const uint16_t ChannelsCfg_0 [] =  { 0x46, 0x47, 0x4F, 0x44, 0x4C, 0x4D, 0x4E, 0x45 };  //ADC0: ad6(A6), ad7(A7), ad15(A8), ad4(A9), ad12(A5), ad13(A4), ad14(A1), ad5b(A0)
const uint16_t ChannelsCfg_1 [] =  { 0x44, 0x45, 0x46, 0x47 };  //ADC1: ad4(A17), ad5(A16), ad6(A18), ad7(A19)

const int ledPin = 13;

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_0[BUF_SIZE];
DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_1[BUF_SIZE];

volatile int d2_active;

void setup() {
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  delay(500); 

  d2_active = 0;
  
  pinMode(2, INPUT_PULLUP);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);

  attachInterrupt(2, d2_isr, FALLING);
  
  Serial.begin(9600);
  
  // clear buffer
  for (int i = 0; i < BUF_SIZE; ++i){
      adcbuffer_0[i] = 50000;
      adcbuffer_1[i] = 50000;
    
  }
   
  setup_adc();
  setup_dma(); 
  
}
elapsedMillis debounce;


void loop() {

  while(1){
    uint8_t pin = 0;
    if(d2_active) pin=1;

    if(pin>0){
      
      for (int i = 0; i < BUF_SIZE; i = i + 4){
        int a = adcbuffer_0[i];
        Serial.print(a);
        Serial.print(".");
        int b = adcbuffer_0[i+1];
        Serial.print(b);
        Serial.print(".");
        int c = adcbuffer_0[i+2];
        Serial.print(c);
        Serial.print(".");
        int d = adcbuffer_0[i+3];
        Serial.print(d);
        Serial.print("...");                
        int e = adcbuffer_1[i];
        Serial.print(e);        
        Serial.print(".");
        int f = adcbuffer_1[i+1];
        Serial.print(f);        
        Serial.print(".");
        int g = adcbuffer_1[i+2];
        Serial.print(g);        
        Serial.print(".");                        
        int h = adcbuffer_1[i+3];
        Serial.println(h);

      }

      digitalWrite(ledPin, HIGH);   // set the LED on
      delay(50);                  // wait for a second
      digitalWrite(ledPin, LOW);    // set the LED off

      d2_active = 0;     
    }
  }

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(100);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(100);   
}

void setup_dma() {
  dma0->begin(true);              // allocate the DMA channel 
  dma0->TCD->SADDR = &ADC0_RA;    // where to read from
  dma0->TCD->SOFF = 0;            // source increment each transfer
  dma0->TCD->ATTR = 0x101;
  dma0->TCD->NBYTES = 2;     // bytes per transfer
  dma0->TCD->SLAST = 0;
  dma0->TCD->DADDR = &adcbuffer_0[0];// where to write to
  dma0->TCD->DOFF = 2; 
  dma0->TCD->DLASTSGA = -2*BUF_SIZE;
  dma0->TCD->BITER = BUF_SIZE;
  dma0->TCD->CITER = BUF_SIZE;    
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma0->disableOnCompletion();    // require restart in code
  dma0->interruptAtCompletion();
  dma0->attachInterrupt(dma0_isr);
  
  dma1->begin(true);              // allocate the DMA channel 
  dma1->TCD->SADDR = &ChannelsCfg_0[0];
  dma1->TCD->SOFF = 2;            // source increment each transfer
  dma1->TCD->ATTR = 0x101;
  dma1->TCD->SLAST = -16;          // num ADC0 samples * 2
  dma1->TCD->BITER = 8;
  dma1->TCD->CITER = 8;
  dma1->TCD->DADDR = &ADC0_SC1A;
  dma1->TCD->DLASTSGA = 0;
  dma1->TCD->NBYTES = 2;
  dma1->TCD->DOFF = 0;
  dma1->triggerAtTransfersOf(*dma0);
  dma1->triggerAtCompletionOf(*dma0);

  dma2->begin(true);              // allocate the DMA channel 
  dma2->TCD->SADDR = &ADC1_RA;    // where to read from
  dma2->TCD->SOFF = 0;            // source increment each transfer
  dma2->TCD->ATTR = 0x101;
  dma2->TCD->NBYTES = 2;     // bytes per transfer
  dma2->TCD->SLAST = 0;
  dma2->TCD->DADDR = &adcbuffer_1[0];// where to write to
  dma2->TCD->DOFF = 2; 
  dma2->TCD->DLASTSGA = -2*BUF_SIZE;
  dma2->TCD->BITER = BUF_SIZE;
  dma2->TCD->CITER = BUF_SIZE;    
  dma2->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dma2->disableOnCompletion();    // require restart in code
  dma2->interruptAtCompletion();
  dma2->attachInterrupt(dma2_isr);
  
  dma3->begin(true);              // allocate the DMA channel 
  dma3->TCD->SADDR = &ChannelsCfg_1[0];
  dma3->TCD->SOFF = 2;            // source increment each transfer
  dma3->TCD->ATTR = 0x101;
  dma3->TCD->SLAST = -8;          // num ADC1 samples * 2
  dma3->TCD->BITER = 4;
  dma3->TCD->CITER = 4;
  dma3->TCD->DADDR = &ADC1_SC1A;
  dma3->TCD->DLASTSGA = 0;
  dma3->TCD->NBYTES = 2;
  dma3->TCD->DOFF = 0;
  dma3->triggerAtTransfersOf(*dma2);
  dma3->triggerAtCompletionOf(*dma2);

  dma0->enable();
  dma1->enable();
  
  dma2->enable();
  dma3->enable();
  
} 

void setup_adc() {
  //ADC0
  //adc->setAveraging(16); // set number of averages
  adc->adc0->setResolution(16); // set bits of resolution
  //adc->setConversionSpeed(ADC_VERY_LOW_SPEED); // change the conversion speed
  //adc->setSamplingSpeed(ADC_HIGH_SPEED); // change the sampling speed
  //adc->adc0->setReference(ADC_REF_1V2);
  
  //ADC1
  //adc->setAveraging(16, ADC_1); // set number of averages
  adc->adc1->setResolution(16); // set bits of resolution
  //adc->setConversionSpeed(ADC_VERY_LOW_SPEED, ADC_1); // change the conversion speed
  //adc->setSamplingSpeed(ADC_HIGH_SPEED, ADC_1); // change the sampling speed
  //adc->adc1->setReference(ADC_REF_1V2);`
  
  ADC1_CFG2 |= ADC_CFG2_MUXSEL;
  
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  adc->adc1->enableDMA();
  
  ADC0_SC1A = ChannelsCfg_0[3];
  ADC1_SC1A = ChannelsCfg_1[3];
} 

void d2_isr(void) {
  if(debounce > 200){
    d2_active = 1;
    debounce = 0;
    }
    else{return;}
}

void dma0_isr(void) {
    dma0->TCD->DADDR = &adcbuffer_0[0];
    dma0->clearInterrupt();
    dma0->enable();
    digitalWriteFast(4, HIGH);  //debug
    digitalWriteFast(4, LOW);   //debug
}

void dma2_isr(void) {
    dma2->TCD->DADDR = &adcbuffer_1[0];
    dma2->clearInterrupt();
    dma2->enable();
    digitalWriteFast(6, HIGH);   //debug
    digitalWriteFast(6, LOW);    //debug
}
 
No luck so far in figuring out why the adcbuffer_0 and adcbuffer_1 array data is not correctly ordered. (IE 3,0,1,2 instead of 0,1,2,3) using the code I posted from #19.
I have attempted to changing some of the settings, mostly with worse results or no results at all.

Is there anyone still following this thread with any ideas to try? I would rather avoid having to work around this bug.

EDIT...
On another note I see why so many of my measurements on post 19 are the same, according to ADC_Module.cpp the only usable resolution settings are "For single-ended measurements: 8, 10, 12 or 16 bits.". So if you want say 13 bit you have to set for 16 and shift the results.
 
Last edited:
No luck so far in figuring out why the adcbuffer_0 and adcbuffer_1 array data is not correctly ordered. (IE 3,0,1,2 instead of 0,1,2,3) using the code I posted from #19.
The hardware is doing exactly, what you asked it to.

ADC0_SC1A = ChannelsCfg_0[3];
This triggers the first conversion, which the last channel. The result is the first DMA transfer.
 
I actually tried changing that and it appears to make no difference what I set it too, even commenting it out makes no difference. I may remove it permanently from the example since it does not appear to do anything in the example.
 
I actually tried changing that and it appears to make no difference what I set it too, even commenting it out makes no difference.
I guess the ADC conversion finished is asserted anyway, so dma0 is kicked of when you enable the channel.

The first transfer from dma1 triggers the second conversion (and you already have a result in dma0 from the first one).

You could for example shift your ChannelsCfg_0 elements, so that they are {ch1, ch2, ch3, ch0}. Or you could make it a DMA circular buffer (the size must be a power of 2) and could initialize the dma1 source pointer with element 1 instead of element 0 (if it's set up as circular buffer, things will properly wrap around).
 
Ya, I was hoping I was just doing something silly or it was a known issue :/
Looks to be something to do with how DMA is initialized. If it ever gets fixed it could play hell with any projects using DMAADC control like this.

Here is an updated 4/4Channel example with a few more notes and the Channel order changed. I also removed the "ADC0_SC1A = ChannelsCfg_0[3];" and ADC1 lines since they aren't doing anything apparent.

Code:
// Converted to only 1 buffer per ADC, reduced to a small example.
//Based on Example by SaileNav


#include "DMAChannel.h"
#include "ADC.h" 

#define BUF_SIZE 256

ADC *adc = new ADC(); // adc object

DMAChannel* dma0 = new DMAChannel(false);
DMAChannel* dma1 = new DMAChannel(false);
DMAChannel* dma2 = new DMAChannel(false);
DMAChannel* dma3 = new DMAChannel(false);


//ChannelsCfg order must be {CH1, CH2, CH3, CH0 }, adcbuffer output will be CH0, CH1, CH2, CH3
//Order must be {Second . . . . . . . . First} no matter the number of channels used.
const uint16_t ChannelsCfg_0 [] =  { 0x47, 0x4F, 0x44, 0x46 };  //ADC0: CH0 ad6(A6), CH1 ad7(A7), CH2 ad15(A8), CH3 ad4(A9)
const uint16_t ChannelsCfg_1 [] =  { 0x45, 0x46, 0x47, 0x44 };  //ADC1: CH0 ad4(A17), CH1 ad5(A16), CH2ad6(A18), CH3 ad7(A19)

const int ledPin = 13;

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_0[BUF_SIZE];
DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_1[BUF_SIZE];

volatile int d2_active;

void setup() {
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  delay(500); 

  d2_active = 0;
  
  pinMode(2, INPUT_PULLUP);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);

  attachInterrupt(2, d2_isr, FALLING);
  
  Serial.begin(9600);
  
  // clear buffer
  for (int i = 0; i < BUF_SIZE; ++i){
      adcbuffer_0[i] = 50000;
      adcbuffer_1[i] = 50000;
    
  }
   
  setup_adc();
  setup_dma(); 
  
}
elapsedMillis debounce;


void loop() {

  while(1){     //this is leftover from the original example, it no longer serves a purpose
    uint8_t pin = 0;
    if(d2_active) pin=1;

    if(pin>0){
      
      for (int i = 0; i < BUF_SIZE; i = i + 4){
        int a = adcbuffer_0[i];
        Serial.print(a);
        Serial.print(".");
        int b = adcbuffer_0[i+1];
        Serial.print(b);
        Serial.print(".");
        int c = adcbuffer_0[i+2];
        Serial.print(c);
        Serial.print(".");
        int d = adcbuffer_0[i+3];
        Serial.print(d);
        Serial.print("...");                
        int e = adcbuffer_1[i];
        Serial.print(e);        
        Serial.print(".");
        int f = adcbuffer_1[i+1];
        Serial.print(f);        
        Serial.print(".");
        int g = adcbuffer_1[i+2];
        Serial.print(g);        
        Serial.print(".");                        
        int h = adcbuffer_1[i+3];
        Serial.println(h);

      }

      digitalWrite(ledPin, HIGH);   // set the LED on
      delay(50);                  // wait for a second
      digitalWrite(ledPin, LOW);    // set the LED off

      d2_active = 0;     
    }
  }

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(100);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(100);   
}

void setup_dma() {
  dma0->begin(true);              // allocate the DMA channel 
  dma0->TCD->SADDR = &ADC0_RA;    // where to read from
  dma0->TCD->SOFF = 0;            // source increment each transfer
  dma0->TCD->ATTR = 0x101;
  dma0->TCD->NBYTES = 2;     // bytes per transfer
  dma0->TCD->SLAST = 0;
  dma0->TCD->DADDR = &adcbuffer_0[0];// where to write to
  dma0->TCD->DOFF = 2; 
  dma0->TCD->DLASTSGA = -2*BUF_SIZE;
  dma0->TCD->BITER = BUF_SIZE;
  dma0->TCD->CITER = BUF_SIZE;    
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma0->disableOnCompletion();    // require restart in code
  dma0->interruptAtCompletion();
  dma0->attachInterrupt(dma0_isr);
  
  dma1->begin(true);              // allocate the DMA channel 
  dma1->TCD->SADDR = &ChannelsCfg_0[0];
  dma1->TCD->SOFF = 2;            // source increment each transfer (n bytes)
  dma1->TCD->ATTR = 0x101;
  dma1->TCD->SLAST = -8;          // num ADC0 samples * 2
  dma1->TCD->BITER = 4;           // num of ADC0 samples
  dma1->TCD->CITER = 4;           // num of ADC0 samples
  dma1->TCD->DADDR = &ADC0_SC1A;
  dma1->TCD->DLASTSGA = 0;
  dma1->TCD->NBYTES = 2;
  dma1->TCD->DOFF = 0;
  dma1->triggerAtTransfersOf(*dma0);
  dma1->triggerAtCompletionOf(*dma0);

  dma2->begin(true);              // allocate the DMA channel 
  dma2->TCD->SADDR = &ADC1_RA;    // where to read from
  dma2->TCD->SOFF = 0;            // source increment each transfer
  dma2->TCD->ATTR = 0x101;
  dma2->TCD->NBYTES = 2;     // bytes per transfer
  dma2->TCD->SLAST = 0;
  dma2->TCD->DADDR = &adcbuffer_1[0];// where to write to
  dma2->TCD->DOFF = 2; 
  dma2->TCD->DLASTSGA = -2*BUF_SIZE;
  dma2->TCD->BITER = BUF_SIZE;
  dma2->TCD->CITER = BUF_SIZE;    
  dma2->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dma2->disableOnCompletion();    // require restart in code
  dma2->interruptAtCompletion();
  dma2->attachInterrupt(dma2_isr);
  
  dma3->begin(true);              // allocate the DMA channel 
  dma3->TCD->SADDR = &ChannelsCfg_1[0];
  dma3->TCD->SOFF = 2;            // source increment each transfer (n bytes)
  dma3->TCD->ATTR = 0x101;
  dma3->TCD->SLAST = -8;          // num ADC1 samples * 2
  dma3->TCD->BITER = 4;           // num of ADC1 samples
  dma3->TCD->CITER = 4;           // num of ADC1 samples
  dma3->TCD->DADDR = &ADC1_SC1A;
  dma3->TCD->DLASTSGA = 0;
  dma3->TCD->NBYTES = 2;
  dma3->TCD->DOFF = 0;
  dma3->triggerAtTransfersOf(*dma2);
  dma3->triggerAtCompletionOf(*dma2);

  dma0->enable();
  dma1->enable();
  
  dma2->enable();
  dma3->enable();
  
} 

void setup_adc() {
  //ADC0
  //adc->setAveraging(16, ADC_0); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  adc->setConversionSpeed(ADC_MED_SPEED, ADC_0); // change the conversion speed
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_0); // change the sampling speed
  //adc->adc0->setReference(ADC_REF_1V2);
  
  //ADC1
  //adc->setAveraging(16, ADC_1); // set number of averages
  adc->adc1->setResolution(12); // set bits of resolution
  adc->setConversionSpeed(ADC_MED_SPEED, ADC_1); // change the conversion speed
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_1); // change the sampling speed
  //adc->adc1->setReference(ADC_REF_1V2);`
  
  ADC1_CFG2 |= ADC_CFG2_MUXSEL;
  
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  adc->adc1->enableDMA();
  
} 

void d2_isr(void) {
  if(debounce > 200){
    d2_active = 1;
    debounce = 0;
    }
    else{return;}
}

void dma0_isr(void) {
    dma0->TCD->DADDR = &adcbuffer_0[0];
    dma0->clearInterrupt();
    dma0->enable();
    digitalWriteFast(4, HIGH);
    digitalWriteFast(4, LOW);
}

void dma2_isr(void) {
    dma2->TCD->DADDR = &adcbuffer_1[0];
    dma2->clearInterrupt();
    dma2->enable();
    digitalWriteFast(6, HIGH);
    digitalWriteFast(6, LOW);
}
 
Last edited:
Status
Not open for further replies.
Back
Top