DMA Modes

Status
Not open for further replies.

Demolishun

Well-known member
I am reading up on DMA related to analog inputs and pwm outputs. It is taking a bit to get my head around it. Paul's source code helps a LOT.

Does anyone know if the scatter/gather modes for DMA transfers would allow 2 analog inputs to be gathered together and then after some processing send some data to a scatter operation to 2 PWM outputs? Reading through chip data I am just not sure, but theoretically (based upon the definition of those modes) it should be.

I am going to be doing some "simple" DMA transfers to get my head around the simple modes, but I was hoping someone else was playing with this and might have some insight.
 
Paul posted a comment in another thread (see here: http://forum.pjrc.com/threads/23253-teensy-3-0-SPI-with-DMA-nice-try?p=46899&viewfull=1#post46899) that scatter/gather might have a problem and he referenced the Freescale errata:

Before anyone wastes a lot of time trying to use the scatter/gather features in the DMA controller, check the errata from Freescale. Apparently that feature is broken in many of the chips.

I did a quick scan on the errata, not digging in too deep. I only could find errata e6966 that has to do with continuous link mode, which to me seems unrelated to scatter/gather mode.

Paul, do you have any further information on this that you could point us to?
 
How about automatic buffer chaining - DMA hardware has a list of 1 or more next-buffer, and switches to it and its length, when the current buffer is full/empty.
 
The new DMAChannel object supports these features.

At the moment, the only documentation is comments in the source code, and messages in this thread.

There are 2 object types: DMAChannel and DMASetting. They both have all the same settings functions, like source(), sourceBuffer(), destinationBuffer(), transferCount(), etc.

Most projects only need DMAChannel objects, where each object allocates 1 of the actual hardware channels. Configure with source, destination, trigger and you're good to do.

The DMASetting object holds settings for DMA in memory only. The "=" operator is supported, so you can assign a DMASetting object to a DMAChannel object, which might be useful if you have several very different settings, where you want to configure them all ahead of time and then use them to later quickly configure a DMAChannel object. But the DMASetting object is most useful for scatter-gather, using the replaceSettingsOnCompletion() function. When those settings are actually used on a DMAChannel object, that function tells which other DMASetting object is to be copied to the DMAChannel automatically. You can set up any number of DMASetting objects to chain to each other using replaceSettingsOnCompletion(). Keep in mind this only works on Teensy 3.1. The scatter-gather feature doesn't work on 3.0.

The triggerAtTransfersOf() and triggerAtCompletionOf() functions are used to cause one DMA channel to trigger another, which is called "channel linking" in Freescale's documentation. Each hardware event can trigger only a single channel, but you can use these to make another channel run in parallel, at all but the last transfer, or at only the last transfer, or at every transfer (use both functions).

Hopefully the objects and functions make using the DMA easier. They automatically allocate unique channels, so your code won't conflict with other libraries using DMAChannel. They also provide a thin layer of abstraction, which gives some hope that code using these functions might later become compatible with other chips that have different underlying DMA hardware.
 
Oh wow Paul! That is amazing. I had been looking at that object, but not really understanding what I was looking at. After reading through the chip docs and seeing what you put together I am impressed. I definitely think more people will be encouraged to experiment with DMA the way you have laid it out. It is a lot more inviting than some of the DMA tables in the docs. ;)

Can I just grab that file or do I need to grab the all the beta files from the repo?
 
Here is my first hack at this. It compiles, but I have not tested. I wanted to see if I am even doing this correctly:
Code:
#include <DMAChannel.h>

DMAChannel dmaChan0;

const unsigned short &analogChan0 = ADC0_RA; // to be set to proper register location
#define ABUFFERLEN 64
unsigned short analogChan0Buffer[ABUFFERLEN];

#define ADCRES 16
#define ADCAVG 4

void setup(){
  
  // setup dmaChan0
  dmaChan0.transferSize(2); // 2 byes for 16 bit analog
  dmaChan0.transferCount(ABUFFERLEN);  // i assume 64 triggers here?
  dmaChan0.source(analogChan0);  // set to hardware analog source location
  dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN);  // destination of samples
  dmaChan0.triggerAtHardwareEvent(IRQ_ADC0); // trigger on ADC0
  dmaChan0.disableOnCompletion();  // require restart in code
  
  // setup analog for continuous sampling
  analogReadResolution(ADCRES);
  analogReadAveraging(ADCAVG);
  analogReference(DEFAULT);
  
  ADC0_SC2 |= ADC_SC2_DMAEN;
  ADC0_SC3 |= ADC_SC3_ADCO;
  ADC0_SC1A = dmaChan0.channel;
  
  // enable dma for analog
  dmaChan0.enable();
}

void loop(){
}

Edit:
Changed analogChan0 to a ref variable.
 
Last edited:
I "think" the DMA is working, but I am getting all zeros in the analog data. So I do not think I am getting any data for some reason.

Code:
#include <DMAChannel.h>

DMAChannel dmaChan0;

const unsigned short &analogChan0 = ADC0_RA; // to be set to proper register location
#define ABUFFERLEN 64
unsigned short analogChan0Buffer[ABUFFERLEN];

#define ADCRES 16
#define ADCAVG 4

#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT)
#define PDB_PERIOD 1087 // 48e6 / 44100

void setup(){
  
  // clear out analog data
  for(int count =0; count < ABUFFERLEN; count++){
    analogChan0Buffer[count] = 0;
  }
  
  // set the programmable delay block to trigger the ADC at 44.1 kHz
  SIM_SCGC6 |= SIM_SCGC6_PDB;
  PDB0_MOD = PDB_PERIOD;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG;
  PDB0_CH0C1 = 0x0101;
  
  // enable the ADC for hardware trigger and DMA
  ADC0_SC2 |= ADC_SC2_ADTRG | ADC_SC2_DMAEN;
  
  // setup dmaChan0
  dmaChan0.transferSize(2); // 2 byes for 16 bit analog
  dmaChan0.transferCount(ABUFFERLEN);  // i assume 64 triggers here?
  dmaChan0.source(analogChan0);  // set to hardware analog source location
  dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN);  // destination of samples
  dmaChan0.triggerAtHardwareEvent(IRQ_ADC0); // trigger on ADC0
  dmaChan0.disableOnCompletion();  // require restart in code
  
  // setup analog for continuous sampling
  analogReadResolution(ADCRES);
  analogReadAveraging(ADCAVG);
  analogReference(DEFAULT);
  
  ADC0_SC2 |= ADC_SC2_DMAEN;
  ADC0_SC3 |= ADC_SC3_ADCO;
  ADC0_SC1A = dmaChan0.channel;
  
  // enable dma for analog
  dmaChan0.enable();  
  
  // serial setup
  Serial.begin(9600);
  
  
}

void loop(){
  
  //Serial.println("Test");  
  
  if(dmaChan0.complete()){
    Serial.println("Analog Data:");
    for(int count =0; count < ABUFFERLEN; count+=8){
      Serial.print(analogChan0Buffer[count], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+1], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+2], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+3], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+4], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+5], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+6], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+7], DEC);
      Serial.println();
    }
    Serial.println("Done Transmitting");
    dmaChan0.clearComplete();
    
    dmaChan0.enable(); 
  }
  if(dmaChan0.error()){
  }
  
  
  delay(5000);
}
 
***bump***
I had it "setup" the AD to channel by doing an analogRead() to at least get the input setup and fed it into the array as the default data.
That data is getting overwritten by 0's so I think the DMA is working. I have no idea why the analog is not getting captured though. I think this code is %99 there and would serve as a simple example for the new DMA code if I can get it to work properly.
Code:
#include <DMAChannel.h>

DMAChannel dmaChan0;

const unsigned short &analogChan0 = ADC0_RA; // to be set to proper register location
#define ABUFFERLEN 64
unsigned short analogChan0Buffer[ABUFFERLEN];

#define ADCRES 16
#define ADCAVG 4

#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT)
#define PDB_PERIOD 1087 // 48e6 / 44100

void setup(){
  
  // clear out analog data
  for(int count =0; count < ABUFFERLEN; count++){
    //analogChan0Buffer[count] = count;
    analogChan0Buffer[count] = analogRead(A0);  // use to setup ADC0 input?
  }
  
  // set the programmable delay block to trigger the ADC at 44.1 kHz
  SIM_SCGC6 |= SIM_SCGC6_PDB;
  PDB0_MOD = PDB_PERIOD;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG;
  PDB0_CH0C1 = 0x0101;
  
  // setup dmaChan0
  dmaChan0.transferSize(2); // 2 byes for 16 bit analog
  dmaChan0.transferCount(ABUFFERLEN);  // i assume 64 triggers here?
  dmaChan0.source((const unsigned short &)ADC0_RA);  // set to hardware analog source location  
  dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN*2);  // destination of samples
  dmaChan0.triggerAtHardwareEvent(IRQ_ADC0); // trigger on ADC0
  dmaChan0.disableOnCompletion();  // require restart in code
  
  // setup analog for continuous sampling
  analogReadResolution(ADCRES);
  analogReadAveraging(ADCAVG);
  analogReference(DEFAULT);
  
  //ADC0_SC2 |= ADC_SC2_DMAEN;
  ADC0_SC2 |= ADC_SC2_ADTRG | ADC_SC2_DMAEN;
  ADC0_SC3 |= ADC_SC3_ADCO;
  ADC0_SC1A = dmaChan0.channel;
  
  // enable dma for analog
  dmaChan0.enable();  
  
  // serial setup
  Serial.begin(9600);
  
  
}

void loop(){
  
  //Serial.println("Test");  
  
  if(dmaChan0.complete()){
    Serial.println("Analog Data:");
    for(int count =0; count < ABUFFERLEN; count+=8){
      Serial.print(analogChan0Buffer[count], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+1], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+2], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+3], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+4], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+5], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+6], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+7], DEC);
      Serial.println();
    }
    Serial.println("Done Transmitting");
    dmaChan0.clearComplete();
    
    dmaChan0.enable(); 
  }
  if(dmaChan0.error()){
    Serial.println("DMA transfer error.");
    dmaChan0.clearError();
  }
  
  
  delay(5000);
}
 
a couple of things jump out to me,

  1. don't need to set transfer size (transferSize()) this is already implied through the destination buffer type. i.e. unsigned short, 16bits
  2. you set destination buffer lenght twice the size of the array you declare, i.e. dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN*2);
  3. get rid of transferCount this is also handled in the destinationBuffer function.
  4. pretty sure you don't need to cast ADC0_RA to unsigned short. Its a 32bit register.
  5. Your triggerAtHardwareEvent should be DMAMUX_SOURCE_ADC0.
 
a couple of things jump out to me,

  1. don't need to set transfer size (transferSize()) this is already implied through the destination buffer type. i.e. unsigned short, 16bits
  2. you set destination buffer lenght twice the size of the array you declare, i.e. dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN*2);
  3. get rid of transferCount this is also handled in the destinationBuffer function.
  4. pretty sure you don't need to cast ADC0_RA to unsigned short. Its a 32bit register.
  5. Your triggerAtHardwareEvent should be DMAMUX_SOURCE_ADC0.

1. Okay, I see that it is set for that in the function itself.
2. Well, when that was set to the ABUFFERLEN it would only fill half the buffer. When I set it to twice it filled it up completely. So I figured it was in bytes not number of transfers.
3. Okay, I can see that too.
4. Just tried this and I get a DMA Transfer error. Before with the cast to 16 bit pointer it actually transfers data. So I think it has something to do with the analog data being sized to 16 bits and the settings the source() function calls.
5. Tried this and I get no transfer whatsoever. So my ADC stuff is probably not being triggered properly.

I appreciate the feedback.
 
I got DMAMUX_SOURCE_ADC0 to work by removing the averaging settings. Now it still does not seem to capture any valid analog data it is triggering the DMA transfer. So something with the analog is still messed up. I am reading through the chip settings and trying to understand what is still not right.
 
Can't really help with the ADC but i think the Audio library uses PDB/DMA w/ADC, might be worth a look.
 
Yep, that is one example I am looking at. You helped me a lot. When I get the everything working I will post the whole thing as an example with comments.
 
It all came down to pointer issues with ADC0_RA:
Code:
/*
Testing DMA functions of Teensy 1.20 rc2
*/

#include <DMAChannel.h>

DMAChannel dmaChan0;

#define ABUFFERLEN 64
DMAMEM static uint16_t analogChan0Buffer[ABUFFERLEN];

#define ADCRES 16  // 16
#define ADCAVG 4

#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT)
#define PDB_PERIOD 1087 // 48e6 / 44100

void setup(){  
  
  // serial setup
  Serial.begin(9600);  
  
  // set the programmable delay block to trigger the ADC at 44.1 kHz
  SIM_SCGC6 |= SIM_SCGC6_PDB;
  PDB0_MOD = PDB_PERIOD;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG;
  PDB0_CH0C1 = 0x0101;
  
  // setup dmaChan0  
  //dmaChan0.source((const unsigned short&)0);  // set to hardware analog source location    
  //dmaChan0.TCD->SADDR = &ADC0_RA;  // ultimate hack, could not get the source functions to accept ADCO_RA pointer at all
  
  dmaChan0.source((const unsigned short*)&ADC0_RA); // added new method to accept pointers
  
  dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN*2);  // destination of samples
  
  dmaChan0.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dmaChan0.disableOnCompletion();  // require restart in code
  
  // enable dma for analog
  dmaChan0.enable();  
  
  // setup analog for continuous sampling
  analogReadResolution(ADCRES);
  analogReadAveraging(ADCAVG);
  analogReference(DEFAULT);
  
  // clear out analog data
  for(int count =0; count < ABUFFERLEN; count++){
    //analogChan0Buffer[count] = count;
    analogChan0Buffer[count] = analogRead(A0);  // use to setup ADC0 input?
  }  
  
  //SIM_SCGC7 |= SIM_SCGC7_DMA;
  //SIM_SCGC6 |= SIM_SCGC6_DMAMUX;
  
  //ADC0_SC2 |= ADC_SC2_ADTRG | ADC_SC2_DMAEN;  // 
  ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  ADC0_SC3 |= ADC_SC3_ADCO;
  ADC0_SC1A = A0; // set to input channel, not dma channel  
 
}

void loop(){
  
  if(dmaChan0.complete()){    
    
    Serial.println("Analog Data:");
    for(int count =0; count < ABUFFERLEN; count+=8){
      Serial.print(analogChan0Buffer[count], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+1], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+2], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+3], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+4], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+5], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+6], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+7], DEC);
      Serial.println();
    }
    Serial.println("Done Transmitting");
    dmaChan0.clearComplete();
    
    dmaChan0.enable(); 
  }
  if(dmaChan0.error()){
    Serial.println("DMA transfer error.");
    dmaChan0.clearError();
  }
    
  delay(5000);
}

I had to add this method to DMAChannel.h:
Code:
void source(const unsigned short *p) {
	TCD->SADDR = p;
	TCD->SOFF = 0;
	TCD->ATTR_SRC = 1;
	if ((uint32_t)p < 0x40000000 || TCD->NBYTES == 0) TCD->NBYTES = 2;
	TCD->SLAST = 0;
}

I don't know why I cannot somehow convert the register pointer to a ref. I looked high and low and could not figure it out. So I finally did a couple of ways to make it work. One was to pass a ref that was of the proper size, but obviously the wrong value:
Code:
dmaChan0.source((const unsigned short&)0);  
dmaChan0.TCD->SADDR = &ADC0_RA;

The other was to add the new function I posted above to make it work.

If someone can tell me how to do this properly with a pointer like ADC0_RA I am all ears.

It now works and I can play with other stuff now. The DMA was the least of my issues. The biggest issue was the stupid pointer conversion.

So there you go Paul. The DMA stuff does work and pretty simple too. Just need to make it easier to use register pointers or explain to me how I was supposed to do this. ;)

Edit:
Input channel was wrong. Instead of 0 is should A0. It seems to grab the data from the right channel now.
 
Last edited:
Okay, I have updated the code:
Code:
#include <DMAChannel.h>

DMAChannel dmaChan0;
DMAChannel dmaChan1;

#define ABUFFERLEN 64
DMAMEM static uint16_t analogChan0Buffer[ABUFFERLEN];
DMAMEM static uint16_t analogChan1Buffer[ABUFFERLEN];
//const volatile uint16_t* analogReg0 = (const volatile uint16_t*)&ADC0_RA;

#define ADCRES 16  // 16
#define ADCAVG 4

#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT)
#define PDB_PERIOD 1087 // 48e6 / 44100

void setup(){  
  
  // serial setup
  Serial.begin(9600);  
  
  // set the programmable delay block to trigger the ADC at 44.1 kHz
  SIM_SCGC6 |= SIM_SCGC6_PDB;
  PDB0_MOD = PDB_PERIOD;
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK;
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG;
  //PDB0_CH0C1 = 0x0303;  //0x0101;
  PDB0_CH0C1 = 0x0101;  //0x0101;
  
  // setup dmaChan0  
  //dmaChan0.source((const unsigned short&)0);  // set to hardware analog source location    
  //dmaChan0.TCD->SADDR = &ADC0_RA;  // ultimate hack, could not get the source functions to accept ADCO_RA pointer at all
  
  dmaChan0.source((const unsigned short*)&ADC0_RA); // added new method to accept pointers
  dmaChan1.source((const unsigned short*)&ADC1_RA);  
  
  dmaChan0.destinationBuffer(analogChan0Buffer, ABUFFERLEN*2);  // destination of samples
  dmaChan1.destinationBuffer(analogChan1Buffer, ABUFFERLEN*2);  // destination of samples
  
  dmaChan0.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dmaChan0.disableOnCompletion();  // require restart in code
  
  dmaChan1.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dmaChan1.disableOnCompletion();  // require restart in code
  
  // enable dma for analog
  dmaChan0.enable();  
  dmaChan1.enable();
  
  // setup analog for continuous sampling
  analogReadResolution(ADCRES);
  //analogReadAveraging(ADCAVG);
  analogReference(DEFAULT);
  //analogReference(INTERNAL);
  
  // clear out analog data
  for(int count =0; count < ABUFFERLEN; count++){
    //analogChan0Buffer[count] = count;
    analogChan0Buffer[count] = analogRead(A0);  // use to setup ADC0 input?
    analogChan1Buffer[count] = analogRead(A1);  // use to setup ADC1 input?
  }   
     
  ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  ADC0_SC3 |= ADC_SC3_ADCO;
  ADC0_SC1A = 5; // set to input channel, read analog.c to get correct value for A0
    
  ADC1_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  ADC1_SC3 |= ADC_SC3_ADCO;
  ADC1_CFG2 &= ~ADC_CFG2_MUXSEL;
  ADC1_SC1A = 5; // set to input channel, read analog.c to get correct value for A15
  
 
}

void loop(){
  
  if(dmaChan0.complete() && dmaChan1.complete()){    
    
    //Serial.println((int)A0);
    
    Serial.println("Analog Data 0:");
    for(int count =0; count < ABUFFERLEN; count+=8){
      Serial.print(analogChan0Buffer[count], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+1], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+2], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+3], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+4], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+5], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+6], DEC);
      Serial.print(":");
      Serial.print(analogChan0Buffer[count+7], DEC);
      Serial.println();
    }
    
    Serial.println("Analog Data 1:");
    for(int count =0; count < ABUFFERLEN; count+=8){
      Serial.print(analogChan1Buffer[count], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+1], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+2], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+3], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+4], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+5], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+6], DEC);
      Serial.print(":");
      Serial.print(analogChan1Buffer[count+7], DEC);
      Serial.println();
    }
    
    Serial.println("Done Transmitting");
    dmaChan0.clearComplete();
    dmaChan1.clearComplete();
    
    dmaChan0.enable(); 
    dmaChan1.enable();
  }
  
  if(dmaChan0.error() || dmaChan1.error()){
    
    if(dmaChan0.error())
      Serial.println("DMA 0 transfer error.");
      
    if(dmaChan1.error())
      Serial.println("DMA 1 transfer error.");      
      
    dmaChan0.clearError();
    dmaChan1.clearError();
  }
    
  delay(1000);
}

The ADC channel selection was wrong. I have updated it for ADC0 which is looking at channel A0. The value for the channel is 5. I am also using ACD1 channel 15 and the value for the channel is 5. I pulled that out of the analogRead() code. There is a lookup table there.

This new version is using 2 DMA channels to read ADC0 A0 channel and ADC1 A15 channel. That way I can sample data from 2 channels simultaneously. The way I have this setup now it will sample 2 channels independently and simultaneously. It also means that I cannot use any other ADC while these DMA channels are operating. FYI if you need more than 2 ADCs in your project.
 
If someone can tell me how to do this properly with a pointer like ADC0_RA I am all ears.

It now works and I can play with other stuff now. The DMA was the least of my issues. The biggest issue was the stupid pointer conversion.

So there you go Paul. The DMA stuff does work and pretty simple too. Just need to make it easier to use register pointers or explain to me how I was supposed to do this. ;)

I'm having the same problem with the pointer to ADC0_RA. I've tried all kinds of casts, but I can't get it to work. It would be wonderful if someone who know's this stuff would have a look at it :)
 
I'm using Paul's DMA library from Teensyduino 1.21, so it should include the bug-fix you mention. However I didn't want to add methods to DMAChannel.h, so I worked around the problem by writing to the registers directly. I will post my code tomorrow.

I still have no idea what type cast is necessary to properly register ADC0_RA as a source through DMAChannel.source().
 
You can find a minimal example of my ADC code below (running the ADC at 128 kHz). I would still prefer using Paul's high-level functions for the DMA.

File pdb.h
Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 * Modified 2015, Ferdinand Keil, ferdinandkeil@gmail.com
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef pdb_h_
#define pdb_h_

#include "kinetis.h"

// Multiple input & output objects use the Programmable Delay Block
// to set their sample rate.  They must all configure the same
// period to avoid chaos.

#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT | PDB_SC_PDBIE | PDB_SC_DMAEN)

#if F_BUS == 60000000
  #define PDB_PERIOD (468-1)
#elif F_BUS == 56000000
  #define PDB_PERIOD (437-1)
#elif F_BUS == 48000000
  #define PDB_PERIOD (375-1)
#elif F_BUS == 36000000
  #define PDB_PERIOD (281-1)
#elif F_BUS == 24000000
  #define PDB_PERIOD (187-1)
#elif F_BUS == 16000000
  #define PDB_PERIOD (125-1)
#else
  #error "Unsupported F_BUS speed"
#endif

#endif

File adc_test.ino
Code:
#include "pdb.h"
#include <DMAChannel.h>

DMAChannel dma(false);

DMAMEM static int16_t adcbuffer[128];

void setup() {
  // Configure the ADC and run at least one software-triggered
  // conversion.  This completes the self calibration stuff and
  // leaves the ADC in a state that's mostly ready to use
  analogReadRes(16);
  analogReference(EXTERNAL); // range 0 to 1.2 volts
  analogReadAveraging(1);
  analogRead(A10);

  // set the programmable delay block to trigger DMA requests
  SIM_SCGC6 |= SIM_SCGC6_PDB; // enable PDB clock
  PDB0_IDLY = 0; // interrupt delay register
  PDB0_MOD = PDB_PERIOD; // modulus register, sets period
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // load registers from buffers
  PDB0_SC = PDB_CONFIG | PDB_SC_SWTRIG; // reset and restart
  PDB0_CH0C1 = 0x0101; // channel n control register?

  dma.begin(true); // allocate the DMA channel first
  dma.TCD->SADDR = &ADC0_RA;
  dma.TCD->SOFF = 0;
  dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1);
  dma.TCD->NBYTES_MLNO = 2;
  dma.TCD->SLAST = 0;
  dma.destinationBuffer(adcbuffer, sizeof(adcbuffer));
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma.enable();
}

void loop() {
  // do nothing here
}
 
Last edited:
I'm using Paul's DMA library from Teensyduino 1.21, so it should include the bug-fix you mention. However I didn't want to add methods to DMAChannel.h, so I worked around the problem by writing to the registers directly. I will post my code tomorrow.

I still have no idea what type cast is necessary to properly register ADC0_RA as a source through DMAChannel.source().
My understanding of C/C++ is to use the cast of the definition of SADDR.
 
SADDR is a 32-bit register, but I want to hand DMAChannel.source() a 16-bit pointer as that's the ADC's resolution. Whenever I tried different casts the Teensy crashed and didn't even enumerate as serial port anymore.
 
SADDR is a 32-bit register, but I want to hand DMAChannel.source() a 16-bit pointer as that's the ADC's resolution. Whenever I tried different casts the Teensy crashed and didn't even enumerate as serial port anymore.

maybe you have a look into the Audio Library "Audio\input_adc.cpp" where Paul does exactly what you want.
You may even consider to use the Audio Library. My understanding is, Paul wrote a powerful GUI for it.
 
maybe you have a look into the Audio Library "Audio\input_adc.cpp" where Paul does exactly what you want.
You may even consider to use the Audio Library. My understanding is, Paul wrote a powerful GUI for it.

In the Audio libary Paul writes to the DMA registers directly instead of using the high-level functions like DMAChannel.source(). So my question remains: what do I have to pass to DMAChannel.source() to make it work with the ADC?
 
According to Paul's examples in previous versions of DMAChannel.h
Code:
	dmax.source( ADC0_RA);
but you may have, to be save
add
Code:
	dma.size(2);
	dma.count(bufsize);
AFTER you declared the destination buffer
I have not tested the following, but this is what should work
Code:
  dma.source(ADC0_RA);
  dma.destinationBuffer(adcbuffer, sizeof(adcbuffer));
  dma.size(2);
  dma.count(sizeof(adcbuffer));
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma.enable();
}
 
Status
Not open for further replies.
Back
Top