Repository navigation
SPI never initialised when settings match the default: configSpi() skips spi_init(), first transfer() hangs forever (3.0.0, F103C8) #3076
Description
On core 3.0.0, SPI.begin() followed by SPI.transfer() hangs forever on an
STM32F103C8 (Blue Pill). The SPI peripheral clock is never enabled and the SPI pins are
never switched to alternate function, so the first transfer spins in an untimed
while (!LL_SPI_IsActiveFlag_TXE(...)).
The cause is in SPIClass::configSpi()
(libraries/SPI/src/SPI.cpp):
void SPIClass::configSpi(const SPISettings &settings)
{
if (_spiSettings != settings) { // <-- skipped when they are equal
_spiSettings = settings;
...
spi_init(&_spi, clock, cspimode, order, cbusmode);
}
}_spiSettings is pre-initialised to SPISettings() (SPI.h):
SPISettings _spiSettings = SPISettings();and that default constructor is 4 MHz / SPI_MODE0 / MSBFIRST / SPI_CONTROLLER
(cores/arduino/api/HardwareSPI.h). SPIClass::begin() then builds:
SPISettings defaultSettings(SPI_SPEED_CLOCK_DEFAULT /* 4000000 */, MSBFIRST, SPI_MODE0, busMode);
configSpi(defaultSettings);which compares equal to the stored value, so spi_init() is never called. Any
library that subsequently calls beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)) -- the most common choice, and what GxEPD2 uses -- also compares equal and
is skipped again. The peripheral is therefore never initialised at all.
It then hangs rather than failing, because on F1 the transfer loop in
libraries/SPI/src/utility/spi_com.c has no timeout:
while (!LL_SPI_IsActiveFlag_TXE(_SPI)); /* unbounded */
...
while (!LL_SPI_IsActiveFlag_RXNE(_SPI)); /* unbounded */
...
while (LL_SPI_IsActiveFlag_BSY(_SPI)); /* unbounded */The SPI_TRANSFER_TIMEOUT check only executes after both spins have already completed,
so it can never break out of a stuck flag.
To Reproduce
Board: BLUEPILL_F103C8, FQBN
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8, core 3.0.0.
#include <SPI.h>
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // LED on: we got this far
SPI.begin();
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
SPI.transfer(0x00); // never returns
SPI.endTransaction();
digitalWrite(LED_BUILTIN, HIGH); // never reached
}
void loop() {}Changing the settings to anything non-default, e.g.
SPISettings(1000000, MSBFIRST, SPI_MODE1), makes it work.
Expected behavior
SPI.begin() initialises the peripheral regardless of whether the requested settings
happen to equal the stored ones.
Register evidence
Halted in the hang with st-util -n + arm-none-eabi-gdb:
#0 spi_transfer ()
#1 SPIClass::transfer(unsigned char, bool) ()
#2 ...
RCC_APB2ENR (0x40021018) = 0x00004015 <- bit 12 SPI1EN CLEAR
SPI1 CR1/CR2/SR/DR (0x40013000) = 0x0 0x0 0x0 0x0
GPIOA CRL (0x40010800) = 0x44411144 <- PA5/PA6/PA7 = 0x4, floating inputs
After forcing one non-default beginTransaction(), the same sketch gives:
RCC_APB2ENR = 0x00005015 <- SPI1EN set
SPI1 CR1 = 0x00000364 <- SPE, MSTR, SSM, SSI, MODE0
SPI1 SR = 0x00000002 <- TXE
GPIOA CRL = 0x99911144 <- PA5/PA6/PA7 alternate-function push-pull
Suggested fix
Force the comparison to miss in begin(), so an explicit begin() always re-initialises
-- for example by resetting _spiSettings to a sentinel before calling configSpi(), or
by having begin() call spi_init() unconditionally and leaving the equality shortcut
to beginTransaction() only.
Separately, the F1 path in spi_com.c should honour SPI_TRANSFER_TIMEOUT inside the
flag spins, so a misconfigured peripheral fails rather than hanging the sketch with no
diagnostic.
Activity
fpistm commentedon Sep 13, 2026
Hi @jmlon
This is already fixed. See #3052.