MK66FN2M0 DMA issues

取消
显示结果 
显示  仅  | 搜索替代 
您的意思是: 
已解决

MK66FN2M0 DMA issues

跳至解决方案
4,151 次查看
RadaD
Contributor III

Hi all!

I am debugging a system were eDMA is used to transfer ADC16 samples. The ADC is triggered via TPM1/2. It is necessary to start sampling and transfering the data via DMA after a GPIO interrupt was caught.

My first question is: Is it advisable to start the DMA transfer (EDMA_StartTransfer()) and ADC sampling (TPM_StartTimer()) in the ISR? Is there a better way to synchronize the sampling/transfer with the external signal?

My second question is more of an observation: While debugging the DMA transfer - which seems to not start from the destination address that is given in the EDMA_PrepareTransfer() but some hundred bytes later (DMA shall transfer ~64KB in total), - I used a break point in the TimerOverflow ISR of TMP1 to see each individual DMA request. However, activating TMP interrupts alone seems to cause the DMA to operate in the desired way, while disabling the TMP interrupts causes the DMA to start at a higher memory address again.

This seems very strange to me and I am not even sure what code sections to provide for you to review.

Any ideas? Thanks in advance!

0 项奖励
回复
1 解答
4,047 次查看
RadaD
Contributor III

I'm not 100% sure at this point, but it seems the problem wasn't in the DMA itselft, but rather in the timing interplay between ADC conversions and DMA requests. I think I triggered ADC conversions (via the TPM1/2 timers) before the ongoing ADC conversion finished, which caused the result to always be zero.

The moral of the story is; pay close (!) attention to your timing (and get your ISRs right; I had one that didn't reset its flags, causing it to be called multiple times per interrupt)!

Thanks, @myke_predko and @ErichStyger, your comments certainly helped!

在原帖中查看解决方案

12 回复数
4,048 次查看
RadaD
Contributor III

I'm not 100% sure at this point, but it seems the problem wasn't in the DMA itselft, but rather in the timing interplay between ADC conversions and DMA requests. I think I triggered ADC conversions (via the TPM1/2 timers) before the ongoing ADC conversion finished, which caused the result to always be zero.

The moral of the story is; pay close (!) attention to your timing (and get your ISRs right; I had one that didn't reset its flags, causing it to be called multiple times per interrupt)!

Thanks, @myke_predko and @ErichStyger, your comments certainly helped!

4,069 次查看
RadaD
Contributor III

@myke_predko: I tested the DMA separately, by disabling ADC conversion and supplying the address of a constant test buffer as the source of the DMA transfer and it seems, the DMA works. The test values are transfered to the correct destination addresses and the major loop takes the expected amount of time to complete. So I think that the TPM1/2 and eDMA configuration is correct. I'm not sure how to test the ADC conversion regarding timing. I have confirmed, that a single ADC conversion takes the expected amount of time. Can you give me any hint on how to further debug this?

0 项奖励
回复
4,062 次查看
myke_predko
Senior Contributor III

Hey @RadaD 

I've been trying to figure out a strategy for you to debug your problem basically from the beginning.  

The only suggestion that I have for you is to fill your RAM2/SRAM_LOWER with something that you can recognize (I usually use something like data = address) before you start your DMA operations.  This will allow you to see if it's being written over when it looks like when the DMA is skipping over memory and maybe give you a clue to what's actually happening. 

The results of the experiment would be:

  • Memory isn't changed.  The DMA Counter is being incremented but nothing is being transferred
  • Memory is changed with an unexpected value.  Then you have to figure out where the data used in the transfers is coming from
  • Memory is changed with recognizable values.  This is the best case scenario and will give you a better idea of what's happening so you can go forwards from there

This would be my start - my hypothesis that DMA is taking place when you're running at full speed but for some reason it's not transferring the ADC data.  If the memory is being overwritten, then you can assume it's by DMA and hopefully the values will give you a clue as to where the data is coming from.  

Maybe somebody else has a better idea of how to analyze what's happening?

4,114 次查看
RadaD
Contributor III

Update: I verified, that immediatelly before the EDMA_StartTransfer(&g_edmaHandle[ADC0_CHANNEL10_EDMA0]); and TPM_StartTimer(TPM1, kTPM_SystemClock); the address in DMA0->TCD.TCD[0].DADDR is correct. Still the first written address is a few hundred bytes higher (I look at it when the Half Full callback is called). Also, the first written address is not the same every time.

0 项奖励
回复
4,117 次查看
RadaD
Contributor III

Thanks for the answers!

@myke_predko: ad 1) I see, thanks. Initially I planned to also reconfigure the DMA in the ISR, i.e., setup the destination addresses, but that seems to consume too much time in my context (I am sampling a signal with 93us periodicity (which is also the rate of the external interrupt event) with 2.5 MS/s with both ADCs and 2 channels each).

ad 2) This is my buffer (global):

/** @cond */__BSS(RAM2) /** @endcond */ volatile message_t dataset;

 The /** @cond */ and /** @endcond */ are doxygen commands. message_t consists of uint16_ts.

With "activating the TMP intterrupts alone" I mean the following: I observed (via placing a breakpoint in the Half Interrupt ISR of the DMA) that the DMA starts writing at higher addresses than &dataset. Therefore, I activated the Timer Overflow interrupt of the timer that triggers the DMA

TPM_EnableInterrupts(TPM1, kTPM_TimeOverflowInterruptEnable);
EnableIRQ(TPM1_IRQn);

 and filled it with a portNOP(). My plan was to place a breakpoint at the NOP, in order to observe individually transferred integers. However, that wasn't necessary, since with the timer ISR in place, the DMA filled the buffer from the correct start address, i.e., &dataset.

@ErichStyger: Refresher never hurts, I'll certainly have a look, thanks!

0 项奖励
回复
4,107 次查看
myke_predko
Senior Contributor III

HI @RadaD 

You didn't quite answer my question on how do you define your destination buffer.  I was really asking to see if you did any kind of alignment with the data.  At the very least you should be ensuring that your buffer starting address is on a word (32bit/4byte) boundary and it doesn't cross the SRAM_UPPER/SRAM_LOWER boundary.  

Sorry, I really don't understand what you are saying in regards to your TPM interrupt.  Could you explain how you've set up your ADC read operation with the DMA storage of the results?   

0 项奖励
回复
4,097 次查看
RadaD
Contributor III

Thanks for coming back, @myke_predko! Sorry for the confusion. Please be patient with me; this whole DMA and large data hustle in general is quite new to me. Also I took over this project with quite a large code base already existing, so it's not unlikely I haven't got hold of some details yet.

I'm still not 100% sure what you mean, but the container is declared to be in RAM2. which starts at 0x1FFF'0000 and barely fits the section; the buffer size is 0xFF80 while the size of RAM2 is 0x1'0000. I have verified at runtime that &dataset indeed starts 0x1FFF'0000. Is that what you mean?

TPM timers are configured to trigger both the ADC conversion as well as the DMA requests (at least that's the intention), but maybe it's best if a provide the ADC and DMA code sections:

 

static void TPM_Configuration(adc_samplerate_e sample_rate)
{
	/* configure ADC0/ADC1 alternate trigger via TPM */
	tmp32 = SIM->SOPT7 & ~(SIM_SOPT7_ADC0TRGSEL_MASK | SIM_SOPT7_ADC1TRGSEL_MASK);
	tmp32 |= SIM_SOPT7_ADC1ALTTRGEN_MASK | SIM_SOPT7_ADC1TRGSEL(15U) | SIM_SOPT7_ADC0ALTTRGEN_MASK | SIM_SOPT7_ADC0TRGSEL(15U);
	SIM->SOPT7 = tmp32;

        tpm_config_t tpmConfig;

	CLOCK_SetTpmClock(1U);
	CLOCK_SetPllFllSelClock(1,0,0);

	TPM_GetDefaultConfig(&tpmConfig);

	TPM_Init(TPM1, &tpmConfig);
	TPM_Init(TPM2, &tpmConfig);

	TPM_SetTimerPeriod(TPM1, NSEC_TO_COUNT(800U, CLOCK_GetFreq(kCLOCK_PllFllSelClk)));
	TPM_SetTimerPeriod(TPM2, NSEC_TO_COUNT(800U, CLOCK_GetFreq(kCLOCK_PllFllSelClk)));

	TPM_SetupOutputCompare(TPM1, kTPM_Chnl_0, kTPM_HighPulseOutput, 0U);
	TPM_SetupOutputCompare(TPM1, kTPM_Chnl_1, kTPM_HighPulseOutput, NSEC_TO_COUNT(400U, CLOCK_GetFreq(kCLOCK_PllFllSelClk)));

	TPM_SetupOutputCompare(TPM2, kTPM_Chnl_0, kTPM_HighPulseOutput, 0U);
	TPM_SetupOutputCompare(TPM2, kTPM_Chnl_1, kTPM_HighPulseOutput, NSEC_TO_COUNT(400U, CLOCK_GetFreq(kCLOCK_PllFllSelClk)));

	TPM1->CONTROLS[kTPM_Chnl_0].CnSC |= TPM_CnSC_DMA_MASK;
	TPM1->CONTROLS[kTPM_Chnl_1].CnSC |= TPM_CnSC_DMA_MASK;
	TPM2->CONTROLS[kTPM_Chnl_0].CnSC |= TPM_CnSC_DMA_MASK;
	TPM2->CONTROLS[kTPM_Chnl_1].CnSC |= TPM_CnSC_DMA_MASK;

    TPM_EnableInterrupts(TPM1, kTPM_TimeOverflowInterruptEnable); // only for the mentioned debugging
    EnableIRQ(TPM1_IRQn);                                         // only for the mentioned debugging
}


static void ADC16_Configuration(void)
{
    adc16_config_t adcConfig;
    adc16_channel_config_t adcChannelConfig;

    ADC16_GetDefaultConfig(&adcConfig);

    adcConfig.clockDivider = kADC16_ClockDivider1;
    adcConfig.clockSource = kADC16_ClockSourceAlt0;
    adcConfig.enableHighSpeed = true;
    adcConfig.longSampleMode = kADC16_LongSampleCycle6;
    adcConfig.resolution = kADC16_ResolutionSE12Bit;
    adcConfig.enableContinuousConversion = false;

    ADC16_Init(ADC0, &adcConfig);
    ADC16_Init(ADC1, &adcConfig);

    ADC16_EnableHardwareTrigger(ADC0, true);
    ADC16_EnableHardwareTrigger(ADC1, true);

    adcChannelConfig.enableDifferentialConversion = false;
    adcChannelConfig.enableInterruptOnConversionCompleted = false;

    adcChannelConfig.channelNumber = ADC0_CHANNEL_I1;
    ADC16_SetChannelConfig(ADC0, 0U, &adcChannelConfig);

    adcChannelConfig.channelNumber = ADC0_CHANNEL_Q1;
    ADC16_SetChannelConfig(ADC0, 1U, &adcChannelConfig);

    adcChannelConfig.channelNumber = ADC1_CHANNEL_I2;
    ADC16_SetChannelConfig(ADC1, 0U, &adcChannelConfig);

    adcChannelConfig.channelNumber = ADC1_CHANNEL_Q2;
    ADC16_SetChannelConfig(ADC1, 1U, &adcChannelConfig)
}

static void DMAMUX_Configuration(void)
{
    DMAMUX_Init(DMAMUX0);

    DMAMUX_SetSource(DMAMUX0, ADC0_CHANNEL10_EDMA0, 28U);
    DMAMUX_SetSource(DMAMUX0, ADC0_CHANNEL11_EDMA1, 29U);
    DMAMUX_SetSource(DMAMUX0, ADC1_CHANNEL10_EDMA2, 30U);
    DMAMUX_SetSource(DMAMUX0, ADC1_CHANNEL11_EDMA3, 31U);

    DMAMUX_EnableChannel(DMAMUX0, ADC0_CHANNEL10_EDMA0);
    DMAMUX_EnableChannel(DMAMUX0, ADC0_CHANNEL11_EDMA1);
    DMAMUX_EnableChannel(DMAMUX0, ADC1_CHANNEL10_EDMA2);
    DMAMUX_EnableChannel(DMAMUX0, ADC1_CHANNEL11_EDMA3);
}

static void EDMA_Configuration(void)
{
    EDMA_GetDefaultConfig(&edmaConfig);
    EDMA_Init(DMA0, &edmaConfig);

    EDMA_CreateHandle(&g_edmaHandle[ADC0_CHANNEL10_EDMA0], DMA0, ADC0_CHANNEL10_EDMA0);
    EDMA_CreateHandle(&g_edmaHandle[ADC0_CHANNEL11_EDMA1], DMA0, ADC0_CHANNEL11_EDMA1);
    EDMA_CreateHandle(&g_edmaHandle[ADC1_CHANNEL10_EDMA2], DMA0, ADC1_CHANNEL10_EDMA2);
    EDMA_CreateHandle(&g_edmaHandle[ADC1_CHANNEL11_EDMA3], DMA0, ADC1_CHANNEL11_EDMA3);

    EDMA_SetCallback(&g_edmaHandle[ADC0_CHANNEL10_EDMA0], EDMA_Callback, (void *) ADC0_CHANNEL10_EDMA0);
    EDMA_SetCallback(&g_edmaHandle[ADC0_CHANNEL11_EDMA1], EDMA_Callback, (void *) ADC0_CHANNEL11_EDMA1);
    EDMA_SetCallback(&g_edmaHandle[ADC1_CHANNEL10_EDMA2], EDMA_Callback, (void *) ADC1_CHANNEL10_EDMA2);
    EDMA_SetCallback(&g_edmaHandle[ADC1_CHANNEL11_EDMA3], EDMA_Callback, (void *) ADC1_CHANNEL11_EDMA3);

    EDMA_PrepareTransfer(&edmaTransferConfig,
						(void *)&ADC0->R[0U],
						sizeof(uint16_t),
						(void *)&dataset.RX1.chirp[0].IQ_pair[0].I,
                                                sizeof(uint16_t),
                                                sizeof(uint16_t),
                                                sizeof(uint16_t)*SAMPLES_PER_CHIRP, kEDMA_PeripheralToMemory);
    EDMA_SubmitTransfer(&g_edmaHandle[ADC0_CHANNEL10_EDMA0], &edmaTransferConfig);

    DMA0->TCD[ADC0_CHANNEL10_EDMA0].DLAST_SGA = -(sizeof(uint16_t)*SAMPLES_PER_CHIRP);
    DMA0->TCD[ADC0_CHANNEL10_EDMA0].DOFF = 4;									
    DMA0->TCD[ADC0_CHANNEL10_EDMA0].CSR &= ~DMA_CSR_DREQ_MASK; // don't disable DMA REQ line

...

    EDMA_EnableChannelInterrupts(DMA0, ADC0_CHANNEL10_EDMA0, kEDMA_HalfInterruptEnable);
    EDMA_EnableChannelInterrupts(DMA0, ADC0_CHANNEL11_EDMA1, kEDMA_HalfInterruptEnable);
    EDMA_EnableChannelInterrupts(DMA0, ADC1_CHANNEL10_EDMA2, kEDMA_HalfInterruptEnable);
    EDMA_EnableChannelInterrupts(DMA0, ADC1_CHANNEL11_EDMA3, kEDMA_HalfInterruptEnable);
}

 

I start the sampling and transfer via

 

EDMA_StartTransfer(&g_edmaHandle[ADC0_CHANNEL10_EDMA0]);
EDMA_StartTransfer(&g_edmaHandle[ADC0_CHANNEL11_EDMA1]);
EDMA_StartTransfer(&g_edmaHandle[ADC1_CHANNEL10_EDMA2]);
EDMA_StartTransfer(&g_edmaHandle[ADC1_CHANNEL11_EDMA3]);

TPM_StartTimer(TPM1, kTPM_SystemClock);
TPM_StartTimer(TPM2, kTPM_SystemClock);

 

 

0 项奖励
回复
4,087 次查看
myke_predko
Senior Contributor III

Hi @RadaD 

First question - what tools are you using for software development.  Your unfamiliarity with SRAM_UPPER/SRAM_LOWER and using RAM2 sounds like you're not using the standard SDKs.  

Regardless, how you seem to have set up your destination buffer sounds fine and you've answered my question.  

What is your operating environment (Bare Metal, an RTOS)?  

When I look at your code, you're setting the timers to the same values and if you're keying your DMAs from the IRQs could you be experiencing a race condition in which the DMA operations are not happening when you expect them to because the IRQ handlers are being pre-empted by currently executing operations?  

Could you share your timer IRQ handlers?  

0 项奖励
回复
4,072 次查看
RadaD
Contributor III

Hi @myke_predko!

SRAM_UPPER (alias 'RAM') is at 0x2000'0000, size 0x3'0000, SRAM_LOWER (alias 'RAM2') is at 0x1FFF'0000, size 0x1'0000, as I said.

I am using MCUXpresso IDE with SDK Version 2.10. Further the project uses the FreeRTOS kernel.

What exactly do you mean with "keying your DMAs from the IRQs"? As far as I understand it, the DMA requests are hardware triggered by TPMT1/2:

// Enable DMA
TPM1->CONTROLS[kTPM_Chnl_0].CnSC |= TPM_CnSC_DMA_MASK;
TPM1->CONTROLS[kTPM_Chnl_1].CnSC |= TPM_CnSC_DMA_MASK;
TPM2->CONTROLS[kTPM_Chnl_0].CnSC |= TPM_CnSC_DMA_MASK;
TPM2->CONTROLS[kTPM_Chnl_1].CnSC |= TPM_CnSC_DMA_MASK;

In the ISR of the externel interrupt I only want to start the DMA transfers:

// Start EDMA transfers
EDMA_StartTransfer(&g_edmaHandle[ADC0_CHANNEL10_EDMA0]);
EDMA_StartTransfer(&g_edmaHandle[ADC0_CHANNEL11_EDMA1]);
EDMA_StartTransfer(&g_edmaHandle[ADC1_CHANNEL10_EDMA2]);
EDMA_StartTransfer(&g_edmaHandle[ADC1_CHANNEL11_EDMA3]);

The ISR on the timer interrupt that I only added for debugging looks like this:

void TPM1_IRQHandler(void)
{
	if(TPM1->SC & 0x0080)
	{
		TPM1->SC |= 0x0080; // delete TOF
	}
}
0 项奖励
回复
4,065 次查看
myke_predko
Senior Contributor III

@RadaD 

Thank you for the follow up information.  

From what you were writing, I thought that the DMA transfers were initiated by software in the IRQs.  

0 项奖励
回复
4,130 次查看
ErichStyger
Specialist I

To add on this: Starting anything from an ISR (as Myke points out) is fine, as long as it is not blocking and does not cause any possible reentrancy issues.

One should consider nesting of interrupts, just in case. The ARM NVIC is pretty powerful but can cause problems if not configured properly.  Just in case as a refresher: https://mcuoneclipse.com/2016/08/14/arm-cortex-m-interrupts-and-freertos-part-1/ although most applies for bare metal too.

 

Erich

4,147 次查看
myke_predko
Senior Contributor III

Hey @RadaD 

I don't know if it's "advisable" to start DMA transfers from an ISR, but I do it all the time without issues.  The usual rules apply, keep your ISR as short as possible and make sure there's no way that you attempt to start a DMA transfer while another is taking place.  

How are you defining the buffer that you are putting DMA transfer into?  I'm not sure what you mean by "activating TMP interrupts alone" - can you explain?