1
votes

I'am using STM32F4 board with CMSIS library and I want setup an interrupt driven SPI, it means an interrupt is triggered each time a byte is sent by the SPI peripheral. The initiaisation function is as below:

void init_SPI1(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  GPIO_InitTypeDef GPIO_InitStruct;
  SPI_InitTypeDef SPI_InitStruct; 

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5|GPIO_Pin_4;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOA, &GPIO_InitStruct);

  // connect SPI1 pins to SPI alternate function
  //GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_SPI1);

  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);

  //Set chip select high 
  GPIOA->BSRRL |= GPIO_Pin_4; // set PE4 high

  // enable peripheral clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

  /* configure SPI1 in Mode 0 
   * CPOL = 0 --> clock is low when idle
   * CPHA = 0 --> data is sampled at the first edge
   */
  SPI_StructInit(&SPI_InitStruct); // set default settings 
  SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO lines
  SPI_InitStruct.SPI_Mode = SPI_Mode_Master;     // transmit in master mode, NSS pin has to be always high
  SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
  SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;        // clock is low when idle
  SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;      // data sampled at first edge
  SPI_InitStruct.SPI_NSS = SPI_NSS_Soft ; // set the NSS management to internal and pull internal NSS high
  SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4
  SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
  SPI_Init(SPI1, &SPI_InitStruct); 

  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  /* Enable SPI1*/
  SPI_Cmd(SPI1, ENABLE);
  return;
}

Then i just loopback SPI_MOSI to SPI_MISO and use a function that transmit the data (a very basic function that takes data from a buffer and then uses CMSIS functions for the transmission). The problem is that when the SPI interrupt is triggered, the program won't get out from the handler. the handler function looks lihe this:

void SPI1_IRQHandler()
{
 int a;
 a++;
 SPI_I2S_ClearITPendingBit(SPI1,SPI_I2S_IT_TXE);
 return;
}

Is it a problem in the CMSIS library, or I am not configuring the SPI interrupt in the good way? Please guide me to the right point.

EDIT

This is the function i use for data transmission

void write_SPI1()
{
 int i;

   for (i=0;i<SPI_TX_MAX; i++)
  {
   SPI_I2S_SendData(SPI1,spiTxBuff[i]);
   SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
  }
}

and the interruption deals with the data reception, it just fill spiRxBuff when receiving new data.

void SPI1_IRQHandler()
{
 while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)== RESET);

  spiRxBuff[spiRxCount]= SPI_I2S_ReceiveData(SPI1);
  spiRxCount++;
}

The variable used for Reception / Transmission are declared as below :

uint8_t spiTxBuff[SPI_TX_MAX] = {0x01,0x02,0x03,0x04,0x05,0x06};
uint8_t spiRxBuff[SPI_RX_MAX];
static volatile int spiRxCount= 0;  // used in SPI1_IRQHandler

what is strange now is that i'am having {0x01,0x02,0x03,0x05,0x06} in spiRxBuff instead of {0x01,0x02,0x03,0x04,0x05,0x06}, but using debug mode the data in spiRxBuff are correct, what goes wrong in your opinion ?

2

2 Answers

2
votes

You did not show the function doing the transmit, so I don't know exactly what are you trying to accomplish

Transmitting in a loop

If you are transmitting from a function (in a loop), then you don't need interrupts at all, just make sure that the TXE flag is set before you transmit. Note that you have to interleave sending and receiving somehow.

void SPI1_Transmit(uint8_t *send, uint8_t *receive, int count) {
  while(count-- > 0) {
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET) {
      if(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)==SET)
        *receive++ = SPI_I2S_ReceiveData(SPI1);
    }
    SPI_I2S_SendData(SPI1, *send++);
  }
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)!=SET) {
    /* wait for the last incoming byte */
  }
  *receive++ = SPI_I2S_ReceiveData(SPI1);
}

Transmitting from interrupt

The TXE interrupt flag is set as long as the SPI device is not busy sending. If you don't do something about it in the interrupt handler, it will trigger an interrupt immediately again and again. You can't clear it manually, but by transmitting another byte, and resetting the transmit interrupt enable flag before sending the last byte.

volatile int spi1_tx_count, spi1_rx_count;
uint8_t *spi1_tx_ptr;
volatile uint8_t *spi1_rx_ptr;
/* set these global variables before enabling interrupts */

void SPI1_IRQHandler() {
  if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_TXE) == SET) {
    if(--spi1_tx_count < 1)
      SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, DISABLE);
    SPI_I2S_SendData(SPI1, *spi1_tx_ptr++);
  }
  if(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) == SET) {
    *spi_rx_ptr++ = SPI_I2S_ReceiveData(SPI1);
    spi1_rx_count++;
  }
}

Using DMA

The above examples are using processor power and cycles for a task that can be handled by the DMA conroller alone. A lot of (if not all) processor cycles, if you are talking to a peripheral at 2 MBit/s.

See Project/STM32F4xx_StdPeriph_Examples/SPI/SPI_TwoBoards in the library for an example.

1
votes

Sorry, I haven't noticed at all that you've amended the question. Look like notifications are sent on new comments or answers, but not on edits.

There are multiple problems with your code. In write_SPI1(), I'd enable RX interrupt only once before the loop, there is no need to do it again and again. Also, you should definitely check whether the TX register is available before sending.

void write_SPI1() {
  int i;
  SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
  for (i=0;i<SPI_TX_MAX; i++) {
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)!=SET)
      ;
    SPI_I2S_SendData(SPI1,spiTxBuff[i]);
  }
}

It is however a bad idea to wait on a flag in the interrupt handler. If RXNE is the only possible interrupt source, then you can proceed straight to receiving.