diff --git a/src/main/drivers/bus_spi_hal_ll.c b/src/main/drivers/bus_spi_hal_ll.c
index fac8a82761a..ce6fbde9595 100644
--- a/src/main/drivers/bus_spi_hal_ll.c
+++ b/src/main/drivers/bus_spi_hal_ll.c
@@ -89,109 +89,159 @@ static const uint32_t spiDivisorMapSlow[] = {
#endif
#if defined(STM32H7)
-static spiDevice_t spiHardwareMap[SPIDEV_COUNT] = {
+#include "bus_spi_stm32h7xx.h"
+
+// Auto-resolve SPI AF per pin from the lookup table in bus_spi_stm32h7xx.h.
+// Targets may still define individual SPI*_SCK/MISO/MOSI_AF values in target.h
+// to override; explicit defines take priority via these #ifndef guards.
#ifdef USE_SPI_DEVICE_1
-#if defined(SPI1_SCK_AF) || defined(SPI1_MISO_AF) || defined(SPI1_MOSI_AF)
-#if !defined(SPI1_SCK_AF) || !defined(SPI1_MISO_AF) || !defined(SPI1_MOSI_AF)
-#error SPI1: SCK, MISO and MOSI AFs should be defined together in target.h!
+#ifndef SPI1_SCK_AF
+#define SPI1_SCK_AF SPI_PIN_AF_HELPER(1, SPI1_SCK_PIN)
#endif
- { .dev = SPI1, .nss = IO_TAG(SPI1_NSS_PIN), .sck = IO_TAG(SPI1_SCK_PIN), .miso = IO_TAG(SPI1_MISO_PIN), .mosi = IO_TAG(SPI1_MOSI_PIN), .rcc = RCC_APB2(SPI1), .sckAF = SPI1_SCK_AF, .misoAF = SPI1_MISO_AF, .mosiAF = SPI1_MOSI_AF, .divisorMap = spiDivisorMapFast },
-#else
- { .dev = SPI1, .nss = IO_TAG(SPI1_NSS_PIN), .sck = IO_TAG(SPI1_SCK_PIN), .miso = IO_TAG(SPI1_MISO_PIN), .mosi = IO_TAG(SPI1_MOSI_PIN), .rcc = RCC_APB2(SPI1), .sckAF = GPIO_AF5_SPI1, .misoAF = GPIO_AF5_SPI1, .mosiAF = GPIO_AF5_SPI1, .divisorMap = spiDivisorMapFast },
+#ifndef SPI1_MISO_AF
+#define SPI1_MISO_AF SPI_PIN_AF_HELPER(1, SPI1_MISO_PIN)
+#endif
+#ifndef SPI1_MOSI_AF
+#define SPI1_MOSI_AF SPI_PIN_AF_HELPER(1, SPI1_MOSI_PIN)
#endif
-#else
- { .dev = NULL }, // No SPI1
#endif
#ifdef USE_SPI_DEVICE_2
-#if defined(SPI2_SCK_AF) || defined(SPI2_MISO_AF) || defined(SPI2_MOSI_AF)
-#if !defined(SPI2_SCK_AF) || !defined(SPI2_MISO_AF) || !defined(SPI2_MOSI_AF)
-#error SPI2: SCK, MISO and MOSI AFs should be defined together in target.h!
+#ifndef SPI2_SCK_AF
+#define SPI2_SCK_AF SPI_PIN_AF_HELPER(2, SPI2_SCK_PIN)
#endif
- { .dev = SPI2, .nss = IO_TAG(SPI2_NSS_PIN), .sck = IO_TAG(SPI2_SCK_PIN), .miso = IO_TAG(SPI2_MISO_PIN), .mosi = IO_TAG(SPI2_MOSI_PIN), .rcc = RCC_APB1L(SPI2), .sckAF = SPI2_SCK_AF, .misoAF = SPI2_MISO_AF, .mosiAF = SPI2_MOSI_AF, .divisorMap = spiDivisorMapSlow },
+#ifndef SPI2_MISO_AF
+#define SPI2_MISO_AF SPI_PIN_AF_HELPER(2, SPI2_MISO_PIN)
+#endif
+#ifndef SPI2_MOSI_AF
+#define SPI2_MOSI_AF SPI_PIN_AF_HELPER(2, SPI2_MOSI_PIN)
+#endif
+#endif
+
+#ifdef USE_SPI_DEVICE_3
+#ifndef SPI3_SCK_AF
+#define SPI3_SCK_AF SPI_PIN_AF_HELPER(3, SPI3_SCK_PIN)
+#endif
+#ifndef SPI3_MISO_AF
+#define SPI3_MISO_AF SPI_PIN_AF_HELPER(3, SPI3_MISO_PIN)
+#endif
+#ifndef SPI3_MOSI_AF
+#define SPI3_MOSI_AF SPI_PIN_AF_HELPER(3, SPI3_MOSI_PIN)
+#endif
+#endif
+
+#ifdef USE_SPI_DEVICE_4
+#ifndef SPI4_SCK_AF
+#define SPI4_SCK_AF SPI_PIN_AF_HELPER(4, SPI4_SCK_PIN)
+#endif
+#ifndef SPI4_MISO_AF
+#define SPI4_MISO_AF SPI_PIN_AF_HELPER(4, SPI4_MISO_PIN)
+#endif
+#ifndef SPI4_MOSI_AF
+#define SPI4_MOSI_AF SPI_PIN_AF_HELPER(4, SPI4_MOSI_PIN)
+#endif
+#endif
+
+static spiDevice_t spiHardwareMap[SPIDEV_COUNT] = {
+#ifdef USE_SPI_DEVICE_1
+ { .dev = SPI1, .nss = IO_TAG(SPI1_NSS_PIN), .sck = IO_TAG(SPI1_SCK_PIN), .miso = IO_TAG(SPI1_MISO_PIN), .mosi = IO_TAG(SPI1_MOSI_PIN), .rcc = RCC_APB2(SPI1), .sckAF = SPI1_SCK_AF, .misoAF = SPI1_MISO_AF, .mosiAF = SPI1_MOSI_AF, .divisorMap = spiDivisorMapFast },
#else
- { .dev = SPI2, .nss = IO_TAG(SPI2_NSS_PIN), .sck = IO_TAG(SPI2_SCK_PIN), .miso = IO_TAG(SPI2_MISO_PIN), .mosi = IO_TAG(SPI2_MOSI_PIN), .rcc = RCC_APB1L(SPI2), .sckAF = GPIO_AF5_SPI2, .misoAF = GPIO_AF5_SPI2, .mosiAF = GPIO_AF5_SPI2, .divisorMap = spiDivisorMapSlow },
+ { .dev = NULL }, // No SPI1
#endif
+
+#ifdef USE_SPI_DEVICE_2
+ { .dev = SPI2, .nss = IO_TAG(SPI2_NSS_PIN), .sck = IO_TAG(SPI2_SCK_PIN), .miso = IO_TAG(SPI2_MISO_PIN), .mosi = IO_TAG(SPI2_MOSI_PIN), .rcc = RCC_APB1L(SPI2), .sckAF = SPI2_SCK_AF, .misoAF = SPI2_MISO_AF, .mosiAF = SPI2_MOSI_AF, .divisorMap = spiDivisorMapSlow },
#else
{ .dev = NULL }, // No SPI2
#endif
#ifdef USE_SPI_DEVICE_3
-#if defined(SPI3_SCK_AF) || defined(SPI3_MISO_AF) || defined(SPI3_MOSI_AF)
-#if !defined(SPI3_SCK_AF) || !defined(SPI3_MISO_AF) || !defined(SPI3_MOSI_AF)
-#error SPI3: SCK, MISO and MOSI AFs should be defined together in target.h!
-#endif
{ .dev = SPI3, .nss = IO_TAG(SPI3_NSS_PIN), .sck = IO_TAG(SPI3_SCK_PIN), .miso = IO_TAG(SPI3_MISO_PIN), .mosi = IO_TAG(SPI3_MOSI_PIN), .rcc = RCC_APB1L(SPI3), .sckAF = SPI3_SCK_AF, .misoAF = SPI3_MISO_AF, .mosiAF = SPI3_MOSI_AF, .divisorMap = spiDivisorMapSlow },
-#else
- { .dev = SPI3, .nss = IO_TAG(SPI3_NSS_PIN), .sck = IO_TAG(SPI3_SCK_PIN), .miso = IO_TAG(SPI3_MISO_PIN), .mosi = IO_TAG(SPI3_MOSI_PIN), .rcc = RCC_APB1L(SPI3), .sckAF = GPIO_AF6_SPI3, .misoAF = GPIO_AF6_SPI3, .mosiAF = GPIO_AF6_SPI3, .divisorMap = spiDivisorMapSlow },
-#endif
#else
{ .dev = NULL }, // No SPI3
#endif
#ifdef USE_SPI_DEVICE_4
-#if defined(SPI4_SCK_AF) || defined(SPI4_MISO_AF) || defined(SPI4_MOSI_AF)
-#if !defined(SPI4_SCK_AF) || !defined(SPI4_MISO_AF) || !defined(SPI4_MOSI_AF)
-#error SPI4: SCK, MISO and MOSI AFs should be defined together in target.h!
-#endif
{ .dev = SPI4, .nss = IO_TAG(SPI4_NSS_PIN), .sck = IO_TAG(SPI4_SCK_PIN), .miso = IO_TAG(SPI4_MISO_PIN), .mosi = IO_TAG(SPI4_MOSI_PIN), .rcc = RCC_APB2(SPI4), .sckAF = SPI4_SCK_AF, .misoAF = SPI4_MISO_AF, .mosiAF = SPI4_MOSI_AF, .divisorMap = spiDivisorMapSlow }
-#else
- { .dev = SPI4, .nss = IO_TAG(SPI4_NSS_PIN), .sck = IO_TAG(SPI4_SCK_PIN), .miso = IO_TAG(SPI4_MISO_PIN), .mosi = IO_TAG(SPI4_MOSI_PIN), .rcc = RCC_APB2(SPI4), .sckAF = GPIO_AF5_SPI4, .misoAF = GPIO_AF5_SPI4, .mosiAF = GPIO_AF5_SPI4, .divisorMap = spiDivisorMapSlow }
-#endif
#else
{ .dev = NULL } // No SPI4
#endif
};
-#else
-static spiDevice_t spiHardwareMap[] = {
+#elif defined(STM32F7)
+#include "bus_spi_stm32f7xx.h"
+
+// Auto-resolve SPI AF per pin from the lookup table in bus_spi_stm32f7xx.h.
+// Targets may still define individual SPI*_SCK/MISO/MOSI_AF values in target.h
+// to override; explicit defines take priority via these #ifndef guards.
#ifdef USE_SPI_DEVICE_1
-#if defined(SPI1_SCK_AF) || defined(SPI1_MISO_AF) || defined(SPI1_MOSI_AF)
-#if !defined(SPI1_SCK_AF) || !defined(SPI1_MISO_AF) || !defined(SPI1_MOSI_AF)
-#error SPI1: SCK, MISO and MOSI AFs should be defined together in target.h!
+#ifndef SPI1_SCK_AF
+#define SPI1_SCK_AF SPI_PIN_AF_HELPER(1, SPI1_SCK_PIN)
#endif
- { .dev = SPI1, .nss = IO_TAG(SPI1_NSS_PIN), .sck = IO_TAG(SPI1_SCK_PIN), .miso = IO_TAG(SPI1_MISO_PIN), .mosi = IO_TAG(SPI1_MOSI_PIN), .rcc = RCC_APB2(SPI1), .sckAF = SPI1_SCK_AF, .misoAF = SPI1_MISO_AF, .mosiAF = SPI1_MOSI_AF, .divisorMap = spiDivisorMapFast },
-#else
- { .dev = SPI1, .nss = IO_TAG(SPI1_NSS_PIN), .sck = IO_TAG(SPI1_SCK_PIN), .miso = IO_TAG(SPI1_MISO_PIN), .mosi = IO_TAG(SPI1_MOSI_PIN), .rcc = RCC_APB2(SPI1), .sckAF = GPIO_AF5_SPI1, .misoAF = GPIO_AF5_SPI1, .mosiAF = GPIO_AF5_SPI1, .divisorMap = spiDivisorMapFast },
+#ifndef SPI1_MISO_AF
+#define SPI1_MISO_AF SPI_PIN_AF_HELPER(1, SPI1_MISO_PIN)
+#endif
+#ifndef SPI1_MOSI_AF
+#define SPI1_MOSI_AF SPI_PIN_AF_HELPER(1, SPI1_MOSI_PIN)
#endif
-#else
- { .dev = NULL }, // No SPI1
#endif
#ifdef USE_SPI_DEVICE_2
-#if defined(SPI2_SCK_AF) || defined(SPI2_MISO_AF) || defined(SPI2_MOSI_AF)
-#if !defined(SPI2_SCK_AF) || !defined(SPI2_MISO_AF) || !defined(SPI2_MOSI_AF)
-#error SPI2: SCK, MISO and MOSI AFs should be defined together in target.h!
+#ifndef SPI2_SCK_AF
+#define SPI2_SCK_AF SPI_PIN_AF_HELPER(2, SPI2_SCK_PIN)
#endif
- { .dev = SPI2, .nss = IO_TAG(SPI2_NSS_PIN), .sck = IO_TAG(SPI2_SCK_PIN), .miso = IO_TAG(SPI2_MISO_PIN), .mosi = IO_TAG(SPI2_MOSI_PIN), .rcc = RCC_APB1(SPI2), .sckAF = SPI2_SCK_AF, .misoAF = SPI2_MISO_AF, .mosiAF = SPI2_MOSI_AF, .divisorMap = spiDivisorMapSlow },
+#ifndef SPI2_MISO_AF
+#define SPI2_MISO_AF SPI_PIN_AF_HELPER(2, SPI2_MISO_PIN)
+#endif
+#ifndef SPI2_MOSI_AF
+#define SPI2_MOSI_AF SPI_PIN_AF_HELPER(2, SPI2_MOSI_PIN)
+#endif
+#endif
+
+#ifdef USE_SPI_DEVICE_3
+#ifndef SPI3_SCK_AF
+#define SPI3_SCK_AF SPI_PIN_AF_HELPER(3, SPI3_SCK_PIN)
+#endif
+#ifndef SPI3_MISO_AF
+#define SPI3_MISO_AF SPI_PIN_AF_HELPER(3, SPI3_MISO_PIN)
+#endif
+#ifndef SPI3_MOSI_AF
+#define SPI3_MOSI_AF SPI_PIN_AF_HELPER(3, SPI3_MOSI_PIN)
+#endif
+#endif
+
+#ifdef USE_SPI_DEVICE_4
+#ifndef SPI4_SCK_AF
+#define SPI4_SCK_AF SPI_PIN_AF_HELPER(4, SPI4_SCK_PIN)
+#endif
+#ifndef SPI4_MISO_AF
+#define SPI4_MISO_AF SPI_PIN_AF_HELPER(4, SPI4_MISO_PIN)
+#endif
+#ifndef SPI4_MOSI_AF
+#define SPI4_MOSI_AF SPI_PIN_AF_HELPER(4, SPI4_MOSI_PIN)
+#endif
+#endif
+
+static spiDevice_t spiHardwareMap[] = {
+#ifdef USE_SPI_DEVICE_1
+ { .dev = SPI1, .nss = IO_TAG(SPI1_NSS_PIN), .sck = IO_TAG(SPI1_SCK_PIN), .miso = IO_TAG(SPI1_MISO_PIN), .mosi = IO_TAG(SPI1_MOSI_PIN), .rcc = RCC_APB2(SPI1), .sckAF = SPI1_SCK_AF, .misoAF = SPI1_MISO_AF, .mosiAF = SPI1_MOSI_AF, .divisorMap = spiDivisorMapFast },
#else
- { .dev = SPI2, .nss = IO_TAG(SPI2_NSS_PIN), .sck = IO_TAG(SPI2_SCK_PIN), .miso = IO_TAG(SPI2_MISO_PIN), .mosi = IO_TAG(SPI2_MOSI_PIN), .rcc = RCC_APB1(SPI2), .sckAF = GPIO_AF5_SPI2, .misoAF = GPIO_AF5_SPI2, .mosiAF = GPIO_AF5_SPI2, .divisorMap = spiDivisorMapSlow },
+ { .dev = NULL }, // No SPI1
#endif
+
+#ifdef USE_SPI_DEVICE_2
+ { .dev = SPI2, .nss = IO_TAG(SPI2_NSS_PIN), .sck = IO_TAG(SPI2_SCK_PIN), .miso = IO_TAG(SPI2_MISO_PIN), .mosi = IO_TAG(SPI2_MOSI_PIN), .rcc = RCC_APB1(SPI2), .sckAF = SPI2_SCK_AF, .misoAF = SPI2_MISO_AF, .mosiAF = SPI2_MOSI_AF, .divisorMap = spiDivisorMapSlow },
#else
{ .dev = NULL }, // No SPI2
#endif
#ifdef USE_SPI_DEVICE_3
-#if defined(SPI3_SCK_AF) || defined(SPI3_MISO_AF) || defined(SPI3_MOSI_AF)
-#if !defined(SPI3_SCK_AF) || !defined(SPI3_MISO_AF) || !defined(SPI3_MOSI_AF)
-#error SPI3: SCK, MISO and MOSI AFs should be defined together in target.h!
-#endif
{ .dev = SPI3, .nss = IO_TAG(SPI3_NSS_PIN), .sck = IO_TAG(SPI3_SCK_PIN), .miso = IO_TAG(SPI3_MISO_PIN), .mosi = IO_TAG(SPI3_MOSI_PIN), .rcc = RCC_APB1(SPI3), .sckAF = SPI3_SCK_AF, .misoAF = SPI3_MISO_AF, .mosiAF = SPI3_MOSI_AF, .divisorMap = spiDivisorMapSlow },
-#else
- { .dev = SPI3, .nss = IO_TAG(SPI3_NSS_PIN), .sck = IO_TAG(SPI3_SCK_PIN), .miso = IO_TAG(SPI3_MISO_PIN), .mosi = IO_TAG(SPI3_MOSI_PIN), .rcc = RCC_APB1(SPI3), .sckAF = GPIO_AF6_SPI3, .misoAF = GPIO_AF6_SPI3, .mosiAF = GPIO_AF6_SPI3, .divisorMap = spiDivisorMapSlow },
-#endif
#else
{ .dev = NULL }, // No SPI3
#endif
#ifdef USE_SPI_DEVICE_4
-#if defined(SPI4_SCK_AF) || defined(SPI4_MISO_AF) || defined(SPI4_MOSI_AF)
-#if !defined(SPI4_SCK_AF) || !defined(SPI4_MISO_AF) || !defined(SPI4_MOSI_AF)
-#error SPI3: SCK, MISO and MOSI AFs should be defined together in target.h!
-#endif
{ .dev = SPI4, .nss = IO_TAG(SPI4_NSS_PIN), .sck = IO_TAG(SPI4_SCK_PIN), .miso = IO_TAG(SPI4_MISO_PIN), .mosi = IO_TAG(SPI4_MOSI_PIN), .rcc = RCC_APB2(SPI4), .sckAF = SPI4_SCK_AF, .misoAF = SPI4_MISO_AF, .mosiAF = SPI4_MOSI_AF, .divisorMap = spiDivisorMapSlow }
-#else
- { .dev = SPI4, .nss = IO_TAG(SPI4_NSS_PIN), .sck = IO_TAG(SPI4_SCK_PIN), .miso = IO_TAG(SPI4_MISO_PIN), .mosi = IO_TAG(SPI4_MOSI_PIN), .rcc = RCC_APB2(SPI4), .sckAF = GPIO_AF5_SPI4, .misoAF = GPIO_AF5_SPI4, .mosiAF = GPIO_AF5_SPI4, .divisorMap = spiDivisorMapSlow }
-#endif
#else
{ .dev = NULL } // No SPI4
#endif
diff --git a/src/main/drivers/bus_spi_stm32f7xx.h b/src/main/drivers/bus_spi_stm32f7xx.h
new file mode 100644
index 00000000000..a43938e1e45
--- /dev/null
+++ b/src/main/drivers/bus_spi_stm32f7xx.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of INAV.
+ *
+ * INAV is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * INAV is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with INAV. If not, see .
+ */
+
+/*
+ * STM32F7 SPI pin alternate function lookup table.
+ *
+ * Usage: SPI_PIN_AF_HELPER(3, PB5) expands to SPI_PIN_AF_SPI3_PB5,
+ * which is defined below as GPIO_AF6_SPI3.
+ *
+ * AF assignments from STM32F722 datasheet (DS11853) and STM32F745 datasheet
+ * (DS10916), Table 9.
+ *
+ * NOTE: The F7 AF table differs from the H7 for SPI3/PB5:
+ * On F7, PB5 SPI3_MOSI is AF6 (the default).
+ * On H7, PB5 SPI3_MOSI is AF7 (an exception — see bus_spi_stm32h7xx.h).
+ *
+ * SPI1, SPI2, SPI4 pins are all AF5 with no exceptions.
+ * SPI3 SCK/MISO are AF6. SPI3 MOSI has pin-dependent exceptions:
+ * PB2 uses AF7 (not AF6). PD6 uses AF5 (not AF6). PC12 and PB5 use AF6.
+ */
+
+#pragma once
+
+#include "common/utils.h"
+
+// Resolves to SPI_PIN_AF_SPIn_Pxy, defined below for each valid pin.
+// If a pin is not in the table the build will fail with "undefined identifier",
+// which is preferable to silently applying the wrong AF.
+#define SPI_PIN_AF_HELPER(spi, pin) CONCAT4(SPI_PIN_AF_SPI, spi, _, pin)
+
+/* SPI1 — all data pins use AF5 */
+#define SPI_PIN_AF_SPI1_PA5 GPIO_AF5_SPI1 // SCK
+#define SPI_PIN_AF_SPI1_PA6 GPIO_AF5_SPI1 // MISO
+#define SPI_PIN_AF_SPI1_PA7 GPIO_AF5_SPI1 // MOSI
+#define SPI_PIN_AF_SPI1_PB3 GPIO_AF5_SPI1 // SCK
+#define SPI_PIN_AF_SPI1_PB4 GPIO_AF5_SPI1 // MISO
+#define SPI_PIN_AF_SPI1_PB5 GPIO_AF5_SPI1 // MOSI
+
+/* SPI2 — all data pins use AF5 */
+#define SPI_PIN_AF_SPI2_PB13 GPIO_AF5_SPI2 // SCK
+#define SPI_PIN_AF_SPI2_PB14 GPIO_AF5_SPI2 // MISO
+#define SPI_PIN_AF_SPI2_PB15 GPIO_AF5_SPI2 // MOSI
+#define SPI_PIN_AF_SPI2_PC1 GPIO_AF5_SPI2 // MOSI
+#define SPI_PIN_AF_SPI2_PC2 GPIO_AF5_SPI2 // MISO
+#define SPI_PIN_AF_SPI2_PC3 GPIO_AF5_SPI2 // MOSI
+
+/*
+ * SPI3 — SCK and MISO use AF6, but MOSI has pin-dependent exceptions.
+ * PB2 carries SPI3_MOSI on AF7 (not AF6). PD6 uses AF5. PB5 and PC12 use AF6.
+ */
+#define SPI_PIN_AF_SPI3_PB2 GPIO_AF7_SPI3 // MOSI — exception: AF7, not AF6
+#define SPI_PIN_AF_SPI3_PB3 GPIO_AF6_SPI3 // SCK
+#define SPI_PIN_AF_SPI3_PB4 GPIO_AF6_SPI3 // MISO
+#define SPI_PIN_AF_SPI3_PB5 GPIO_AF6_SPI3 // MOSI (AF6 on F7; H7 uses AF7 for this pin)
+#define SPI_PIN_AF_SPI3_PC10 GPIO_AF6_SPI3 // SCK
+#define SPI_PIN_AF_SPI3_PC11 GPIO_AF6_SPI3 // MISO
+#define SPI_PIN_AF_SPI3_PC12 GPIO_AF6_SPI3 // MOSI
+#define SPI_PIN_AF_SPI3_PD6 GPIO_AF5_SPI3 // MOSI — exception: AF5, not AF6
+
+/* SPI4 — all data pins use AF5 */
+#define SPI_PIN_AF_SPI4_PE2 GPIO_AF5_SPI4 // SCK
+#define SPI_PIN_AF_SPI4_PE5 GPIO_AF5_SPI4 // MISO
+#define SPI_PIN_AF_SPI4_PE6 GPIO_AF5_SPI4 // MOSI
+#define SPI_PIN_AF_SPI4_PE12 GPIO_AF5_SPI4 // SCK
+#define SPI_PIN_AF_SPI4_PE13 GPIO_AF5_SPI4 // MISO
+#define SPI_PIN_AF_SPI4_PE14 GPIO_AF5_SPI4 // MOSI
diff --git a/src/main/drivers/bus_spi_stm32h7xx.h b/src/main/drivers/bus_spi_stm32h7xx.h
new file mode 100644
index 00000000000..286e5859816
--- /dev/null
+++ b/src/main/drivers/bus_spi_stm32h7xx.h
@@ -0,0 +1,81 @@
+/*
+ * This file is part of INAV.
+ *
+ * INAV is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * INAV is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with INAV. If not, see .
+ */
+
+/*
+ * STM32H7 SPI pin alternate function lookup table.
+ *
+ * Usage: SPI_PIN_AF_HELPER(3, PB5) expands to SPI_PIN_AF_SPI3_PB5,
+ * which is defined below as GPIO_AF7_SPI3.
+ *
+ * This allows bus_spi_hal_ll.c to resolve the correct AF per pin automatically,
+ * without requiring each target to define SPI*_SCK_AF / SPI*_MISO_AF / SPI*_MOSI_AF
+ * manually in target.h. Targets may still override individual values if needed.
+ *
+ * Alternate function assignments are from the STM32H743 datasheet (DS12110),
+ * Tables 10-14.
+ */
+
+#pragma once
+
+#include "common/utils.h"
+
+// Resolves to SPI_PIN_AF_SPIn_Pxy, which is defined below for each valid pin.
+// If a pin is not in the table the build will fail with "undefined identifier",
+// which is preferable to silently applying the wrong AF.
+#define SPI_PIN_AF_HELPER(spi, pin) CONCAT4(SPI_PIN_AF_SPI, spi, _, pin)
+
+/* SPI1 — all data pins use AF5 */
+#define SPI_PIN_AF_SPI1_PA5 GPIO_AF5_SPI1 // SCK
+#define SPI_PIN_AF_SPI1_PA6 GPIO_AF5_SPI1 // MISO
+#define SPI_PIN_AF_SPI1_PA7 GPIO_AF5_SPI1 // MOSI
+#define SPI_PIN_AF_SPI1_PB3 GPIO_AF5_SPI1 // SCK
+#define SPI_PIN_AF_SPI1_PB4 GPIO_AF5_SPI1 // MISO
+#define SPI_PIN_AF_SPI1_PB5 GPIO_AF5_SPI1 // MOSI
+#define SPI_PIN_AF_SPI1_PD7 GPIO_AF5_SPI1 // MOSI
+
+/* SPI2 — all data pins use AF5 */
+#define SPI_PIN_AF_SPI2_PA9 GPIO_AF5_SPI2 // SCK
+#define SPI_PIN_AF_SPI2_PB10 GPIO_AF5_SPI2 // SCK
+#define SPI_PIN_AF_SPI2_PB13 GPIO_AF5_SPI2 // SCK
+#define SPI_PIN_AF_SPI2_PB14 GPIO_AF5_SPI2 // MISO
+#define SPI_PIN_AF_SPI2_PB15 GPIO_AF5_SPI2 // MOSI
+#define SPI_PIN_AF_SPI2_PC1 GPIO_AF5_SPI2 // MOSI
+#define SPI_PIN_AF_SPI2_PC2 GPIO_AF5_SPI2 // MISO
+#define SPI_PIN_AF_SPI2_PC3 GPIO_AF5_SPI2 // MOSI
+#define SPI_PIN_AF_SPI2_PD3 GPIO_AF5_SPI2 // SCK
+
+/*
+ * SPI3 — SCK and MISO use AF6, but MOSI has pin-dependent exceptions.
+ * PB2 and PB5 carry SPI3_MOSI on AF7 (not AF6). PD6 uses AF5.
+ * This is the only SPI bus on STM32H743 where a single-AF fallback is wrong.
+ */
+#define SPI_PIN_AF_SPI3_PB2 GPIO_AF7_SPI3 // MOSI — exception: AF7, not AF6
+#define SPI_PIN_AF_SPI3_PB3 GPIO_AF6_SPI3 // SCK
+#define SPI_PIN_AF_SPI3_PB4 GPIO_AF6_SPI3 // MISO
+#define SPI_PIN_AF_SPI3_PB5 GPIO_AF7_SPI3 // MOSI — exception: AF7, not AF6
+#define SPI_PIN_AF_SPI3_PC10 GPIO_AF6_SPI3 // SCK
+#define SPI_PIN_AF_SPI3_PC11 GPIO_AF6_SPI3 // MISO
+#define SPI_PIN_AF_SPI3_PC12 GPIO_AF6_SPI3 // MOSI
+#define SPI_PIN_AF_SPI3_PD6 GPIO_AF5_SPI3 // MOSI — exception: AF5, not AF6
+
+/* SPI4 — all data pins use AF5 */
+#define SPI_PIN_AF_SPI4_PE2 GPIO_AF5_SPI4 // SCK
+#define SPI_PIN_AF_SPI4_PE5 GPIO_AF5_SPI4 // MISO
+#define SPI_PIN_AF_SPI4_PE6 GPIO_AF5_SPI4 // MOSI
+#define SPI_PIN_AF_SPI4_PE12 GPIO_AF5_SPI4 // SCK
+#define SPI_PIN_AF_SPI4_PE13 GPIO_AF5_SPI4 // MISO
+#define SPI_PIN_AF_SPI4_PE14 GPIO_AF5_SPI4 // MOSI