Why Use the STM32 Arduino IDE Core?
If you have outgrown the 8-bit limitations of the Arduino Uno or the Wi-Fi-centric ESP32, the STM32 family offers a massive leap in raw compute, memory, and peripheral density. Using the official STM32Core package bridges the gap between the complex STM32CubeIDE HAL ecosystem and the accessible Arduino framework. You get 32-bit ARM Cortex-M performance, hardware floating-point units (FPU), and multiple I2C/SPI buses while keeping the familiar setup() and loop() structure.
Target Board Variant: WeAct Studio STM32F401CCU6 'Black Pill'. This code and wiring guide specifically target the F401 variant, not the older F103 'Blue Pill'.
Hardware Spec Sheet & Parts List
For new builds in 2026, skip the legacy STM32F103C8T6 'Blue Pill'. It lacks a hardware FPU, has limited flash (64KB), and requires an external CH340 serial adapter for USB communication. The STM32F401CCU6 'Black Pill' runs at 84MHz, includes a hardware FPU, boasts 256KB of flash, and features native USB pins routed directly to the Type-C connector.
| Component | Exact Variant / Model | Notes & Pricing (Approx.) |
|---|---|---|
| Microcontroller | WeAct Studio STM32F401CCU6 Core Board | Ensure it is the WeAct v3.1 layout. (~$6.50) |
| Programmer | ST-Link V2 (Genuine or high-quality clone) | Required for SWD flashing and hardware debugging. (~$4.00) |
| Sensor | Bosch BME280 Breakout (3.3V I2C) | Must be 3.3V logic. Do not use 5V-tolerant BME280 modules. (~$5.00) |
| Wiring | 28 AWG silicone jumper wires, 4-pin SWD ribbon | Keep SWD traces under 10cm to prevent signal reflection. |
Pin Mapping & Wiring Guide
The STM32 Arduino IDE core maps physical pins to logical names. Unlike the Uno, hardware I2C1 on the Black Pill defaults to PB6 (SCL) and PB7 (SDA). Hardware Serial1 (UART1) defaults to PA9 (TX) and PA10 (RX).
| Black Pill Pin | STM32 Name | Function | Connect To |
|---|---|---|---|
| 3V3 | VDD | 3.3V Power | ST-Link 3.3V & BME280 VIN |
| GND | VSS | Ground | ST-Link GND & BME280 GND |
| D21 | PB6 | I2C1 SCL | BME280 SCL |
| D22 | PB7 | I2C1 SDA | BME280 SDA |
| D8 | PA9 | USART1 TX | USB-Serial RX (if not using ST-Link VCOM) |
| D2 | PA14 | SWCLK | ST-Link SWCLK |
| D4 | PA13 | SWDIO | ST-Link SWDIO |
Numbered Wiring Steps
- Power the Target: Connect the ST-Link
3.3Vpin to the Black Pill3V3pin. Never connect the ST-Link 5V pin to the 3V3 rail; the F401 I/O is not 5V tolerant and will instantly destroy the silicon. - Wire SWD: Connect
SWDIOtoPA13andSWCLKtoPA14. ConnectGNDtoGND. - Wire I2C: Connect the BME280
SDAtoPB7andSCLtoPB6. The Black Pill has internal pull-ups on the I2C lines, but adding external 4.7kΩ pull-ups to 3.3V is recommended for cable runs over 5cm. - Verify Boot Jumpers: Ensure the
BOOT0jumper on the Black Pill is set to0(Flash boot) for normal operation, not1(System memory/DFU boot).
Complete Code: BME280 I2C with Hardware Serial & Error Handling
This sketch targets the WeAct STM32F401 Black Pill. It initializes hardware I2C1, reads the BME280, and outputs data via Hardware Serial1 (UART1). It includes explicit timeout handling to prevent the microcontroller from hanging if the serial monitor is disconnected or the I2C bus locks up.
#include
#include
#include
// --- PIN DEFINITIONS (WeAct STM32F401 Black Pill) ---
#define I2C_SDA_PIN PB7
#define I2C_SCL_PIN PB6
#define SERIAL_TX_PIN PA9
#define SERIAL_RX_PIN PA10
#define STATUS_LED PC13 // Active LOW on Black Pill
// --- OBJECTS ---
Adafruit_BME280 bme;
TwoWire myWire(I2C_SDA_PIN, I2C_SCL_PIN); // Explicit I2C1 mapping
void setup() {
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, HIGH); // LED OFF (Active LOW)
// Initialize Hardware Serial1 (UART1)
Serial1.setRx(SERIAL_RX_PIN);
Serial1.setTx(SERIAL_TX_PIN);
Serial1.begin(115200);
// Non-blocking serial wait with 3-second timeout
unsigned long startMillis = millis();
while (!Serial1 && (millis() - startMillis < 3000)) {
delay(10);
}
Serial1.println("STM32F401 Black Pill Booting...");
// Initialize I2C with explicit 400kHz Fast Mode
myWire.setClock(400000);
myWire.begin();
// BME280 Initialization with error handling
// 0x76 is the default I2C address for most Adafruit/generic BME280 modules
if (!bme.begin(0x76, &myWire)) {
Serial1.println("FATAL: Could not find a valid BME280 sensor on I2C1!");
Serial1.println("Check PB6/PB7 wiring and 3.3V power.");
// Blink LED rapidly to indicate hardware fault
while (1) {
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
delay(100);
}
}
Serial1.println("BME280 initialized successfully.");
bme.setSampling(Adafruit_BME280::MODE_NORMAL,
Adafruit_BME280::SAMPLING_X2, // Temp
Adafruit_BME280::SAMPLING_X16, // Pressure
Adafruit_BME280::SAMPLING_X1, // Humidity
Adafruit_BME280::FILTER_X16,
Adafruit_BME280::STANDBY_MS_500);
}
void loop() {
// Read sensors and check for NaN (Not a Number) errors
float temp = bme.readTemperature();
float pressure = bme.readPressure() / 100.0F;
float humidity = bme.readHumidity();
if (isnan(temp) || isnan(pressure) || isnan(humidity)) {
Serial1.println("ERROR: I2C read timeout or NaN received. Resetting I2C bus...");
myWire.end();
delay(50);
myWire.begin();
myWire.setClock(400000);
} else {
Serial1.print("Temp: "); Serial1.print(temp); Serial1.print(" C | ");
Serial1.print("Press: "); Serial1.print(pressure); Serial1.print(" hPa | ");
Serial1.print("Hum: "); Serial1.print(humidity); Serial1.println(" %");
// Toggle LED to show heartbeat
digitalWrite(STATUS_LED, LOW);
delay(50);
digitalWrite(STATUS_LED, HIGH);
}
delay(2000);
}
Debugging: 'ST-LINK error' and Upload Failures
The most common roadblock when adopting the STM32 Arduino IDE is the upload process. Unlike AVRs, STM32s use the SWD (Serial Wire Debug) protocol. If your upload fails, you will likely see this exact error string in the Arduino IDE console:
ST-LINK error (dev_1): USB init failedOR
Failed to connect to target / No STM32 target found!
The First 3 Things to Check When It Fails
- Power Delivery: Is the 3.3V pin connected? The ST-Link cannot communicate with the SWD controller if the STM32 core is unpowered. Do not rely on parasitic power from the SWDIO line.
- SWD Wiring Swap: SWDIO and SWCLK are frequently swapped on clone ST-Link dongles. Verify the pinout printed on your specific dongle's PCB, not just the plastic case.
- Target Reset State: If the STM32 is in a deep sleep mode or has SWD pins disabled in firmware, the ST-Link cannot connect. Fix: Press and hold the Black Pill's
RESETbutton, click 'Upload' in the Arduino IDE, and release theRESETbutton the exact moment the console says 'Connecting to target'.
Ranked Causes for Persistent Upload Failures
| Rank | Cause | Fix / Action Required |
|---|---|---|
| 1 | Windows USB Driver Mismatch | Download Zadig. Select 'STLink Debug', and replace the driver with WinUSB. The default Windows driver often blocks libusb access. |
| 2 | Read-Out Protection (ROP) Enabled | Download STM32CubeProgrammer. Connect via ST-Link, go to the 'OBK' (Option Bytes) tab, and disable Read-Out Protection. This will mass-erase the chip. |
| 3 | ST-Link Firmware Outdated | Use the official ST-Link Upgrade utility (included with STM32CubeIDE) to flash the latest V2 JTAG/SWD firmware to your programmer dongle. |
| 4 | Board Variant Mismatch in IDE | In Arduino IDE Tools menu, ensure 'Board part number' is set to BlackPill F401CC and 'Upload method' is set to STLink, not 'Serial' or 'DFU'. |
STM32 Arduino IDE FAQ
How do I install the official STM32 Arduino IDE core?
Open the Arduino IDE and navigate to File > Preferences. In the 'Additional Boards Manager URLs' field, paste: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json. Next, open the Boards Manager (Tools > Board > Boards Manager), search for 'STM32 MCU based boards', and install the package published by STMicroelectronics. Restart the IDE, and select your specific Black Pill variant under Tools > Board > STM32 boards groups.
Why does my STM32 Arduino code compile but fail to run on the Black Pill?
This is almost always caused by the BOOT0 jumper. If BOOT0 is bridged to 3.3V (position 1), the STM32 boots into its internal system memory (DFU/Serial bootloader) rather than executing your compiled code from Flash. Move the BOOT0 jumper to 0 (GND), press the hardware RESET button, and your sketch will execute normally. Additionally, ensure your code isn't hanging in setup() waiting for a Serial connection that isn't physically wired.
How can I extend this build to use the Black Pill's native USB CDC?
To simplify the build and eliminate the need for an external USB-to-Serial adapter on PA9/PA10, you can use the STM32F401's native USB port. In the Arduino IDE Tools menu, change 'USB support' to CDC (generic 'Serial' supersede U(S)ART). In your code, replace all instances of Serial1 with Serial. The Arduino core will automatically route Serial.print() through the Type-C connector. Note: You still need the ST-Link to flash the initial firmware via SWD, but subsequent updates can be done via the USB DFU bootloader.
Can I use the STM32 Arduino IDE core with PlatformIO instead of the Arduino IDE?
Yes, and it is highly recommended for complex projects. PlatformIO uses the exact same Arduino_Core_STM32 backend but offers vastly superior build caching, integrated SWD debugging via Cortex-Debug, and automated library management. To set it up in VS Code, create a new PlatformIO project, select the blackpill_f401cc board, and set the framework to arduino in your platformio.ini file. The pin mappings and code remain identical to what is shown in this guide.






