Author Topic: STM32 SPI interrupt  (Read 1886 times)

0 Members and 1 Guest are viewing this topic.

Offline HackedTopic starter

  • Contributor
  • Posts: 36
  • Country: hr
STM32 SPI interrupt
« on: October 28, 2022, 04:39:32 pm »
Hello all,

Again me :)
I am doing little project with STM32F030F4 microcontroller.
Application is simple, controlling WS2812 LEDs via SPI DMA.
In HAL I implemented it and it's all working as intended, transfer data via DMA to SPI and then when SPI transmission is over trigger SPI interrupt which stops DMA transfer.
I am trying to do the same via keil uvision and bare metal registry level programming and I partially succeeded. The part I can't implement is SPI interrupt.
Program enters infinite loop when enabling interrupt via NVIC_EnableIRQ(SPI1_IRQn) function. To be more clear, program never exits that function.
Systick interrupt works as intended, but all other interrupts produces same behavior as SPI which is strange.
Here is paste code:
https://pastecode.io/s/7138jq0f
I don't know what I am doing wrong, any help would be appreciated :)
 

Online voltsandjolts

  • Supporter
  • ****
  • Posts: 2370
  • Country: gb
Re: STM32 SPI interrupt
« Reply #1 on: October 28, 2022, 05:14:04 pm »
At a glance, you are perhaps a little confused about which interrupts to use for DMA.
First off, I would recommend to send stuff through spi using dma without any interrupts and go from there.
Something like this...although its for L051 so maybe some small differences.

Code: [Select]
static void dma_mem2spi(uint8_t *pBuf, uint32_t length) {
RCC->AHBENR |= RCC_AHBENR_DMA1EN; /* enable dma subsystem clock */
DMA1_Channel3->CCR  &= ~DMA_CCR_EN;
DMA1_CSELR->CSELR   &= ~DMA_CSELR_C3S;
DMA1_CSELR->CSELR   |= 0b0001 << DMA_CSELR_C3S_Pos;  /* select SPI1_TX for dma chan3 */
DMA1_Channel3->CPAR  = (uint32_t)(&(SPI1->DR)); /* peripheral address */
DMA1_Channel3->CMAR  = (uint32_t)pBuf; /* memory address */
DMA1_Channel3->CNDTR = length;
DMA1_Channel3->CCR   =  DMA_CCR_DIR /*DIR=1 mem2periph*/ ;
DMA1_Channel3->CCR   |= DMA_CCR_MINC /*mem inc*/;
DMA1->IFCR = DMA_ISR_TCIF3;
DMA1_Channel3->CCR  |= DMA_CCR_EN;

SPI1->CR2 |= SPI_CR2_TXDMAEN; /* start the transfer */
}

No interrupts needed for dma or the spi peripheral, spi setup could be as simple as:

Code: [Select]
  SPI1->CR1 = /*SPI_CR1_LSBFIRST | */ SPI_CR1_MSTR /* | SPI_CR1_BR_1 */ | SPI_CR1_BR_0 | SPI_CR1_CPHA | SPI_CR1_CPOL;
  SPI1->CR2 = SPI_CR2_SSOE;
  SPI1->CR1 |= SPI_CR1_SPE;

  IO_ALTFUNC(GPIOB, 3, 0); /* PB3 AF0 = SPI1 CLK */
  IO_ALTFUNC(GPIOB, 4, 0); /* PB4 AF0 = SPI1 MISO */    <---all for L051 to be checked!
  IO_ALTFUNC(GPIOB, 5, 0); /* PB5 AF0 = SPI1 MOSI */

In general I would say, use spi peripheral interrupts if not using dma. If using dma, don't use spi interrupts, optionally use dma interrupts. In simple cases at least.
« Last Edit: October 28, 2022, 05:32:19 pm by voltsandjolts »
 

Offline HackedTopic starter

  • Contributor
  • Posts: 36
  • Country: hr
Re: STM32 SPI interrupt
« Reply #2 on: October 28, 2022, 06:36:54 pm »
At a glance, you are perhaps a little confused about which interrupts to use for DMA.
First off, I would recommend to send stuff through spi using dma without any interrupts and go from there.
Something like this...although its for L051 so maybe some small differences.

Code: [Select]
static void dma_mem2spi(uint8_t *pBuf, uint32_t length) {
RCC->AHBENR |= RCC_AHBENR_DMA1EN; /* enable dma subsystem clock */
DMA1_Channel3->CCR  &= ~DMA_CCR_EN;
DMA1_CSELR->CSELR   &= ~DMA_CSELR_C3S;
DMA1_CSELR->CSELR   |= 0b0001 << DMA_CSELR_C3S_Pos;  /* select SPI1_TX for dma chan3 */
DMA1_Channel3->CPAR  = (uint32_t)(&(SPI1->DR)); /* peripheral address */
DMA1_Channel3->CMAR  = (uint32_t)pBuf; /* memory address */
DMA1_Channel3->CNDTR = length;
DMA1_Channel3->CCR   =  DMA_CCR_DIR /*DIR=1 mem2periph*/ ;
DMA1_Channel3->CCR   |= DMA_CCR_MINC /*mem inc*/;
DMA1->IFCR = DMA_ISR_TCIF3;
DMA1_Channel3->CCR  |= DMA_CCR_EN;

SPI1->CR2 |= SPI_CR2_TXDMAEN; /* start the transfer */
}

No interrupts needed for dma or the spi peripheral, spi setup could be as simple as:

Code: [Select]
  SPI1->CR1 = /*SPI_CR1_LSBFIRST | */ SPI_CR1_MSTR /* | SPI_CR1_BR_1 */ | SPI_CR1_BR_0 | SPI_CR1_CPHA | SPI_CR1_CPOL;
  SPI1->CR2 = SPI_CR2_SSOE;
  SPI1->CR1 |= SPI_CR1_SPE;

  IO_ALTFUNC(GPIOB, 3, 0); /* PB3 AF0 = SPI1 CLK */
  IO_ALTFUNC(GPIOB, 4, 0); /* PB4 AF0 = SPI1 MISO */    <---all for L051 to be checked!
  IO_ALTFUNC(GPIOB, 5, 0); /* PB5 AF0 = SPI1 MOSI */

I already accomplish sending data with DMA to SPI without interrupts.
The part that I can't figure is why program stalls when entering NVIC_EnableIRQ. Systick is the only interrupt that works, every else (Timer, SPI, DMA etc...) doesn't.

In general I would say, use spi peripheral interrupts if not using dma. If using dma, don't use spi interrupts, optionally use dma interrupts. In simple cases at least.
Ok, but same thing happens when using DMA interrupt, when program gets to NVIC_EnableIRQ function it stalls.
 

Online voltsandjolts

  • Supporter
  • ****
  • Posts: 2370
  • Country: gb
Re: STM32 SPI interrupt
« Reply #3 on: October 28, 2022, 06:56:13 pm »
OK, you got it working already without interrupts. That's a great start.

Sounds like when you enable the interrupt, the interrupt is being called endlessly, because you're not clearing the interrupt flag.
The SPI1_IRQHandler needs to clear the flag in the SPI1 peripheral that caused the interrupt.

But I would say, since you are using DMA, start the DMA transfer and enable the transfer complete interrupt, which needs a DMA interrupt handler. Don't use SPI interrupts, so SPI1_IRQHandler is not needed.

something like this maybe...

Code: [Select]
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-prototypes"
void DMA1_Channel2_3_IRQHandler(void)
{
  if((DMA1->ISR & DMA_ISR_TCIF3) == DMA_ISR_TCIF3)
  {
    DMA1->IFCR = DMA_ISR_TCIF3; /* clear the 'dma transfer complete' flag */
    /* do whatever...disable dma...set a flag var for background code to do next transfer or something */
  }
}
#pragma clang diagnostic pop


static void dma_mem2spi(uint8_t *pBuf, uint32_t length)
{
  RCC->AHBENR |= RCC_AHBENR_DMA1EN; /* enable dma subsystem clock */
  DMA1_Channel3->CCR  &= ~DMA_CCR_EN;
  DMA1_CSELR->CSELR   &= ~DMA_CSELR_C3S;
  DMA1_CSELR->CSELR   |= 0b0001 << DMA_CSELR_C3S_Pos;  /* select SPI1_TX for dma chan3 */
  DMA1_Channel3->CPAR  = (uint32_t)(&(SPI1->DR)); /* peripheral address */
  DMA1_Channel3->CMAR  = (uint32_t)pBuf; /* memory address */
  DMA1_Channel3->CNDTR = length;
  DMA1_Channel3->CCR   =  DMA_CCR_DIR /* DIR=1 mem2periph */ | DMA_CCR_MINC /* mem inc */;

  DMA1->IFCR = DMA_ISR_TCIF3; /* clear interrupt flag */

  NVIC_SetPriority(DMA1_Channel2_3_IRQn, 1); /* dma ch2 ch3 used for spi1 rx tx respectively */
  NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);

  DMA1_Channel3->CCR  |= DMA_CCR_EN | DMA_CCR_TCIE; /* enable transfer complete interrupt */

  SPI1->CR2 |= SPI_CR2_TXDMAEN; /* start the transfer */
}

Same simple spi setup I gave above. No need for spi interrupts.

« Last Edit: October 29, 2022, 11:36:46 am by voltsandjolts »
 

Offline HackedTopic starter

  • Contributor
  • Posts: 36
  • Country: hr
Re: STM32 SPI interrupt
« Reply #4 on: October 28, 2022, 08:36:49 pm »
That solved my problem.
I didn't know that I need to clear interrupt flag.  |O
I used DMA interrupt as you suggested.
Thank you for help :)
 
The following users thanked this post: voltsandjolts


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf