Skip to main content
FLewz.1
Associate III
November 30, 2020
Solved

STM32F446 I2S stuck on Busy

  • November 30, 2020
  • 11 replies
  • 3489 views

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?

This topic has been closed for replies.
Best answer by waclawek.jan

And can you see the sample frequency on LRCK?

Try to measure directly on the pin (suspecting bad solder joints) and check for shorts - unless this is a "known good" board such as Nucleo or Disco. Try to set the pin to GPIO output and wiggle it, measuring level on the output.

Coincidentally I'm working on I2S1 on a 'F446 and do have PA4/PA5 as LRCK/BICK, and can confirm that yes they do work when AFR=5.

If you're stuck, read out and check/post all the relevant registers content.

JW

11 replies

waclawek.jan
Super User
November 30, 2020

Do you have the I2S2 (yes, confusingly named, referring to "I2S on APB2", which is the case of SPI1) clock set up in RCC_DCKCFGR, and if PLL clock is used, do you have that PLL up and running?

Then, observe the pins using oscilloscope/LA for clock activity.

Finally, if everything fails, read out all the SPI/I2S and relevant RCC and GPIO registers content and check/post.

Also, observe the 2.1.5 Delay after an RCC peripheral clock enabling erratum.

JW

PS. Style: don't do changes gradually into a register if there's no specific need for it. Read it into a temporary variable and do the changes there, or simply write a single expression generating the literal to be written into the register.

FLewz.1
FLewz.1Author
Associate III
December 1, 2020

> Do you have the I2S2 (yes, confusingly named, referring to "I2S on APB2", which is the case of SPI1) clock set up in RCC_DCKCFGR, and if PLL clock is used, do you have that PLL up and running?

Oh so that explains why nothing would work, when trying to get the clock configured manually, because I assumed I had to write to I2S1!

I got frustrated and took the clock config from a cube generated project. Now it is not stuck on the busy state anymore, but I still can't get sound out.

Unfortunately I only have a multimeter to measure frequency. On the word clock I get a reading of ~49.8 kHZ which is not good, but would suffice for me to get a sound out of it. I can't get a reading from the bit clock, but since the word clock is derived from it, this should be ok as well,right?

Edit: I could verify on a Teensy 4.0 board, that my I2s module works, which I doubted at this point, and that my multimeter was capable of measuring into the tens of Mhz.

So now the problem is, that I don't see a frequency on the BCLK pin PA5, so it seems there is something wrong here. I upped the speed to highest, but no luck.

The register values for the relevant GPIOA Pins (4,5,7, all same values)

MODER - 0x02

OTYPER - 0x00

PUPDR - 0x00

ODR - 0x00

LCKR - 0x00

AFRL - 0x05

Edit2:

> The I2Sx clock is always the system clock

What is this phrase on p. 878 of the reference manual 0390 supposed to mean? I was led to believe, that the I2S Clock is my main clock i.e. 144 MHz. But I can see, that in the clock configuration there is a perippheral clock for the I2S and SAI Modules

waclawek.jan
waclawek.janBest answer
Super User
December 1, 2020

And can you see the sample frequency on LRCK?

Try to measure directly on the pin (suspecting bad solder joints) and check for shorts - unless this is a "known good" board such as Nucleo or Disco. Try to set the pin to GPIO output and wiggle it, measuring level on the output.

Coincidentally I'm working on I2S1 on a 'F446 and do have PA4/PA5 as LRCK/BICK, and can confirm that yes they do work when AFR=5.

If you're stuck, read out and check/post all the relevant registers content.

JW

FLewz.1
FLewz.1Author
Associate III
December 1, 2020

So I just tried to pick I2S2, and everything works perfectly. There must be something wrong with my pins, although this is a nucleo board and I am fairly sure I didn't put multiple functions unto one pin.

waclawek.jan
Super User
December 1, 2020

PA5 is the green LED, so it should be easy to test whether it's alive.

> The I2Sx clock is always the system clock

Yes, this is wrong.

Unfortunately, ST's documentation is not famous for being clear and concise. This appears to be some leftover from a different STM32 model's description - I see this sentence in 'F0 RM (where it's true). A similar sentence is in 'F3's RM0316, where it's not true.

@Imen DAHMEN​ , can this please be chalked up for correction? Thanks.

JW

ST Technical Moderator
December 2, 2020

Hello @Community member​ ,

Thank you for pointing this out to me.

I raised it internally for correction.

Thanks @FLewz.1​  and @Community member​  for your contribution.

Imen

In order to give better visibility on the answered topics, please click on 'Best answer' on the reply which solved your issue or answered your question. Thanks
waclawek.jan
Super User
December 2, 2020

Thanks, Imen.

Jan

FLewz.1
FLewz.1Author
Associate III
December 1, 2020

The green LED was always on, and I didn't get a frequency on that pin, so it was always low. All of this while having the pin in AF5.

FLewz.1
FLewz.1Author
Associate III
December 1, 2020

To piggyback on this: What is the configuration to get the behaviour described in the RM as '16-bit data frame extended to 32-bit channel frame'?

Right now I have CHLEN to 0 and DATLEN to 0, so that Data length and channel lenght are both 16 bit, however measuring the time between two TXE interrupts I get a big variation in cycles from ~1550 to ~1670 at a SysClock of 180 MHz. The BCLK is correct at 48 kHz.

waclawek.jan
Super User
December 2, 2020

> 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

FLewz.1
FLewz.1Author
Associate III
December 2, 2020

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.

waclawek.jan
Super User
December 2, 2020

> 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

FLewz.1
FLewz.1Author
Associate III
December 2, 2020

> 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
Principal III
December 2, 2020

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.

FLewz.1
FLewz.1Author
Associate III
December 2, 2020

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?

waclawek.jan
Super User
December 3, 2020

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

JW