I’m using an NXP i.MX RT1064 MCU and encountering an issue where eDMA round-robin arbitration doesn’t seem to balance DMA activity between ADC1 and ADC2 when operating in parallel.
Here’s my setup:
ADC1 and ADC2 are both hardware-triggered via ADC_ETC in sync mode.
The pulse signal is generated by DMA Channel 0, which transfers precomputed pulse bytes from memory to GPIO2->DR. These pulses are timed using a PIT timer, with one minor loop per pulse.
Each logic low pulse triggers ADC conversions via XBARA to ADC_ETC, which in turn triggers:
DMA Channel 1 for ADC1 result transfer to memory.
DMA Channel 2 for ADC2 result transfer to memory.
Both DMA channels are configured with a major loop count of N, expecting one minor loop transfer per pulse.
Despite enabling round-robin arbitration in the eDMA controller, only one of the DMA channels completes its major loop. The other channel appears to be stalled or blocked.
I’ve verified that:
The ADC conversions are occurring on both ADCs.
The DMAMUX sources are correctly assigned.
Minor loop mapping is enabled.
Channel preemption is disabled for both.
PIT timing is conservative enough that the eDMA shouldn't be overwhelmed.
I've tried to simplify the code as much as possible. Please note that EDMA channel 0 and PIT timer are configured in "APPTask()" while the remaining peripherals such as EDMA channels 1, 2 and ADC peripherals are configured in peripherals.c.
Many thanks,
Fabio
#include "fsl_common.h"
#include "fsl_edma.h"
#include "fsl_dmamux.h"
#include "fsl_adc.h"
#include "fsl_adc_etc.h"
#include "fsl_xbara.h"
#include "fsl_pit.h"
#define EDMA_CHANNEL_0 0U
#define EDMA_CHANNEL_1 1U
#define EDMA_CHANNEL_2 2U
AT_QUICKACCESS_SECTION_DATA_ALIGN(edma_tcd_t tcdPool[LINE_COUNT], sizeof(edma_tcd_t));
volatile bool DMA_0_transfer_done = false;
uint8_t* g_dest_data;
edma_handle_t EDMA_Handle_0;
edma_config_t userConfig;
edma_transfer_config_t transferConfig;
void DMA_Callback_0(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds)
{
if (transferDone)
{
DMA_0_transfer_done = true;
}
}
void setup_scatter_gather(
edma_handle_t *EDMA_handle,
edma_transfer_config_t *transfer_config,
GPIO_Type *gpio)
{
for (uint32_t i = 0; i < 130; i++)
{
EDMA_PrepareTransfer(&transferConfig,
(uint32_t*)&signal_data[0],
sizeof(uint32_t),
&gpio->DR,
sizeof(uint32_t),
sizeof(uint32_t),
sizeof(uint32_t),
kEDMA_MemoryToPeripheral);
EDMA_SubmitTransfer(&dmaHandle0, &transferConfig);
}
}
void APPTask(void)
{
// PIT init
pit_config_t pitConfig;
PIT_GetDefaultConfig(&pitConfig);
PIT_Init(PIT, &pitConfig);
PIT_SetTimerPeriod(PIT, kPIT_Chnl_0, 19U);
PIT_StartTimer(PIT, kPIT_Chnl_0);
edma_handle_t EDMA_Handle_0;
edma_config_t userConfig;
edma_transfer_config_t transferConfig;
// DMA channel 0: triggered by PIT channel 0
DMAMUX_SetSource(DMAMUX, EDMA_CHANNEL_0, kPIT_Chnl_0);
DMAMUX_EnablePeriodTrigger(DMAMUX, EDMA_CHANNEL_0);
DMAMUX_EnableChannel(DMAMUX, EDMA_CHANNEL_0);
EDMA_CreateHandle(&dmaHandle0, DMA0, EDMA_CHANNEL_0);
EDMA_SetCallback(&dmaHandle0, DMA_Callback_0, NULL);
EDMA_InstallTCDMemory(&dmaHandle0, tcdPool, 130);
// Setup DMA channel 0 scatter gather
setup_scatter_gather(&EDMA_Handle_0, &transferConfig, GPIO2);
EDMA_InstallTCD(DMA, EDMA_CHANNEL_0, &tcdPool[0]);
EDMA_EnableChannelRequest(DMA, EDMA_CHANNEL_0);
EDMA_EnableChannelRequest(DMA, EDMA_CHANNEL_1);
EDMA_EnableChannelRequest(DMA, EDMA_CHANNEL_2);
while (!DMA_0_transfer_done) {
/*
GPIO signal generated by DMA channel 0 triggers ADC1 + ADC2 via ADC_ETC.
Trigger groups 0 and 4 in sync mode to initiate ADC1 and ADC2 conversions in parallel.
Each ADC1 and ADC2 conversion triggers DMA channel 1 and DMA channel 2 minor loop transfer from
ADC1 and ADC2 result registers to memory.
Once DMA chanel 0 completes, DMA channels 1 and 2 DMA's should also complete
*/
__asm volatile ("nop");
}
}
void main (void)
{
BOARD_ConfigMPU();
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
g_dest_data = (uint8_t*)0x13CB0;
BOARD_InitBootPeripherals();
// Connect external input to ADC_ETC via XBARA
XBARA_Init(XBARA1);
XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputIomuxXbarInout17, kXBARA1_OutputAdcEtcXbar0Trig0);
while (1) {
APPTask()
}
}
peripherals.c
#include "peripherals.h"
const edma_config_t DMA0_config = {
.enableContinuousLinkMode = false,
.enableHaltOnError = false,
.enableRoundRobinArbitration = true,
.enableDebugMode = false
};
/* Tansactional transfer configuration */
edma_transfer_config_t DMA0_DMA_SENSEA_TRANSFER0_config;
/* Tansactional transfer configuration */
edma_transfer_config_t DMA0_DMA_SENSEB_TRANSFER0_config;
edma_handle_t DMA0_DMA_SENSEA_Handle;
edma_handle_t DMA0_DMA_SENSEB_Handle;
static void DMA0_init(void) {
status_t status;
(void)status;
/* DMA0 minor loop mapping */
EDMA_EnableMinorLoopMapping(DMA0_DMA_BASEADDR, true);
/* Channel 1 initialization */
/* Set the source kDmaRequestMuxADC1 request in the DMAMUX */
DMAMUX_SetSource(DMA0_DMAMUX_BASEADDR, DMA0_DMA_SENSEA_DMA_CHANNEL, DMA0_DMA_SENSEA_DMA_REQUEST);
/* Enable the channel 1 in the DMAMUX */
DMAMUX_EnableChannel(DMA0_DMAMUX_BASEADDR, DMA0_DMA_SENSEA_DMA_CHANNEL);
/* Create the eDMA DMA0_DMA_SENSEA_Handle handle */
EDMA_CreateHandle(&DMA0_DMA_SENSEA_Handle, DMA0_DMA_BASEADDR, DMA0_DMA_SENSEA_DMA_CHANNEL);
/* DMA callback initialization */
EDMA_SetCallback(&DMA0_DMA_SENSEA_Handle, ADC_DMA_CALLBACK0, NULL);
/* DMA0 transfer DMA_SENSEA_TRANSFER0 configuration */
EDMA_PrepareTransferConfig(
&DMA0_DMA_SENSEA_TRANSFER0_config,
(void *) ((uint8_t *) (&ADC1->R[0])),
1 << kEDMA_TransferSize1Bytes, 0,
(void *) (uint8_t*) &g_dest_data[0],
1 << kEDMA_TransferSize1Bytes,
2,
1U,
10000U
);
/* DMA0 transfer DMA_SENSEA_TRANSFER0 submit */
status = EDMA_SubmitTransfer(
&DMA0_DMA_SENSEA_Handle,
&DMA0_DMA_SENSEA_TRANSFER0_config
);
assert(status == kStatus_Success);
/* Channel 2 initialization */
/* Set the source kDmaRequestMuxADC2 request in the DMAMUX */
DMAMUX_SetSource(DMA0_DMAMUX_BASEADDR, DMA0_DMA_SENSEB_DMA_CHANNEL, DMA0_DMA_SENSEB_DMA_REQUEST);
/* Enable the channel 2 in the DMAMUX */
DMAMUX_EnableChannel(DMA0_DMAMUX_BASEADDR, DMA0_DMA_SENSEB_DMA_CHANNEL);
/* Create the eDMA DMA0_DMA_SENSEB_Handle handle */
EDMA_CreateHandle(&DMA0_DMA_SENSEB_Handle, DMA0_DMA_BASEADDR, DMA0_DMA_SENSEB_DMA_CHANNEL);
/* DMA callback initialization */
EDMA_SetCallback(&DMA0_DMA_SENSEB_Handle, ADC_DMA_CALLBACK1, NULL);
/* DMA0 transfer DMA_SENSEB_TRANSFER0 configuration */
EDMA_PrepareTransferConfig(
&DMA0_DMA_SENSEB_TRANSFER0_config,
(void *) ((uint8_t *) (&ADC2->R[0])),
1 << kEDMA_TransferSize1Bytes,
0, (void *) (uint8_t*)&g_dest_data[1],
1 << kEDMA_TransferSize1Bytes,
2,
1U,
10000U
);
/* DMA0 transfer DMA_SENSEB_TRANSFER0 submit */
status = EDMA_SubmitTransfer(
&DMA0_DMA_SENSEB_Handle,
&DMA0_DMA_SENSEB_TRANSFER0_config);
assert(status == kStatus_Success);
}
const adc_config_t ADC_SENSEA_SENSETP_config = {
.enableOverWrite = false,
.enableContinuousConversion = false,
.enableHighSpeed = false,
.enableLowPower = false,
.enableLongSample = true,
.enableAsynchronousClockOutput = true,
.referenceVoltageSource = kADC_ReferenceVoltageSourceAlt0,
.samplePeriodMode = kADC_SamplePeriodLong12Clcoks,
.clockSource = kADC_ClockSourceIPG,
.clockDriver = kADC_ClockDriver2,
.resolution = kADC_Resolution8Bit
};
const adc_channel_config_t ADC_SENSEA_SENSETP_channels_config[2] = {
{
.channelNumber = SENSEA,
.enableInterruptOnConversionCompleted = true
},
{
.channelNumber = SENSETP,
.enableInterruptOnConversionCompleted = false
}
};
static void ADC_SENSEA_SENSETP_init(void) {
/* Initialize ADC1 peripheral. */
ADC_Init(ADC_SENSEA_SENSETP_PERIPHERAL, &ADC_SENSEA_SENSETP_config);
/* Enable DMA requests from ADC1. */
ADC_EnableDMA(ADC_SENSEA_SENSETP_PERIPHERAL, true);
/* Perform ADC1 auto calibration. */
ADC_DoAutoCalibration(ADC_SENSEA_SENSETP_PERIPHERAL);
/* Enable ADC1 hardware trigger. */
ADC_EnableHardwareTrigger(ADC_SENSEA_SENSETP_PERIPHERAL, true);
/* Initialize ADC1 channel 6. */
ADC_SetChannelConfig(ADC_SENSEA_SENSETP_PERIPHERAL, ADC_SENSEA_SENSETP_CH0_CONTROL_GROUP, &ADC_SENSEA_SENSETP_channels_config[0]);
const adc_config_t ADC_SENSEB_config = {
.enableOverWrite = false,
.enableContinuousConversion = false,
.enableHighSpeed = false,
.enableLowPower = false,
.enableLongSample = true,
.enableAsynchronousClockOutput = true,
.referenceVoltageSource = kADC_ReferenceVoltageSourceAlt0,
.samplePeriodMode = kADC_SamplePeriodLong12Clcoks,
.clockSource = kADC_ClockSourceIPG,
.clockDriver = kADC_ClockDriver2,
.resolution = kADC_Resolution8Bit
};
const adc_channel_config_t ADC_SENSEB_channels_config[1] = {
{
.channelNumber = SENSEB,
.enableInterruptOnConversionCompleted = true
}
};
static void ADC_SENSEB_init(void) {
/* Initialize ADC2 peripheral. */
ADC_Init(ADC_SENSEB_PERIPHERAL, &ADC_SENSEB_config);
/* Enable DMA requests from ADC2. */
ADC_EnableDMA(ADC_SENSEB_PERIPHERAL, true);
/* Perform ADC2 auto calibration. */
ADC_DoAutoCalibration(ADC_SENSEB_PERIPHERAL);
/* Enable ADC2 hardware trigger. */
ADC_EnableHardwareTrigger(ADC_SENSEB_PERIPHERAL, true);
/* Initialize ADC2 channel 5. */
ADC_SetChannelConfig(ADC_SENSEB_PERIPHERAL, ADC_SENSEB_CH0_CONTROL_GROUP, &ADC_SENSEB_channels_config[0]);
}
const adc_etc_config_t ADC_ETC_SENSE_config = {
.clockPreDivider = 0,
.dmaMode = kADC_ETC_TrigDMAWithLatchedSignal,
.enableTSCBypass = false,
.enableTSC0Trigger = false,
.enableTSC1Trigger = false,
.TSC0triggerPriority = 0,
.TSC1triggerPriority = 0,
.XBARtriggerMask = 17
};
/* ADC_ETC triggers configuration */
const adc_etc_trigger_config_t ADC_ETC_SENSE_trigger_config[2] = {
{
.triggerPriority = 0,
.enableSWTriggerMode = false,
.enableSyncMode = false,
.initialDelay = 0,
.sampleIntervalDelay = 1,
.triggerChainLength = 0
},
{
.triggerPriority = 0,
.enableSWTriggerMode = false,
.enableSyncMode = true,
.initialDelay = 0,
.sampleIntervalDelay = 0,
.triggerChainLength = 0
}
};
/* ADC_ETC chain configurations for appropriate (see name of these arrays) trigger configuration */
const adc_etc_trigger_chain_config_t ADC_ETC_SENSE_TC_0_chain_config[1] = {
{
.enableB2BMode = false,
.ADCHCRegisterSelect = 1,
.ADCChannelSelect = 6,
.InterruptEnable = kADC_ETC_InterruptDisable,
}
};
const adc_etc_trigger_chain_config_t ADC_ETC_SENSE_TC_1_chain_config[1] = {
{
.enableB2BMode = false,
.ADCHCRegisterSelect = 1,
.ADCChannelSelect = 5,
.InterruptEnable = kADC_ETC_InterruptDisable,
}
};
static void ADC_ETC_SENSE_init(void) {
/* ADC_ETC initialization */
ADC_ETC_Init(ADC_ETC_SENSE_PERIPHERAL, &ADC_ETC_SENSE_config);
/* Initialize ADC_ETC trigger 0. */
ADC_ETC_SetTriggerConfig(ADC_ETC_SENSE_PERIPHERAL, ADC_ETC_SENSE_TC_0_TG, &ADC_ETC_SENSE_trigger_config[ADC_ETC_SENSE_TC_0]);
/* Initialize ADC_ETC chain configuration for trigger 0. */
ADC_ETC_SetTriggerChainConfig(ADC_ETC_SENSE_PERIPHERAL, ADC_ETC_SENSE_TC_0_TG, 0U, &ADC_ETC_SENSE_TC_0_chain_config[0]);
/* Initialize ADC_ETC trigger 4. */
ADC_ETC_SetTriggerConfig(ADC_ETC_SENSE_PERIPHERAL, ADC_ETC_SENSE_TC_1_TG, &ADC_ETC_SENSE_trigger_config[ADC_ETC_SENSE_TC_1]);
/* Initialize ADC_ETC chain configuration for trigger 4. */
ADC_ETC_SetTriggerChainConfig(ADC_ETC_SENSE_PERIPHERAL, ADC_ETC_SENSE_TC_1_TG, 0U, &ADC_ETC_SENSE_TC_1_chain_config[0]);
}
void BOARD_InitPeripherals(void)
{
/* Global initialization */
DMAMUX_Init(DMA0_DMAMUX_BASEADDR);
EDMA_Init(DMA0_DMA_BASEADDR, &DMA0_config);
/* Initialize components */
DMA0_init();
ADC_SENSEA_SENSETP_init();
ADC_SENSEB_init();
ADC_ETC_SENSE_init();
}
/***********************************************************************************************************************
* BOARD_InitBootPeripherals function
**********************************************************************************************************************/
void BOARD_InitBootPeripherals(void)
{
BOARD_InitPeripherals();
}
Hi @mayliu1 , thank you for your reply. Regarding your questions:
1: Do you use MIMXRT1064-EVK board or Custom Development Board?
- The MIMXRT1064-EVK board was used.
2: I'm not very clear about this code. What is this address?
- That address was simply a place in SRAM_ITC memory which has sufficient space for the array that will store the data transferred from both ADC1 and ADC2 result registers. Is there a better/recommended way to allocate this?
3:I'd like to know why it's set this way.
- This setting should actually be false for SENSEA. This is incorrect in this example but it is correct in the real project.
- SENSETP is not yet implemented and not currently used.
I will try to give you some additional context. The code I posted is an isolated example of the ADC and DMA setup excluding other peripherals. However I would be happy to share the real code files privately if needed.
In the real application we have a pulse wave signal connected to a XBARA input as a trigger for the ADC_ETC.
XBARA_SetSignalsConnection(XBARA1, kXBARA1_InputIomuxXbarInout17, kXBARA1_OutputAdcEtcXbar0Trig0);
This pulse is generated by the Channel 0 DMA by transferring pulse bytes from an array in memory to GPIOx->DR where each minor loop is timed by the PIT timer.
The ADC_ETC is setup so that conversions on ADC1 (Trigger group 0) and ADC2(Trigger group 4) are triggered simultaneously in sync mode each time the pulse from DMA0 triggers it.
In turn both ADC1 and ADC2 have "Enable DMA request" active.
The purpose of this is that every time the ADC_ETC is triggered a conversion is done and transferred to the g_dest_data array by each DMA (Channel 1 DMA for ADC1, Channel 2 DMA for ADC2).
We believe this is where our problem is. Because both DMA channel 1 and channel 2 are triggered simultaneously, one appears to starve the other. We have tried both round-robin arbitration and fixed priority but this does not affect the outcome. Channel 0 DMA transfer is not affected.
The only way we found to overcome this problem was for ADC1 to trigger Channel 1 DMA and then use DMA channel linking to trigger Channel 2 DMA sequentially. But this isn't ideal as the major loop count has to be reduced when using channel linking.
Is there another way to ensure both DMA channel 1 and 2 can transfer data without blocks?
Please let me know if you need additional information.
Thanks,
Fabio
Hi @fsimz ,
Thanks for your updated information and patience.
Question:
That address was simply a place in SRAM_ITC memory which has sufficient space for the array that will store the data transferred from both ADC1 and ADC2 result registers. Is there a better/recommended way to allocate this?
Answer: I suggest you define a buffer instead of using specified Address.
I think you can refer to SDK demo "evkmimxrt1064_edma_scatter_gather"
For your reset question:
I think maybe you can check the next points:
1:
2: Could you check the DMA and ADC ETC status registers value when question occur.
3: there is a very important setting about ADCETC, ADC_ETC Support up to 8 external trigger inputs, input from XBAR (4 for each ADC, TRIG0- TRIG3 for ADC1, and TRIG4-TRIG7 for ADC2).
adcEtcConfig.XBARtriggerMask, I check its setting in your app code, I think your setting is correct. Could you check it again.
Wish it helps you.
If you still have question about it, please kindly let me know.
Wish you a nice day!
Best Regards
MayLiu
Hi @fsimz ,
Thank you so much for your interest in our products and for using our community.
Could you please provide more information to describe your project architecture and the functions it implements?
1: Do you use MIMXRT1064-EVK board or Custom Development Board?
If you use MIMXRT1064-EVK board, I will did test for your question.
2: I'm not very clear about this code. What is this address?
3:I'd like to know why it's set this way.
Wish it helps you.
If you still have question about it, please kindly let me know.
Wish you a nice day!
Best Regards
MayLiu