2026-04-30 2:56 AM - last edited on 2026-04-30 3:10 AM by mƎALLEm
Hi all,
I’m debugging a reproducible SPI6 slave issue on STM32H753ZI and would like feedback from anyone familiar with H7 SPI v2 behaviour.
Initially master read 0xFF consistently; after removing pull-up resistors on the MISO path, reads became 0x00.
This was line-bias behaviour, not a functional fix:
So the core issue remained: slave not reliably driving/qualifying frames.
I instrumented both HAL and LL test images (counters + CR1/CFG1/CFG2/SR + pin-level edge counters).
No meaningful MISO data recovery in continuous host bursts.
Using one-byte HAL_SPI_TransmitReceive_IT single-shot:
Unsynchronised arm (arm regardless of current NSS level)
-> irq=0, cplt=0 (no qualified frame)
Synchronised arm (wait until NSS is inactive/high, then arm)
-> irq>0, cplt=1, expected SCK/NSS edge counts for one frame
This strongly indicates the failure is linked to NSS edge qualification timing in H7 SPI v2 slave:
Is this expected/known behaviour on H7 SPI v2 slave, and is the recommended robust approach:
If needed, I can share concise register dumps and test logs.
2026-04-30 3:15 AM
Hello @S_Chou and welcome to the St community,
What do you mean by "SPI v2"?
2026-04-30 3:24 AM
second-generation SPI peripheral IP block used in STM32H7 family (different from older F4/F7/L4 “classic SPI”).
2026-04-30 3:29 AM - edited 2026-04-30 3:29 AM
Is there something in the documentation telling this is a 2nd generation? it could be a 3rd generation or may be another generation ..
Just need to know the source of that information.
2026-04-30 3:44 AM
Hello,
Thank you for the quick reply.
You are right to ask for the exact source. After checking, I could not find any official ST document (RM0433, UM2217, or the H7 training material) that explicitly calls the SPI peripheral in the STM32H7 series “SPI v2” or “2nd generation”.
The term “SPI v2” appears to be community and driver shorthand used because:
So in practice, when people say “H7 SPI v2”, they mean the SPI peripheral architecture introduced with the STM32H7 series (the one described in RM0433 chapters 54–55 with the CFG1/CFG2 registers), not an official ST version number like “Rev 2.0”.
Would you prefer that I avoid the “v2” label in future posts and simply say “STM32H7 SPI (RM0433)” instead?
Thank you again for your time.
2026-04-30 3:48 AM - edited 2026-04-30 3:48 AM
@S_Chou wrote:
Would you prefer that I avoid the “v2” label in future posts and simply say “STM32H7 SPI (RM0433)” instead?
Yes indeed we need to avoid this versioning number on the peripherals to avoid confusions. By telling which MCU part number and which peripheral you are using members have idea about the environment you have.
2026-04-30 4:07 AM
Hello,
I am debugging a reproducible SPI6 slave issue on a STM32H753ZI (Nucleo-H753ZI) with a Raspberry Pi 5 acting as SPI master.
Hardware
Important note on observed data
Initially the master saw constant 0xFF. After removing the 10 kΩ pull-up resistors on MISO, it became constant 0x00. This was only line bias (Hi-Z + pull-up → 0xFF; Hi-Z without pull-up → 0x00). The real problem is that the slave is not reliably driving MISO or qualifying frames.
Test that revealed the root cause
Using a minimal HAL test (main_spi6_rm43_ref.c) with single-shot HAL_SPI_TransmitReceive_IT(1 byte) and extra telemetry:
Changing NSSPolarity, AFCNTR, IOSwap, soft/hard NSS, etc. did not recover reliable operation. Only arming after seeing NSS inactive makes the transfer succeed.
Question
On the STM32H753ZI SPI6, is it expected behaviour that the slave requires a fresh inactive→active NSS edge after the peripheral is fully armed (SPE=1 / HAL_SPI_TransmitReceive_IT called) in order to qualify a frame?
We plan to implement a robust policy (arm only on NSS inactive, preferably triggered by EXTI on NSS). Any official guidance would be very helpful.
Thank you!
main_spi6_rm43_ref.c
#include "stm32h7xx_hal.h"
#include <stdio.h>
void PAT_SystemClock_Config(void);
#ifndef PAT_SPI6_RM43_REF_SINGLESHOT
#define PAT_SPI6_RM43_REF_SINGLESHOT 0U
#endif
#ifndef PAT_SPI6_RM43_REF_UDR_TUNE
#define PAT_SPI6_RM43_REF_UDR_TUNE 0U
#endif
#ifndef PAT_SPI6_RM43_REF_CFG2_FORCE_SSI
#define PAT_SPI6_RM43_REF_CFG2_FORCE_SSI 0U
#endif
#ifndef PAT_SPI6_RM43_REF_PREFILL_BYTES
#define PAT_SPI6_RM43_REF_PREFILL_BYTES 0U
#endif
#ifndef PAT_SPI6_RM43_REF_TX_BYTE
#define PAT_SPI6_RM43_REF_TX_BYTE 0xA5U
#endif
#ifndef PAT_SPI6_RM43_REF_HARD_NSS
#define PAT_SPI6_RM43_REF_HARD_NSS 1U
#endif
#ifndef PAT_SPI6_RM43_REF_IOSWAP
#define PAT_SPI6_RM43_REF_IOSWAP 0U
#endif
#ifndef PAT_SPI6_RM43_REF_NSS_ACTIVE_LOW
#define PAT_SPI6_RM43_REF_NSS_ACTIVE_LOW 1U
#endif
#ifndef PAT_SPI6_RM43_REF_AFCNTR_FORCE
#define PAT_SPI6_RM43_REF_AFCNTR_FORCE 0U
#endif
#ifndef PAT_SPI6_RM43_REF_SYNC_ARM
#define PAT_SPI6_RM43_REF_SYNC_ARM 0U
#endif
UART_HandleTypeDef huart3;
SPI_HandleTypeDef hspi6;
static uint8_t s_tx = (uint8_t)PAT_SPI6_RM43_REF_TX_BYTE;
static uint8_t s_rx = 0x00U;
static uint8_t s_prefill_remaining = (uint8_t)PAT_SPI6_RM43_REF_PREFILL_BYTES;
static const uint8_t s_prefill_bytes[] = {0xCDU, 0xEFU};
static volatile uint32_t g_spi6_irq_count;
static volatile uint32_t g_spi6_txrx_complete_count;
static volatile uint32_t g_spi6_error_count;
static volatile uint32_t g_spi6_rearm_fail_count;
static volatile uint32_t g_pg8_edge_count;
static volatile uint32_t g_pa5_edge_count;
static volatile uint32_t g_arm_count;
static volatile uint32_t g_arm_nss_high_count;
static volatile uint32_t g_arm_timeout_count;
static volatile uint8_t g_last_rx;
static volatile uint8_t g_last_tx;
void Error_Handler(void);
int _write(int fd, const char *ptr, int len)
{
(void)fd;
if (HAL_UART_Transmit(&huart3, (uint8_t *)ptr, (uint16_t)len, 1000U) != HAL_OK) {
return 0;
}
return len;
}
static void MX_GPIO_LED_Init(void)
{
GPIO_InitTypeDef g = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
g.Pin = GPIO_PIN_0;
g.Mode = GPIO_MODE_OUTPUT_PP;
g.Pull = GPIO_NOPULL;
g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &g);
}
static void MX_USART3_UART_Init(void)
{
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200U;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart3.Init.ClockPrescaler = UART_PRESCALER_DIV1;
if (HAL_UART_Init(&huart3) != HAL_OK) {
Error_Handler();
}
}
static void MX_SPI6_Slave_Init(void)
{
hspi6.Instance = SPI6;
hspi6.Init.Mode = SPI_MODE_SLAVE;
hspi6.Init.Direction = SPI_DIRECTION_2LINES;
hspi6.Init.DataSize = SPI_DATASIZE_8BIT;
hspi6.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi6.Init.CLKPhase = SPI_PHASE_2EDGE;
#if (PAT_SPI6_RM43_REF_HARD_NSS != 0U)
hspi6.Init.NSS = SPI_NSS_HARD_INPUT;
#else
hspi6.Init.NSS = SPI_NSS_SOFT;
#endif
hspi6.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi6.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi6.Init.TIMode = SPI_TIMODE_DISABLE;
hspi6.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi6.Init.CRCPolynomial = 0x7U;
hspi6.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
#if (PAT_SPI6_RM43_REF_NSS_ACTIVE_LOW != 0U)
hspi6.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
#else
hspi6.Init.NSSPolarity = SPI_NSS_POLARITY_HIGH;
#endif
hspi6.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
hspi6.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
hspi6.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
hspi6.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
hspi6.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
hspi6.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
hspi6.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
#if (PAT_SPI6_RM43_REF_IOSWAP != 0U)
hspi6.Init.IOSwap = SPI_IO_SWAP_ENABLE;
#else
hspi6.Init.IOSwap = SPI_IO_SWAP_DISABLE;
#endif
if (HAL_SPI_Init(&hspi6) != HAL_OK) {
Error_Handler();
}
#if (PAT_SPI6_RM43_REF_UDR_TUNE != 0U)
MODIFY_REG(hspi6.Instance->CFG1, SPI_CFG1_UDRCFG, SPI_UNDERRUN_BEHAV_REGISTER_PATTERN);
MODIFY_REG(hspi6.Instance->CFG1, SPI_CFG1_UDRDET, SPI_UNDERRUN_DETECT_END_DATA_FRAME);
#endif
#if (PAT_SPI6_RM43_REF_CFG2_FORCE_SSI != 0U)
#ifdef SPI_CFG2_SSM
SET_BIT(hspi6.Instance->CFG2, SPI_CFG2_SSM);
#endif
#ifdef SPI_CFG2_SSI
SET_BIT(hspi6.Instance->CFG2, SPI_CFG2_SSI);
#endif
#endif
#ifdef SPI_CFG2_AFCNTR
#if (PAT_SPI6_RM43_REF_AFCNTR_FORCE != 0U)
SET_BIT(hspi6.Instance->CFG2, SPI_CFG2_AFCNTR);
#else
CLEAR_BIT(hspi6.Instance->CFG2, SPI_CFG2_AFCNTR);
#endif
#endif
HAL_NVIC_SetPriority(SPI6_IRQn, 6U, 0U);
HAL_NVIC_EnableIRQ(SPI6_IRQn);
}
void SPI6_IRQHandler(void)
{
g_spi6_irq_count++;
HAL_SPI_IRQHandler(&hspi6);
}
static HAL_StatusTypeDef spi6_arm_it(void)
{
uint8_t nss_high = 0U;
#if (PAT_SPI6_RM43_REF_SYNC_ARM != 0U)
for (uint32_t i = 0U; i < 200000U; i++) {
if ((GPIOG->IDR & GPIO_PIN_8) != 0U) {
nss_high = 1U;
break;
}
}
if (nss_high == 0U) {
g_arm_timeout_count++;
} else {
g_arm_nss_high_count++;
}
#else
nss_high = ((GPIOG->IDR & GPIO_PIN_8) != 0U) ? 1U : 0U;
if (nss_high != 0U) {
g_arm_nss_high_count++;
}
#endif
g_arm_count++;
g_last_tx = s_tx;
return HAL_SPI_TransmitReceive_IT(&hspi6, &s_tx, &s_rx, 1U);
}
static inline uint8_t spi6_next_tx(void)
{
if (s_prefill_remaining > 0U) {
uint8_t idx = (uint8_t)(PAT_SPI6_RM43_REF_PREFILL_BYTES - s_prefill_remaining);
if (idx >= (uint8_t)(sizeof(s_prefill_bytes) / sizeof(s_prefill_bytes[0]))) {
idx = (uint8_t)((sizeof(s_prefill_bytes) / sizeof(s_prefill_bytes[0])) - 1U);
}
s_prefill_remaining--;
return s_prefill_bytes[idx];
}
return (uint8_t)PAT_SPI6_RM43_REF_TX_BYTE;
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi != &hspi6) {
return;
}
g_spi6_txrx_complete_count++;
g_last_rx = s_rx;
g_last_tx = s_tx;
s_tx = spi6_next_tx();
#if (PAT_SPI6_RM43_REF_SINGLESHOT == 0U)
if (spi6_arm_it() != HAL_OK) {
g_spi6_rearm_fail_count++;
}
#endif
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi != &hspi6) {
return;
}
g_spi6_error_count++;
__HAL_SPI_CLEAR_OVRFLAG(&hspi6);
__HAL_SPI_CLEAR_UDRFLAG(&hspi6);
__HAL_SPI_CLEAR_FREFLAG(&hspi6);
__HAL_SPI_CLEAR_MODFFLAG(&hspi6);
__HAL_SPI_CLEAR_CRCERRFLAG(&hspi6);
s_tx = spi6_next_tx();
#if (PAT_SPI6_RM43_REF_SINGLESHOT == 0U)
if (spi6_arm_it() != HAL_OK) {
g_spi6_rearm_fail_count++;
}
#endif
}
void Error_Handler(void)
{
while (1) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(200U);
}
}
int main(void)
{
HAL_Init();
PAT_SystemClock_Config();
MX_GPIO_LED_Init();
MX_USART3_UART_Init();
setvbuf(stdout, NULL, _IONBF, 0);
printf("\r\nPAT: SPI6 RM0433 reference slave (1B IT, tx=0x%02X, mode1, nss=%s, nss_active=%s, ioswap=%u, afcntr=%u, sync_arm=%u, singleshot=%u, prefill=%u, udr=%u, cfg2_ssi=%u)\r\n",
(unsigned)PAT_SPI6_RM43_REF_TX_BYTE,
#if (PAT_SPI6_RM43_REF_HARD_NSS != 0U)
"hard_input",
#else
"soft",
#endif
(PAT_SPI6_RM43_REF_NSS_ACTIVE_LOW != 0U) ? "low" : "high",
(unsigned)PAT_SPI6_RM43_REF_IOSWAP,
(unsigned)PAT_SPI6_RM43_REF_AFCNTR_FORCE,
(unsigned)PAT_SPI6_RM43_REF_SYNC_ARM,
(unsigned)PAT_SPI6_RM43_REF_SINGLESHOT,
(unsigned)PAT_SPI6_RM43_REF_PREFILL_BYTES,
(unsigned)PAT_SPI6_RM43_REF_UDR_TUNE,
(unsigned)PAT_SPI6_RM43_REF_CFG2_FORCE_SSI);
printf("Pins: SCK=PA5 NSS=PG8 MISO=PG12 MOSI=PG14 AF8\r\n");
s_tx = spi6_next_tx();
g_last_tx = s_tx;
g_last_rx = s_rx;
MX_SPI6_Slave_Init();
if (spi6_arm_it() != HAL_OK) {
printf("SPI6 start failed\r\n");
Error_Handler();
}
{
uint8_t last_pg8 = (uint8_t)((GPIOG->IDR >> 8) & 0x1U);
uint8_t last_pa5 = (uint8_t)((GPIOA->IDR >> 5) & 0x1U);
uint32_t last_hb = 0U;
for (;;) {
uint8_t pg8 = (uint8_t)((GPIOG->IDR >> 8) & 0x1U);
uint8_t pa5 = (uint8_t)((GPIOA->IDR >> 5) & 0x1U);
if (pg8 != last_pg8) {
g_pg8_edge_count++;
last_pg8 = pg8;
}
if (pa5 != last_pa5) {
g_pa5_edge_count++;
last_pa5 = pa5;
}
{
uint32_t now = HAL_GetTick();
if ((now - last_hb) >= 1000U) {
last_hb = now;
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
printf("HB irq=%lu cplt=%lu err=%lu rearm_fail=%lu arm=%lu arm_nss_hi=%lu arm_to=%lu pg8e=%lu pa5e=%lu tx=0x%02X rx=0x%02X hspi_err=0x%08lX CR1=0x%08lX CFG1=0x%08lX CFG2=0x%08lX SR=0x%08lX G_IDR=0x%08lX A_IDR=0x%08lX\r\n",
(unsigned long)g_spi6_irq_count,
(unsigned long)g_spi6_txrx_complete_count,
(unsigned long)g_spi6_error_count,
(unsigned long)g_spi6_rearm_fail_count,
(unsigned long)g_arm_count,
(unsigned long)g_arm_nss_high_count,
(unsigned long)g_arm_timeout_count,
(unsigned long)g_pg8_edge_count,
(unsigned long)g_pa5_edge_count,
(unsigned)g_last_tx,
(unsigned)g_last_rx,
(unsigned long)hspi6.ErrorCode,
(unsigned long)hspi6.Instance->CR1,
(unsigned long)hspi6.Instance->CFG1,
(unsigned long)hspi6.Instance->CFG2,
(unsigned long)hspi6.Instance->SR,
(unsigned long)GPIOG->IDR,
(unsigned long)GPIOA->IDR);
}
}
}
}
}
2026-04-30 7:44 AM
I am bringing up SPI6 in slave mode on a NUCLEO-H753ZI (STM32H753) using Zephyr v3.7.0 and a Raspberry Pi 5 as SPI master (spidev, mode 1, 1 byte per xfer2() with a short gap between frames).
Pinout used (same as our J2 stack):
SCK PA5, NSS PG8, MISO PG12, MOSI PG14.
In Zephyr I enable &spi6 and set pinctrl-0 to the generated labels from the STM32 HAL pinctrl pack: spi6_sck_pa5, spi6_nss_pg8, spi6_miso_pg12, spi6_mosi_pg14. I had to disable &spi1 in an overlay because the default Nucleo board uses PA5 for SPI1 SCK — otherwise PA5 is double-booked.
Build/flash: west workspace on Zephyr 3.7.0, toolchain GNU Arm Embedded 14.2 (ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb), west build -b nucleo_h753zi, west flash (OpenOCD / ST-Link V3).
Application: minimal loop: spi_transceive() in slave mode, 8-bit, SPI_MODE_CPHA (mode 1), TX byte cycling 0x3C 0xC3 0x5A 0xA5 so the master can sanity-check MISO.
Result on the wire (Pi histogram):
At 100 kHz and 200 frames, MISO is mostly the expected quartet with some 0x00 gaps (pattern match ~86% in our quick script). At 500 kHz the same test shows more 0x00 — still clearly the pattern, but timing/CPU vs spi_transceive blocking likely needs tuning (or async/DMA later; aware SPI6/BDMA/SRAM4 constraints on H7).
Takeaway: SPI6 slave on H753 under Zephyr does work for us on those balls with the stock STM32 SPI driver + correct pinctrl; the main gotcha was PA5 vs default SPI1 and using the named pinctrl nodes from the SoC *-pinctrl.dtsi rather than hand-rolled STM32_PINMUX groups (our first overlay missed pinmux on the group and failed DTS).
Happy to compare notes with anyone else running SPI6 slave on H7 in Zephyr (NSS hard vs soft, rates, or driver limits).
2026-04-30 8:32 AM
Hello @S_Chou
@S_Chou wrote:In Zephyr I enable &spi6 and set pinctrl-0 to the generated labels from the STM32 HAL pinctrl pack: spi6_sck_pa5, spi6_nss_pg8, spi6_miso_pg12, spi6_mosi_pg14. I had to disable &spi1 in an overlay because the default Nucleo board uses PA5 for SPI1 SCK — otherwise PA5 is double-booked.
How PA5 can be double booked if the SPI1 is not initialized?