Sending Data to PIOs via DMAC

Hi,

I would like to toggle a digital pin via DMA.

Is it possible to do this? Or is there a better way?
If it is possible to do this would anyone be able to guide me as to what I am doing wrong?

I'm trying to go through the Atmel documentation for the DMAC, but it is quite dense. I have seen a few examples through SPI, but some things are still unclear.

I have this so far, but it doesn't work.

int dataBlock[10] = {1,0,1,0,1,0,1,0,1,0};


void setup() {

 pmc_set_writeprotect(false);
 pmc_enable_periph_clk(ID_DMAC);
 DMAC->DMAC_EN = DMAC_EN_ENABLE;

 
 DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_FIXED;
 DMAC->DMAC_CHDR = DMAC_CHDR_DIS0;

DMAC->DMAC_CH_NUM[0].DMAC_SADDR = (uint32_t)dataBlock;                   
DMAC->DMAC_CH_NUM[0].DMAC_DADDR = (uint32_t)REG_PIOC_SODR; //I know this is wrong (but im not sure what else would work here) since SODR will set pins high, but a 0 won't do anything.                 
DMAC->DMAC_CH_NUM[0].DMAC_DSCR = 0;    
                   
DMAC->DMAC_CH_NUM[0].DMAC_CTRLA =   DMAC_CTRLA_SRC_WIDTH_BYTE  //I labeled the array as an int would that affect this?
                                  | DMAC_CTRLA_DST_WIDTH_BYTE;
                                  
                                  
DMAC->DMAC_CH_NUM[0].DMAC_CTRLB =   DMAC_CTRLB_SRC_INCR_INCREMENTING
                                  | DMAC_CTRLB_SRC_DSCR_FETCH_DISABLE 
                                  | DMAC_CTRLB_DST_INCR_FIXED 
                                  | DMAC_CTRLB_DST_DSCR_FETCH_DISABLE 
                                  | DMAC_CTRLB_FC_MEM2PER_DMA_FC;

                                  
DMAC->DMAC_CH_NUM[0].DMAC_CFG =     DMAC_CFG_DST_H2SEL_HW 
                                  | DMAC_CFG_DST_PER(13);  //(where can i find information on this? I thought this might be from chapter 9 of the Atmel Documentation, the instance ID of a periph, In this case ID 13 is related to PIOC)
                                  
  
   DMAC->DMAC_CHER = DMAC_CHER_ENA0;
}

void loop() {}

A DMA would be useful if you would want to set or clear a pin repeatedly, although you would have to trigger an interrupt with a completed DMA action to pace the pin toggling, resulting in a lot of lost clock cycles, or copy a variable to a pin without a defined pace with an List Linked Item (LLI) . But if you want to toggle a pin only once, a DMA is useless.

Use PIO_ODSR with an XOR to toggle a pin, e.g; :
PIOB->PIO_ODSR ^= PIO_ODSR_P27; // Toggle PIOB27

Here is an example sketch (not fully tested though) to copy repeatedly a pin state to a variable with a DMA.

#define DMAC_CH (5)
#define Burst_size (256)

typedef struct {

  uint32_t  DMAC_SADDR;
  uint32_t  DMAC_DADDR;
  uint32_t  DMAC_CTRLA;
  uint32_t  DMAC_CTRLB;
  uint32_t  DMAC_DSCR;

} LLI_Type;
LLI_Type LLI1;

uint32_t dst1[Burst_size];

void cpy_DMA(LLI_Type* first_LLI) {
  
  DMAC->DMAC_CHDR = DMAC_CHDR_DIS0 << DMAC_CH;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_DSCR = (uint32_t)first_LLI ;  
  
  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CTRLB = DMAC_CTRLB_FC_MEM2MEM_DMA_FC |
                                          DMAC_CTRLB_SRC_INCR_FIXED |
                                          DMAC_CTRLB_DST_INCR_INCREMENTING;

  DMAC->DMAC_CH_NUM[DMAC_CH].DMAC_CFG = DMAC_CFG_SOD |
                                        DMAC_CFG_FIFOCFG_ASAP_CFG;

  DMAC->DMAC_CHER = DMAC_CHER_ENA0 << DMAC_CH;

}

void InitLLI (LLI_Type* LLI, void* dst, LLI_Type* next_LLI)  {
  
  LLI->DMAC_SADDR = (uint32_t) &PIOB->PIO_PDSR;

  LLI->DMAC_DADDR = (uint32_t) dst;

  LLI->DMAC_CTRLA |= DMAC_CTRLA_BTSIZE(Burst_size)                  
                     | DMAC_CTRLA_SRC_WIDTH_WORD
                     | DMAC_CTRLA_DST_WIDTH_WORD;

  LLI->DMAC_DSCR = (uint32_t) next_LLI;                    

}

void setup() {
  PMC->PMC_PCER0 = PMC_PCER0_PID12;       // PIOB power on
  PIOB->PIO_OSR &= ~PIO_OSR_P25;          // Pin 2 in INPUT

  PMC->PMC_PCER1 |= PMC_PCER1_PID39;      // DMAC power on

  DMAC->DMAC_EN = DMAC_EN_ENABLE;         // Enable DMAC

  InitLLI (&LLI1, (void*) dst1, &LLI1);

 cpy_DMA(&LLI1);
}

void loop() {


}

Thanks, I'll try to play around with this code.

Yup, I am trying to see If I can toggle a digital pin multiple times with the DMA. (I guess another possibility would be to adjust the Duty cycle of a PWM wave, and store the duty cycle values in a DMA? Ideally I would like to be able to control how long a toggle duration is, if that makes sense)

Is there anyway to tie a timer to the DMA so that it only transfers once when the timer counter reaches a specific value? I know the DAC can be configured in a similar way where it outputs a new voltage level once the timer counter value has been reached,

I'd got the same problem with the SAM3 chip. It looks like that there isn't any possibility to trigger the DMA transfer from some timer (PWM or one of TCs). I wrote to the Microchip support https://microchipsupport.force.com/5001N00000iq9tm, but they didn't answered me anything useful so far.

There are 2 PWM DMA possibilities:

First one, a PDC DMA with synchro channels. That is very useful whenever you want e.g. output 3 PWM waves, 120° out of phase from each other. This DMA is from a buffer to PWM channels.

See an example sketch of this in this thread, reply #3:
https://forum.arduino.cc/index.php?topic=530217.0

Second one, a AHB DMA. BUT, a register is missing in the documentation AND in the PWM.h header file, BUT you can see a PWM AHB DMA in AHB DMA chapter. All you have to do is creat the missing PWM register (PWM_DMAR), the same you can find in SAM7 PWM header file ( I mean with the exact same offset). This register gives the same possibility to output and automatically dispatch a buffer to all synchro channels, plus the possibility of a transfer from a peripheral to the PWM peripheral.

In component_pwm, add PWM_DMAR:

....... 
  
  RoReg      PWM_ISR1;      /**< \brief (Pwm Offset: 0x1C) PWM Interrupt Status Register 1 */
  RwReg      PWM_SCM;       /**< \brief (Pwm Offset: 0x20) PWM Sync Channels Mode Register */
  RwReg      PWM_DMAR;      /**< \brief (Pwm Offset: 0x24) PWM DMAR Register */
  RwReg      PWM_SCUC;      /**< \brief (Pwm Offset: 0x28) PWM Sync Channels Update Control Register */
  RwReg      PWM_SCUP;      /**< \brief (Pwm Offset: 0x2C) PWM Sync Channels Update Period Register */
  WoReg      PWM_SCUPUPD;   /**< \brief (Pwm Offset: 0x30) PWM Sync Channels Update Period Update Register */

.......