Can't get DMA to work with ADC on BW16 RTL8720DN

Trying to use GDMA to transfer ADC readings to memory.

I’ve set the ADC to be triggered by timer, and - I think - done all the setup necessary on the ADC to have its trigger to the DMA enabled, called ADC_RXGDMA_Init and enabled it.

The ADC is reading - I can see the FIFO go full - but the DMA is not being activated. Hardware platform is BW16 with RTL8720DN SoC.

I can read values from the ADC successfully using programmed IO (with appropriate configuration and the ADC_Readbuf() function)

My source is below (the comments on ‘how to do it’ are copied from the SDK file rtl8721d_adc.h)

void morse_adc_init(){

u8 EfuseBuf[2];
u32 index;
u32 addressOffset = 0x1D0;
u32 addressGain = 0x1D2;
#define pin _PB_3
gpio_t adcpin;
//First set the ADC we want to use and set the corresponding pad to ADC mode
Pinmux_Swdoff(); //Disable the Software Debug function of port B bit 3 module pin 2
//Set relevant GPIO mode to be an input with no pull-up or pulldown
gpio_init(&adcpin,_PB_3);
gpio_dir(&adcpin,PIN_INPUT);
gpio_mode(&adcpin,PullNone);

//If we are using the api initialization, the calibration is done the first time the ADC_Init function is called

memset((void )adcbuf,0,sizeof(adcbuf)); //Clear the receive buffer
/
*****************************************************************************************

  • How to use ADC in DMA mode (copied from rtl8721d_adc.h )

  •   To use ADC in DMA mode, the following steps are mandatory:
    
  •  1. Enable the ADC interface clock:
    
  • 	RCC_PeriphClockCmd(APBPeriph_ADC, APBPeriph_ADC_CLOCK, ENABLE);
    

/
RCC_PeriphClockCmd(APBPeriph_ADC, APBPeriph_ADC_CLOCK, ENABLE);
/
2. Fill the ADC_InitStruct with the desired parameters.
**/

uint32_t adc_idx;

adc_idx = pinmap_peripheral(_PB_3, PinMap_ADC);
printf("analogin_init [%x:%x ]\n",pin, adc_idx);
assert_param(adc_idx != NC);

/* Shutdown ADC gpio pin to prevent leakage current */
if(pin <= PB_31) {
	PAD_CMD(pin, DISABLE);
}

ADC_InitTypeDef ADC_InitStruct; //Defined in rtl8721d_adc.h l120 Scope: Local, non-persistent

/* Initialize ADC */
ADC_StructInit(&ADC_InitStruct);   //Sets some basic parameters, some of which we over-write
ADC_InitStruct.ADC_OpMode = ADC_TIM_TRI_MODE;	
//ADC_InitStruct.ADC_OpMode = ADC_AUTO_MODE;
ADC_InitStruct.ADC_CvlistLen = 0;
ADC_InitStruct.ADC_Cvlist[0] = adc_idx;

ADC_InitStruct.ADC_DMAThresholdLevel = 3; //Grab four readings at a time
ADC_InitStruct.ADC_ChIDEn = ENABLE; //Optional: Place the channel ID in the output reading (MS Nibble)

/*
ADC_INTConfig(BIT_ADC_IT_FIFO_FULL_EN, ENABLE);

InterruptRegister((IRQ_FUN)adc_irq_handle, ADC_IRQ, NULL, 10);
InterruptEn(ADC_IRQ, 10);
*/

/* 3. Init Hardware use step2 parameters.

  • 	ADC_Init(ADC_InitTypeDef* ADC_InitStruct).
    

*/
ADC_Init(&ADC_InitStruct);

/* 4. Enable DMA read mode.

  • 	ADC_SetDmaEnable().
    

*/
ADC_SetDmaEnable(ENABLE); //Set the flag in the ADC registers

/* 5. Init and Enable ADC RX GDMA, configure GDMA related configurations(source address/destination address/block size etc.)

  • 	ADC_RXGDMA_Init().
    

*/
GDMA_InitTypeDef GDMA_InitStruct;
//Note: Block length must be from 0 to 4095 (it is masked with 0xFFF)
DCache_CleanInvalidate((u32)adcbuf, 8192); //Copied from the uart DMA driver
uint32_t dmaRes = ADC_RXGDMA_Init(&GDMA_InitStruct,(IRQ_FUN)DMA_Callback,(void *)&DMA_CbData,(u8 )&adcbuf,(u32)128);
//NVIC_SetPriority(GDMA_GetIrqNum(0, GDMA_InitStruct.GDMA_ChNum), 12);
/
6. Activate the ADC peripheral:

  • 	ADC_Cmd(ENABLE).
    

*/

printf(“dmaRes: %x, chan: %d; Hitting trigger\n\r”,dmaRes,GDMA_InitStruct.GDMA_ChNum);
dmaDone = false;
ticks1 = tCounter;
ADC_Cmd(ENABLE); //If the mode is set to AUTO rather than timer triggered, this will start the system
/* 7. Enable specified mode:
*

  • ADC_TimerTrigCmd(Tim_Idx, PeriodMs, ENABLE)
    

*/

ADC_TimerTrigCmd(2, 1, ENABLE); //Temporarily set to 1ms sampling rate
//ADC_ReceiveBuf(adcbuf,8192);
ticks2 = tCounter;
}

Below is a refined configuration snippet.
Add this logic before you enable the ADC to ensure the “pipes” are connected:

GDMA_InitTypeDef GDMA_InitStruct;

// 1. Basic GDMA Setup
GDMA_StructInit(&GDMA_InitStruct);
GDMA_InitStruct.GDMA_ChNum = 0; // Ensure this channel isn't used elsewhere
GDMA_InitStruct.GDMA_Index = 0; // Use GDMA0
GDMA_InitStruct.GDMA_IsCircular = 0;

// 2. Hardware Handshake - THIS IS CRITICAL
// The RTL8721D ADC handshake ID is 7
GDMA_InitStruct.GDMA_Dir = TT_FC_PeripheralToMemory; 
GDMA_InitStruct.GDMA_SrcMsk = 7;                            // 7 is the ADC Handshake ID
GDMA_InitStruct.GDMA_SrcAddr = (u32)&ADC->ADC_FIFO_READ;    // Source is ADC FIFO

// 3. Data Alignment & Burst logic
GDMA_InitStruct.GDMA_DstAddr = (u32)adcbuf;
GDMA_InitStruct.GDMA_BlockSize = 128;                // Total number of 16-bit items
GDMA_InitStruct.GDMA_SrcDataWidth = TrWidthHalfWord; // 2 bytes per sample
GDMA_InitStruct.GDMA_DstDataWidth = TrWidthHalfWord;

// Match MSize to your ADC_DMAThresholdLevel (3)
// ADC Threshold 3 means "trigger when 4 items in FIFO"
// Therefore, MSize must be 4
GDMA_InitStruct.GDMA_SrcChunkSize = MSizeFour;       
GDMA_InitStruct.GDMA_DstChunkSize = MSizeFour;

// 4. Update the Cache and Initialize
DCache_CleanInvalidate((u32)adcbuf, 8192);
GDMA_Init(GDMA_InitStruct.GDMA_Index, GDMA_InitStruct.GDMA_ChNum, &GDMA_InitStruct);

// 5. Register Callback and Enable
InterruptRegister((IRQ_FUN)DMA_Callback, GDMA_GetIrqNum(0, GDMA_InitStruct.GDMA_ChNum), (u32)&DMA_CbData, 10);
InterruptEn(GDMA_GetIrqNum(0, GDMA_InitStruct.GDMA_ChNum), 10);
GDMA_Cmd(GDMA_InitStruct.GDMA_Index, GDMA_InitStruct.GDMA_ChNum, ENABLE);


In your code, you set ADC_InitStruct.ADC_DMAThresholdLevel = 3. In the RTL872x series, the DMA request is typically only asserted when the FIFO level exceeds the threshold. If your GDMA block size or transfer count doesn’t align perfectly with how the ADC triggers its “Burst Request,” the DMA might wait forever for a signal that never comes.

  • Try this: Set ADC_InitStruct.ADC_DMAThresholdLevel = 0 (trigger on every sample) Just to test if the handshake starts working.

  • Check alignment: Ensure your ADC_RXGDMA_Init block size is a multiple of your threshold + 1.

The ADC_RXGDMA_Init function in the AmebaD SDK is a wrapper. Internally, it must map the GDMA channel to the specific hardware handshake interface for the ADC.

  • The RTL8720DN has multiple GDMA handshake lines. If the GDMA_InitStruct isn’t explicitly told that the source is the ADC (usually via GDMA_InitStruct.GDMA_SrcMsk or GDMA_SrcReq), The DMA engine is looking at the wrong “trigger wire.”

  • Verify that ADC_RXGDMA_Init is actually calling GDMA_Init with GDMA_InitStruct.GDMA_SrcMsk set to the ADC’s hardware ID.

You are using DCache_CleanInvalidate((u32)adcbuf, 8192);.

Hi Tmmsunny,

Thank you for your response.
Following the call stack from my source code, the GDMA initialisation runs as follows:

My code calls ADC_RXGDMA_Init() in rtl8721d_adc.c (in componenet/soc/realtek/amebad/fwlib/ram_common)

This carries out setting of the initialisation struct, including setting the ADC as the trigger (ten lines down):

	_memset((void *)GDMA_InitStruct, 0, sizeof(GDMA_InitTypeDef));

	GDMA_InitStruct->MuliBlockCunt      = 0;
	GDMA_InitStruct->MaxMuliBlock       = 1;//MaxLlp;	
	GDMA_InitStruct->GDMA_SrcDataWidth = TrWidthTwoBytes;
	GDMA_InitStruct->GDMA_DstDataWidth = TrWidthTwoBytes;
	GDMA_InitStruct->GDMA_SrcMsize   = MsizeEight;
	GDMA_InitStruct->GDMA_DstMsize  = MsizeEight;
	GDMA_InitStruct->GDMA_SrcInc       = NoChange;
	GDMA_InitStruct->GDMA_DstInc       = IncType;
	GDMA_InitStruct->GDMA_DIR       = TTFCPeriToMem;
	GDMA_InitStruct->GDMA_SrcHandshakeInterface     = GDMA_HANDSHAKE_INTERFACE_ADC_RX;
	GDMA_InitStruct->GDMA_ReloadSrc  = 1;
	GDMA_InitStruct->GDMA_IsrType        = (BlockType|TransferType|ErrType);
	GDMA_InitStruct->GDMA_ChNum              = GdmaChnl;
	GDMA_InitStruct->GDMA_Index          =  0;

	GDMA_InitStruct->GDMA_BlockSize  =   DataLen;
	GDMA_InitStruct->GDMA_SrcAddr              =   (u32)&(ADC->ADC_DATA_GLOBAL);
	GDMA_InitStruct->GDMA_DstAddr              =   (u32)pDataBuf;

	/* GDMA initialization */
	GDMA_Init(GDMA_InitStruct->GDMA_Index, GDMA_InitStruct->GDMA_ChNum, GDMA_InitStruct);
	GDMA_Cmd(GDMA_InitStruct->GDMA_Index, GDMA_InitStruct->GDMA_ChNum, ENABLE);
	

That calls GDMA_Init() in rtl8721d_gdma_ram.c (in same folder)

I find the typedef for GDMA_InitStruct in rtl8721d_gdma.h (in component/sec/realtek/amebad/fwlib/include)

The typedef does not contain some of the fields you mention, but the ‘trigger source’ appears to be defined through InitStruct.GDMASrcHandshakeInterface which is set to GDMA_HANDSHAKE_INTERFACE_ADC_RX, which is defined as 5

GDMA_SrcMsk does not appear as a member of the structure (or indeed in any files in the SDK).

Furthrmore, ADC_FIFO_READ does not show up as an element of the ADC registers, only ADC_DATA_GLOBAL

I’ve looked in both the ameba-rtos SDK (which doesn’t appear to support the rtl8720DN) and the ameba-rtos-d SDK (which does)

Any further thoughts would be most welcome - thanks in advance,

Chris

PS - I have tried to find proper datasheets for the ADC and the GDMA modules - other embedded environments I work with (e.g. Microchip, TI) have datasheets for each peripheral containing a block diagram, a functional description and details of control registers, giving a description of each field - I’ve not found anything like that for these Realtek components. Are such documents available to us as developers?

PPS - I have found the following table in rtl8721d_gdma.h:

/** @defgroup GDMA0_HS_Interface_definition GDMA HandShake Interface Definition 
  * @{
  */
#define GDMA_HANDSHAKE_INTERFACE_UART0_TX	(0)
#define GDMA_HANDSHAKE_INTERFACE_UART0_RX	(1)
#define GDMA_HANDSHAKE_INTERFACE_UART1_TX	(2)
#define GDMA_HANDSHAKE_INTERFACE_UART1_RX	(3)
#define GDMA_HANDSHAKE_INTERFACE_UART3_TX	(6)
#define GDMA_HANDSHAKE_INTERFACE_UART3_RX	(7)
#define GDMA_HANDSHAKE_INTERFACE_SPI0_TX	(4)
#define GDMA_HANDSHAKE_INTERFACE_SPI0_RX	(5)
#define GDMA_HANDSHAKE_INTERFACE_SPI1_TX	(6)
#define GDMA_HANDSHAKE_INTERFACE_SPI1_RX	(7)
#define GDMA_HANDSHAKE_INTERFACE_I2C0_TX	(2)
#define GDMA_HANDSHAKE_INTERFACE_I2C0_RX	(3)
#define GDMA_HANDSHAKE_INTERFACE_ADC_RX		(5)
#define GDMA_HANDSHAKE_INTERFACE_AUDIO_TX	(10)
#define GDMA_HANDSHAKE_INTERFACE_AUDIO_RX	(11)
#define GDMA_HANDSHAKE_INTERFACE_USI0_TX     (8)
#define GDMA_HANDSHAKE_INTERFACE_USI0_RX     (9)
#define GDMA_HANDSHAKE_INTERFACE_SGPIO_TX   (4)

I notice that several numbers are common - so for example the SPI-Tx trigger sems to have the same number as the ADC_Rx trigger. That must surely be wrong?