STM32 SPI Slow Compute
Asked Answered
W

2

6

I'm using a STM32F4 and its SPI to talk to a 74HC595 like in this tutorial. Difference is for starters I'm using the non-DMA version for simplicity. I used STMCubeMX to configure SPI and GPIO

Issue is: I'm not getting the latch PIN, that I set to PA8 to toggle during transmission fast enough.

enter image description here

The code I'm using:

        spiTxBuf[0] = 0b00000010;

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);


        HAL_SPI_Transmit(&hspi1, spiTxBuf, 1, HAL_MAX_DELAY);
//        while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);

        HAL_Delay(1);

Things I tried:

  1. Set the Maxium Output Speed of the Pin PA8 to Very Fast enter image description here

  2. Wait for the SPI to be done (see commented line above)

  3. Use DMA for the SPI as in here, that made it actually slower.

How do i get that to toggle faster? Should i create and interrupt for when the SPI is done and set the latch there?

Wagonette answered 4/6, 2019 at 8:54 Comment(2)
Which controller is that, on what clock frequency? What is the requirement, the acceptable delays before/after?Aegean
is @berendi wrote in his answer if you use HAL be prepared those functions to be slow.Evangelista
A
9

How do i get that to toggle faster?

If possible, use the hardware NSS pin

Some STM32 controllers can toggle their NSS pin automatically, with a configurable delay after transmission. Check the Reference Manual, if yours is one of these, reconnect the latch pin of the shifter to the SPIx_NSS pin on the MCU.

Don't use HAL

HAL is quite slow and overcomplicated for anything with tight timing requirements. Don't use it.

Just implement the SPI transmit procedure in the Reference Manual.

SPI->CR1 |= SPI_CR1_SPE; // this is required only once
GPIOA->BSRR = 1 << (8 + 16);
*(volatile uint8_t *)&SPI->DR = 0b00000010;
while((SPI->SR & (SPI_SR_TXE | SPI_SR_BSY)) != SPI_SR_TXE)
    ;
GPIOA->BSRR = 1 << 8;
Aegean answered 4/6, 2019 at 10:8 Comment(10)
Replace SPI with the actual SPI controller, e.g. SPI1 or SPI2 or whichever you actually use.Aegean
yeah figured that out :) Cant find the SPI_SR_SPE flag though, google yields nothing as well. What does it do?Wagonette
@Wagonette Sorry, it was a typo. Code is fixed now.Aegean
Hmm, doesnt work with this code: SPI1->SR |= SPI_CR1_SPE; // this is required only once GPIOA->BSRR = 1 << (8 + 16); *(volatile uint8_t *)SPI1->DR = 0b00000010; while((SPI1->SR & (SPI_SR_TXE | SPI_SR_BSY)) != SPI_SR_TXE); GPIOA->BSRR = 1 << 8;Wagonette
@Wagonette take a close look at the first lineAegean
yeah i'm executing that before the while loop :) Adding the 1 to CR1 didn't help either.Wagonette
That did it! Last question: how would I go about sending multiple bytes, in a for loop?Wagonette
Wait for ((SPI->SR & SPI_SR_TXE) != 0) before sending each byte.Aegean
Hmm that doesnt seem to work. while((SPI1->SR & (SPI_SR_TXE | SPI_SR_BSY)) != SPI_SR_TXE); in the for loop works but costs time between each byte. Your snippet only sends 2 bytes out of 5.Wagonette
The last check is only required after sending the last byte, i.e. it's the prerequisite of raising the latch signal. Read the Data transmission and reception procedures section in the Reference Manual.Aegean
W
6

So after some input I figured out a solution where I redefined the HAL functions and basically threw everything out that was slow:

void HAL_GPIO_WritePin_Fast(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{

    if(PinState != GPIO_PIN_RESET)
    {
        GPIOx->BSRR = GPIO_Pin;
    }
    else
    {
        GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
    }
}

HAL_StatusTypeDef HAL_SPI_Transmit_fast(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
//    uint32_t tickstart = 0U;
    HAL_StatusTypeDef errorcode = HAL_OK;

    /* Check Direction parameter */

    /* Process Locked */
    __HAL_LOCK(hspi);

    /* Init tickstart for timeout management*/
//    tickstart = HAL_GetTick();

//    if(hspi->State != HAL_SPI_STATE_READY)
//    {
//        errorcode = HAL_BUSY;
//        goto error;
//    }
//
//    if((pData == NULL ) || (Size == 0))
//    {
//        errorcode = HAL_ERROR;
//        goto error;
//    }

    /* Set the transaction information */
    hspi->State       = HAL_SPI_STATE_BUSY_TX;
    hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
    hspi->pTxBuffPtr  = (uint8_t *)pData;
    hspi->TxXferSize  = Size;
    hspi->TxXferCount = Size;

    /*Init field not used in handle to zero */
    hspi->pRxBuffPtr  = (uint8_t *)NULL;
    hspi->RxXferSize  = 0U;
    hspi->RxXferCount = 0U;
    hspi->TxISR       = NULL;
    hspi->RxISR       = NULL;

    /* Configure communication direction : 1Line */
    if(hspi->Init.Direction == SPI_DIRECTION_1LINE)
    {
        SPI_1LINE_TX(hspi);
    }

#if (USE_SPI_CRC != 0U)
    /* Reset CRC Calculation */
  if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);
  }
#endif /* USE_SPI_CRC */

    /* Check if the SPI is already enabled */
    if((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
    {
        /* Enable SPI peripheral */
        __HAL_SPI_ENABLE(hspi);
    }

    /* Transmit data in 16 Bit mode */
    if(hspi->Init.DataSize == SPI_DATASIZE_16BIT)
    {
        if((hspi->Init.Mode == SPI_MODE_SLAVE) || (hspi->TxXferCount == 0x01))
        {
            hspi->Instance->DR = *((uint16_t *)pData);
            pData += sizeof(uint16_t);
            hspi->TxXferCount--;
        }
        /* Transmit data in 16 Bit mode */
        while (hspi->TxXferCount > 0U)
        {
            /* Wait until TXE flag is set to send data */
            if(__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
            {
                hspi->Instance->DR = *((uint16_t *)pData);
                pData += sizeof(uint16_t);
                hspi->TxXferCount--;
            }
            else
            {
//                /* Timeout management */
//                if((Timeout == 0U) || ((Timeout != HAL_MAX_DELAY) && ((HAL_GetTick()-tickstart) >=  Timeout)))
//                {
//                    errorcode = HAL_TIMEOUT;
//                    goto error;
//                }
            }
        }
    }
        /* Transmit data in 8 Bit mode */
    else
    {
        if((hspi->Init.Mode == SPI_MODE_SLAVE)|| (hspi->TxXferCount == 0x01))
        {
            *((__IO uint8_t*)&hspi->Instance->DR) = (*pData);
            pData += sizeof(uint8_t);
            hspi->TxXferCount--;
        }
        while (hspi->TxXferCount > 0U)
        {
            /* Wait until TXE flag is set to send data */
            if(__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
            {
                *((__IO uint8_t*)&hspi->Instance->DR) = (*pData);
                pData += sizeof(uint8_t);
                hspi->TxXferCount--;
            }
            else
            {
//                /* Timeout management */
//                if((Timeout == 0U) || ((Timeout != HAL_MAX_DELAY) && ((HAL_GetTick()-tickstart) >=  Timeout)))
//                {
//                    errorcode = HAL_TIMEOUT;
//                    goto error;
//                }
            }
        }
    }





    /* Clear overrun flag in 2 Lines communication mode because received is not read */
    if(hspi->Init.Direction == SPI_DIRECTION_2LINES)
    {
        __HAL_SPI_CLEAR_OVRFLAG(hspi);
    }
#if (USE_SPI_CRC != 0U)
    /* Enable CRC Transmission */
  if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
     SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);
  }
#endif /* USE_SPI_CRC */

    if(hspi->ErrorCode != HAL_SPI_ERROR_NONE)
    {
        errorcode = HAL_ERROR;
    }

    error:
    hspi->State = HAL_SPI_STATE_READY;
    /* Process Unlocked */
    __HAL_UNLOCK(hspi);
    return errorcode;
}

That's definitely an option but probably not the most elegant :) It sped up the time dramatically though:

enter image description here

Edit: berendis solution is even faster:

enter image description here

Heres the code for multiple bytes:

spiTxBuf[0] = 0b00000110;
spiTxBuf[1] = 0b00000111;
spiTxBuf[2] = 0b00000111;
spiTxBuf[3] = 0b00000111;
spiTxBuf[4] = 0b00000111;

GPIOA->BSRR = 1 << (8 + 16);
for(int i=0; i<5; i++){
    *(volatile uint8_t *)&SPI1->DR = spiTxBuf[i];
    while ((SPI1->SR & SPI_SR_TXE) == RESET);

}

while((SPI1->SR & (SPI_SR_TXE | SPI_SR_BSY)) != SPI_SR_TXE);
GPIOA->BSRR = 1 << 8;
HAL_Delay(100);
Wagonette answered 4/6, 2019 at 11:34 Comment(5)
This is some serious bloatware... all you should need to do for simplex communication is: setup SPI including baudrate and clock, once. Set /SS pin. Start of loop. Write to data register. Wait for status flag. End of loop. Clear /SS pin.Irradiation
I've bookmarked this Q&A now because it shows exactly how inefficient HAL is. Thanks for the scope shots:)Aegean
youre welcome, if I get your code to run, ill make a third :)Wagonette
Another typo fixed in it (missing &)Aegean
Yup, that worked, you can steal that last screenshot for your answer :)Wagonette

© 2022 - 2024 — McMap. All rights reserved.