cancel
Showing results for 
Search instead for 
Did you mean: 

SAI - how to synchronize the data transmit between 2 blocks

SLasn.1
Associate II

Hi!

I am still playing with the SAI of my STM32F446RE. It has one SAI (SAI1) which has two blocks, A and B.

The idea is to use the two blocks synchronously in TDM mode to transmit in total 8 slots, 4 slots for each block.

I can get it to work fine, though I noticed the two blocks do not necessarily send the data at the same time - i.e. as soon as I put something in the DR registers it gets sent. I went through the datasheet and a few PDF presentations multiple times, but I still do not get how I can get the 2 blocks to send the data at the same time (for example I put 4 bytes in each block, then say "go" and the 8 bytes are sent together using the 8 slots - i.e. one frame).

I guess it is something to do with the FIFIO thresholds? I tried setting them to Full (in my case half full will be 4 bytes, so it will be perfect) but it does not change anything.

Could someone explain to me how I am supposed to configure the SAI blocks?

Thank you in advance!

Simon

PS: I am using CubeMX and the HAL lib.

10 REPLIES 10
SLasn.1
Associate II

Note that ideally I want to do this with DMA. Maybe there is a way to do it on the DMA level, with the DMA FIFO?

SLasn.1
Associate II

Anyone? 🙂

Note that there's only a handful of users answering questions here; I don't remember many threads with SAI/TDM. (I used a single block SAI for I2S and then other for SPDIF-Tx, and all this was quite some time ago, I don't really remember the details).

So, without looking into the manual, IMO the blocks are master-slave, and slave won't transmit before master does. So, what I'd do is, start DMA into the slave's data register, make sure the FIFO is full, and then start DMA into the master's data register. They should start transmitting simultaneously when data arrive to master; DMA is there to top up the FIFO as transmitter in SAI drains it.

JW

Hi Jan

Yes it's more than fair, I did not want to push anyone, just thinking the post passed through the nets a bit 🙂

Thanks for the tip I'll try that and write what I get here 🙂

Ok this was great info jan - seems to work this way:

  • If both SAI blocks are disabled, I can fill up the FIFO of the slave block and activate it without any data being transmitted
  • I can then fill up the FIFO of the master block and activate it - then both blocks start transmitting in sync
  • Though if both blocks are activated but the FIFO's are empty then any data put in the slave FIFO will be transmitted right away!

Which made me realize that I got my workflow wrong - the blocks should always be deactivated unless there is a constant flow of data being sent out, is that correct?

Because that does not fit with the HAL_SAI_DMAPause function, it does not disable the SAI block, so when I call Resume, even if I start with the slave block first it will transmit the data right away and there is a chance the two blocks will be out of sync.

SLasn.1
Associate II

I tested the DMAPause/Resume and I can confirm it gets out of sync when resuming. BUT it turns out that within one frame the data is still where it should be - so that the data is still sent to the correct audio output (see below).

Though the slave and master can still be one frame out of sync, and can get more and more out of sync each time the HAL_SAI_DMAPause/Resume functions are called.

Notice that in terms of audio one frame out of sync means some audio channels will be 1/fs out of phase, f.x. 0.02ms (with fs=44.1kHz) - I am thinking this should not be audible really?

Here is my test setup:

  • I am sending 8 samples with DMA set to Half-Word Circular
  • I start transmitting with DMA in the main()
  • In the loop I call Pause/Resume when I press a button

The code:

// test data - dummy data very visual on the logic analyzer
static const uint16_t data[8] = { 0b1,   0b101,   0b10101,   0b1010101,   0b101010101,   0b10101010101,   0b1010101010101,   0b101010101010101 };
 
int main(void) {
 
    // [...]
 
    HAL_SAI_Transmit_DMA(SAI_BlockSlave, (uint8_t*)data, 8); // slave first
    HAL_SAI_Transmit_DMA(SAI_BlockMaster, (uint8_t*)data, 8);
    
    while (1)  {
 
        // [...]
 
        if (buttonPressed) {
	    if (paused) {
		  HAL_SAI_DMAResume(SAI_BlockSlave); // slave first
		  HAL_SAI_DMAResume(SAI_BlockMaster);
	    }
  	    else {
		  HAL_SAI_DMAPause(SAI_BlockMaster); // master first
		  HAL_SAI_DMAPause(SAI_BlockSlave);
	    }
 
            // [...]
	}
    }
}

When I start the SAIs I get that:

0693W000003RBmqQAG.png

So far so good, you can see both data lines are in sync, and also that the data is in the right place in the frame (since this is TDM, the DAC will send the samples to different outputs according to their position in the frame).

Then I press the button (calling HAL_SAI_DMAPause) and it does not (always) stop at the end of the frame, nor does the two data lines stop at the time (again: not always).

0693W000003RBoiQAG.png

When I press again (calling HAL_SAI_DMAResume), and it seems the DMA/SAI make sure that the data within each frame is at its place, but since one SAI starts transmitting a bit before the other, then the other come one frame later sometimes, like here:

0693W000003RC4LQAW.png

If I am not mistaken, here stopping the DMA simply means the FIFO is empty and is contantly in "underrun" until the DMA is resumed?

As the UM explains:

0693W000003RCNSQA4.png

I guess I am doing something wrong here - I keep reading the UM, and even going through the registers it seems the only way to stop sending data from the FIFO (sending zeros if it is empty as shown in the screenshot above) is to disable the SAI with SAIEN=0. Problem is when I disable the master then the Master Clock stops being output.

I have no idea what could be DMAPause/Resume. I don't use Cube/HAL and don't intend to debug it; and I also doubt anybody does.

Why don't just simply stop, and start afresh?

Normally, pause in audio is to be performed by filling the Tx buffer with a neutral value (zeros), while clocks and DMA keeps running; and simply ignoring the incoming data.

JW

My bad I thought everybody used HAL - the HAL_SAI_DMAPause/Resume functions simply reset/set the DMAEN bit in the CR register.

And yes I agree it seems the best option is to simply keep the DMA/SAI running constantly and fill the Tx buffer with zeros when paused or stopped - that would work with me. But it also means the DMA is still constantly moving the zeros from the buffer to the FIFO, and that the SAI is constantly sending zeros from the FIFO to the data lines... isn't that waste of energy consumed? (I do not want to run on a battery or something but I am still thinking it sounds like a lot of work for sending zeros)