Can DMA to i2s Data Transfer Get Corrupted by a Write to Source Location

Status
Not open for further replies.

Neal

Well-known member
I am working with a Teensy 4.1 and the Audio Adapter. I extracted some code from the Audio Library output_i2s to do some direct writing to the sgtl5000 DAC. Here is the begin function I am using:

void CodecDAC_begin(void)
{
CodecDAC_dma.begin(true); // Allocate the DMA channel first
CodecDAC_config_i2s();
CORE_PIN7_CONFIG = 3; //1:TX_DATA0 pin 7 on uP
CodecDAC_dma.TCD->SADDR = myi2s_tx_buffer; //source address
CodecDAC_dma.TCD->SOFF = 2; // source buffer address increment per transfer in bytes
CodecDAC_dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); // specifies 16 bit source and destination
CodecDAC_dma.TCD->NBYTES_MLNO = 2; // bytes to transfer for each service request///////////////////////////////////////////////////////////////////
CodecDAC_dma.TCD->SLAST = -sizeof(myi2s_tx_buffer); // last source address adjustment
CodecDAC_dma.TCD->DOFF = 0; // increments at destination
CodecDAC_dma.TCD->CITER_ELINKNO = sizeof(myi2s_tx_buffer) / 2;
CodecDAC_dma.TCD->DLASTSGA = 0; // destination address offset
CodecDAC_dma.TCD->BITER_ELINKNO = sizeof(myi2s_tx_buffer) / 2;
CodecDAC_dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; // enables interrupt when transfers half and full complete
CodecDAC_dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); // I2S1 register DMA writes to
CodecDAC_dma.triggerAtHardwareEvent(DMAMUX_SOURCE_ SAI1_TX); // i2s channel that will trigger the DMA transfer when ready for data
CodecDAC_dma.enable();

I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE;
I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE;

CodecDAC_dma.attachInterrupt(CodecDAC_isr);
}


This may be a naïve question, but because I am not double buffering the output myi2s_tx_buffer, I was wondering if is it possible to corrupt the DMA transfer if I write to the myi2s_tx_buffer while the DMA transfer is in progress?? I am getting some sporadic results and think that might be the cause.

Thanks for any help.

Neal
 
DMA transfers are not atomic. That is to say, the DMA controller can't "freeze" the memory before transferring its contents. If you write to the buffer while it is being transferred, the DMA controller will dutifully do what you told it to, and what comes out the other end will be partially what was there before and partially what you overwrote the buffer with.
 
Sorry I have not played with I2S, so not sure what all of your issues are...

But I have played around with DMA for things like display drivers and the like...

One thing to watch out for, is you can run into some interesting things with DMA depending on what type of memory you are working with.

If your memory location for myi2s_tx_buffer is within the lower memory DTCM or ITCM than things work without thinking about it.

But for example if your memory is in some other areas like marked DMAMEM or allocated by malloc or free (or on T4.1 and using external memory).

Than there are things you need to think about.

That is if you have code like:
Code:
uint8_t *myi2s_tx_buffer  = malloc(myi2s_tx_buffer);
for (int i = 0; i < myi2s_tx_buffer; i++) myi2s_tx_buffer[i] = i & 0xff; 
<DO DMA of this>

You would think that what you would output would be bytes like: 0, 1, 2, 3, ...

And it may be or it might not? Why?

your code that does something like: myi2s_tx_buffer = 0;
Will in this region of memory write to the memory cache associated with that memory location the value 0 And at some point that memory in cache will make it's way to be actually written to the real (slower) memory...

But DMA always works from the real memory..... So you can easily not output what you think should be there...

Likewise if you do DMA into memory, and then your code reads from that memory location... You may get the cached values from before and not the current actual memory.

To get around this, when you do DMA you may need to tell the cache to flush everything out in that range or delete what it thinks it knows in that range...

There is some code in the SPI library for example to try to handle this: that is if yo uare doing a write from a memory that might be cached we do:

if ((uint32_t)write_data >= 0x20200000u) arm_dcache_flush(write_data, count);
and likewise if we do dma into this memory SPI does:
if ((uint32_t)retbuf >= 0x20200000u) arm_dcache_delete(retbuf, count);

But again I have no idea if this is the type of issue you are having.
 
KurtE, thanks for the explanation of what the arm_dcache_flush() and arm_dcache_delete() methods do. I have seen them used in various places in the Audio Library and I wasn't sure what they did. I looked pretty extensively (in the i.MX RT1060 Processor Reference Manual) and couldn't find any information about them other than the comments in the imxrt.h file.

Can you point me to a document that details the cache memory interaction?

I have also read a lot of information in posts you have sent explaining various things you discovered about the M7 memory regions. That is another big mystery to me and I think the documentation I have seen so far is very sparse on this topic as well.


BTW, I fixed the problem I was having in my original post by double buffering my writes to the i2s serial peripheral. I believe I was corrupting the DMA transfers (thanks Pilot) by writing to the memory buffer while the DMA was sending data. It is working fine now.

Appreciate the help!

Neal
 
As you mentioned ARM functions like this don't show up in their manuals... Or any good documentation on it.

I started a thread awhile ago, where I tried to collect some information about the different memory regions on T4 (and now 4.1)
https://forum.pjrc.com/threads/5732...-different-regions?highlight=arm_dcache_flush

I know it is mentioned in that thread... Also in some other threads, like you really should try to start DMA operations on 32 byte boundaries...

Not sure if the Unofficial WIKI has any more information on this or not... https://github.com/TeensyUser/doc/wiki
 
Status
Not open for further replies.
Back
Top