This content originally appeared on DEV Community and was authored by Alex Mo
TL;DR (Quick Start)
Install STM32CubeIDE (Windows/macOS/Linux).
Create Project: File → New → STM32 Project → pick your MCU or Board (e.g., NUCLEO-L053R8).
Configure Peripherals: in Device Configuration Tool (a.k.a. CubeMX-in-IDE).
Generate Code → open Core/Src/main.c.
Write Code inside /* USER CODE BEGIN / … / USER CODE END */ blocks.
Build (hammer icon).
Program/Debug (green bug ▶️). Press the board button, watch the LED respond. 🎉
What Is STM32?
STM32 is STMicroelectronics’ family of 32-bit microcontrollers based on ARM Cortex-M cores (M0/M0+, M3, M4, M7, M33…). The range spans ultra-low-power parts to high-performance models with rich peripherals (ADC/DAC, timers, SPI/I²C/UART, USB, CAN, Ethernet, SDMMC, etc.). ST’s MCU Finder helps you filter by power, speed, memory, package, peripherals, and price.
What You Need
1) Documentation (keep it handy)
Datasheet (device features, electricals, pinout)
Reference Manual (peripheral registers & behavior)
Board User Manual (Nucleo/Discovery pin maps, LED/button labels)
You don’t have to memorize it all—just know where things live.
2) Language
C is the right place to start on STM32. (You can mix in C++ later.) Learn the basics of types, pointers, headers, and build systems.
3) Hardware
Nucleo (recommended): On-board ST-LINK debugger/programmer; Arduino headers for shields.
Discovery: Often includes sensors/displays; also has on-board ST-LINK.
BluePill / custom board: Use an external ST-LINK/V2 (or a Nucleo as a programmer). Connect SWDIO, SWCLK, GND, 3V3; set BOOT0=0 for normal boot.
Tip: On Nucleo boards, keep the ST-LINK jumpers in the default “on-board target” position. You can remove them later to use the Nucleo as an external programmer.
4) Software (free)
STM32CubeIDE = Eclipse + CubeMX + GCC + GDB + ST-LINK tools in one install. It handles:
MCU/Board selector
Pin/peripheral configurator
Code generator (HAL/LL)
Compiler, linker
Debugger & programmer
5) Framework Options (how you code)
Bare-metal registers (“pure C”)
Max control & speed; steep learning curve; live in the Reference Manual. Great for experts and ultra-tight targets.
Arduino core for STM32
Easy for small demos; limited board support and quality varies. Not ideal if you plan to move to professional workflows.
Mbed OS
C/C++ with online/offline tooling and drivers. Feels heavier and opinionated; fine if you want its RTOS/driver ecosystem.
LL (Low-Layer) drivers
Thin, close to registers; efficient; more work than HAL.
HAL (Hardware Abstraction Layer) ✅ Recommended for beginners
High-level, readable APIs; fast to prototype; plenty of examples.
You can mix HAL and LL per peripheral in Cube settings.
The STM32CubeIDE Workflow (End-to-End)
Step 0 — Install STM32CubeIDE
Download from ST’s website, install with defaults. On Windows, the installer can also add ST-LINK drivers. On macOS/Linux, you’re generally good out of the box.
Step 1 — Create a New Project
File → New → STM32 Project
Board Selector: search your exact Nucleo/Discovery (e.g., NUCLEO-L053R8) and select it.
Peripherals like LED and USER button are pre-mapped for you.
Name your project (e.g., hello_world) → Finish.
When asked, accept Initialize all peripherals with default.
Step 2 — Configure Peripherals (Device Configuration Tool)
You’re now in the integrated CubeMX view:
Pinout & Configuration: Enable what you need (GPIO, UART, I²C, SPI, timers…). Cube assigns pins automatically; you can remap if needed.
Clock Configuration: Adjust PLL and bus clocks if you’re using an external crystal or need specific peripheral clocks.
Project Manager → Code Generator:
✅ Check “Generate peripheral initialization as a pair of .c/.h per peripheral” for a cleaner structure.
Example: Enable SPI1 as Full Duplex Master; Cube maps SCK/MISO/MOSI and creates spi.c/h with init code.
Step 3 — Generate the Project
Project → Generate Code. This writes the Core/ and Drivers/ scaffolding and peripheral init functions.
Step 4 — Understand the Files
Typical structure:
Core/
Inc/ // headers (main.h, gpio.h, ... )
Src/ // sources (main.c, gpio.c, ... )
Drivers/
STM32xx_HAL_Driver/ // HAL sources
CMSIS/ // Cortex & device headers
Your entry point is Core/Src/main.c.
Only write inside the marked regions:
/* USER CODE BEGIN Includes */
...
/* USER CODE END Includes */
Anything outside can be overwritten the next time you regenerate code.
Step 5 — “Hello World”: Button → LED
Goal: When the button is pressed, turn the LED on; otherwise off.
Open main.c and in the main loop add:
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin))
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
}
/* USER CODE END WHILE */
Notes:
B1_* and LD2_* macros are board-specific and come from the Board setup. You don’t need to hard-code ports/pins.
To toggle instead, you can use:
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
HAL_Delay(200); // 200 ms
Step 6 — Build & Flash
Build: click the hammer (or Project → Build All). Zero errors? Great.
Program/Debug: click the bug. STM32CubeIDE will:
Detect ST-LINK, suggest a debug config, and offer firmware update if needed.
Load your .elf to the target and start a debug session.
You can now set breakpoints, step, watch variables, and hit Resume to run.
Going Deeper (Quick Wins)
A) Debounce the Button (simple)
uint32_t t0 = 0;
while (1) {
GPIO_PinState s = HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin);
if (s == GPIO_PIN_SET && (HAL_GetTick() - t0) > 30) {
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
t0 = HAL_GetTick();
}
}
B) Use External Interrupt (EXTI)
In Pinout, set the button pin to GPIO_EXTI.
Cube generates HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin). In it:
if (GPIO_Pin == B1_Pin) {
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
NVIC tab: ensure EXTI interrupt is enabled.
C) Print Debug Text
Easiest: enable USART in Cube; in retarget.c or syscalls.c map printf to UART.
Alternative: SWV ITM (single-wire viewer) for printf-like tracing without using a UART (supported on many cores).
D) Mix HAL & LL
In Project Manager → Advanced Settings, set specific peripherals to LL for tighter control/latency, while keeping others on HAL for convenience.
Common Pitfalls & Fixes
“Target not found” / connect fails
Use a good USB cable.
Board powered (USB) and ST-LINK jumpers in default position.
Try a slower SWD frequency in the debug config.
On custom boards: verify SWDIO/SWCLK/GND/3V3 and NRST.
My code disappeared after regenerating
You edited outside /* USER CODE */ blocks. Move your code into the blocks and regenerate.
LED/Buttons don’t match pins
Check your board’s User Manual and the Pinout view. Board names (LD2, B1) map correctly when you picked the right Board in the selector.
HAL_Delay blocks everything
Use timers or interrupts for accurate, non-blocking tasks.
Adapting to a BluePill or Custom Board
In New Project, choose the exact MCU (e.g., STM32F103C8Tx) instead of a Board.
Manually enable GPIO, clocks, and any peripherals you need.
Program via external ST-LINK/V2 (SWDIO, SWCLK, GND, 3V3).
If you used an external crystal, configure it in Clock Configuration.
On first connect, do a Full Chip Erase from the debugger if needed.
A Clean Project Template You Can Reuse
Core/Inc/app.h, Core/Src/app.c for your application logic.
Leave main.c minimal (init + scheduler loop).
One module per peripheral/feature (e.g., led.c, buttons.c, uart.c).
Keep code inside USER CODE blocks; add your own blocks within your modules for consistency.
FAQ
Q: HAL vs. LL vs. Registers—what should I pick?
A: Start with HAL. Move hot paths or special cases to LL, and only drop to registers when you truly need it.
Q: Can I reuse my code across STM32 families?
A: Yes—HAL helps a lot. Pin names/peripheral instances may change; keep hardware-dependent bits isolated.
Q: Do I need an RTOS?
A: Not for simple apps. For multiple time-sensitive tasks, consider FreeRTOS (Cube can add it for you).
Next Steps (Mini Roadmap)
UART: send/receive text and logs.
I²C/SPI: talk to sensors and displays.
Timers/PWM: measure signals, drive motors/LEDs.
ADC/DAC: read analog sensors, generate waveforms.
FreeRTOS: structure complex apps with tasks, queues, timers.
Bootloaders & OTA: production-grade updates.
Low-Power modes: for battery devices (STOP/STANDBY, RTC wakeups).
Sample “Hello Button–LED” (Complete Minimal Main Loop)
Where to paste: Core/Src/main.c inside the /* USER CODE BEGIN WHILE */ loop.
/* USER CODE BEGIN WHILE */
while (1)
{
if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_SET) {
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
}
}
/* USER CODE END WHILE */
This content originally appeared on DEV Community and was authored by Alex Mo

Alex Mo | Sciencx (2025-09-18T06:59:12+00:00) How to Program STM32 with STM32CubeIDE – The Complete Guide. Retrieved from https://www.scien.cx/2025/09/18/how-to-program-stm32-with-stm32cubeide-the-complete-guide/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.