0

SAMD51 DAC using DMA seems too fast?
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

SAMD51 DAC using DMA seems too fast?

by DSP_Mike on Tue Oct 13, 2020 11:44 am

Hello Everyone,
I'm working on implementing some code using the SAMD51 on the Adafruit Metro M4 express.
I create a single period sinusoid (4000 samples long) and block-write it using DMA to the DAC at what I thought was 1M Sample/second.
Problem is: the sinusoid when looked at on the scope has a period of 200uS - which would make it a whopping 20M Samples/second - seems incorrect....
the DMA is triggered every 800uS - which looks good on the scope (time between pulses).
Can anyone tell me what I'm missing here - I'm sure that I'll be kicking myself...
thank you in advance for any help!

Code using arduino IDE follows........

Code: Select all | TOGGLE FULL SIZE
#define HWORDS 4000
uint16_t data[HWORDS];
float phase = 3.14159 * 2./HWORDS;
int i;
static DmacDescriptor descriptor1 __attribute__((aligned(16)));
void dma_init() {
 
   static DmacDescriptor descriptor __attribute__((aligned(16)));
   static DmacDescriptor descriptor1 __attribute__((aligned(16)));
   static DmacDescriptor wrb __attribute__((aligned(16)));
   static uint32_t chnl0 = 0;  // DMA channel
    DMAC->BASEADDR.reg = (uint32_t)&descriptor1;   
    DMAC->WRBADDR.reg = (uint32_t)&wrb;           
    DMAC->CTRL.bit.LVLEN0 =1 ;
    DMAC->CTRL.bit.LVLEN1 =1 ;                   
    DMAC->CTRL.bit.LVLEN2 =1 ;                   
    DMAC->CTRL.bit.LVLEN3 =1 ;                   
    DMAC->CTRL.bit.DMAENABLE = 1;               
    DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(0x0) |   // Set DMAC t0 software trigger
                                 DMAC_CHCTRLA_TRIGACT_BLOCK;                // DMAC block transfer
    descriptor1.DESCADDR.reg = (uint32_t) &descriptor1;
    descriptor1.BTCTRL.bit.VALID    = 0x1; //Its a valid channel
    descriptor1.BTCTRL.bit.BEATSIZE = 0x1;  // HWORD.
    descriptor1.BTCTRL.bit.SRCINC   = 0x1;   //Source increment is enabled
    descriptor1.BTCTRL.bit.DSTINC   = 0x0;   //Destination increment disabled
    descriptor1.BTCNT.reg           = HWORDS;   //HWORDS points to send
    descriptor1.SRCADDR.reg         = (uint32_t)&data + 2*HWORDS;   //send from the data vevtor
    descriptor1.DSTADDR.reg         = (uint32_t)&DAC->DATA[1].reg;   //to the DAC output
    // start channel
    DMAC ->Channel[0].CHCTRLA.bit.ENABLE = 0x1;     //OK
    DMAC->CTRL.bit.DMAENABLE = 1;
}
void dac_init(){
    GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(4) |       // Divide the 48MHz clock source by divisor 4: 48MHz/4 = 12MHz (max for DAC)
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
           
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               
    GCLK->PCHCTRL[42].reg = GCLK_PCHCTRL_CHEN |        // Enable the DAC peripheral channel
                              GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to DAC
    MCLK->APBDMASK.bit.DAC_ = 1;
    DAC->CTRLA.bit.SWRST = 1;
    while(DAC->CTRLA.bit.SWRST);
    DAC->DACCTRL[1].reg = DAC_DACCTRL_REFRESH(2) |
          DAC_DACCTRL_CCTRL_CC12M |
          DAC_DACCTRL_ENABLE;// |
          DAC_DACCTRL_LEFTADJ;
    DAC->CTRLA.reg = DAC_CTRLA_ENABLE;
    while(DAC->SYNCBUSY.bit.ENABLE);
    while(!DAC->STATUS.bit.READY1);
    PORT->Group[0].DIRSET.reg = (1 << 2);
    PORT->Group[0].PINCFG[5].bit.PMUXEN = 1;
    PORT->Group[0].PMUX[1].bit.PMUXE = 1;
}
void setup() {
  for (i=0;i<HWORDS;i++) data[i]= 4*(sinf(i*phase) * 510.0f + 512.0f);  //Make a single-period sine wave.
  dac_init();
  dma_init();   
}
void loop() {
  DMAC->SWTRIGCTRL.reg |= (1 << 0);  //Software trigger to set off the DAC
  delayMicroseconds(800);
}
 


Attachments
capture.png
capture.png (308.39 KiB) Viewed 303 times

DSP_Mike
 
Posts: 5
Joined: Tue Oct 13, 2020 8:09 am

Re: SAMD51 DAC using DMA seems too fast?

by User_UMjT7KxnxP8YN8 on Thu Oct 15, 2020 6:14 pm

I haven't used the DMAC yet, though it's on my to-do list. Let's talk through it and see if that helps.

First, why do you expect a 1 Msample/sec rate? What page of the datasheet are you looking at to get that?

My first guess would be that the rate calculation doesn't work the way you initially thought.

User_UMjT7KxnxP8YN8
 
Posts: 235
Joined: Tue Jul 17, 2018 1:28 pm

Re: SAMD51 DAC using DMA seems too fast?

by User_UMjT7KxnxP8YN8 on Sat Oct 17, 2020 6:11 pm

I found the 1Msps spec and confirmed that you're really generating a 4000-sample waveform. Doesn't solve your problem but does eliminate 2 possible problems.

I gather you're accustomed to working with the scope you used to capture the waveform?

User_UMjT7KxnxP8YN8
 
Posts: 235
Joined: Tue Jul 17, 2018 1:28 pm

Re: SAMD51 DAC using DMA seems too fast?

by westfw on Sun Oct 18, 2020 7:27 am

It look to me like you may be setting up a memory to memory dma transfer from your data to the DAC register, (as fast as the memory bus will handle), when you want to set up the dma controller to accept dma REQUESTS from the dac...

westfw
 
Posts: 1781
Joined: Fri Apr 27, 2007 1:01 pm
Location: SF Bay area

Re: SAMD51 DAC using DMA seems too fast?

by westfw on Sun Oct 18, 2020 7:21 pm

Is that the actual code producing the trace you showed?
I tried to duplicate it here, but I don't get any output. (I can see an analogWrite() loop fine, so I think my wiring and scope settings are OK.)

(EDIT: Never mind. I was looking at the wrong DAC output.)

This makes me really nervous:

Code: Select all | TOGGLE FULL SIZE
    descriptor1.SRCADDR.reg         = (uint32_t)&data + 2*HWORDS;   //send from the data vevtor


I'm not entirely positive about order of operations. I guess it's correct, to see the waveform you got. But.... How about:

Code: Select all | TOGGLE FULL SIZE
    descriptor1.SRCADDR.reg         = (uint32_t)&data[HWORDS];   //send from the data vevtor

westfw
 
Posts: 1781
Joined: Fri Apr 27, 2007 1:01 pm
Location: SF Bay area

Re: SAMD51 DAC using DMA seems too fast?

by westfw on Mon Oct 19, 2020 2:00 am

So if I zoom in on the waveform, I see
SDS00001.png
SDS00001.png (17.57 KiB) Viewed 252 times

A staircase where every microsecond, the voltage goes up by about 40mV
That's consistent with my previous theory - that the DMA is writing the the DATA register at essentially "full speed", and the DAC is converting the DATA register to the analog output at the slower (and expected) data rate of 1Ms/s

What you were hoping for is that a new DMA transfer would happen at each end-of-conversion. It's not configured that way, and I don't think I'm seeing a way to make it behave that way. The hardware seems to consider end-of-conversion pretty insignificant - no DMA, no Event, no Interrupt. That seems strange to me, and I haven't really understood the 'filtering" aspects of the DAC (which apparently can also trigger a DMA), but it may be that the only way to get close to the behavior you're looking for is to set up a separate timer to generate a "start" event on DAC. (as MartinL suggested over in the Arduino Forums, I think.)

westfw
 
Posts: 1781
Joined: Fri Apr 27, 2007 1:01 pm
Location: SF Bay area

Re: SAMD51 DAC using DMA seems too fast?

by User_UMjT7KxnxP8YN8 on Mon Oct 19, 2020 6:36 am

Should be able to configure the DMA to generate an interrupt when the last value has been sent.

Also, the datasheet (47.8.3 Event Control) says the DAC supports "Data Buffer Empty" events (DAC->EVCTRL.EMPTYEO1 & EMPTYEO0) for both D/As and Start Conversion Event Input for both (DAC->EVCTRL.STARTEI1 and STARTEI0). I haven't used them but unless there's something in the errata that says they don't work you should be able to use the data buffer empty event to start a new conversion. You can read more about using events in 31. EVSYS – Event System.

If you can't use events for some reason, the DAC also supports 'data buffer empty' interrupts (DAC->INTENSET.EMPTY1 & EMPTY0) that will trigger when you're buffer's empty so your code can initiate another cycle with minimal delay.

User_UMjT7KxnxP8YN8
 
Posts: 235
Joined: Tue Jul 17, 2018 1:28 pm

Re: SAMD51 DAC using DMA seems too fast?

by westfw on Mon Oct 19, 2020 10:11 pm

Configuring the DMA for "burst" transfer instead, gets it to transfer only one element in the sine wave array table to the DAC upon receipt of a trigger.


Ah hah! The DMA docs talk about "beats", "bursts", and "blocks", but the register settings for "Trigger Action" talk about "Blocks", "Bursts", and "Transactions." Since I was pretty sure I wanted one "beat" for each trigger, I was ... confused. Setting the action to "transaction" turns out to behave the same as "block", and I was SO SURE that I didn't want "bursts."

Sigh. I DID want bursts. Apparently the size of bursts is controllable separately, down to a single "beat."

So changing the code to:
Code: Select all | TOGGLE FULL SIZE
     DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(0x49) |   // Set DMAC to DAC "empty" trigger
                                    DMAC_CHCTRLA_TRIGACT_BURST;


Seems to get a lot closer to the behavior I was looking for: self-clocked by the DAC itself, and resulting in a nice 250Hz waveform from the 4000-element table and 12MHz DAC Clock.

It doesn't stop after a single waveform; I need to do some more playing with that, I guess...
SDS00001.png
SDS00001.png (30.78 KiB) Viewed 235 times

westfw
 
Posts: 1781
Joined: Fri Apr 27, 2007 1:01 pm
Location: SF Bay area

Re: SAMD51 DAC using DMA seems too fast?

by westfw on Tue Oct 20, 2020 12:47 am

Instead of using the SW trigger, we can use the suspend/resume feature of the DMA:
Code: Select all | TOGGLE FULL SIZE
  descriptor1.BTCTRL.bit.BLOCKACT = 0x2;   //Suspend after block complete.
     :
void loop() {
  DMAC ->Channel[0].CHCTRLB.reg = 0x2;     // resume
  delayMicroseconds(8000);
}

SDS00002.png
SDS00002.png (33.2 KiB) Viewed 235 times


Here's the whole sketch.

Code: Select all | TOGGLE FULL SIZE
#define HWORDS 4000
uint16_t data[HWORDS];
float phase = 3.14159 * 2. / HWORDS;
int i;
static DmacDescriptor descriptor1 __attribute__((aligned(16)));
void dma_init() {

  static DmacDescriptor descriptor __attribute__((aligned(16)));
  static DmacDescriptor descriptor1 __attribute__((aligned(16)));
  static DmacDescriptor wrb __attribute__((aligned(16)));
  static uint32_t chnl0 = 0;  // DMA channel
  DMAC->BASEADDR.reg = (uint32_t)&descriptor1;
  DMAC->WRBADDR.reg = (uint32_t)&wrb;
  DMAC->CTRL.bit.LVLEN0 = 1 ;
  DMAC->CTRL.bit.LVLEN1 = 1 ;
  DMAC->CTRL.bit.LVLEN2 = 1 ;
  DMAC->CTRL.bit.LVLEN3 = 1 ;
  DMAC->CTRL.bit.DMAENABLE = 1;
  DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(0x49) |   // DAC DATA trigger
                                 DMAC_CHCTRLA_TRIGACT_BURST;    // Burst transfers
  descriptor1.DESCADDR.reg = (uint32_t) &descriptor1;
  descriptor1.BTCTRL.bit.VALID    = 0x1; //Its a valid channel
  descriptor1.BTCTRL.bit.BEATSIZE = 0x1;  // HWORD.
  descriptor1.BTCTRL.bit.SRCINC   = 0x1;   //Source increment is enabled
  descriptor1.BTCTRL.bit.DSTINC   = 0x0;   //Destination increment disabled
  descriptor1.BTCTRL.bit.BLOCKACT = 0x2;   //Suspend after block complete.
  // ("Burst" size will be 1 "beat" = 1 HWORD, by default)
  descriptor1.BTCNT.reg           = HWORDS;   //HWORDS points to send
  descriptor1.SRCADDR.reg         = (uint32_t)(&data[HWORDS]); //send from the data vevtor
  descriptor1.DSTADDR.reg         = (uint32_t)&DAC->DATA[1].reg;   //to the DAC output
  // start channel
  DMAC ->Channel[0].CHCTRLA.bit.ENABLE = 0x1;     //OK
  DMAC->CTRL.bit.DMAENABLE = 1;
}

void dac_init() {
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(4) |       // Divide the 48MHz clock source by divisor 4: 48MHz/4 = 12MHz (max for DAC)
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source

  while (GCLK->SYNCBUSY.bit.GENCTRL7);
  GCLK->PCHCTRL[42].reg = GCLK_PCHCTRL_CHEN |        // Enable the DAC peripheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to DAC
  MCLK->APBDMASK.bit.DAC_ = 1;
  DAC->CTRLA.bit.SWRST = 1;
  while (DAC->CTRLA.bit.SWRST);
  DAC->DACCTRL[1].reg = DAC_DACCTRL_REFRESH(2) |
                        DAC_DACCTRL_CCTRL_CC12M |
                        DAC_DACCTRL_ENABLE
                        //                        | DAC_DACCTRL_FEXT
                        ;
  DAC_DACCTRL_LEFTADJ;
  DAC->CTRLA.reg = DAC_CTRLA_ENABLE;
  while (DAC->SYNCBUSY.bit.ENABLE);
  while (!DAC->STATUS.bit.READY1);
  PORT->Group[0].DIRSET.reg = (1 << 2);
  PORT->Group[0].PINCFG[5].bit.PMUXEN = 1;
  PORT->Group[0].PMUX[1].bit.PMUXE = 1;
}

void setup() {
  for (i = 0; i < HWORDS; i++) {
    data[i] = 4 * (sinf(i * phase) * 510.0f + 512.0f); //Make a single-period sine wave.
  }
#ifdef ANALOGWRITESQUARE
  //  for (i = 0; i < 100000; i++) {
  while (0) {
    analogWrite(A0, 999);
    delayMicroseconds(100);
    analogWrite(A0, 0);
    delayMicroseconds(100);
  }
#endif


#ifdef ANALOGWRITERAMP
  while (1) {
    i++;
    analogWrite(A0, i & 0x3FF);
  }
#endif
  dac_init();
  dma_init();
}

//#define ANALOGWRITESINE 1
void loop() {
#ifdef ANALOGWRITESINE
  for (i = 0; i < HWORDS; i++) {
    analogWrite(A0, data[i]);
  }
#endif
  DMAC ->Channel[0].CHCTRLB.reg = 0x2;     // resume
  delayMicroseconds(8000);
}

westfw
 
Posts: 1781
Joined: Fri Apr 27, 2007 1:01 pm
Location: SF Bay area

Re: SAMD51 DAC using DMA seems too fast?

by User_UMjT7KxnxP8YN8 on Tue Oct 20, 2020 11:20 am

To get a single waveform, move the trigger to setup().

Code: Select all | TOGGLE FULL SIZE
DMAC->SWTRIGCTRL.reg |= (1 << 0);  //Software trigger to set off the DAC

User_UMjT7KxnxP8YN8
 
Posts: 235
Joined: Tue Jul 17, 2018 1:28 pm

Re: SAMD51 DAC using DMA seems too fast?

by DSP_Mike on Wed Oct 21, 2020 2:59 pm

THANK You Everyone!
Yep - it looks like the DMA just blasts it over the DAC and nobody is the wiser.
Changing to burst with the dac empty trigger does the trick!
Thanks again for all your help!!

DSP_Mike
 
Posts: 5
Joined: Tue Oct 13, 2020 8:09 am

Re: SAMD51 DAC using DMA seems too fast?

by Beringer83 on Wed Oct 21, 2020 5:30 pm

DSP_Mike wrote: Hello Everyone,
I'm working on implementing some code using the SAMD51 on the Adafruit Metro M4 express.
I create a single period sinusoid (4000 samples long) and block-write it using DMA to the DAC at what I thought was 1M Sample/second.
Problem is: the sinusoid when looked at on the scope has a period of 200uS - which would make it a whopping 20M Samples/second - seems incorrect....
the DMA is triggered every 800uS - which looks good on the scope (time between pulses).
Can anyone tell me what I'm missing here - I'm sure that I'll be kicking myself...
thank you in advance for any help!

Code using arduino IDE follows........
Rachat de crédit avec trésorerie
Code: Select all | TOGGLE FULL SIZE
#define HWORDS 4000
uint16_t data[HWORDS];
float phase = 3.14159 * 2./HWORDS;
int i;
static DmacDescriptor descriptor1 __attribute__((aligned(16)));
void dma_init() {
 
   static DmacDescriptor descriptor __attribute__((aligned(16)));
   static DmacDescriptor descriptor1 __attribute__((aligned(16)));
   static DmacDescriptor wrb __attribute__((aligned(16)));
   static uint32_t chnl0 = 0;  // DMA channel
    DMAC->BASEADDR.reg = (uint32_t)&descriptor1;   
    DMAC->WRBADDR.reg = (uint32_t)&wrb;           
    DMAC->CTRL.bit.LVLEN0 =1 ;
    DMAC->CTRL.bit.LVLEN1 =1 ;                   
    DMAC->CTRL.bit.LVLEN2 =1 ;                   
    DMAC->CTRL.bit.LVLEN3 =1 ;                   
    DMAC->CTRL.bit.DMAENABLE = 1;               
    DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(0x0) |   // Set DMAC t0 software trigger
                                 DMAC_CHCTRLA_TRIGACT_BLOCK;                // DMAC block transfer
    descriptor1.DESCADDR.reg = (uint32_t) &descriptor1;
    descriptor1.BTCTRL.bit.VALID    = 0x1; //Its a valid channel
    descriptor1.BTCTRL.bit.BEATSIZE = 0x1;  // HWORD.
    descriptor1.BTCTRL.bit.SRCINC   = 0x1;   //Source increment is enabled
    descriptor1.BTCTRL.bit.DSTINC   = 0x0;   //Destination increment disabled
    descriptor1.BTCNT.reg           = HWORDS;   //HWORDS points to send
    descriptor1.SRCADDR.reg         = (uint32_t)&data + 2*HWORDS;   //send from the data vevtor
    descriptor1.DSTADDR.reg         = (uint32_t)&DAC->DATA[1].reg;   //to the DAC output
    // start channel
    DMAC ->Channel[0].CHCTRLA.bit.ENABLE = 0x1;     //OK
    DMAC->CTRL.bit.DMAENABLE = 1;
}
void dac_init(){
    GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(4) |       // Divide the 48MHz clock source by divisor 4: 48MHz/4 = 12MHz (max for DAC)
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
           
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               
    GCLK->PCHCTRL[42].reg = GCLK_PCHCTRL_CHEN |        // Enable the DAC peripheral channel
                              GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to DAC
    MCLK->APBDMASK.bit.DAC_ = 1;
    DAC->CTRLA.bit.SWRST = 1;
    while(DAC->CTRLA.bit.SWRST);
    DAC->DACCTRL[1].reg = DAC_DACCTRL_REFRESH(2) |
          DAC_DACCTRL_CCTRL_CC12M |
          DAC_DACCTRL_ENABLE;// |
          DAC_DACCTRL_LEFTADJ;
    DAC->CTRLA.reg = DAC_CTRLA_ENABLE;
    while(DAC->SYNCBUSY.bit.ENABLE);
    while(!DAC->STATUS.bit.READY1);
    PORT->Group[0].DIRSET.reg = (1 << 2);
    PORT->Group[0].PINCFG[5].bit.PMUXEN = 1;
    PORT->Group[0].PMUX[1].bit.PMUXE = 1;
}
void setup() {
  for (i=0;i<HWORDS;i++) data[i]= 4*(sinf(i*phase) * 510.0f + 512.0f);  //Make a single-period sine wave.
  dac_init();
  dma_init();   
}
void loop() {
  DMAC->SWTRIGCTRL.reg |= (1 << 0);  //Software trigger to set off the DAC
  delayMicroseconds(800);
}
 



Hello, is this the oscilloscope data for you recommendable from what you say here

Beringer83
 
Posts: 1
Joined: Wed Oct 21, 2020 5:20 am

Please be positive and constructive with your questions and comments.