2020-08-31 02:50 AM
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.
2020-08-31 03:13 AM
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?
2020-09-01 10:58 AM
Anyone? :)
2020-09-01 01:54 PM
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
2020-09-03 07:17 AM
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 :)
2020-09-09 03:50 AM
Ok this was great info jan - seems to work this way:
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.
2020-09-09 07:37 AM
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:
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:
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).
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:
2020-09-09 08:13 AM
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:
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.
2020-09-09 10:02 AM
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
2020-09-09 11:18 AM
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)