Skip to main content
Explorer
January 8, 2024
Question

SAI with blocking mode

  • January 8, 2024
  • 13 replies
  • 5883 views

Hello,

I have this code

 

 

 // Initialiser le bloc A
 fresult= HAL_SAI_Init(&hsai_BlockA1);
 if (fresult != HAL_OK)
 	{
 	return HAL_ERROR;
 	}
 // Initialiser le bloc B
 fresult= HAL_SAI_Init(&hsai_BlockB1);
 if (fresult != HAL_OK)
	{
 	return HAL_ERROR;
	}

 // Transmission
 fresult = HAL_SAI_Transmit(&hsai_BlockB1, (uint8_t *)playbuf, (sizeof(playbuf))/4, 0xFF);
 if (fresult != HAL_OK)
 	{
 	return HAL_ERROR;
 	}
 // Reception
 fresult = HAL_SAI_Receive(&hsai_BlockA1, (uint8_t *)playbuf_RX, (sizeof(playbuf_RX))/4, 0xFF);
 if (fresult != HAL_OK)
 	{
 	return HAL_ERROR;
 	}

 

 

I am not receiving some of the data, do you know where I can find the documentation to implement SAI blocking mode? thanks in advance.

DYann1_0-1704728511248.png

In my reception array I have only a part of data, and yet my TX table is well initialized like this :

DYann1_1-1704728708060.png

    This topic has been closed for replies.

    13 replies

    Graduate II
    January 9, 2024

    You need to understand what's going on there:

    - HAL_SAI_Transmit() seems to return when data transfer to the SAI's FIFO has finished

    - HAL_SAI_Receive() is started when there is only the data in the TX FIFO

    Again, DMA is for most applications the best solution.

    Anyway, isn't there some combined start function for TX & RX? Check the SAI HAL file...

    DYann.1Author
    Explorer
    January 9, 2024

    For the 'isn't there some combined start function for TX & RX?' I'm looking for just to understand how the SAI works in blocking mode. In another post I ran the SAI in DMA mode but it only worked once I asked the question on the forum but I didn't get any feedback.

    https://community.st.com/t5/stm32-mcus-products/dma-with-sai/m-p/607742#M227413

     

     

     

    Graduate II
    January 9, 2024

    You try to understand the HAL stuff, not the SAI. The SAI peripheral has no "blocking mode".

    For understanding the HAL functions, go line by line through the HAL source files, compare register settings with the ref manual.

    That way, you will also understand the SAI peripheral a little better. 

    In another post I ran the SAI in DMA mode but it only worked once I asked the question on the forum but I didn't get any feedback.

    I thought that problem was solved.
    You have not yet shown the DMA init for the SAI, so we couldn't comment on that.

    Try first to get the SAI TX with DMA running, check the clock and data out pins with a scope if it's running continuously (hint: trigger on LRCK = FS, then you should see the always changing data output in sync).

    DYann.1Author
    Explorer
    January 9, 2024

    'You try to understand the HAL stuff,....' Not really I can understand the HAL functions, the difficulty is knowing how to place the transmission and reception function in the right places and there is no example or documentation on this to implement it correctly.

    > In another post I ran the SAI in DMA mode but it only worked once I asked the question on the forum but I didn't get any feedback.

    Yes it works but only once. In reality I have to acquire measurements continuously. For the DMA init, I don't see too much difficulty there since it was the STM32cubeMX which generated the code for me but you can see my init below :

    static void MX_DMA_Init(void)
    {
    
     /* DMA controller clock enable */
     __HAL_RCC_DMAMUX1_CLK_ENABLE();
     __HAL_RCC_DMA1_CLK_ENABLE();
    
     /* DMA interrupt init */
     /* DMAMUX1_IRQn interrupt configuration */
     HAL_NVIC_SetPriority(DMAMUX1_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ(DMAMUX1_IRQn);
     /* DMA1_Channel1_IRQn interrupt configuration */
     HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
     /* DMA1_Channel2_IRQn interrupt configuration */
     HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
    
    }

    In this case, I'll abandon the SAI blocking mode and work on the DMA so that operation is continuous

    Graduate II
    January 9, 2024

    If you had taken a closer look at HAL_SAI_Transmit(), you would have seen that it returns only after the transfer has completely finished.

    Anyway, what you show as DMA init is not what I meant, I meant the SAI / DMA init, where you set circular mode and so on. If the DMA transfer runs only once there's some bug in your code.

    Here's an example for a SAI DMA as RX on a F7:

    /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /* SAI 1 A - RX ADC 1&2 */
    	if( SAI1_Block_A == hsai->Instance )
    	{
    		SAI1_client++;
    
    	/* SAI1_A_Block_A GPIO Configuration */
    	/* NO MCLK output!
    	 * 	MCLK is done by I2S2
    	 *	because STM32 SAI needs fixed oversampling ratio of 256
    	 */
    		GPIO_InitStruct.Pin 		= SAI1_SCLK_Pin | SAI1_LRCK_Pin | SAI1_DAT_A_Pin | SAI1_DAT_B_Pin;
    		GPIO_InitStruct.Mode 		= GPIO_MODE_AF_PP;
    		GPIO_InitStruct.Pull 		= GPIO_NOPULL;
    		GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_HIGH;
    		GPIO_InitStruct.Alternate 	= GPIO_AF6_SAI1;
    		HAL_GPIO_Init(SAI1_CLKDAT_GPIO_Port, &GPIO_InitStruct);
    
    	/* Peripheral DMA init */
    		hDMA_Sai_1_A.Instance = DMA2_Stream1;
    
    		hDMA_Sai_1_A.Init.Channel 				= DMA_CHANNEL_0;
    		hDMA_Sai_1_A.Init.Direction 			= DMA_PERIPH_TO_MEMORY;
    		hDMA_Sai_1_A.Init.PeriphInc 			= DMA_PINC_DISABLE;
    		hDMA_Sai_1_A.Init.MemInc 				= DMA_MINC_ENABLE;
    		hDMA_Sai_1_A.Init.PeriphDataAlignment 	= DMA_PDATAALIGN_WORD;
    		hDMA_Sai_1_A.Init.MemDataAlignment 		= DMA_MDATAALIGN_WORD;
    		hDMA_Sai_1_A.Init.Mode 					= DMA_CIRCULAR;
    		hDMA_Sai_1_A.Init.Priority 				= DMA_PRIORITY_MEDIUM;	//	DMA_PRIORITY_HIGH; DMA_PRIORITY_VERY_HIGH;
    		hDMA_Sai_1_A.Init.FIFOMode 				= DMA_FIFOMODE_ENABLE;
    		hDMA_Sai_1_A.Init.FIFOThreshold 		= DMA_FIFO_THRESHOLD_FULL;
    		hDMA_Sai_1_A.Init.MemBurst 				= DMA_MBURST_SINGLE;
    		hDMA_Sai_1_A.Init.PeriphBurst 			= DMA_PBURST_SINGLE;
    
    		if( HAL_DMA_Init(&hDMA_Sai_1_A) != HAL_OK ) Error_Handler_FL(__FILE__, __LINE__);
    
    		__HAL_LINKDMA(hsai, hdmarx, hDMA_Sai_1_A);
    		__HAL_LINKDMA(hsai, hdmatx, hDMA_Sai_1_A);
    
    	/* DBM - double buffer mode activation
    		- AFTER LINK to DMA!
    		- BEFORE DMA enable!
    	 */
    		u32RegTemp = hsai->hdmarx->Instance->CR;
    		hsai->hdmarx->Instance->CR &= ~DMA_SxCR_EN;
    		hsai->hdmarx->Instance->CR |= DMA_SxCR_DBM;
    		u32RegTemp &= DMA_SxCR_EN;
    		hsai->hdmarx->Instance->CR |= u32RegTemp;
    		/* CT - current buffer reset */
    		hsai->hdmarx->Instance->CR &= ~DMA_SxCR_CT;
    	}

     

    Next thing needed is the DMA ISR, quite simple, the important work is done within the HAL_DMA_IRQHandler(), like resetting interrupt flags. Maybe that was missing in your code?

    /* SAI DMA interrupt handlers */
    /* the global HAL_DMA_IRQHandler():
     *	- resets all flags
     *	- calls the half- / complete callback functions
     */
    
    /* SAI 1 A as ADC */
    void DMA2_Stream1_IRQHandler(void)
    {
    	HAL_DMA_IRQHandler(&hDMA_Sai_1_A);
    }
    

    And the callbacks for half / complete, which override the __weak callbacks in the HAL source, which you can use for your custom stuff. 
    Maybe toggle an LED, set / clear flags, ...

     

     

     

     

    DYann.1Author
    Explorer
    January 9, 2024

    Thank you for your helps ! Give me time to read and understand. Now I can see what am I missing in my code. IRQ and the circular mechanism to retrieve data. I see that in your code the DMA directly accesses ADC which is probably not the case for me (I'm not sure).

    Graduate II
    January 9, 2024

    For now, forget the double buffer mode (DBM) init at the end of the DMA init.

    DYann.1Author
    Explorer
    January 10, 2024

    Just one think, I used this configuration in my previous post :

    DYann1_0-1704873891770.png

    Can I use this same method : Connecting TX with the RX (so for the moment the CODEC is of no use to me). Can I code (circulaire mechanism, IRQ...) with this configuration before actually plugging in the CODEC ?

    Graduate II
    January 10, 2024

    Sure, why not?

    IMPORTANT: do not only rely on software tools, monitor the SD TX pin with a scope. And as I said before, trigger scope on FS = LRCK.

    And focus on getting the TX part running continuously via DMA. Only then start the RX part.
    It seems you try to do too much different stuff at once, it seems. ;)

    DYann.1Author
    Explorer
    January 10, 2024

    Yes you're right, but when I don't know where to start then it seems a little confusing. Thank you for this verification method.

    Graduate II
    January 16, 2024

    1) So TX is running continuously? If yes, great!
    Now better check that with both LRCK and TX data at the scope, with trigger on LRCK, just to make sure...

    2) Your code:

    a) dataReadyFlag is set at HALF complete. Why?
    b) dataReadyFlag is reset where?

    c) I wonder why you ever see playbuf_RX[] not = 0, because your while loop makes no sense.

    What your while loop does:

    - START: reset playbuf_RX[] = 0
    - wait 1 ms
    - if dataReadyFlag is set in HALF complete, then you copy playbuf (the DAC buffer?) into playbuf_RX, basically overwriting what the DMA put in there
    - and then, 1 ms later you go to START and reset playbuf_RX again

    This makes NO sense at all and you're really molesting the DMA buffer by constantly resetting and copying - or I just don't get it.

     

    Here's my suggestion:

    1) set dataReadyFlag in RX full complete callback, not in half complete

    2) in while loop start only with this, reset the flag, count, toggle an LED

    uint32_t cntRxTest = 0;
    while(1)
    {
     if( dataReadyFlag != 0 )
     {
     dataReadyFlag = 0;
     cntRxTest++;
     toggle_LED;
     }
    }

    If that is working, maybe reset the playbuf_RX to zero within the if statement. 

    Maybe make the buffer smaller so that the resetting is done within one sample duration.

     

    DYann.1Author
    Explorer
    January 17, 2024

    Hi,

    Thank you for your comments

    DYann1_0-1705478298550.png

    I don't understand where the DMA data is stored ? In my configuration SAI1_A (the slave : CODEC)

    DYann1_1-1705478474244.png

    Peripheral : SAI_RX 

    Memory : ? Where and how to read this datas when the Flag RX =1 (HAL_SAI_RxCpltCallback, I make a mistake here).   

    For the Delay (1), it's just being able to make a breakpoint and see if the playbuf_RX table is indeed reset to 0 for the next acquisition, and it's probably not a good method because I'm 'really molesting the DMA buffer' but how to make sure if the data is transmitted correctly when Flag =1 (RxCpltCallback) ? you may lose a frame of data but then you will have a complete frame, right ?

    I have 2 signals (blue : data (probe x10). yellow : LRCLK)

    DYann1_0-1705491476053.png

    Graduate II
    January 17, 2024

    > I don't understand where the DMA data is stored ?

    You should at least read the HAL source function descriptions.

    Then you would find for HAL_SAI_Receive_DMA

    HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t *)playbuf_RX, (sizeof(playbuf_RX))/4);

    playbuf_RX (= memory) is the data buffer you hand over to the DMA, and that's where DMA writes the data it gets from the SAI RX peripheral.
    That's why I said it does not make sense to constantly "work" on this buffer.

     

    Scope:

    Your scope does NOT show LRCK ( = FS in STM lingo), but probably SCLK ( = CK or so, the bit clock).
    LRCK frequency equals the sampling rate, and within one half period you should see one data sample.

    If you have 3 channels, then simply add LRCK, and trigger on that.

     

    DYann.1Author
    Explorer
    January 17, 2024

    For the scope you're right about FS (yellow) Data (blue) now I have this :

    DYann1_0-1705506158398.png

    Now I understand better what DMA and memory are. A confusion on my part however, I think that we need a way to check that what we have received corresponds to the transmission. Maybe a pointer to this playbuf_RX array so as not to slow down DMA reception ?

    Graduate II
    January 17, 2024

    Scope: now that looks better!

    I guess that there's some up-counting going on, because only the LSBs are changing.

    LRCK ~= 36 kHz - is that what you expected from your SAI TX settings?

    At 36 kHz, one sampling period is about 27 µs. Check how many buffers you could read or reset within that time, depends on your CPU clock. As a rule of thumb, take factor 4 for each for loop iteration.

    To get more "time" to read the buffer, you could now use both the HALF and the complete interrupts:
    - at HALF you set half flag within ISR
    - in the loop check half flag, if set copy 1st half of the buffer
    - at complete set another flag within ISR
    - in the loop check complete flag, if set copy 2nd half of the buffer

    A pointer is basically an address, so if you want to check what's the content of that address, it doesn't really make a difference concerning the buffer access.

     

    DYann.1Author
    Explorer
    January 18, 2024

    Hi, 

    Thank you very much for this very useful information (at least for me). I think after my configuration the LRCL is correct (Real Audio Frequency ?) : 

    DYann1_0-1705571858437.png

    I don't know how long it will take to read the receive memory but for this loop for example :

    void Reset_playbuf_RX()
    {static int32_t j;
     for (j=0;j<8192;j++)
     {playbuf_RX[j]=0;}
    }

    I think my CPU clock is 32.768 kHz about 30 µs. 

    DYann1_1-1705572576791.png

    2 instructions x 8192 x 27 µs = 0.44 s x 4 (factor) = 1.7s. I think the best method is to do is to read 2 halfs buffers, I'll modify the configuration via '.ioc' to do it. I'll see how to deal with interruptions.

    Graduate II
    January 18, 2024

    I think my CPU clock is 32.768 kHz about 30 µs. 

    Hopefully not!

    Maybe find some basic tutorial about STM32 / clock setup.

    What you show is the LSE, which stands for Low Speed External clock, that should only / mostly be used for a real time clock (RTC).

    Your CPU clock should at least be in the MHz range, which STM32 type + board are you using?
    CPU clock source should be somewhere from HSI (not good for audio due to jitter) or HSE.

    So again, at HALF complete interrupt:

    - set half flag within ISR
    - in the loop check half flag, if set:
        - copy 1st half of playbuf_RX to checkbuf_RX, or simply compare to the TX buffer
        - reset 1st half of playbuf_RX

    at full complete interrupt same with 2nd half of buffer

    DYann.1Author
    Explorer
    January 18, 2024

    Hi,

    After reading some information about the clock I understand a little better. My CPU clock is 16 MHz

    DYann1_0-1705583947425.png

    For the moment my objective is just communication by SAI, after of course I need to optimize my electronic board. I have an evaluation board : STM32L552E-EV with STM32L552ZET6QU microcontroller. I understand the 'HALF complete interrupt' after your explanation and I'll implement with this method. You're right, I've to reduce the size of my array just to see at first, for example an array of 100 values.

    Graduate II
    January 18, 2024

    My CPU clock is 16 MHz

    Not, it probably does not.
    Don't stop looking and thinking just because you found one number.
    HSI = internal RC oscillator is the clock source.

    Which then goes through the PLL, with PLLM as the first divider, as can be seen in the little pic you posted.

     

    You really need to "go to school" and / or read  / watch lots of basic stuff, about the basics of programming in C, controllers, and probably electronics in general. 

    I'm out for now, sorry.

    Graduate II
    January 18, 2024

    What I meant is to learn more basics, read and watch basic tutorials, ask colleagues, friends, whatever.

    Right now you're kind of running through the dark, no offense meant, everybody has to start somewhere. And an STM32 is quite a big challenge for a beginner.

    DYann.1Author
    Explorer
    January 18, 2024

    You've already helped me a lot and I thank you very much. I'll follow your advice. Only reading the datasheets or watching the tutorials is not a good method (I2S#SAI, not the same µP, not the same function prototypes....) I need examples and practice to realize that it's not as simple as that. But I've no choice, this SAI part has to work !