ESP32 core 3.x - pinMode() can break your SPI bus
My SD card module needed a pull-up resistor on MISO. Many SD modules don’t have one, and SD cards need it. The ESP32 has internal pull-ups, so the obvious line after starting the bus is this one.
sdSpi.begin(PIN_SD_SCLK, PIN_SD_MISO, PIN_SD_MOSI, PIN_SD_CS);
pinMode(PIN_SD_MISO, INPUT_PULLUP); // don't do this in core 3.x
In the Arduino core for ESP32 version 3.x, that line disconnects MISO from the SPI bus. The ESP32 can still send to the card, but it no longer hears the answers, so SD.begin() fails. Nothing in the code looks wrong.
Why it happens
Core 3.x added a “peripheral manager”. It remembers which peripheral owns each pin (SPI, I2C, UART, PWM and so on). When you call pinMode(), the core takes the pin back as a plain GPIO and releases it from whatever owned it before. For SPI’s MISO pin, releasing it runs the SPI code that detaches MISO from the bus. You can see it in the core’s source, in esp32-hal-gpio.c (pinMode() calls perimanClearPinBus()) and esp32-hal-spi.c (spiDetachBus_MISO()).
It’s a good idea in general. Two drivers can’t fight over one pin by accident. But it turns pinMode() into “take this pin away from its peripheral”, not just “set the pull-up”.
The fix
Set the pull-up directly with the ESP-IDF function, which changes only the pull-up and leaves the pin attached to SPI. It’s available in every Arduino sketch for the ESP32.
#include <driver/gpio.h>
sdSpi.begin(PIN_SD_SCLK, PIN_SD_MISO, PIN_SD_MOSI, PIN_SD_CS);
// Cards need MISO pulled up and many modules lack the resistor. Not pinMode():
// in core 3.x that would detach the pin from the SPI bus.
gpio_pullup_en((gpio_num_t)PIN_SD_MISO);
Call it after begin(), so nothing configures the pin again afterwards. That’s what runs in my ESP32 photo viewer.
The internal pull-up is weak (around 45 kΩ). That’s fine for an SD card at 20 MHz on short wires. With long wires or problems at high speed, an external 10 kΩ resistor from MISO to 3.3 V is more solid.
The same trap elsewhere
The rule is the same for any pin that a peripheral owns, not only SPI.
- Don’t call
pinMode()on I2C, UART, SPI or PWM pins after starting that peripheral. - To change only the pull-up or pull-down on such a pin, use
gpio_pullup_en(),gpio_pulldown_en()or their_dis()versions. - If a peripheral suddenly stops working after a core update, search your code for
pinMode()on its pins.