Memory to DAC using eDMA (Kinetis K28)

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Memory to DAC using eDMA (Kinetis K28)

1,538 Views
eldarfiring
Contributor III

Hi,

I'm using Kinetis K28-FRDM and SDK 2.3. I have a test application where I want to feed the DAC at 500kHz sampling rate using eDMA and PDB modules.  The data to be converted is a large pre-stored buffer in RAM. So far, I am able to do so only if  I transfer 16 words (size of DAC data registers) at at time, and manually increase the source pointer and setup a new DMA TCD for every 16 words in the eDMA callback (interrupt). But what I would like to achieve is to setup the eDMA, PDB and DAC to convert the entire source buffer without the need handle the eDMA IRQ every 2 x 16 us. It leaves very little time to do anything else. That should be possible or what ? 

If I change this that works : 

/* DAC_DATL_COUNT  = 16 */

EDMA_PrepareTransfer(&m_transferConfig, (void *) (s_dmaBuffer), sizeof(uint16_t), (void *) DAC_DATA_REG_ADDR, sizeof(uint16_t), DAC_DATL_COUNT * sizeof(uint16_t), DAC_DATL_COUNT * sizeof(uint16_t), EDMA_MemoryToMemory );

to :

#define SOURCE_BUFF_SIZE 1024*16

EDMA_PrepareTransfer(&m_transferConfig, (void *) (s_dmaBuffer),sizeof(uint16_t), (void *) DAC_DATA_REG_ADDR, sizeof(uint16_t),DAC_DATL_COUNT * sizeof(uint16_t), SOURCE_BUFF_SIZE * sizeof(uint16_t), EDMA_MemoryToMemory );

...there is no eDMA interrupt and DAC output is 0 v all the time (major loop > 1)

If I change EDMA_MemoryToMemory  to kEDMA_MemoryToPeripheral,  the eDMA interrupts come as expected when the entire source buffer is completed.  The source pointer is automatically increased in-between by eDMA,  but it seems that only the first DAC data register is converted, the rest (15) are all 0v.

 

What did I miss ?

 

Initialization Code : 

void MyGenerator::initDMAMux()
{
 /* Configure DMAMUX */
 DMAMUX_Init(DMAMUX_BASEADDR);
 DMAMUX_SetSource(DMAMUX_BASEADDR, DMA_CHANNEL, DMA_DAC_SOURCE); /* Map ADC source to channel 0 */
 DMAMUX_EnableChannel(DMAMUX_BASEADDR, DMA_CHANNEL);
}

void MyGenerator::initProgrammableDelayBlock()
{

pdb_config_t pdbConfigStruct;
 pdb_dac_trigger_config_t pdbDacTriggerConfigStruct;
 const uint16_t PDB_DAC_INTERVAL_VALUE = 150U; /* delay count (hold time) for each DAC level */

 PDB_GetDefaultConfig (&pdbConfigStruct);
 pdbConfigStruct.prescalerDivider = kPDB_PrescalerDivider1;
 pdbConfigStruct.dividerMultiplicationFactor = kPDB_DividerMultiplicationFactor1;
 pdbConfigStruct.enableContinuousMode = true;
 pdbConfigStruct.triggerInputSource = kPDB_TriggerInput4; /* PIT0 */

PDB_Init(PDB_BASEADDR, &pdbConfigStruct);
#if 0
 PDB_EnableInterrupts(PDB_BASEADDR, kPDB_DelayInterruptEnable);
#endif
 PDB_EnableDMA(PDB_BASEADDR, true);
 PDB_SetModulusValue(PDB_BASEADDR, PDB_MODULUS_VALUE );
 PDB_SetCounterDelayValue(PDB_BASEADDR, PDB_DELAY_VALUE);

/* Set DAC trigger. */
 pdbDacTriggerConfigStruct.enableExternalTriggerInput = false;
 pdbDacTriggerConfigStruct.enableIntervalTrigger = true;
 PDB_SetDACTriggerConfig(PDB_BASEADDR, PDB_DAC_CHANNEL, &pdbDacTriggerConfigStruct);
 PDB_SetDACTriggerIntervalValue(PDB_BASEADDR, PDB_DAC_CHANNEL, PDB_DAC_INTERVAL_VALUE);

/* Load PDB values. */
 PDB_DoLoadValues(PDB_BASEADDR);

}

void MyGenerator::initEDMA()
{
 edma_config_t userConfig;

EDMA_GetDefaultConfig(&userConfig);
 EDMA_Init(DMA0, &userConfig);
 EDMA_CreateHandle(&m_EDMA_Handle, DMA0, DMA_CHANNEL);

EDMA_SetCallback(&m_EDMA_Handle, Edma_Callback, this);

EDMA_PrepareTransfer(&m_transferConfig, (void *) (s_dmaBuffer),
 sizeof(uint16_t), (void *) DAC_DATA_REG_ADDR, sizeof(uint16_t),
 DAC_DATL_COUNT * sizeof(uint16_t),
 SINE_BUFF_SIZE * sizeof(uint16_t), kEDMA_MemoryToPeripheral );


 EDMA_SubmitTransfer(&m_EDMA_Handle, &m_transferConfig);
 /* Enable interrupt when transfer is done. */
 EDMA_EnableChannelInterrupts(DMA0, DMA_CHANNEL,kEDMA_MajorInterruptEnable);

#if defined(FSL_FEATURE_EDMA_ASYNCHRO_REQUEST_CHANNEL_COUNT) && FSL_FEATURE_EDMA_ASYNCHRO_REQUEST_CHANNEL_COUNT
 /* Enable async DMA request. */
 EDMA_EnableAsyncRequest(DMA0, DMA_CHANNEL, true);

#endif /* FSL_FEATURE_EDMA_ASYNCHRO_REQUEST_CHANNEL_COUNT */

/* Enable transfer. */
 EDMA_StartTransfer(&m_EDMA_Handle);
}

void MyGenerator::initDAC()
{
 dac_config_t dacConfigStruct;
 dac_buffer_config_t dacBufferConfigStruct;

DAC_GetDefaultConfig(&dacConfigStruct);
 DAC_Init(DAC_BASEADDR, &dacConfigStruct);
 DAC_Enable(DAC_BASEADDR, true); /* Enable output. */

/* Configure the DAC buffer. */
 DAC_EnableBuffer(DAC_BASEADDR, true);
 DAC_GetDefaultBufferConfig(&dacBufferConfigStruct);
 dacBufferConfigStruct.triggerMode = kDAC_BufferTriggerByHardwareMode;


 DAC_SetBufferConfig(DAC_BASEADDR, &dacBufferConfigStruct);
 DAC_SetBufferReadPointer(DAC_BASEADDR, 0U); /* Make sure the read pointer to the start. */

/* Enable DMA. */

 DAC_EnableBufferInterrupts(DAC_BASEADDR,kDAC_BufferReadPointerBottomInterruptEnable);
 DAC_EnableBufferDMA(DAC_BASEADDR, true);
}


void MyGenerator::eDMAcallback(edma_handle_t *handle, bool transferDone, uint32_t tcds)
{

/* Clear Edma interrupt flag. */
 EDMA_ClearChannelStatusFlags(DMA0, DMA_CHANNEL, kEDMA_InterruptFlag);

EDMA_PrepareTransfer(&m_transferConfig, (void *) (s_dmaBuffer),
 sizeof(uint16_t), (void *) DAC_DATA_REG_ADDR, sizeof(uint16_t),
 DAC_DATL_COUNT * sizeof(uint16_t),
 SINE_BUFF_SIZE * sizeof(uint16_t), kEDMA_MemoryToPeripheral );

EDMA_SetTransferConfig(DMA0, DMA_CHANNEL, &m_transferConfig, NULL);
 /* Enable transfer. */
 EDMA_StartTransfer(&m_EDMA_Handle);
}


static void Edma_Callback(edma_handle_t *handle, void *userData, bool transferDone, uint32_t tcds)
{
 GPIO_WritePinOutput(BOARD_INITPINS_SIGCodeTime_GPIO, BOARD_INITPINS_SIGCodeTime_GPIO_PIN, 1U);
 (static_cast<MyGenerator*>(userData))->eDMAcallback(handle, transferDone,tcds);
 GPIO_WritePinOutput(BOARD_INITPINS_SIGCodeTime_GPIO, BOARD_INITPINS_SIGCodeTime_GPIO_PIN, 0U);
}
Labels (1)
Tags (2)
0 Kudos
5 Replies

925 Views
mjbcswitzerland
Specialist V

Hi Eldar

Below I have copied working code from the uTasker project which does PDB triggered DMA transfer from a buffer to the DAC (according to references in http://www.utasker.com/docs/uTasker/uTaskerADC.pdf):

x is DMA channel to be used

unsigned short usADC_buffer[y];                    // buffer with the samples to be sent

DMAx_TCD_SOFF = 2;                                 // source increment (buffer - short words)
DMAx_TCD_DOFF = 0;                                 // destination not incremented
DMAx_TCD_DLASTSGA = 0;                             // no destination displacement on transmit buffer completion
DMAx_TCD_SLAST = (-(signed long)(sizeof(usADC_buffer))); // when the buffer has been transmitted set the destination back to the start of it
DMAx_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_16 | DMA_TCD_ATTR_SSIZE_16); // transfer sizes words
DMAx_TCD_SADDR = (unsigned long)usADC_buffer;      // source buffer
DMAx_TCD_NBYTES_ML = 2;                            // each request starts a single transfer of this size
DMAx_TCD_CSR = 0;                                  // free-running mode without any interrupt
DMAx_TCD_DADDR = (unsigned long)DAC0_BASE_ADD;     // destination is the DAC data register
DMAx_TCD_BITER_ELINK = DMAx_TCD_CITER_ELINK = (signed short)(sizeof(usADC_buffer)/2); // the number of service requests to be performed each cycle
*(unsigned char *)(DMAMUX0_BLOCK + x) = (DMAMUX0_CHCFG_SOURCE_PDB + DMAMUX_CHCFG_ENBL); // DMA trigger source is the PDB
DMA_ERQ |= (1 << x);                               // enable DMA channel operation

You should be able to use this to work out where the problem is in the code that you presently have.
It will continuously send the buffer content (as circular buffer) at the speed set up in the PDB without any CPU intervention. It also continues if the debugger pauses the processor.

Regards

Mark

925 Views
eldarfiring
Contributor III

Thanks a lot your reply Mark !

I did the same setup as you suggested, but with same result as before. So I now think the DMA/TCD part is OK.

If I understand your setup correctly, you are not using DAC data register buffer (DACBFEN=0) and the converted data is always the first word?  If I do that, I get no DAC output what so ever.  Can I ask you what setup you used for DAC / PDB ?

If I enable DAC buffers (which I believe is wrong with current TCD setup) I have output like the picture (it's suppose to be a continuous line, but it raises for DAC data register 0, and drops down for DAC data registers 1-15 I guess). That makes sense.

with_dac_buffer.PNG

For the PDB I am using DAC interval trigger enabled (external disabled).

DAC setup : 


DAC0->C0 = 0xc1; /* enable DAC , vref_2 + buttom flag interrupt enabled. With disabled bottom flag (0xc0) -> No DAC output */
DAC0->C1 = 0x81; /* DNA and DAC buffer enabled.  With disabled DAC buffer (0x80)  -> No DAC output */
DAC0->C2 = 0x0f;
DAC0->SR = 0x0;

DMA TCD setup : 

/* Clear Edma interrupt flag. */
DMA0->CINT = DMA_CHANNEL;
/* Setup transfer */

edma_tcd_t *tcd = (edma_tcd_t *) &DMA0->TCD[DMA_CHANNEL];

tcd->SOFF = sizeof(uint16_t); // source increment (buffer - short words)
tcd->DOFF = 0U; // destination not incremented
tcd->DLAST_SGA = 0; // no destination displacement on transmit buffer completion
tcd->SLAST = (-(signed long)(sizeof(s_dmaBuffer))); // when the buffer has been transmitted set the destination back to the start of it
tcd->ATTR = DMA_ATTR_SSIZE(kEDMA_TransferSize2Bytes) | DMA_ATTR_DSIZE(kEDMA_TransferSize2Bytes); // transfer sizes words
tcd->SADDR = (uint32_t)(s_dmaBuffer); // source buffer
tcd->NBYTES = sizeof(uint16_t); // number of bytes minor loop;
tcd->CSR = 0; // free-running mode without any interrupt
tcd->DADDR = DAC_DATA_REG_ADDR; // destination is the DAC data register
tcd->BITER = (signed short)(sizeof(s_dmaBuffer)/2); // the number of service requests to be performed each cycle
tcd->CITER = (signed short)(sizeof(s_dmaBuffer)/2); // the number of service requests to be performed each cycle

/* Enable transfer. */
DMA0->SERQ = DMA_SERQ_SERQ(DMA_CHANNEL);

0 Kudos

925 Views
mjbcswitzerland
Specialist V

Hi Eldar

I use the DAC in non-buffered, software triggered mode.

DAC0_C0 = 0xe0;
DAC0_C1 = 0;

//DAC0_C2 =is default 0x0f;

Each PDB trigger causes a write to the first DAC output as if it were a SW write.

I don't think there are any advantages of buffered modes.

Also I don't think that you want to trigger DMA from the DAC since it will then just copy as fast as it can, and if you choose PDB as DMA channel trigger source it probably is not doing anything anyway.

I don't use the PDB to trigger the DAC, but instead PDB->DMA->DATA0 write in SW mode.

My full configuration uses the uTasker HAL (where I haven't shown any ADC trigger settings, although they are used also in some configurations - see document for some diagrams) [the interface is also Kineti parts independent so will run also on KL parts with DMA and PDB. Since it is not triggering the DAC by PDB it is also compatible with Kinetis parts that have no PDB if a PIT is used to trigger the DMA channel instead. Most KL parts do however need their RAM buffer modulo 2 aligned to be fully compatible due to a less powerful DMA controller type]:

DAC_SETUP dac_setup;
dac_setup.int_type = DAC_INTERRUPT;
dac_setup.int_handler = 0;                                       // no interrupt used
dac_setup.int_priority = 15;                                     // lowest priority (not used in this case)
dac_setup.dac_mode = (DAC_CONFIGURE | DAC_REF_VDDA | DAC_NON_BUFFERED_MODE | DAC_FULL_BUFFER_DMA | DAC_ENABLE | DAC_BUFFER_DMA_START); // configure the DAC to use VDDA as reference voltage in non-buffered mode (using DMA)
dac_setup.int_dac_controller = 0;                                // DAC 0
dac_setup.ucDmaChannel = 7;                                      // use DMA channel 7
dac_setup.usDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_PDB;
dac_setup.ptrDAC_Buffer = (unsigned short *)sADC_buffer;         // DAC transmit buffer to be used
dac_setup.ulDAC_buffer_length = sizeof(sADC_buffer);             // physical length of the buffer
fnConfigureInterrupt((void *)&dac_setup);                        // configure DAC


PDB_SETUP pdb_setup;                                             // interrupt configuration parameters
pdb_setup.int_type = PDB_INTERRUPT;
pdb_setup.int_handler = 0;                                       // no interrupt
pdb_setup.int_priority = PRIORITY_PDB;    
pdb_setup.pdb_mode = PDB_PERIODIC_DMA;                           // use DMA to trigger DAC data writes
pdb_setup.prescaler = (PDB_PRESCALER_4 | PDB_MUL_1);             // pre-scaler values of 1, 2, 4, 8, 16, 32, 64 and 128 are possible (with multipliers of 1, 10, 20 or 40)
pdb_setup.period = PDB_FREQUENCY(4, 1, 8000);                    // frequency of PDB cycle is 8kHz
pdb_setup.int_match = 0;                                         // PDB interrupt/DMA at the start of the period
pdb_setup.dac0_delay_0 = 0;
pdb_setup.pdb_trigger = PDB_TRIGGER_SW;                          // triggered by software (started immediately)
fnConfigureInterrupt((void *)&pdb_setup);                        // configure PDB interrupt

Regards

Mark

925 Views
eldarfiring
Contributor III

Hi Mark,

Thank you for your reply! I have now figured it out. As you indicated, I could not use DAC to trigger the DMA (also since DAC buffer mode is disabled). 

Instead I use PIT0 :

#define DMA_CHANNEL 0U

/* Configure DMAMUX */
DMAMUX_Init(DMAMUX_BASEADDR);
DMAMUX_SetSource(DMAMUX_BASEADDR, DMA_CHANNEL, 60 ); /* Map to always enabled source */
DMAMUX_EnablePeriodTrigger(DMAMUX_BASEADDR,DMA_CHANNEL); /* use periodic trigger mode - DMA Channel 0 - PIT 0 */
DMAMUX_EnableChannel(DMAMUX_BASEADDR, DMA_CHANNEL);

I'm now transferring the memory buffer to DAC at high speed (~1MHz), and it works fine. The CPU has plenty of time to do other stuff.

Thanks again!

Best Regards,

Eldar

0 Kudos

925 Views
mjbcswitzerland
Specialist V

Hi Eldar

Yes, doing this doesn't require the PDB in particular since it can be done with any timer with DMA trigger capability. The PITs are a bit limited to which DMA channels they can actually trigger on but otherwise a straight-forward method.

Regards

Mark

0 Kudos