SAM E70 using TC0 to trigger SPI DMA transfer

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

Hi all,

 

I've been trying for some time to trigger a DMA transfer from the SPI hardware using Timer/Counter 0. I have DMA reading from SPI using the SPI subsystem as a trigger, but I want to sample using an external ADC at exactly 96kHz. To guarantee 96kHz sampling rate the thought was to use the timer/counter to trigger each DMA transfer. I am trying to do this by setting the DMA to trigger from TC0 in wave generation mode and setting TC0 values to generate a trigger on TIOA at exactly 96kHz. I am configuring the TC set up to generate the signal at close to 96kHz using the code below.

 

pmc_enable_periph_clk(ID_XDMAC);
pmc_enable_periph_clk(ID_TC0);

/*Configure the TC system for DMA triggering*/
PMC->PMC_PCK[6] = PMC_PCK_PRES(0) | PMC_MCKR_CSS_MAIN_CLK;        //Programmable clock used for TC0; no prescaler, MAINCK (12MHz) as source
TC0->TC_CHANNEL[0].TC_CMR = (TC_CMR_ACPC_CLEAR |                 //Clear TIOA0 on RC match
TC_CMR_ACPA_SET |                                                 //Set TIOA0 on RA match
TC_CMR_WAVE |
TC_CMR_WAVSEL_UP_RC |
TC_CMR_TCCLKS_TIMER_CLOCK2
);
TC0->TC_CHANNEL[0].TC_RA = 15;
TC0->TC_CHANNEL[0].TC_RC = 16;                                  //Count to 16 before resetting (1.5MHz/16=93kHz)
TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN;         //Enable the TC

	

//SPI RX DMA Channel:

XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CSA = (uint32_t) &SPI0->SPI_RDR;   //Source address is SPI0 RDR
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CBC = 0;                           //Single microblock at a time
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CC = (XDMAC_CC_TYPE_PER_TRAN |     //peripheral to memory transcation
XDMAC_CC_MBSIZE_SINGLE |                                                //Memory burst size 1
XDMAC_CC_SAM_FIXED_AM |                                                 //Source address fixed (SPI0 RDR)
XDMAC_CC_DAM_INCREMENTED_AM |                                           //Dest address increment (buffer)
XDMAC_CC_DSYNC_PER2MEM |                                                //Peripheral to memory transfer
XDMAC_CC_CSIZE_CHK_1 |                                                  //Chunk size 1
XDMAC_CC_DWIDTH_HALFWORD |                                              //16 bit data width
XDMAC_CC_SIF_AHB_IF1 |                                                  //See datasheet section 19.6.3 for system bus connections
XDMAC_CC_DIF_AHB_IF0 |
XDMAC_CC_PERID(XDMAC_CHANNEL_HWID_TC0)                              //Using TC0 to trigger DMA requests at 96kHz 
);
	
//SPI TX DMA Channel:
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CDA = (uint32_t) &SPI0->SPI_TDR;   //Destination address is SPI_TDR
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CBC = 0;                           //Single microblock at a time
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CC = (XDMAC_CC_TYPE_PER_TRAN |     //memory to peripheral transcation
XDMAC_CC_MBSIZE_SINGLE |                                                //Memory burst size 1
XDMAC_CC_SAM_INCREMENTED_AM |                                           //Source address increment (buffer)
XDMAC_CC_DAM_FIXED_AM |													//Dest address fixed (SPI TDR)
XDMAC_CC_DSYNC_MEM2PER |                                                //Mem to peripheral transfer
XDMAC_CC_CSIZE_CHK_1 |                                                  //Chunk size 1
XDMAC_CC_DWIDTH_HALFWORD |                                              //16 bit data width
XDMAC_CC_SIF_AHB_IF0 |                                                  //See datasheet section 19.6.3 for system bus connections
XDMAC_CC_DIF_AHB_IF1 |
XDMAC_CC_PERID(XDMAC_CHANNEL_HWID_TC0)                              //Using TC0 to trigger DMA requests at 96kHz 
);
	

Then, when I want to do a DMA transfer, I use this code.

 

uint32_t volatile xdmaint;
    
/*Configure the DMA*/
    

//SPI TX: 
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CSA = (uint32_t) tx_buffer;   //Source address is TX buffer
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CUBC = num_samples;                //Set microblock size to be the size requested
xdmaint = XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CIS;                     //Clear any pending interrupts for this channel
//XDMAC->XDMAC_GIE |= 0x1 << DMA_CH_SPI_TX;                               //global interrupt enable on this channel
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CNDC = 0;                          //Clear a bunch of registers we need to clear before enabling DMA
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CDS_MSP = 0;
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CSUS = 0;
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CDUS = 0;
XDMAC->XDMAC_CHID[DMA_CH_SPI_TX].XDMAC_CIE |= 0x7F;            //microblock interrupt enable
//XDMAC->XDMAC_GIE |= 0x1 << DMA_CH_SPI_TX;                               //global interrupt enable on this channel


//SPI RX:
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CDA = (uint32_t) rx_buffer;			//dest address is RX buffer
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CUBC = num_samples;                //Set microblock size to be the size requested
xdmaint = XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CIS;                     //Clear any pending interrupts for this channel
//XDMAC->XDMAC_GIE |= 0x1 << DMA_CH_SPI_RX;                               //global interrupt enable on this channel
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CNDC = 0;                          //Clear a bunch of registers we need to clear before enabling DMA
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CDS_MSP = 0;
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CSUS = 0;
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CDUS = 0;
XDMAC->XDMAC_CHID[DMA_CH_SPI_RX].XDMAC_CIE |= 0x7F;            //microblock interrupt enable
//XDMAC->XDMAC_GIE |= 0x1 << DMA_CH_SPI_RX;                               //global interrupt enable on this channel

XDMAC->XDMAC_GE |= 1 << DMA_CH_SPI_RX;                                    //enable RX channel
XDMAC->XDMAC_GE |= 1 << DMA_CH_SPI_TX;                                    //enable TX channel

 

The problem is that using the DMA in this way never seems to trigger the DMA. Is this a valid way to trigger DMA transfers? The datasheet is sparse on what exactly triggers DMA when using the TC subsystem as the hardware request.

 

Another problem I am having is that I cannot seem to get TC0 to work using the PCK6 clock. If I program TC0 to PCK6 it never starts counting. I need to be using PCK6 because otherwise the desired 96kHz sampling rate does not divide evenly. I can program PCK6 to be at MCK (12MHz) and then set the TC0 to trigger after counting 125 times. The other clock options for TC0 are MCK/8, MCK/32, etc.. which don't allow me to trigger at exactly 96k. I can make TC0 work using MCK/8, but that only allows me to sample at either 100kHz or 93750Hz by setting TC0 to count to 15 or 16 respectively.

 

I notice in the SAME70 errata document for the MCAN subsystem that "On Revision A silicon, TC Counter 0 is not connected to PCK6 and PCK7." Can anybody confirm this?

 

I can currently sample at close to 96k by setting the SPI baud rate appropriately, but I was hoping for something exact.

 

Thanks!

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

 

It sounds like you want a TC firing an INT at 96kHz, and then in this ISR you want to initiate a SPI DMA READ transfer

 

So:

TC ISR

{

 XDMAC_GE channels enabled for SPI RX/TX

}

 

Also for the SPI DMA, assuming you want your application to know when the transfer is complete, so there must be a block DMA interrupt set, but this should be done for the RX channel, though your code shows the GIE bit set for the TX ch.

 

Source Buffer->DMA->TDR

... waiting for shift register..

RDR->DMA->Destination buffer   (DMA Block INT must be on this CH or you will break the alignment for the next read in a fun way)

 

The TC should have no problem making a 96kHz rate

 

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

MikeWMTS wrote:

 

It sounds like you want a TC firing an INT at 96kHz, and then in this ISR you want to initiate a SPI DMA READ transfer

 

 

Well, no, not quite. I'm trying to make the DMA do a 4096-byte microblock with each individual byte read at 96kHz, that's why I tried to make TC0 trigger the DMA.

 

MikeWMTS wrote:

 

Also for the SPI DMA, assuming you want your application to know when the transfer is complete, so there must be a block DMA interrupt set, but this should be done for the RX channel, though your code shows the GIE bit set for the TX ch.

 

Source Buffer->DMA->TDR

... waiting for shift register..

RDR->DMA->Destination buffer   (DMA Block INT must be on this CH or you will break the alignment for the next read in a fun way)

 

 

Thanks, will update the code.

 

MikeWMTS wrote:

 

The TC should have no problem making a 96kHz rate

 

 

Yeah, that's what I figured, but as far as I can tell I can't make it do exactly 96kHz because I can't set TC0 to the PCK6 clock.. Just MCK/8..

 

Edit: D'oh. Should have read the documentation more closely. TC Extended mode will allow me to operate the TC at the peripheral clock speed (for future reference if anyone finds this thread set bit NODIVCLK in the TC_EMR register) Also it would appear that I was confusing MCK and MAINCK.

Last Edited: Sun. Mar 25, 2018 - 05:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So I currently have the TC set up to fire an interrupt at 96kHz, and in the ISR I'm handling performing a SPI transfer. This works, but I would really like to use the DMA to perform this transfer. Has anyone had success getting the TC to trigger a DMA transfer between unrelated subsystems?

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

I´m also very interested in this problem. I also interface with an external ADC and want to have a fixed intervall SPI Transfer.

 

As some already have failed with TC0, I went the PWM0 Route. In theory it should also be possible to make a DMA Transfer Request with the Comparsion Units in PWM Synchronous mode.

The DS states "These comparisons are intended to generate software interrupts ...  and to trigger DMA Controller transfer requests.".

The designated use case is to update the PWM duty cycles when such an DMA Request occurs. But my hope is it should also be possible to just generate a periodical DMA Request for arbitrary DMA Transfers including SPI.

I had no success yet.

 

Anybody found a solution for this by now (No matter whether TC or PWM peripheral used)?

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

I'm also interested.

In my application I've set up a timer ISR which starts an SPI DMA and fires an interrupt after the last DMA in the chain is done. I did not succeed in running DMA in an endless loop by writing the address of the first descriptor into the last descriptor.

I noticed that I have to re-write the RX and TX DMA descriptor address (XDMAC_CNDA) in the XDMAC controller otherwise the DMA does not restart. This DMA re-initialization includes DMA disable/enable and takes some considerable time which limits the maximum frequency. I can't get higher than 50kHz.

SAME newbie

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

it might be better to make the ADC free running at the target frequency (adjusting a clock source as needed) and have data ready event trigger the DMA.

jeff

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

it might be better to make the ADC free running at the target frequency (adjusting a clock source as needed) and have data ready event trigger the DMA.

We are using external ADCs, so this is not really an option.

EDIT: Ok maybe you meant to use the data ready event as a kind of replacement for a timer trigger. This should work as well. This would still also need a dummy read to the result register to make the trigger reapear (I guess...).

 

In my application I've set up a timer ISR which starts an SPI DMA and fires an interrupt after the last DMA in the chain is done. I did not succeed in running DMA in an endless loop by writing the address of the first descriptor into the last descriptor.

I noticed that I have to re-write the RX and TX DMA descriptor address (XDMAC_CNDA) in the XDMAC controller otherwise the DMA does not restart. This DMA re-initialization includes DMA disable/enable and takes some considerable time which limits the maximum frequency. I can't get higher than 50kHz.

I´m not sure what you are trying to achieve. If you trigger DMA in a timer interrupt this implies to me, you want to let it run based on timer Event. So letting it run in infinite loop (With SPI Trigger) does not help a lot (unless maybe the SPI Clk might be used as a sample Clock). But I can confirm it is possible to let transfers run in circular mode in general. There are some pitfalls. Despite setting up next descriptor address (XDMAC_CNDA) you also have to enable Next Descriptor fetching and the right descriptor View in XDMAC_CNDC  before starting the transfer. Furthermore in the descriptor itself needs also define Descriptor fetch, which is in this case stored in the MBR_UBC field. Also in the descriptors NDA field you store a self reference.

 

 

Ok I have to unveil, that I have a solution for "Timer Triggered SPI DMA transfer". As written before I used a PWM unit instead of TC. Unfortunately I must not C&P the project code here. The general approach is the following:

-Stop PWM

-Setup PWM Channel 0 in synchronous mode (No other mode works in DMA)

-Setup PWM Comparsion Unit (I used Unit 0) to trigger DMA Update Events

-Setup A SPI TX DMA (Dest=SPI_TDR) Channel with Trigger = PWM0 (In my case the ADC does not need any specific data, so the Channel runs w/o source increment and always transfers a dummy word)

-Setup A SPI RX DMA (Source=SPI_RDR) Channel with Trigger = SPI Rx which may write in circular mode to a ring buffer

-And VERY IMPORTANT: You need to make a write to PWM_DMAR (which will update the duty cycle of your channel). If it is not written to, the DMA Trigger PWM will only trigger once

-So Setup A PWM DMA Channel (Dest=PWM_DMAR) with Trigger = PWM0. Write the same duty cycle into the register as you don´t really want to change the duty cycle. I did a DMA Transfer w/o Source increment, so the same value is written over and over again.

-Start PWM smiley

 

In order to make the HW loop run infinite you have to use self referencing Descriptor Views.

 

I hope this is useful and somebody can follow. What I´m curious about is, whether there is a guarantee or not, that two DMA Channels who have the same trigger will run both for the same event, i.e. is a Transfer edge triggered.

My practical observation is, that the PWM Trigger will successful run a transfer on both DMA channels I used with that trigger.

 

Good luck

Last Edited: Thu. Jul 12, 2018 - 07:48 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

regjoe wrote:

I'm also interested.

In my application I've set up a timer ISR which starts an SPI DMA and fires an interrupt after the last DMA in the chain is done. I did not succeed in running DMA in an endless loop by writing the address of the first descriptor into the last descriptor.

I noticed that I have to re-write the RX and TX DMA descriptor address (XDMAC_CNDA) in the XDMAC controller otherwise the DMA does not restart. This DMA re-initialization includes DMA disable/enable and takes some considerable time which limits the maximum frequency. I can't get higher than 50kHz.

 

Sorry for the thread revival, but I'm seemingly having this exact issue. I've got the Next Descriptor Address (NDA) of the final linked list descriptor set to the first, so the DMA transfer should revert back to the first descriptor and continue writing data, but it doesn't.

 

Has anyone been able to successfully use a circular linked list XDMAC process?