cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F446 I2S stuck on Busy

FLewz.1
Associate III

Hi,

I've been trying to get I2S to work without using the HAL to get to know the MCU a bit more. I configured the Peripheral as mentioned in the reference manual, but whenever I write to the SPI->DR the status is stuck on busy.

This is my init function

	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;	// enable GPIOA Port CLK; PA4 I2S1-WS; PA5 I2S1-CK; PA7 I2S1-SD
	GPIOA->MODER |= GPIO_MODER_MODE4_1
					| GPIO_MODER_MODE5_1
					| GPIO_MODER_MODE7_1;
	GPIOA->MODER &= ~(GPIO_MODER_MODE4_0 | GPIO_MODER_MODE5_0 | GPIO_MODER_MODE7_0);
	GPIOA->AFR[0] |= (GPIO_AF5_SPI1 << GPIO_AFRL_AFSEL4_Pos);
	GPIOA->AFR[0] |= (GPIO_AF5_SPI1 << GPIO_AFRL_AFSEL5_Pos);
	GPIOA->AFR[0] |= (GPIO_AF5_SPI1 << GPIO_AFRL_AFSEL7_Pos);
 
	RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;		// enable I2S Peripheral CLK
	SPI1->I2SPR  |= SPI_I2SPR_I2SDIV & 47u; // set I2S Prescaler to 47
	SPI1->I2SPR  &= ~SPI_I2SPR_ODD;			// set I2S ODD to 0
	SPI1->I2SCFGR &= ~SPI_I2SCFGR_CKPOL;	// set Clock Polarity to LOW when IDLE
	SPI1->I2SCFGR |= SPI_I2SCFGR_I2SMOD;	// set Mode to I2S
	SPI1->I2SCFGR |= SPI_I2SCFGR_I2SCFG_1;  // set Mode to I2S Master Transmit
	SPI1->I2SCFGR &= ~SPI_I2SCFGR_DATLEN;	// set Data Length to 16 Bit
	SPI1->I2SCFGR |= SPI_I2SCFGR_CHLEN;	// set Channel Length to 32 Bit
	SPI1->I2SCFGR &= ~SPI_I2SCFGR_I2SSTD;	// set Channel to Philips Standard
//	SPI1->CR2 |= SPI_CR2_TXEIE;
//	NVIC_EnableIRQ(SPI1_IRQn);
	SPI1->I2SCFGR |= SPI_I2SCFGR_I2SE;		// enable I2S

In my main i simply check if TXE is set and then write a new value to the DR but since it never clears there is nothing happening there either.

What am I doing wrong?

17 REPLIES 17

> measuring the time between two TXE interrupts I get a big variation in cycles from ~1550 to ~1670 at a SysClock of 180 MHz.

Something's rotten. Shouldn't that be 180MHz/48kHz*2*16bit=120'000?

Strip down your code to bare minimum but complete compilable exhibiting the problem, and post.

JW

Now that I think about it maybe the cycles aren't all that incorrect. After commenting my while loop and only being left with this interrupt routine:

void SPI2_IRQHandler()
{
	get_time_elapsed();
	if( (SPI2->SR & SPI_SR_TXE) > 0u)
	{
		SPI2->DR = 0x8000;
	}
	get_start_cycle();
}

all measurements are 1768 cycles, which could be down to both channels being transmitted, although I thought this was solved by setting 16 bit data in 16 bit data frame. 180,000,000/48,000/2 channels = 1875. The remaining cycles could be the handling of the cycle counter, which is implemented like this:

void init_perf_timer()
{
	ITM->TCR |= 15;
	DWT->CTRL |= 1;
	idx = 0;
}
 
void get_start_cycle()
{
	start_cycle[idx] = DWT->CYCCNT;
}
 
uint32_t get_time_elapsed()
{
	uint32_t cycles = DWT->CYCCNT;
	if(cycles > start_cycle[idx])
	{
		times[idx] = cycles - start_cycle[idx];
	}else
	{
		times[idx] = 0xFFFFFFFF - start_cycle[idx] + cycles;
	}
	if(idx > 999)
	{
		asm("nop");
	}else
	{
		idx++;
	}
	return times[idx];
}

If everything is correct, that leaves me however with very few cycles, to do a lot of arithmetic to synthesize waveforms for audio.

> 180,000,000/48,000/2 channels = 1875

You've said, "BCLK is correct at 48 kHz." So, obviously, that was LRCK (aka WS), wasn't it?

> both channels being transmitted

In I2S, ALWAYS both channels are transmitted. 16-bit data in 32-bit frame means, that each channel has 32 bits, of which 16 bits are the user-supplied data, remaining 16 bits are padding zeros.

> If everything is correct, that leaves me however with very few cycles, to do a lot of arithmetic to synthesize waveforms for audio.

Yes, of course. Nobody is serving I2S data in interrupt, that's what DMA is for.

> if(cycles > start_cycle[idx])

> etc.

No, simply subtract. The unsigned 32-bit arithmetics takes care of the rollover.

> idx++;

> }

> return times[idx];

Doesn't this just return zero in the first 1000 calls of the function?

JW

> You've said, "BCLK is correct at 48 kHz." So, obviously, that was LRCK (aka WS), wasn't it?

S​orry yeah that was LRCK.

> In I2S, ALWAYS both channels are transmitted.

S​o how do I serve the other channel, which is useless to me? Do I just check for the CHSIDE flag and write 0 to it?

That would give me more cycles to calculate ​samples.

> Yes, of course. Nobody is serving I2S data in interrupt, that's what DMA is for.

G​ood point, but how do I know when to calculate the next sample? Do I keep the TXE Interrupt?

Piranha
Chief II

Audio is typically streamed based on some longer buffers than one sample and double buffering. While one buffer is being streamed with DMA, the CPU renders the data to the other buffer. When DMA transaction is complete, DMA interrupt happens and the buffers are swapped. That can be implemented in at least three ways: DMA with dual memory buffer support, single DMA buffer with half-transfer interrupt and even stop/configure/start DMA with software.

For the not needed audio channel the easiest is to transmit zeros or the same data from the needed channel. Why don't you use the SAI peripheral? It's much more flexible and also has a mono mode, which does all this for you in hardware:

When the mono mode is selected, slot 0 data are duplicated on slot 1 when the audio block operates as a transmitter. In reception mode, the slot1 is discarded and only the data received from slot 0 are stored.

Read the RM0390 section 28.3.12 Specific features, Mono/stereo mode.

Yeah, most examples on I2S I found used entire Sound files, but I figured I would make it work ​synthesizing samples just in time, since I have the MAX98357 Module.

I​ haven't looked into SAI so far, but from what I've seen it seems to be a broader term for I2S?

It's a different module in the STM32, capable of several protocols, most of them used within the audio-conversion realm.

JW

FLewz.1
Associate III

There's one more thing I can't figure out for the life of me.

My LRCK is running perfectly at 48kHz. The I2S Clock is set at 76.8Mhz so as to get an exact 48kHz with DIV = 25. However my interrupt is called around every 1750 cycles with core frequency at 180MHz which would be a sample rate of 96kHz.

This is only when I have getNextSample() in the interrupt handler however.

I know I shouldn't call a function in an interrupt handler, I still have to set up a DMA.

It shouldn't be the second channel because I check for that in my interrupt service:

if(SPI2->SR == 0x02)
	{
		SPI2->DR = last_val;
	}else
	{
                last_val = getNextSample()
		SPI2->DR = last_val;
	}

Havin getNextSample in the while loop, and double buffering, makes the behaviour even more unpredictable. Now the CHSEL always stays at 1 and the interrupt gets called after about 150 cycles again, then after about 1600 cycles and then again after about 1750 cycles. So all in all its the correct sample period of 3500 cycles, but too many interrupts.