Erroneous Second DMA Transfer for Conditional Block Transfer with Beat Peripheral Triggers

Go To Last Post
3 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello everyone,

 

I'm encountering a very strange problem when using a DMA transfer of type "Conditional Block Transfer with Beat Peripheral Triggers". The end goal is to receive 197 bytes from SPI when I receive a particular event.

 

Here's the code setting up the descriptors and DMA channels for the SERCOM RX & TX at the platform boot. You'll notice that for TX I'm always sending the very same byte (to save space):

 

 

/* Setup transfer descriptor for accelerometer TX */
  dma_descriptors[3].BTCTRL.reg = DMAC_BTCTRL_VALID; // Valid descriptor
  dma_descriptors[3].BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val; // 1 byte address increment
  dma_descriptors[3].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_SRC_Val; // Step selection for source
  dma_descriptors[3].BTCTRL.bit.SRCINC = 0; // Destination Address Increment is disabled.
  dma_descriptors[3].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_BYTE_Val; // Byte data transfer
  dma_descriptors[3].BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_NOACT_Val; // Once data block is transferred, do nothing
  dma_descriptors[3].DESCADDR.reg = 0; // No next descriptor address
   
  /* Setup DMA channel */
  DMAC->CHID.reg = DMAC_CHID_ID(3); // Use channel 3
  dma_chctrlb_reg.reg = 0; // Clear temp register
  dma_chctrlb_reg.bit.LVL = 2; // Priority level
  dma_chctrlb_reg.bit.TRIGACT = DMAC_CHCTRLB_TRIGACT_BEAT_Val; // One trigger required for each beat transfer
  dma_chctrlb_reg.bit.TRIGSRC = ACC_DMA_SERCOM_TXTRIG; // Select TX trigger
  dma_chctrlb_reg.bit.EVIE = 1; // Enable event input action
  dma_chctrlb_reg.bit.EVACT = DMAC_CHCTRLB_EVACT_CBLOCK; // Conditional block transfer
  DMAC->CHCTRLB = dma_chctrlb_reg; // Write register
   
  /* Setup transfer descriptor for accelerometer RX */
  dma_descriptors[5].BTCTRL.reg = DMAC_BTCTRL_VALID; // Valid descriptor
  dma_descriptors[5].BTCTRL.bit.STEPSIZE = DMAC_BTCTRL_STEPSIZE_X1_Val; // 1 byte address increment
  dma_descriptors[5].BTCTRL.bit.STEPSEL = DMAC_BTCTRL_STEPSEL_DST_Val; // Step selection for destination
  dma_descriptors[5].BTCTRL.bit.DSTINC = 1; // Destination Address Increment is enabled.
  dma_descriptors[5].BTCTRL.bit.BEATSIZE = DMAC_BTCTRL_BEATSIZE_BYTE_Val; // Byte data transfer
  dma_descriptors[5].BTCTRL.bit.BLOCKACT = DMAC_BTCTRL_BLOCKACT_INT_Val; // Once data block is transferred, generate interrupt
  dma_descriptors[5].DESCADDR.reg = 0; // No next descriptor address

 

 
  /* Setup DMA channel */
  DMAC->CHID.reg = DMAC_CHID_ID(5); // Use channel 5
  dma_chctrlb_reg.reg = 0; // Clear temp register
  dma_chctrlb_reg.bit.LVL = 2; // Priority level
  dma_chctrlb_reg.bit.TRIGACT = DMAC_CHCTRLB_TRIGACT_BEAT_Val; // One trigger required for each beat transfer
  dma_chctrlb_reg.bit.TRIGSRC = ACC_DMA_SERCOM_RXTRIG; // Select RX trigger
  DMAC->CHCTRLB = dma_chctrlb_reg; // Write register
  DMAC->CHINTENSET.reg = DMAC_CHINTENSET_TCMPL; // Enable channel transfer complete interrupt

 

And here's the code arming both DMA channels, executed at boot and then every time I get a transfer complete interrupt for the RX channel:

 

cpu_irq_enter_critical();
   
  /* SPI RX DMA TRANSFER */
  /* Setup transfer size */
  dma_descriptors[5].BTCNT.bit.BTCNT = (uint16_t)size;
  /* Source address: DATA register from SPI */
  dma_descriptors[5].SRCADDR.reg = (uint32_t)spi_data_p;
  /* Destination address: given value */
  dma_descriptors[5].DSTADDR.reg = (uint32_t)datap + size;
   
  /* Resume DMA channel operation */
  DMAC->CHID.reg= DMAC_CHID_ID(5);
  DMAC->CHCTRLA.reg = DMAC_CHCTRLA_ENABLE;
   
  /* SPI TX DMA TRANSFER */
  /* Setup transfer size */
  dma_descriptors[3].BTCNT.bit.BTCNT = (uint16_t)size;
  /* Source address: DATA register from SPI */
  dma_descriptors[3].DSTADDR.reg = (uint32_t)spi_data_p;
  /* Destination address: given value */
  dma_descriptors[3].SRCADDR.reg = (uint32_t)read_cmd;
   
  /* Resume DMA channel operation */
  DMAC->CHID.reg= DMAC_CHID_ID(3);
  DMAC->CHCTRLA.reg = DMAC_CHCTRLA_ENABLE;
   
  cpu_irq_leave_critical();

 

DMA round robin is enabled.

The first DMA transfer is perfectly done: I get the trigger, then exactly 197 bytes are transferred. I then get the RX complete interrupt (roughly 100us after the bytes are transmitted / received) and then call the very same code above.

However, the moment I call the code above exactly one byte is immediatly transferred and the remaining 196 bytes are transferred once I get the trigger.

 

For information, this particular trigger is an external interrupt, going through the EIC (detecting high state only) then EVSYS (checking for a low to high transition to generate the DMA event). I've spent lots of time making sure that EIC & EVSYS configurations were not at fault. I'm indeed using the resynchronized path for the EVSYS as mentioned in the "User Multiplexer Selection" table. Moreoever, if it was an EIC & EVSYS misconfiguration issue I'd expect to see at least 2 197 bytes transfers and not the behavior described above.

 

Any help is greatly appreciated

My electronic projects blog >> www.limpkin.fr

Last Edited: Tue. Apr 10, 2018 - 08:33 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For anyone finding this thread and looking for a workaround, I've done the following:

- first DMA transfer size is set to X bytes

- any following DMA transfer is set to X+1 bytes

- while your SPI nCS is high, wait for the "buggy" 1 byte transfer to be done:

 

if (bug_fix != FALSE)
    {
        /* wait for channel to get busy */
        while ((DMAC->BUSYCH.reg & (1 << 5)) == 0);
        /* then wait for busy to be over */
        while ((DMAC->BUSYCH.reg & (1 << 5)) != 0);
    }

 

 

 

 

My electronic projects blog >> www.limpkin.fr

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

update: microchip team is currently looking at the very low level of the SAMD*, as it is currently being identified as a silicon bug.

My electronic projects blog >> www.limpkin.fr