cancel
Showing results for 
Search instead for 
Did you mean: 

HAL SAI cannot run quick enough for basic audio sampling?

RBamf.1
Associate III

I am trying to take a microphone sample every 62.5 microseconds (16,000Hz) which is the standard rate for audio files.

The problem i have found is that HAL_SAI_Receive_DMA() returns HAL_BUSY for some samples. So we dont receive the required 16,000 samples in the 1 second, it only manages 14750.

The SAI component can reach rates of 192KHz so it makes no sense that it cannot handle 16KHz? So how do i fix the problem of receiving HAL_BUSY?

Thanks!

Here is an example sketch running on an STML452RE:

#include <SoftwareSerial.h>
#include <Arduino.h>
#include <HardwareTimer.h>
 
#include "stm32l4xx.h"
#include "stm32l4xx_hal.h"
#include "stm32l4xx_hal_def.h"
#include "stm32l4xx_hal_gpio.h"
#include "stm32l4xx_hal_sai.h"
#include "stm32l4xx_hal_exti.h"
#include "stm32l4xx_hal_cortex.h"
 
#include "Sysclock_Config.h"
 
 
 
GPIO_InitTypeDef gpioInit;
SAI_HandleTypeDef saiHandle;
RCC_PeriphCLKInitTypeDef periphInit;
DMA_HandleTypeDef hdmaInit;
 
uint8_t	SAIDataBuffer[8] = { 0 };
HardwareTimer hardwareTimer(TIM3);
 
void assert_failed(uint8_t* inFileName, uint32_t line)
{
	char* fileNameAsString = (char*)inFileName;
	Serial2.println(F("*** [HAL ASSERT FAILED] ***"));
	Serial2.printf("%s\r\n", (char*)inFileName);
	Serial2.printf("%i\r\n", line);
}
void _Error_Handler(const char* file, int line)
{
	Serial2.println(F("*** [ERROR HANDLED] ***"));
	Serial2.printf("%s\r\n", file);
	Serial2.printf("%i\r\n", line);
}
 
extern "C"
{
	void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef* hsai)
	{
	}
 
	void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef* hsai)
	{
	}
 
	void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef* hsai)
	{
	}
 
	void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef* hsai)
	{
	}
 
	void HAL_SAI_ErrorCallback(SAI_HandleTypeDef* hsai)
	{
	}
 
	__weak void SAI1_IRQHandler(void)
	{
		HAL_SAI_IRQHandler(&saiHandle);
	}
 
	__weak void DMA2_Channel1_IRQHandler(void)
	{
		/* USER CODE BEGIN DMA2_Channel1_IRQn 0 */
 
		/* USER CODE END DMA2_Channel1_IRQn 0 */
		HAL_DMA_IRQHandler(&hdmaInit);
		/* USER CODE BEGIN DMA2_Channel1_IRQn 1 */
 
		/* USER CODE END DMA2_Channel1_IRQn 1 */
	}
}
 
void samplerCallback();
 
 
// the setup function runs once when you press reset or power the board
void setup()
{
	delay(5000);
 
	Serial2.begin(115200);
	Serial2.println("Starting");
 
 
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_DMA2_CLK_ENABLE();
	__HAL_RCC_SAI1_CLK_ENABLE();
 
	gpioInit.Pin = GPIO_PIN_15 | GPIO_PIN_10 | GPIO_PIN_12;
	gpioInit.Mode = GPIO_MODE_AF_PP;
	gpioInit.Pull = GPIO_NOPULL;
	gpioInit.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	gpioInit.Alternate = GPIO_AF13_SAI1;
	HAL_GPIO_Init(GPIOB, &gpioInit);
 
	pinMode(PB1, OUTPUT);
	digitalWrite(PB1, LOW);
 
	saiHandle.Instance = SAI1_Block_A; // audio block A.
	saiHandle.Init.Protocol = SAI_FREE_PROTOCOL;
	saiHandle.Init.FirstBit = SAI_FIRSTBIT_MSB;
	saiHandle.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K;
	saiHandle.Init.AudioMode = SAI_MODEMASTER_RX; // block a must provide clock signals and receive from the data line.
	saiHandle.Init.Synchro = SAI_ASYNCHRONOUS;	// we only want to use this one audio block.
	saiHandle.Init.SynchroExt = SAI_SYNCEXT_DISABLE; // disable sychronizing the 2 audio blocks.
	saiHandle.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE; // assume to power the data?
	saiHandle.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE; // any frame length allowed.
	saiHandle.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;	// used for interrupts.
	saiHandle.Init.MonoStereoMode = SAI_STEREOMODE; // mono mode only available in transmission mode.
	saiHandle.Init.CompandingMode = SAI_NOCOMPANDING; // telecommunications specification (not needed)
	saiHandle.Init.TriState = SAI_OUTPUT_NOTRELEASED; // assume the SAI is ma
	saiHandle.FrameInit.FrameLength = 64; // 64 bit frame. (2 slots)
	saiHandle.FrameInit.ActiveFrameLength = 32; // Frame synchronization active level length. (half the frame length)
	saiHandle.FrameInit.FSDefinition = SAI_FS_CHANNEL_IDENTIFICATION;
	saiHandle.FrameInit.FSPolarity = SAI_FS_ACTIVE_LOW;
	saiHandle.FrameInit.FSOffset = SAI_FS_BEFOREFIRSTBIT;
	saiHandle.SlotInit.FirstBitOffset = 0; // no offset in receive mode -> FBOFF <= (SLOTSZ - DS)
	saiHandle.SlotInit.SlotSize = SAI_SLOTSIZE_32B; // 32 bits per slot to contain the 32 data bits (24 data, 8 zeroed)
	saiHandle.SlotInit.SlotNumber = 2;
	saiHandle.SlotInit.SlotActive = SAI_SLOTACTIVE_ALL;
 
	hdmaInit.Instance = DMA2_Channel1;
	hdmaInit.Init.Request = DMA_REQUEST_1;
	hdmaInit.Init.Direction = DMA_PERIPH_TO_MEMORY;
	hdmaInit.Init.PeriphInc = DMA_PINC_DISABLE;
	hdmaInit.Init.MemInc = DMA_MINC_ENABLE;
	hdmaInit.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
	hdmaInit.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
	hdmaInit.Init.Mode = DMA_NORMAL;
	hdmaInit.Init.Priority = DMA_PRIORITY_VERY_HIGH;
	if (HAL_DMA_Init(&hdmaInit) != HAL_OK)
	{
		Error_Handler();
	}
 
	/* Several peripheral DMA handle pointers point to the same DMA handle.
	 Be aware that there is only one channel to perform all the requested DMAs. */
	__HAL_LINKDMA(&saiHandle, hdmarx, hdmaInit);
	__HAL_LINKDMA(&saiHandle, hdmatx, hdmaInit);
 
	HAL_NVIC_SetPriority(DMA2_Channel1_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(DMA2_Channel1_IRQn);
 
	HAL_NVIC_SetPriority(SAI1_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(SAI1_IRQn);
 
 
	HAL_StatusTypeDef saiStatus = HAL_SAI_InitProtocol(&saiHandle,
													   SAI_I2S_STANDARD, // runs the SAI_InitI2S() function.
													   SAI_PROTOCOL_DATASIZE_24BIT,// 24 bits (24 bit is standard for I2S).
													   2);			 // number of slots per frame - 1
 
 
	__HAL_SAI_ENABLE(&saiHandle);
 
	if (saiStatus != HAL_OK)
	{
		Serial2.println("SAI ERROR");
		while (1) {}
	}
	else
	{
		Serial2.println("Sai init ok.");
	}
 
	hardwareTimer.setMode(1, TIMER_OUTPUT_COMPARE, NC); // remove this for new version of STM32Dino, required version 1.8.0.
	hardwareTimer.setOverflow(16000, HERTZ_FORMAT);
	hardwareTimer.detachInterrupt();
	hardwareTimer.attachInterrupt(samplerCallback);
	hardwareTimer.resume();
 
	Serial2.flush();
 
	start = millis();
}
 
 
 
int32_t GetSample()
{
	uint8_t a1 = SAIDataBuffer[0];
	uint8_t b1 = SAIDataBuffer[1];
	uint8_t c1 = SAIDataBuffer[2];
	uint8_t d1 = SAIDataBuffer[3];
 
	uint8_t a2 = SAIDataBuffer[4];
	uint8_t b2 = SAIDataBuffer[5];
	uint8_t c2 = SAIDataBuffer[6];
	uint8_t d2 = SAIDataBuffer[7];
 
	// value as signed 24 bit integer
	int32_t slot24sb = 0;
	slot24sb = slot24sb | (a1 << 8);
	slot24sb = slot24sb | (b1 << 16);
	slot24sb = slot24sb | (c1 << 24);
	slot24sb = slot24sb >> 8;
 
	return slot24sb;
}
 
int num_samples = 0;
 
void samplerCallback(void)
{
	HAL_StatusTypeDef rxResponse;
 
	rxResponse = HAL_SAI_Receive_DMA(&saiHandle, SAIDataBuffer, 2U);
 
	int sample = GetSample();
 
	if (rxResponse != HAL_OK)
	{
		//Serial2.println("Error in SAI");
	}
	else
	{
		num_samples++;
	}
}
 
 
// the loop function runs over and over again until power down or reset
void loop()
{
	if (millis() - start > 1000)
	{
		Serial2.println(num_samples);
		while (1) {}
	}
}

1 ACCEPTED SOLUTION

Accepted Solutions
RBamf.1
Associate III

The issue was that the Master Clock Divider was not configured correctly and that samples need to be handled in the "Transfer Complete" callback. Thank you @Community member​ and @Community member​ 

View solution in original post

10 REPLIES 10

>>How do i fix the problem of receiving HAL_BUSY?

Use a larger buffer, the way normal people approach this is to have a pair of large ping/pong buffers, so as not to miss samples, and not deal with 16 KHz interrupts

Similarly when recording to a file, write 8192 bytes, not 8 bytes, at a time.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
RBamf.1
Associate III

How can i use a buffer?

How can i use a buffer? HAL_SAI_Receive wont provide me with 1 sample every 16000hz

Ah i see, i increased the buffer size to 1024. But it still only gets "14848" samples instead of 16000.

But there is an error occuring in HAL_SAI_ErrorCallback() that is HAL_SAI_ERROR_OVR

As Clive said:

> a pair of large ping/pong buffers

For this, use both the Transfer Complete and Half Transfer interrupts from the DMA.

Read the DMA chapter of Reference Manual (RM) of your STM32, then translate what you've read into the Cube/HAL and Arduino lingo.

JW

Hm ok, i think i see what to do but i'm getting the error "HAL_SAI_ERROR_OVR".

Think its working! =)

Still no luck, even with a huge buffer there doesn't look like much of an increase.

Still no luck, even with a huge buffer there doesn't look like much of an increase.