Currently working on STM32F407ZET6 MCU. I am trying to generate an interrupt based SPI communication with nordic nrf24l01+ chipset. The MCU is in Master mode. I am able to establish an SPI communication but looks like it is using polling method.

I use the api uint8_t SPI1_send(uint8_t data) to send and receive data with SPI.

Need help with generating code for Interrupt driven SPI. If I use the interrupt driven SPI, do I still need to use the SPI1_send() API that I mentioned?

void init_SPI1(void){

    GPIO_InitTypeDef GPIO_InitStruct;
    SPI_InitTypeDef SPI_InitStruct;

    // enable clock for used IO pins
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    /* configure pins used by SPI1
     * PA5 = SCK
     * PA6 = MISO
     * PA7 = MOSI
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
    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_PinSource5, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);

    // enable clock for used IO pins

    /* Configure the chip select pin
       in this case we will use PE7 , NOW PA4 */
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIOA->BSRRL |= GPIO_Pin_4; // set PE7 high; NOW PA4

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

    /* configure SPI1 in Mode 0 
     * CPOL = 0 --> clock is low when idle
     * CPHA = 0 --> data is sampled at the first edge
    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 | SPI_NSSInternalSoft_Set; // set the NSS management to internal and pull internal NSS high
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // SPI frequency is APB2 frequency / 4 = 84Mhz/4 = 21Mhz 
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
    SPI_Init(SPI1, &SPI_InitStruct); 

    SPI_Cmd(SPI1, ENABLE); // enable SPI1     

/* This funtion is used to transmit and receive data 
* with SPI1
*           data --> data to be transmitted
*           returns received value
uint8_t SPI1_send(uint8_t data){

    SPI1->DR = data; // write data to be transmitted to the SPI data register
    while( !(SPI1->SR & SPI_I2S_FLAG_TXE) ); // wait until transmit complete
    while( !(SPI1->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
    while( SPI1->SR & SPI_I2S_FLAG_BSY ); // wait until SPI is not busy anymore
    return SPI1->DR; // return received data from SPI data register  

The above code is self explanatory, it has init SPI function and Send/Receive function. I can add how I call these functions in main() also. When I need to send or receive data through SPI to nordic chip, I use the function SPI1_send(). As I said, If I use interrupt mode, do I still need to use the function SPI1_send() as ISR will take care of sending/receiving I suppose.

  • \$\begingroup\$ Please edit your question and include your current code regarding the SPI configuration and usage. \$\endgroup\$ – Bence Kaulics Jan 25 '16 at 14:34
  • \$\begingroup\$ I will edit my question in about a day as I donot have the source code with me now, sorry for that \$\endgroup\$ – Pradeep Ch Jan 25 '16 at 15:50

The HAL provides this functionality.

You would call HAL_SPI_Transmit_IT with a pointer to the SPI structure, a pointer to the data buffer, and the size of the data buffer. This will send your whole buffer, interrupt style.

Obviously this requires you to include/switch over to the HAL, and configure and initialize it properly.

See ST User Manual UM1725 for a description of the HAL SPI library.

  • \$\begingroup\$ Currently, I am using Standard Peripheral library \$\endgroup\$ – Pradeep Ch Jan 26 '16 at 21:51

I have looked at the example and there is no need to call your own SPI1_Send() function since everything is handled in the ISR. aTxbuffer and aRxbuffer are used to hold data until they are ready to be sent (TX) or until the STM is ready to read received data (RX). The buffer size is chosen depending on how many bytes you transmit or receive in any burst and also how fast you read data that you have received. It works this way:

The STM keeps track of its current position within both buffers (ubRxIndex and ubTxIndex); once the SPI Data register is empty (TXE), it means the previous byte has been transmitted and the STM jumps to the ISR and writes the next byte from the aTxbuffer using SPI_I2S_SendData(), after which it increments ubTxIndex. Once the SPI data register is NOT empty (RXNE), the STM jumps to the ISR and reads the byte from the SPI register into aRxbuffer, after which it increments ubRxIndex. This continues until all the bytes in the TX buffer have been transmitted and the RX buffer is filled with received bytes.

I don't think its possible to eliminate polling entirely; even using these buffers, any time you send a byte, you still need to check if anything is available() before you read. Also if you want to send, you need to be sure that the previous byte is already gone from the TX buffer. The only difference is that now you will be polling variables you created, which will tell you for CERTAIN (since they are based on interrupts and not pesky flags) when it is time to read or write. If you still want to go ahead with this, you could simply modify the length of the TX and RX buffers to 1 byte each. Whenever you want to send to the NRF, you reset ubTxindex to zero and simply write to the 1-byte TX buffer. As soon as the Data register is empty, the data will be sent; you have to wait for this though, else you may try to send another byte without reading the reply for the first byte. The same goes for receiving; you have to wait till the ubRxIndex has been incremented in the ISR before you reset the ubRxIndex to zero and then read a byte from the buffer.

To do this, in main.h, change BUFFERSIZE to 1. Then setup SPI and enable the interrupts like in SPI_config() and main() in main.c. Then define these variables and functions in main.c:

uint8_t ubTxIndex = 0;
uint8_t ubRxIndex = 0;

uint8_t SPI_Send(uint8_t data){
    ubTxIndex = 0    // set TX buffer to the start
    ubRxIndex = 0  // reset the RX index to 0
    aTxBuffer[ubTxIndex] = data;  // write the byte into the buffer
    SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, ENABLE);  //enable tx int
    SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);  //enable rx int in case it was disabled
    while (ubRxIndex < BUFFERSIZE);  // wait for a byte to be received into the RX buffer
    return aRxBuffer[ubRxIndex - BUFFERSIZE];

You can use this to send and receive bytes from the NRF module, without checking the usual SPI flags which aren't that reliable anyways.

  • \$\begingroup\$ SPI1_Send() function is not specified anywhere, how does it get called when an ISR is generated, I could have used a different name for this function. \$\endgroup\$ – Pradeep Ch Jan 25 '16 at 15:49
  • \$\begingroup\$ Enabling interrupts will enable you know when the TX register is empty; you can then send a byte in the ISR. You said you are using an API? Do you mean you created your own SPI1_Send, and you arent using the peripheral libraries? \$\endgroup\$ – TisteAndii Jan 25 '16 at 15:58
  • \$\begingroup\$ When we can send a byte in the ISR, do we still use this SPI1_Send() function. Yes, I was using the peripheral libraries. \$\endgroup\$ – Pradeep Ch Jan 25 '16 at 16:39
  • \$\begingroup\$ To send a byte ANYWHERE, that byte must somehow be transferred into the TX register. You can do this either with the peripheral library functions or you can write the byte into the register yourself. If you want to write to the register yourself, you can look at SPI1_Send in the SPI peripheral header file to see how its done. \$\endgroup\$ – TisteAndii Jan 25 '16 at 16:48
  • \$\begingroup\$ In SPI1_Send(), the byte is written to SPIx->DR while transmitting, the same is happening in the ISR at function call SPI_I2S_SendData(SP11, Data). So, it means I can comment the SPI1_Send() function. The code for ISR is follows: \$\endgroup\$ – Pradeep Ch Jan 26 '16 at 22:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.