Hello,
On the FRDM-KL25Z, I'm attempting to get DMA to transfer data to DAC, however, it appears to only be transferring only every other value in the source array (second, fourth, sixth etc).
I've gone over the settings and manual countless times and cannot explain this behaviour.
What I'm looking to achieve is:
However, what I'm observing is that only every second integer in the array is transferred, the others are just skipped (no errors etc).
How I read the manual is that:
With these settings and an 8 value array of 16-bit integers, only four PIT trigger events occur, on each event the BCR is decremented by 4 bytes and only the even numbered values in the array ever touch the DAC.
I can't reconcile the behaviour I'm seeing with the configuration I've provided.
Please see the source code below, this example just attempts varying levels on the DAC analog out pin (other boiler plate code is assumed), the odd numbered sample values are never transferred (ie. 1000), regardless of value:
#define SAMPLES_SIZE (8)
uint16_t SAMPLES[SAMPLES_SIZE] = { 1000, 3000, 1000, 3000, 1000, 3000, 1000, 3000 };
#define LED1 7
const uint32_t LED_DMA = (1UL << LED1);
// These are just debug counter values
volatile uint32_t dma_count = 0;
volatile uint32_t pit_count = 0;
void config_dma(void);
void config_dma(void)
{
/* Set source and destination for DMA transfer */
DMA0->DMA[0].SAR = DMA_SAR_SAR((uint32_t) SAMPLES);
DMA0->DMA[0].DAR = DMA_DAR_DAR((uint32_t) (&(DAC0->DAT[0])));
/* Set byte count */
DMA0->DMA[0].DSR_BCR = DMA_DSR_BCR_BCR(SAMPLES_SIZE*2);
/* Clear done flag */
DMA0->DMA[0].DSR_BCR &= ~DMA_DSR_BCR_DONE_MASK;
/* Enable DMA muxer */
DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_ENBL_MASK;
}
void PIT_IRQHandler(void)
{
pit_count++; // Debug
PRINTF("p:%u d:%u b:%u\n",pit_count,dma_count, DMA0->DMA[0].DSR_BCR & DMA_DSR_BCR_BCR_MASK); // Debug
PIT->CHANNEL[0].TFLG |= PIT_TFLG_TIF(0);
}
void DMA0_IRQHandler(void)
{
DMA0->DMA[0].DSR_BCR |= DMA_DSR_BCR_DONE_MASK;
config_dma();
dma_count++; // Debug
}
int main(void) {
// Board initialising code omitted
// Configure clock for PORTE, DMA Multiplexer, DMA, DAC & PIT
SIM->SCGC5 |= SIM_SCGC5_PORTE(1);
SIM->SCGC6 |= SIM_SCGC6_DMAMUX(1) | SIM_SCGC6_PIT(1) | SIM_SCGC6_DAC0(1);
SIM->SCGC7 |= SIM_SCGC7_DMA(1);
/* Set Port E pin 30 to analog */
PORTE->PCR[30] &= ~PORT_PCR_MUX_MASK;
PORTE->PCR[30] |= PORT_PCR_MUX(0);
/* Disable buffer mode */
DAC0->C1 = 0;
DAC0->C2 = 0;
/* Enable DAC with VDDA as ref voltage */
DAC0->C0 = DAC_C0_DACEN_MASK | DAC_C0_DACRFS_MASK;
// Disable DMAMUX channel and PIT timer
DMAMUX0->CHCFG[0] = 0;
PIT->CHANNEL[0].TCTRL &= ~PIT_TCTRL_TEN(1);
// Configure DMA Source and Destination addresses
DMA0->DMA[0].DCR = DMA_DCR_ERQ_MASK | DMA_DCR_CS_MASK | DMA_DCR_EINT_MASK
| DMA_DCR_SINC_MASK
| DMA_DCR_SSIZE(2)
| DMA_DCR_DSIZE(2);
// Configure PIT - runs off bus clock 24mhz!
PIT->CHANNEL[0].LDVAL = (SystemCoreClock/2) - 1; // one second interval
PIT->CHANNEL[0].TCTRL |= PIT_TCTRL_TEN(1) | PIT_TCTRL_TIE(1);
PIT->MCR = 0; /* Module enabled, don't freeze in debug */
// Enable DMAMUX0 Channel 0, always on and triggered
DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_TRIG(1);
/* Set up interrupt controller */
NVIC_SetPriority(PIT_IRQn, 2);
NVIC_ClearPendingIRQ(PIT_IRQn);
NVIC_EnableIRQ(PIT_IRQn);
NVIC_SetPriority(DMA0_IRQn, 0);
NVIC_ClearPendingIRQ(DMA0_IRQn);
NVIC_EnableIRQ(DMA0_IRQn);
DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_SOURCE(60);
config_dma();
As always, any help greatly appreciated.
Regards,
Paul Swanson
Solved! Go to Solution.
Hi Paul
Generally (although not your issue in this case):
Note that the SDK reference code uses things like
DMA_DCR_DSIZE(2)
which is an incredibly error-prone method.
You are assuming that DMA_DCR_DSIZE() is set to the correct bits in the register (although probably always correct) and then you need to study the register to find out what 2 means (in the case of this register 1 means "byte", 2 means "half word" and 0 means "long word") but it is up to the programmer to know these details and possibly get it wrong the first time and lose half a day in the process. I have no idea why this technique is used since it would be so simple to make it fool proof with three defines instead.
I didn't originally see the problem so I checked with the uTasker PIT->DAC DMA reference (after selecting the KL25):
unsigned long ulDMA_rules = (DMA_DIRECTION_OUTPUT | DMA_HALF_WORDS); // DMA transfer is from a buffer to a fixed address and each transfer is a half-word in size ptrDAC_regs->DAC_C1 = 0; if ((ptrDAC_settings->dac_mode & DAC_FULL_BUFFER_DMA_AUTO_REPEAT) != 0) { ulDMA_rules |= DMA_AUTOREPEAT; } if ((ptrDAC_settings->dac_mode & DAC_HALF_BUFFER_DMA) != 0) { ulDMA_rules |= DMA_HALF_BUFFER_INTERRUPT; } fnConfigDMA_buffer(ptrDAC_settings->ucDmaChannel, ptrDAC_settings->usDmaTriggerSource, ptrDAC_settings->ulDAC_buffer_length, ptrDAC_settings->ptrDAC_Buffer, ptrDAC_regs, ulDMA_rules, ptrDAC_settings->int_handler, ptrDAC_settings->int_priority); // source is the DAC buffer and destination is the DAC data[0] register if ((ptrDAC_settings->dac_mode & DAC_BUFFER_DMA_START) != 0) { fnDMA_BufferReset(ptrDAC_settings->ucDmaChannel, DMA_BUFFER_START); // start DMA operation }
The routine fnConfigDMA_buffer() does the detailed work (and adapts itself to the type of processor and DMA controller in question).
Its operation on the KL25 is (when copying an array of half words to the DAC output - single pass without auto-repeat, with interrupt at end) is - I have also written the hex values that it writes:
ptrDMA->DMA_DSR_BCR = DMA_DSR_BCR_DONE; // clear the DONE flag and clear errors etc. ptrDMA->DMA_DCR = (DMA_DCR_DSIZE_16 | DMA_DCR_SSIZE_16 | DMA_DCR_DMOD_OFF | DMA_DCR_SMOD_OFF); // transfer size half-words [0x00024000] ptrDMA->DMA_DCR |= (DMA_DCR_SINC); // transfers with increment only on source [0x00064000] ptrDMA->DMA_SAR = (unsigned long)ptrBufSource; // set source buffer ptrDMA->DMA_DAR = (unsigned long)ptrBufDest; // set destination buffer ptrDMA->DMA_DSR_BCR = ulBufLength; // set transfer count (don't set DMA_DSR_BCR_DONE at the same time otherwise BCR is reset) fnEnterInterrupt((irq_DMA0_ID + ucDMA_channel), int_priority, (void (*)(void))_DMA_Interrupt[ucDMA_channel]); // enter DMA interrupt handler on buffer completion ptrDMA->DMA_DCR |= (DMA_DCR_EINT | DMA_DCR_D_REQ); // interrupt when the transmit buffer is empty and stop operation after full buffer has been transferred [0x80640080] POWER_UP_ATOMIC(6, DMAMUX0); // enable DMA multiplexer 0 *(unsigned char *)(DMAMUX0_BLOCK + ucDMA_channel) = (unsigned char)(usDmaTriggerSource | DMAMUX_CHCFG_ENBL); // connect trigger to DMA channel ptrDMA->DMA_DCR |= (DMA_DCR_CS | DMA_DCR_EADREQ); // enable peripheral request - single cycle for each request (asynchronous requests enabled in stop mode) [0xa0e40080]
but then it gave me this exception:
#if defined ERRATA_ID_5746 if ((ptrDMA->DMA_DCR & DMA_DCR_CS) != 0) { _EXCEPTION("PIT0 trigger generates two data transfers when in cycle-steal mode!!"); } #endif
This is enabled since the KL25 has the mask 2N97F (unless there is a newer one possible) and therefore the errata
#define ERRATA_ID_5746 // PIT: when using the PIT to trigger DMA transfers using cycle steal mode, two data transfers per request are generated
This is the errata description and proposed workarounds:
e5746: PIT: When using the PIT to trigger DMA transfers using cycle steal mode, two
data transfers per request are generated
Errata type: Errata
Description: If the PIT is used to trigger DMA transfers using cycle steal mode, DMA_DCRn[CS] = 1, each
transfer request will cause the source data to be written twice. The data will be written first to
the desired destination address and then a second time to the destination address + 1. The
destination address pointer increments by 2 for each transfer request.
Workaround: It is recommended that the PIT not be used for triggering DMA transfers and the low power
timer (LPTMR) be used instead.
If it is required to use the PIT to trigger DMA transfers, the destination address must be in RAM
and the buffer size must be twice the amount of data being transferred. Software must then
skip every second entry in the destination buffer.
So it turns out that you are probably not doing anything wrong but are being bitten by this chip problem. It looks like you need to use a different DMA trigger source to workaround it in your case. uTasker users are immediately informed of such errata when such use is attempted, which can save quite a large amount of lost time when one doesn't think about checking the erratas.....
Regards
Mark
Complete Kinetis solutions for professional needs, training and support: http://www.utasker.com/kinetis.html
Kinetis KL25:
- http://www.utasker.com/kinetis/FRDM-KL25Z.html
- http://www.utasker.com/kinetis/TWR-KL25Z48M.html
uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market
Request Free emergency remote desk-top consulting at http://www.utasker.com/services.html
Open Source version at https://github.com/uTasker/uTasker-Kinetis
Paul
Try doing the copy to a memory array so that you can verify what is actually transferred (this will exclude DAC effects and verify the DMA setup).
Regards
Mark
Hi Paul
Generally (although not your issue in this case):
Note that the SDK reference code uses things like
DMA_DCR_DSIZE(2)
which is an incredibly error-prone method.
You are assuming that DMA_DCR_DSIZE() is set to the correct bits in the register (although probably always correct) and then you need to study the register to find out what 2 means (in the case of this register 1 means "byte", 2 means "half word" and 0 means "long word") but it is up to the programmer to know these details and possibly get it wrong the first time and lose half a day in the process. I have no idea why this technique is used since it would be so simple to make it fool proof with three defines instead.
I didn't originally see the problem so I checked with the uTasker PIT->DAC DMA reference (after selecting the KL25):
unsigned long ulDMA_rules = (DMA_DIRECTION_OUTPUT | DMA_HALF_WORDS); // DMA transfer is from a buffer to a fixed address and each transfer is a half-word in size ptrDAC_regs->DAC_C1 = 0; if ((ptrDAC_settings->dac_mode & DAC_FULL_BUFFER_DMA_AUTO_REPEAT) != 0) { ulDMA_rules |= DMA_AUTOREPEAT; } if ((ptrDAC_settings->dac_mode & DAC_HALF_BUFFER_DMA) != 0) { ulDMA_rules |= DMA_HALF_BUFFER_INTERRUPT; } fnConfigDMA_buffer(ptrDAC_settings->ucDmaChannel, ptrDAC_settings->usDmaTriggerSource, ptrDAC_settings->ulDAC_buffer_length, ptrDAC_settings->ptrDAC_Buffer, ptrDAC_regs, ulDMA_rules, ptrDAC_settings->int_handler, ptrDAC_settings->int_priority); // source is the DAC buffer and destination is the DAC data[0] register if ((ptrDAC_settings->dac_mode & DAC_BUFFER_DMA_START) != 0) { fnDMA_BufferReset(ptrDAC_settings->ucDmaChannel, DMA_BUFFER_START); // start DMA operation }
The routine fnConfigDMA_buffer() does the detailed work (and adapts itself to the type of processor and DMA controller in question).
Its operation on the KL25 is (when copying an array of half words to the DAC output - single pass without auto-repeat, with interrupt at end) is - I have also written the hex values that it writes:
ptrDMA->DMA_DSR_BCR = DMA_DSR_BCR_DONE; // clear the DONE flag and clear errors etc. ptrDMA->DMA_DCR = (DMA_DCR_DSIZE_16 | DMA_DCR_SSIZE_16 | DMA_DCR_DMOD_OFF | DMA_DCR_SMOD_OFF); // transfer size half-words [0x00024000] ptrDMA->DMA_DCR |= (DMA_DCR_SINC); // transfers with increment only on source [0x00064000] ptrDMA->DMA_SAR = (unsigned long)ptrBufSource; // set source buffer ptrDMA->DMA_DAR = (unsigned long)ptrBufDest; // set destination buffer ptrDMA->DMA_DSR_BCR = ulBufLength; // set transfer count (don't set DMA_DSR_BCR_DONE at the same time otherwise BCR is reset) fnEnterInterrupt((irq_DMA0_ID + ucDMA_channel), int_priority, (void (*)(void))_DMA_Interrupt[ucDMA_channel]); // enter DMA interrupt handler on buffer completion ptrDMA->DMA_DCR |= (DMA_DCR_EINT | DMA_DCR_D_REQ); // interrupt when the transmit buffer is empty and stop operation after full buffer has been transferred [0x80640080] POWER_UP_ATOMIC(6, DMAMUX0); // enable DMA multiplexer 0 *(unsigned char *)(DMAMUX0_BLOCK + ucDMA_channel) = (unsigned char)(usDmaTriggerSource | DMAMUX_CHCFG_ENBL); // connect trigger to DMA channel ptrDMA->DMA_DCR |= (DMA_DCR_CS | DMA_DCR_EADREQ); // enable peripheral request - single cycle for each request (asynchronous requests enabled in stop mode) [0xa0e40080]
but then it gave me this exception:
#if defined ERRATA_ID_5746 if ((ptrDMA->DMA_DCR & DMA_DCR_CS) != 0) { _EXCEPTION("PIT0 trigger generates two data transfers when in cycle-steal mode!!"); } #endif
This is enabled since the KL25 has the mask 2N97F (unless there is a newer one possible) and therefore the errata
#define ERRATA_ID_5746 // PIT: when using the PIT to trigger DMA transfers using cycle steal mode, two data transfers per request are generated
This is the errata description and proposed workarounds:
e5746: PIT: When using the PIT to trigger DMA transfers using cycle steal mode, two
data transfers per request are generated
Errata type: Errata
Description: If the PIT is used to trigger DMA transfers using cycle steal mode, DMA_DCRn[CS] = 1, each
transfer request will cause the source data to be written twice. The data will be written first to
the desired destination address and then a second time to the destination address + 1. The
destination address pointer increments by 2 for each transfer request.
Workaround: It is recommended that the PIT not be used for triggering DMA transfers and the low power
timer (LPTMR) be used instead.
If it is required to use the PIT to trigger DMA transfers, the destination address must be in RAM
and the buffer size must be twice the amount of data being transferred. Software must then
skip every second entry in the destination buffer.
So it turns out that you are probably not doing anything wrong but are being bitten by this chip problem. It looks like you need to use a different DMA trigger source to workaround it in your case. uTasker users are immediately informed of such errata when such use is attempted, which can save quite a large amount of lost time when one doesn't think about checking the erratas.....
Regards
Mark
Complete Kinetis solutions for professional needs, training and support: http://www.utasker.com/kinetis.html
Kinetis KL25:
- http://www.utasker.com/kinetis/FRDM-KL25Z.html
- http://www.utasker.com/kinetis/TWR-KL25Z48M.html
uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market
Request Free emergency remote desk-top consulting at http://www.utasker.com/services.html
Open Source version at https://github.com/uTasker/uTasker-Kinetis
Wow, I wasn't even daring to suspect a chip issue. Also, fair point about uTasker, I will definitely give it a look soon.
Thanks so much for all your time on this.
Paul Swanson
Good thought. So I implemented a simple array copy and it shows the previously outlined behaviour of only copying every second value from the array.
#define SAMPLES_SIZE (8)
// Source array
uint16_t SAMPLES[SAMPLES_SIZE] = { 1, 2, 3, 4, 5, 6, 7, 8 };
// Destination array
uint16_t TEST[SAMPLES_SIZE] = { 0xDE, 0xAD, 0xBE, 0xEF,0xDE, 0xAD, 0xBE, 0xEF };
Then utilising the PIT handler (which I won't eventually need), included the following to observe how the state of the destination array changes with each trigger:
for (uint32_t i = 0; i < SAMPLES_SIZE; i++) {
printf("%x ", TEST[i], TEST[i]);
}
This results in the following output (one line for each PIT trigger):
2 ad be ef de ad be ef
4 ad be ef de ad be ef
6 ad be ef de ad be ef
8 ad be ef de ad be ef
However, if I also set the DINC mask, I get the following:
1 2 be ef de ad be ef
1 2 3 4 de ad be ef
1 2 3 4 5 6 be ef
1 2 3 4 5 6 7 8
So, with DINC enabled it transfers two bytes at a time. Without DINC, it's just transferring every second byte.
Also, if I reduce the SSIZE and DSIZE to '1' (which should be 1 byte), I see that with DINC enabled it continues to work as expected. However, if I disable DINC, I only get a zero value written to the destination.
So to redefine the problem: DMA transfers only work as expected with DINC enabled.
Regards,
Paul Swanson