Author Topic: SPI1 interrupt won't fire (STM32U5)  (Read 3435 times)

0 Members and 1 Guest are viewing this topic.

Offline davegravyTopic starter

  • Regular Contributor
  • *
  • Posts: 248
  • Country: ca
SPI1 interrupt won't fire (STM32U5)
« on: January 26, 2022, 06:07:25 am »
I'm trying to port my fairly minimal project that's HAL code generated by CubeMX to bare metal. I've stepped through the HAL code and reproduced all the register changes manually.

In my bare metal code the SPI transfer completes without ever firing an interrupt.  I also have an EXTI7 interrupt which fires reliably (and often, about once per 38us). I discovered that disabling the EXTI7 interrupt allows the SPI ISR to be reached.

In the HAL implementation my EXTI7 ISR (which only initiates the SPI xfer) is much more bloated, and there I don't have this issue. I suspect there is some other kind of conflict happening between the interrupts rather than EXTI7 hogging clock cycles. I took a look at EXTI mux, but I don't see any HAL calls to manage this so I don't think that could be it. How should I debug this?

PS yes, I know such frequent interrupts aren't a good method for controlling SPI. I will be looking at DMA shortly, but I just wanted to get my working HAL code into bare-metal form to learn with.

« Last Edit: January 26, 2022, 06:37:16 am by davegravy »
 

Offline ve7xen

  • Super Contributor
  • ***
  • Posts: 1221
  • Country: ca
    • VE7XEN Blog
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #1 on: January 26, 2022, 09:10:48 am »
First the obvious:

Is the desired SPI1 interrupt enabled in NVIC?

Are the desired SPI interrupts unmasked in the SPI peripheral?

Have you verified this with a debugger?

Which SPI interrupt(s) have you enabled, and are you certain their conditions are being met?

You're sure your handler has the correct 'magic' name? Not sure about CubeMX but often if the name is wrong, it will just compile it as a normal function and you get no warning that the linker has stubbed out the ISR. Remember that C is case sensitive.

The less obvious:

What are you actually doing in EXTI7, and what is triggering it? If it 'initiates the SPI xfer', is it possible it is clearing the enabled interrupt flags in the SPI peripheral e.g. by adding data to the FIFO? By default EXTI has higher priority than SPI1 and will be serviced first. If it's running frequently / has a long run time, or even worse, if its timing gets synchronized with the SPI peripheral, it's could be very likely it will be running when the SPI interrupt fires. If its behaviour clears the SPI interrupt flags, then I believe the pending SPI interrupt doesn't get serviced (or any flag checks you do in the ISR may fail unexpectedly).

As to how to approach this, I would disable EXTI7 and do something in main() with the SPI peripheral that you think should trigger the interrupt. If it's not getting triggered, get out the debugger and interrogate the SPI registers and interrupt flags as the transfer gets executed, probably you've misconfigured the peripheral.
73 de VE7XEN
He/Him
 
The following users thanked this post: davegravy

Offline davegravyTopic starter

  • Regular Contributor
  • *
  • Posts: 248
  • Country: ca
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #2 on: January 26, 2022, 01:49:08 pm »
First the obvious:

Is the desired SPI1 interrupt enabled in NVIC?

Are the desired SPI interrupts unmasked in the SPI peripheral?

Have you verified this with a debugger?

Which SPI interrupt(s) have you enabled, and are you certain their conditions are being met?

You're sure your handler has the correct 'magic' name? Not sure about CubeMX but often if the name is wrong, it will just compile it as a normal function and you get no warning that the linker has stubbed out the ISR. Remember that C is case sensitive.


I think I can eliminate these because the SPI interrupt fires successfully when I disable the EXTI7 interrupt, right?

Just to be sure I'm doing:

Code: [Select]
WRITE_REG(SPI1->IER, (SPI_IER_EOTIE | SPI_IER_DXPIE | SPI_IER_TXPIE | SPI_IER_RXPIE | SPI_IER_TXTFIE | SPI_IER_OVRIE | SPI_IER_UDRIE | SPI_IER_CRCEIE | SPI_IER_TIFREIE | SPI_IER_MODFIE));

prioritygroup = NVIC_GetPriorityGrouping();
NVIC_SetPriority(SPI1_IRQn, NVIC_EncodePriority(prioritygroup, 0, 0));
NVIC_EnableIRQ(SPI1_IRQn);

and then for my ISR function:

Code: [Select]
void SPI1_IRQHandler(void)
{
}


The less obvious:

What are you actually doing in EXTI7, and what is triggering it? If it 'initiates the SPI xfer', is it possible it is clearing the enabled interrupt flags in the SPI peripheral e.g. by adding data to the FIFO? By default EXTI has higher priority than SPI1 and will be serviced first. If it's running frequently / has a long run time, or even worse, if its timing gets synchronized with the SPI peripheral, it's could be very likely it will be running when the SPI interrupt fires. If its behaviour clears the SPI interrupt flags, then I believe the pending SPI interrupt doesn't get serviced (or any flag checks you do in the ISR may fail unexpectedly).

Here's the entirety of my EXTI7 ISR:
Code: [Select]
void EXTI7_IRQHandler(void)
{
//Clear EXTI7 interrupt
EXTI->FPR1 = (0x08);

uint8_t Size = 4;

//Set xfer size
MODIFY_REG(SPI1->CR2, SPI_CR2_TSIZE, Size);

//Enable SPI1 peripheral
SET_BIT(SPI1->CR1, SPI_CR1_SPE);

//Enable SPI interrupts
WRITE_REG(SPI1->IER, (SPI_IER_EOTIE | SPI_IER_DXPIE | SPI_IER_TXPIE | SPI_IER_RXPIE | SPI_IER_TXTFIE | SPI_IER_OVRIE | SPI_IER_UDRIE | SPI_IER_CRCEIE | SPI_IER_TIFREIE | SPI_IER_MODFIE));

/* Master transfer start */
SET_BIT(SPI1->CR1, SPI_CR1_CSTART);

The priority thing you mention is interesting, is it possible the generated HAL code is somehow achieving a different priority balance between the two interrupts than my code? I looked through it with this in mind but didn't see anything. I am setting priority in main() for the EXTI7 interrupt similar to how I do it for SPI in the EXTI7 ISR:

Code: [Select]
uint32_t prioritygroup;
prioritygroup = NVIC_GetPriorityGrouping();
NVIC_SetPriority(EXTI7_IRQn, NVIC_EncodePriority(prioritygroup, 0, 0));
NVIC_EnableIRQ(EXTI7_IRQn);

 

Offline davegravyTopic starter

  • Regular Contributor
  • *
  • Posts: 248
  • Country: ca
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #3 on: January 26, 2022, 06:50:55 pm »
OK, so I've been trying to understand how the NVIC registers work. I can't seem to find any detailed documentation on these - they aren't covered at all  in the STM32U5 TRM and the Cortex M33 TRM only mentions that they're there, not how they actually function - like what does a 1 at a particular bit position do?

Moving on, I took the HAL code and replaced only the EXTI7 ISR call to HAL_SPI_Receive_IT() with my minimal bare metal version:

Code: [Select]
if (hspi1.State == HAL_SPI_STATE_READY) {
uint8_t Size = 4;

//Set xfer size
MODIFY_REG(SPI1->CR2, SPI_CR2_TSIZE, Size);

//Enable SPI1 peripheral
SET_BIT(SPI1->CR1, SPI_CR1_SPE);

//Enable SPI interrupts
WRITE_REG(SPI1->IER, (SPI_IER_EOTIE | SPI_IER_DXPIE | SPI_IER_TXPIE | SPI_IER_RXPIE | SPI_IER_TXTFIE | SPI_IER_OVRIE | SPI_IER_UDRIE | SPI_IER_CRCEIE | SPI_IER_TIFREIE | SPI_IER_MODFIE));

//Master transfer start
SET_BIT(SPI1->CR1, SPI_CR1_CSTART);
}

With this the SPI interrupt did fire, so I think I can rule out my EXTI7 ISR as the source of the problem. It must be related to init of the peripherals or interrupts somehow.
« Last Edit: January 26, 2022, 06:52:46 pm by davegravy »
 

Offline ve7xen

  • Super Contributor
  • ***
  • Posts: 1221
  • Country: ca
    • VE7XEN Blog
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #4 on: January 26, 2022, 07:30:37 pm »
NVIC is part of the CPU core, so it's covered in the ARM documentation. Though honestly it's pretty shit documentation. ST might have some app notes or whatnot about it, but the canonical source is https://developer.arm.com/documentation/100230/0004/functional-description/nested-vectored-interrupt-controller/nvic-programmers-model/nvic-register-summary?lang=en

Quote
I think I can eliminate these because the SPI interrupt fires successfully when I disable the EXTI7 interrupt, right?

Sounds reasonable.

If you're setting both interrupts to the same priority, then if an ISR interrupt comes in while EXTI7 is being serviced, it will be queued until the ISR is complete. By default, each interrupt has a different priority (this is documented in the vector table in the STM32 reference manual). It's probably good practice (and likely what CubeMX does) to leave them unchanged unless you have reason to, the priorities generally 'make sense' for most applications. If you do change them though, set the SPI1 priority lower than EXTI7 if you want to guarantee it gets serviced (it will interrupt EXTI7), as long as you aren't turning off interrupts during EXTI7.

I think your EXTI7 ISR will at least clear TXC, but I'm not really sure what the peripheral does if you trigger a transfer without anything in the FIFO (writing to DR). I think unless you're operating it in RX-only mode, you need to stuff some data into the FIFO somewhere...

If it's handling setting up the next SPI transfer, what is triggering EXTI7? Shouldn't this be part of the SPI ISR, triggered when the transfer is complete or FIFOs have room for another packet?
73 de VE7XEN
He/Him
 
The following users thanked this post: davegravy

Offline davegravyTopic starter

  • Regular Contributor
  • *
  • Posts: 248
  • Country: ca
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #5 on: January 26, 2022, 10:31:26 pm »
External ADC is pulsing the Data Ready Line (DRL) high every ~38us which is my trigger to do an SPI Rx on the STM32. After each DRL pulse I have a small time window to read and then SCK has to go quiet (else digital switching causes noise).  I know I probably need to do this with DMA and maybe timers but I had interrupts working pretty quickly in HAL so I thought this would be an easier step 1.

I believe I have a strong clue as to the problem. I physically disconnected the DRL line and the EXTI7 interrupt is still constantly firing. Not sure why yet since I tried to copy the HAL interrupt inits exactly.

EDIT:

I was clearing the EXTI7 interrupt flag incorrectly, writing 0x8 to EXTI->FPR1 instead of 0x80.  :palm:  :-DD
« Last Edit: January 26, 2022, 11:04:49 pm by davegravy »
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17806
  • Country: fr
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #6 on: April 26, 2026, 05:01:03 pm »
Just working on a STM32U5 project and I'm running into issues with its SPI peripheral. Bumping this thread as it may increase the odds of having people who have actually worked on that specifically.

Not using HAL at all - so also baremetal here. I want to do blocking, full-duplex transfers, and that's where things, which were pretty simple with other STM32 series, get hairy. The SPI peripheral in the STM32U5 is an absolute nightmare. They probably meant to make it pretty smart, but it's horrific to use and the documentation is, uh, not great. There's basically a TX/RX FIFO behavior that looks almost untractable, especially for the RX FIFO.

There is the "RX FIFO packing Level" and the "FIFO Threshold", both interacting in insane ways. The bottom line is that it's impossible to know exactly how many bytes of data are ready to read in the RX FIFO unless you do a dance with several flags. When looking at the way they have implemented blocking full-duplex transfers in the HAL, it looks absolutely obnoxious.

The bottom line is it's hard to write a blocking function that will for certain read all received bytes.

It looks like it's much easier when using DMA transfers, as with DMA, all quirks of the SPI FIFOs are dealt with automatically. But using DMA (2 channels) for transmitting a few tens of bytes in a blocking way looks like using a bazooka for killing a fly. So if anyone has any experience with using SPI on this STM32U5 series, I'll be curious to hear about that.

 

Offline Pseudobyte

  • Frequent Contributor
  • **
  • Posts: 358
  • Country: us
  • Embedded Systems Engineer / PCB Designer
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #7 on: April 27, 2026, 02:12:09 pm »
The default value of CFG1->FTHLV and CFG1->DSIZE is set to 1 frames of 8 bits. RXP is asserted when there are N >= FTHLV frames in the FIFO.

You should just be able to drain the FIFO completely simply by polling SR->RXP which is asserted whenever there is data in the RX FIFO using the default config.
“They Don’t Think It Be Like It Is, But It Do”
 
The following users thanked this post: Alien Brother

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17806
  • Country: fr
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #8 on: April 27, 2026, 02:43:39 pm »
Yes, it looks like things get hairy when you start using a different width than 8-bit, a higher FIFO threshold, and so on. Then there is "packing" and a lot of quirks. This is one of the most insane SPI peripherals I've seen. ;D And the documentation looks like it was written by someone who was drunk.

One thing that bit me, although it was documented in the registers description, is that the transfer size (TSIZE) can only be set when the SPI peripheral is disabled. It's similar on other STM32 families (at least some others). Consequence is that if you attempt to set it when SPI is enabled, that appears to leave it in a broken state that will make it go bonkers.

All in all, I like the STM32U5 for its LPBAM and powerful peripherals, but the documentation is pretty awful. I know it's the case for other STM32 families too, but this one takes the cake.
 

Offline mark03

  • Frequent Contributor
  • **
  • Posts: 782
  • Country: us
Re: SPI1 interrupt won't fire (STM32U5)
« Reply #9 on: April 29, 2026, 05:26:05 pm »
Yes, it looks like things get hairy when you start using a different width than 8-bit, a higher FIFO threshold, and so on. Then there is "packing" and a lot of quirks. This is one of the most insane SPI peripherals I've seen. ;D And the documentation looks like it was written by someone who was drunk.
[...]
All in all, I like the STM32U5 for its LPBAM and powerful peripherals, but the documentation is pretty awful. I know it's the case for other STM32 families too, but this one takes the cake.

This sounds very much like the SPI peripheral in the STM32H7---it's probably the one they'll use in all new families.  Having pulled out a fair bit of hair getting it to work, I can empathize.  My best advice is to read the register descriptions with an attitude of "if I don't know what this is, I don't want it" and assume that, for the most part, default values are safest.  I also second using DMA, which I did in my driver.  The good news is once you have it working, you'll eventually forget the pain.  I'm sure the peripheral is capable of 10x more fancy features/performance than I'm using, but if they wanted folks to use it, they should have documented it, and they plainly did not.

The documentation for STM32 families is primarily a function of age.  Older families were reasonably well documented by early 2000s standards, by which I mean the documentation was pretty bad, but you could normally figure out what the writer was trying to say after several close readings.  Newer families are much worse, with genuinely unreadable prose that fails to make sense no matter how much embedded experience you have, or how many times you read it.  Welcome to the future :palm:  At some point the quality edge over Chinese MCUs will shrink to the point where you may as well use the cheaper option.  I mean, you're going to have to reverse-engineer the part anyway, right?  It would be ironic if this ends up being the main driver of RISC-V adoption.
« Last Edit: April 29, 2026, 05:30:55 pm by mark03 »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->