cancel
Showing results for 
Search instead for 
Did you mean: 

H7 migration, send SPI without using HAL

jean
Senior

Hello dear community!

I've migrated from F7 to H7, everything fine so far.

To save CPU time I don't want to use HAL for sending two SPI bytes.

My F7 transmit SPI code was very simple and perfectly working :

SPI6->DR = byte_0;
SPI6->DR = byte_1;

But on the H7, the same code doesn't work :

SPI6->TXDR = byte_0;
SPI6->TXDR = byte_1;

In both F7 and H7 projects, this is always working (but time-consuming) :

HAL_SPI_Transmit(&hspi6, const_cast<uint8_t*>(buffer), 2, 1000);

Do you know why bypassing HAL is not possible on the H7 ?

Thanks!

Jean

10 REPLIES 10
Pavel A.
Evangelist III

Perhaps because in H7 SPI data register is sensitive to the width of access. To write a byte, use 8-bit access.

Without HAL, use LL.

--pa

The 'H7 SPI is an overly complex beast.

The master at full duplex (or in any transmit-only mode) starts to communicate when the SPI

is enabled, the CSTART bit is set and the TxFIFO is not empty, or with the next write to

TxFIFO.

JW

*((volatile uint8_t *)&SPI6->TXDR) = byte_0; // 8-bit transaction on the data bus

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
jean
Senior

Thanks a lot for your helpful answers!

I ended up by using the low level driver and it working!

LL_SPI_TransmitData8(SPI6, byte_0);
LL_SPI_TransmitData8(SPI6, byte_1);
LL_SPI_StartMasterTransfer(SPI6);

Okay so I looked it up... yep, no surprise, it's the CSTART bit:

__STATIC_INLINE void LL_SPI_StartMasterTransfer(SPI_TypeDef *SPIx)
{
  SET_BIT(SPIx->CR1, SPI_CR1_CSTART);
}

If you don't use transactions, i.e. you keep TSIZE=0, it's enough to set it once, at the beginning, and then the SPI should behave in the same way as in older models.

JW

PS. You may want to read AN5543. It's poorly written - CSTART is not mentioned there and TSIZE is mentioned only marginally - but at least there's a table with SPI versions in different STM32 models.

Pavel A.
Evangelist III

I'm struggling with this 'H7 SPI too. Learned to use these "transfers" - but was happy too soon...

After few "transfers", they start failing. The end of transfer flag SPI_SR_EOT fails to go up after a reasonable time, and I don't understand why.

Using the simplest mode: Motorola, mode 0, unit=byte, no CRC or auto NSS manipulations.

Noticed some errata item about the EOT but don't quite understand what it means.

Tried to disable & enable the SPI after every transfer, as they recommend - this only made situation worse.

So I'm going to get rid of "transfers", set SPI_CR2_TSIZE to 0 (another recommended workaround)...

--pa

P.S. The "transfer-less" mode seems to work well in my case.

This mode is demonstrated in "BSP" example for Nucleo H743 in the Cube H7.

Pavel A.
Evangelist III

 I have tried both "transfer" (by setting TSIZE to 1 to send a single 8-bit data frame) and "transferless" modes. Unfortunately, neither seem to work.

"Transferless" works for me on 'H753 and it is used in the "BSP" Cube example: 

Projects/NUCLEO-H743ZI/Examples/BSP/Src/stm32h7xx_nucleo_bus.c

Start from the example, use a scope to verify.

My SPI init code snippet: (fast & dirty, mostly borrowed from the example. not something to boast about) :

 

 

#include "stm32h7xx_ll_spi.h"
....
SPI_TypeDef *SPIx = SPI1;
.....

  /* Enable SPI1 Clock */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);

  /* Pulse reset of SPI1 */
  LL_APB2_GRP1_ForceReset(LL_APB2_GRP1_PERIPH_SPI1);
  LL_APB2_GRP1_ReleaseReset(LL_APB2_GRP1_PERIPH_SPI1);

............
  LL_SPI_InitTypeDef SPI_InitStruct = {};
  SPI_InitStruct.Mode              = LL_SPI_MODE_MASTER;
  SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
  /* The connected device needs: MODE0, CPOL=0, CPHA=0 */
  SPI_InitStruct.ClockPhase        = LL_SPI_PHASE_1EDGE;
  SPI_InitStruct.ClockPolarity     = LL_SPI_POLARITY_LOW;
  SPI_InitStruct.BitOrder          = LL_SPI_MSB_FIRST;
  SPI_InitStruct.DataWidth         = LL_SPI_DATAWIDTH_8BIT;
  SPI_InitStruct.NSS               = LL_SPI_NSS_SOFT; // no NSS, using a GPIO as SS!
  SPI_InitStruct.CRCCalculation    = LL_SPI_CRCCALCULATION_DISABLE;
  SPI_InitStruct.BaudRate          = SPIx_GetBitrateDivisor(SPIx, g_SPI_bitrate); // HCLK divisor
  if (0 != LL_SPI_Init(SPIx, &SPI_InitStruct)) {
      printf("SPI init failed!\n");
      return;
  }

  /* This locks SPI pins in AF mode when the SPI is disabled, to avoid glitches on CLK */
  /* Temporary disable may be required to recover from weird states; see errata - pa01 */
  LL_SPI_EnableGPIOControl(SPIx);
  LL_SPI_DisableMasterRxAutoSuspend(SPIx);

  /* Set transfer size = 0 (not using 'transfers') */
  LL_SPI_SetTransferSize(SPIx, 0);
  LL_SPI_SetFIFOThreshold(SPIx, LL_SPI_FIFO_TH_01DATA);

  /* Enable the SPI */
  LL_SPI_Enable(SPIx);
  /* Start master in automatic (no-transaction) mode */
  LL_SPI_StartMasterTransfer(SPIx);

 

 

Note at the end the LL_SPI_StartMasterTransfer call, mentioned by Jan W.

Good luck!

Pavel A.
Evangelist III

I forgot to insert read & write functions. Will add later.  Please meanwhile try to make the example work, it uses the most simple, reliable mode.

Unfortunately bare register-based code is hard to read, most people use some library. But others use different libraries and don't grok yours one. It's complicated. 

Hi again,

I have successfully identified the issue: the default kernel clock source for SPI2 is pll_ck, but I don't have PLL set up, so the SPI module was not getting any clock signal. The issue was resolved by changing the SPI clock source to per_ck.

Thank you for your time, Pavel, I really appreciate it. I apologize for possible inconvenience. Good luck with all of your future endeavors!
-Ivan