Hi,
I wrote a KL26 UART driver using circular tx/rx buffers and DMA. It works fine, except once in a while it seems to skip a byte on transmit. I suspect the problem might have something to do with the way I update BCR (the counter in the DSR_BCR register) when adding data to the transmit buffer. The code looks like this (data to send is pointed to by "data" and length "len"):
// Disable the UART Tx DMA request while we work.
DMA0->DMA[n].DCR &= ~(DMA_DCR_ERQ_MASK);
uint32_t old_bcr = DMA0->DMA[n].DSR_BCR & DMA_DSR_BCR_BCR_MASK;
if (old_bcr + len < circular_buffer_size)
{
// Add new data to tx buffer and update "put" pointer.
uint32_t bytesToEnd = (1 << UART_TX_BUF_SIZE_ORDER) - txPutPtr;
if (len <= bytesToEnd)
{
memcpy(&txBuf[txPutPtr], data, len);
}
else
{
memcpy(&txBuf[txPutPtr], &data[0], bytesToEnd);
memcpy(&txBuf[0], &data[bytesToEnd], len-bytesToEnd);
}
txPutPtr = (txPutPtr + len) & (circular_buffer_size - 1);
// Update the DMA channel BCR counter to account for the new data.
DMA0->DMA[n].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // must clear DONE first, then write BCR
DMA0->DMA[n].DSR_BCR = DMA_DSR_BCR_BCR(old_bcr + len);
}
// Resume the Tx DMA.
DMA0->DMA[n].DCR |= DMA_DCR_ERQ_MASK;
My question is, is it safe to modify BCR in this manner? Or is there a better way to ensure DMA is paused while I modify it?
Thanks.
Hello Mark,
I think your code have no problem , while I have some suggestion for you .
1 ) The KL26 RM says "
During a channel's execution, writes to programming model
registers can corrupt the data transfer. The DMA module itself
does not have a mechanism to prevent writes to registers during
a channel's execution."
so , I suggest you do this on the DMA complete ISR;
2) The L series processor UART have not receive/send FIFO, so it easily occur byte lost when reconfigure the DMA, so if can , reduce the UART baud rate.
In Kinetis K series , the UART have receive/send FIFO , it is convenient for reconfigure DMA.
Hope it helps !
Alice
Hi Alice,
The KL26 RM is somewhat ambiguous on what is ok and what is not ok here. I am still hoping someone with firsthand experience, or knowledge of the internal DMA peripheral structure, will respond with suggestions. I'd prefer not to have to use the DMA-complete interrupt, but that's my backup plan if I can't figure this out. (As designed, a DMA-complete interrupt will never occur unless the circular buffer runs out of space. I like this design, but...)
My working hypothesis as to what may be happening goes something like this:
1) Transmit DMA is active when the code above is called. So the code might intersect the running DMA at any point---during the read phase, the write phase, or whenever.
2) Suppose that when ERQ is cleared, a DMA transaction was half-complete: the read from txBuf had occurred, but the write to UART_D had not yet occurred.
3) When ERQ is set (after the BCR change), a new DMA transaction starts, and the old byte which had not yet been written to UART_D is lost.
This is just a guess. The manuals don't specify the internal workings of the DMA engine in enough detail to confirm or deny. What do you think?
Thanks,
Mark
Hi Mark,
Best Regards
Alice
Hi Mark,
I think customer code exists the risk.
During DMA transfer data to UART data register, calling above code will pause the DMA transfer.
The data could not be transferred to UART data register successfully.
Then the current UART transmit request will be omitted (no data transmit at UART port).
Customer can consider to use DMA cycle-steal mode to transfer data to UART data register.
When customer want to modify DMA BCR counter value, customer can write 0 to UARTx_C2[TE] register to disable the UART transmit.
The current transmit activity in progress will be completed.
The code can polling the UARTx_S1[TDRE] status flag is set to indicate current transmit data buffer empty.
Then customer could call related DMA BCR modification code to change the DMA BCR counter value.
After that, customer can re-enable UART transmit via set UARTx_C2[TE] register.
Wish it helps.
best regards
Ma Hui
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------
Thanks, I will try this and report back. So I will clear UARTx_C2[TE], then poll for UARTx_S1[TDRE]. But when TDRE is set, won't that initiate another DMA cycle? Because the UART TDMAE bit is still enabled. Or is the UART TX DMA request output gated by TE?
Mark
Hi Mark,
My above suggestion also exists the risk. So, I suggest you can use below way:
1> At first, disable the DMA request;
2> The code add some delay, it need consider DMA response time and DMA transfer time, I would recommend to use 40 cycles core clock for worst case.
3> There no need to disable UART transmit, it just need polling UARTx_S1[TC] flag to make sure the UART transmit complete.
4> Then customer can start to modify DMA BCR value.
That will make sure no data will be omitted during DMA transfer, while it add some delay time for current UART transmit finished.
Wish it helps.
best regards
Ma Hui
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------