Writing to the VDAC (DAC) COMBDATA Register with LDMA (DMA)
12/351/2018 | 10:44 PM
I'm trying to output two sine waves to the two VDAC channels on the EFR32BG13 by reading these values from a table and writing them to the VDAC_COMBDATA register with LDMA (later I'm going to output data in the same fashion as it is received via BLE).
I setup two 16-bit buffers with the data and initialize the LDMA as follows:
#define BUFFER_SIZE 4
uint16_t pingBuffer[BUFFER_SIZE];
uint16_t pongBuffer[BUFFER_SIZE];
// Ping-pong transfer count
uint32_t ppCount;
// Descriptor linked list for LDMA transfers
LDMA_Descriptor_t descLink[2];
void initLdmaPingPong(void)
{
CMU_ClockEnable(cmuClock_LDMA, true);
// Basic LDMA configuration
LDMA_Init_t ldmaInit = LDMA_INIT_DEFAULT;
LDMA_Init(&ldmaInit);
// Configure LDMA transfer type
cfg = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_VDAC0_CH0);
// Use LINK descriptor macros for ping-pong transfers
LDMA_Descriptor_t xfer[] =
{
LDMA_DESCRIPTOR_LINKREL_M2P_BYTE(
&pingBuffer, // source
&(VDAC0->COMBDATA), // destination
BUFFER_SIZE, // data transfer size
1), // link relative offset (links to next)
LDMA_DESCRIPTOR_LINKREL_M2P_BYTE(
&pongBuffer, // source
&(VDAC0->COMBDATA), // destination
BUFFER_SIZE, // data transfer size
-1) // link relative offset(links to previous)
};
descLink[0] = xfer[0];
descLink[1] = xfer[1];
descLink[0].xfer.ignoreSrec = true; // ignores single requests to reduce energy usage
descLink[0].xfer.size = ldmaCtrlSizeHalf; // transfers half words instead of bytes
descLink[1].xfer.ignoreSrec = true; // ignores single requests to reduce energy usage
descLink[1].xfer.size = ldmaCtrlSizeHalf; // transfers half words instead of bytes
LDMA_IntEnable(LDMA_IF_DONE_DEFAULT);
// Start Ping-Pong transfer
ppCount = 0;
LDMA_StartTransfer(LDMA_CHANNEL, (void*)&cfg, (void*)&descLink);
NVIC_ClearPendingIRQ(LDMA_IRQn);
NVIC_EnableIRQ(LDMA_IRQn);
}
A TIMER triggers the VDAC output via the PRS, and I fill the buffers accordingly when the LDMA triggers its DONE interrupt.
The problem I have is that I only get the correct output on channel 0. If I transfer longwords using the LDMA, then I get a signal from both outputs, but the output is not correct, e.g. every fourth ping-pong transfer fails, and I get 0V output for this period.
What is the correct structure to write to the COMBDATA register? How do I manage to accomplish this with a ping-pong buffer?
Our user's setup of the LDMA here is mostly correct except for one major flaw:
descLink1[0].xfer.ignoreSrec = true; // ignores single requests to reduce energy usage
descLink1[0].xfer.size = ldmaCtrlSizeHalf; // transfers half words instead of bytes
descLink1[1].xfer.ignoreSrec = true; // ignores single requests to reduce energy usage
descLink1[1].xfer.size = ldmaCtrlSizeHalf; // transfers half words instead of bytes
While this looks correct, in theory, the problem is with the attempt to use halfwords for the transfer size. While there is no issue itself with the LDMA performing a halfword read or write, it cannot do so when the target register is a memory-mapped peripheral. All write accesses to peripheral registers must be longword writes. In the case of the COMBDATA register, this actually makes perfect sense when you consider that its sole purpose is to update both VDAC output channels simultaneously.
So, in the customer's case, the solution is fairly simple. Instead of maintaining two ping-pong buffer arrays of type uint16_t, the data to be buffered needs to be stored to permit the two 16-bit output values to be read as 32 bits that are written together to the COMBDATA register. This could be done by simple ordering the data so that the 0th entry in the buffer is the first channel 1 output value (VDAC_COMBDATA_CH1DATA), the 1st entry is the first channel 0 output value (VDAC_COMBDATA_CH0DATA), the 2nd entry is the second channel 1 output value, the 3rd entry is the second channel 0 output value, etc. The same thing could be done with a typedef struct that has as its two components variables for the channel 1 and channel 0 output values.
Having made this change, the LDMA descriptors would be modified for 32-bit transfers:
descLink1[0] = xfer[0];
descLink1[1] = xfer[1];
descLink1[0].xfer.ignoreSrec = true; // ignores single requests to reduce energy usage
descLink1[0].xfer.size = ldmaCtrlSizeWord; // transfers words instead of bytes
descLink1[1].xfer.ignoreSrec = true; // ignores single requests to reduce energy usage
descLink1[1].xfer.size = ldmaCtrlSizeWord; // transfers words instead of bytes
If there's a need to generate a waveform on one output, then have it remain unchanged while the other channel is updated, this could be done with the original code above with the proviso that the destination for each descriptor would be &(VDAC0->CH0DATA) or &(VDAC0->CH1DATA) as needed.
NOTE: While this discussion involves the VDAC on Series 1 EFM32 and EFR32 devices, it applies identically to the DAC on Series 0 EFM32 and EZR32 devices. The requirement for aligned longword accesses to peripheral registers is universal across all EFM32, EFR32, and EZR32 devices.
Tiny Gecko
Gecko
Leopard Gecko
32-bit MCUs
Jade Gecko
Giant Gecko
Pearl Gecko
Blue Gecko Bluetooth Low Energy Modules
Giant Gecko S1
Flex Gecko
Tiny Gecko S1
Blue Gecko Bluetooth Low Energy SoCs
Knowledge Base Articles
Mighty Gecko SoCs
Wonder Gecko
Hi John, thanks for clearing this up for other people!
But, If I recall right one should adapt the transfer size of the ldma to meet the new requirement (transfering words), so the transfer size has to be BUFFER_SIZE/2, as the buffer consists of uint16 values.
Writing to the VDAC (DAC) COMBDATA Register with LDMA (DMA)
I'm trying to output two sine waves to the two VDAC channels on the EFR32BG13 by reading these values from a table and writing them to the VDAC_COMBDATA register with LDMA (later I'm going to output data in the same fashion as it is received via BLE).
I setup two 16-bit buffers with the data and initialize the LDMA as follows:
A TIMER triggers the VDAC output via the PRS, and I fill the buffers accordingly when the LDMA triggers its DONE interrupt.
The problem I have is that I only get the correct output on channel 0. If I transfer longwords using the LDMA, then I get a signal from both outputs, but the output is not correct, e.g. every fourth ping-pong transfer fails, and I get 0V output for this period.
What is the correct structure to write to the COMBDATA register? How do I manage to accomplish this with a ping-pong buffer?
Our user's setup of the LDMA here is mostly correct except for one major flaw:
While this looks correct, in theory, the problem is with the attempt to use halfwords for the transfer size. While there is no issue itself with the LDMA performing a halfword read or write, it cannot do so when the target register is a memory-mapped peripheral. All write accesses to peripheral registers must be longword writes. In the case of the COMBDATA register, this actually makes perfect sense when you consider that its sole purpose is to update both VDAC output channels simultaneously.
So, in the customer's case, the solution is fairly simple. Instead of maintaining two ping-pong buffer arrays of type uint16_t, the data to be buffered needs to be stored to permit the two 16-bit output values to be read as 32 bits that are written together to the COMBDATA register. This could be done by simple ordering the data so that the 0th entry in the buffer is the first channel 1 output value (VDAC_COMBDATA_CH1DATA), the 1st entry is the first channel 0 output value (VDAC_COMBDATA_CH0DATA), the 2nd entry is the second channel 1 output value, the 3rd entry is the second channel 0 output value, etc. The same thing could be done with a typedef struct that has as its two components variables for the channel 1 and channel 0 output values.
Having made this change, the LDMA descriptors would be modified for 32-bit transfers:
If there's a need to generate a waveform on one output, then have it remain unchanged while the other channel is updated, this could be done with the original code above with the proviso that the destination for each descriptor would be &(VDAC0->CH0DATA) or &(VDAC0->CH1DATA) as needed.
NOTE: While this discussion involves the VDAC on Series 1 EFM32 and EFR32 devices, it applies identically to the DAC on Series 0 EFM32 and EZR32 devices. The requirement for aligned longword accesses to peripheral registers is universal across all EFM32, EFR32, and EZR32 devices.
Hi John, thanks for clearing this up for other people!
But, If I recall right one should adapt the transfer size of the ldma to meet the new requirement (transfering words), so the transfer size has to be BUFFER_SIZE/2, as the buffer consists of uint16 values.
Kind regards,
Andres.