STM32 - Docker bare-metal C/C++ project Container
ℹ️ Introduction
This project sets up a Linux (Debian) container running on Docker Desktop for Windows, tailored for STM32 MCU development. It uses the STM32 ST-Link to flash programs to the device. Example startup programs are included for the STM32F4-Discovery board.
✨ Features
- Minimal yet flexible bare-metal C/C++ projects for STM32F4-Discovery and related STM32F4 devices, showing:
- Custom linker script usage
- How startup code integrates with the linker script
- Implementation of C system calls for functions like
printf()andscanf() - A working
USART(serial interface) for serial input\output (e.g., PuTTY) - Use of memory-mapped hardware registers
- Built using make (no IDE dependency, Visual Studio Code supported)
- C project template (./projects/project-1_c)
- Demonstrates polymorphic design in C to support multiple independent applications (
app1,app2) Where each app can be configured wit different device initialization code or application initalization code - Demonstrates:
- USART communication (tested with PuTTY)
- Blinking LEDs
- Includes standard C runtime integration (e.g.,
malloc,printf,scanf) through custom system call implementations
- Demonstrates polymorphic design in C to support multiple independent applications (
- C++ project template (./projects/project-1_cpp)
- Same basic functionality as the C project. Startup code in C; the rest in C++
- Improved Makefile
- Utility to display include dependencies
- Preconfigured Visual Studio Code files (see paragraph 4)
launch.jsonto flash and debug the programtasks.jsonto build and run commands inside the container
⚙️ Build and configure the container
🛠️ Build container
To build the docker container enter the root folder of the STM32F4 project and execute the following command:
docker-compose up --build
This should create and start the container.
🔌 Steps to expose the USB device to WSL
To connect the host USB to the Docker container, we must ensure the USB device is accessible inside the WSL environment. Since Docker Desktop uses WSL, once it’s visible there, it will also be available to the Docker container.
Note You only need to do this if the USB device is not yet attached to WSL. There’s no need to reattach it after every reboot unless it detaches.
List USB Devices (in PowerShell)
To expose the USB device from Windows to WSL, we use the utility driver usbipd-win. Open an Administrator PowerShell and run:
usbipd list
This should return something like: STM#@ DTLINK (i.e: 3-3) # 3-3 is your BUSID
Note When
usbipdis not installed, download it from usbipd-win GitHub Releases
Attach the USB to the WSL
First, identify which WSL distro is used by Docker Desktop:
Docker Desktop → Settings → Resources → WSL Integration
This should return something like: Ubuntu-docker-App-X11-Win32Dev*
Then, in an Administrator PowerShell, use the commands below. Replace the BUSID (e.g., 3-3) and the WSL distro name with the ones you found earlier:
usbipd bind --busid 3-3 # 1e share the BUS ID device usbipd attach --wsl Ubuntu-docker-App-X11-Win32Dev --busid 3-3
Running usbipd list again should show: STATE: Attached
Is USB connected, disconnect USB (WSL/VSC)
Check the USB status
- Start the WSL
wsl -d Ubuntu-docker-App-X11-Win32Dev # Also use: wsl -l- Or start a terminal from within Visual Studio Code (see section 4) and run:
lsusb- If lsusb is not available, install it:
apt-get install usbutilsExpected output: ‘Bus 001 Device 002: ID 0483:3748 STMicroelectronics ST-LINK/V2’ -for more details run: dmesg | grep -i usb
- If lsusb is not available, install it:
Detach the USB
From Admin PowerShell:
usbipd detach --busid <BUSID>
⚡ Build and flash the program
This section describes a few commands to manually test the environment using the Docker CLI.
The actual development workflow is intended to be used from Visual Studio Code (see Section 4), which provides the same functionality and much more.
⚙️ Build the sample project111
In the directory: /workspace/Projects/Project execute the following command:
make
make should return Successfully finished… and has created a new ./bin directory with the binary results
🛠️ Manual Debug
From a CLI terminal inside the Docker container, run:
st-info --probe
This should return: Found 1 stlink programmer along with some additional information.
In a new Docker CLI terminal, start OpenOCD to listen for debugger (GDB) connections:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg` *NOT NEED VSC DOES THIS IMPLICITE!*
You should see: Info : Listening on port 3333 for gdb connections and OpenOCD waits for GDB commands
No Need for it in VS Code
This command is not needed when using Visual Studio Code, it is done automatically. Running this manually while debugging in VS Code will cause an error
In another terminal (inside the container), from the ./bin directory, run:
gdb-multiarch project.elf
Then inside the debugger, you can execute command like these:
(gdb) target remote localhost:3333 (gdb) load (gdb) monitor reset init (gdb) monitor reset run # Leaves the program running after quit command, wait (gdb) continue
To exit the debuger and rest the controller
(gdb) monitor reset halt # reset device (gdb) detach # leave debug session (gdb) quit
Other sample
(gdb) target remote localhost:3333 (gdb) load (gdb) monitor reset init (gdb) break main (gdb) use 'step' and other debug commands .... (gdb) monitor reset run (gdb) CTRL-X -> Exit # Program is flashed, manually reset(button) device to run it
💡 For more commands and usage tips, consult the official GDB documentation online.
🔦 Manual Flash
To manually flash your device you can use make, Run:
make burn
Burn with Window ST-Link Utility
When for a reason the above flash fails or your unsure it went well you can docnload ths STM32 utility andflash the program with it. Use the file open command, select the .bin file that was created with the make file and, select the oprion Program and Verify this should load the program, verify it and after that reset the device so it start to run(there are some settings that control this so make sure to check these!)
Visual code
Make sure the following extensions are installed and activated (inside the container):
- C/C++ Extention Pack (ms-vscode.cpptools)
- Remote- containers
- Cortex-Debug (OpenOCD) (marus25.cortex-debug)
- Make file tools(ms-vscode.makefile-tools)
- Arm Assembly (dan-c-underwood.arm)
- live server, optional for html content and images (ritwickdey.LiveServer)
Open container
- Press
F1and choose: Remote-Containers: Attach to Running Container - Select: stm32-dev-vxx
📁 Open the following folder inside the container
Two projects are provided:
/workspace/code-files/projects/project-1_c
A sample C project/workspace/code-files/projects/project-1_cpp
A sample C++ Project
🛠️ Build the Project
Use Terminal -> Run Task... in Visual Studio Code to access available make tasks.
These tasks are named starting with STM32-.
🐞 Debug the Project
The launch file in .vscode/launch.json is already configured. To debug:
- Ensure OpenOCD is not running manually — Visual Studio Code will start it automatically.
- Press
F5to start debugging. You should see something like this in the Debug Console:
make burn
ortex-Debug: VSCode debugger extension version 1.12.1 git(652d042). Usage info: https://github.com/Marus/cortex-debug#usage
"configuration": {
"name": "Debug STM32",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd"
...
License
This file is part of: Net-Core-Template Stack Copyright (c) 2025-2026 Nico Jan Eelhart.This repository is MIT licensed and free to use. For optional commercial support, customization, training, or long-term maintenance, see COMMERCIAL.md.
Some header files in this project are © STMicroelectronics and used under their permissive license. See license notices in each file, and refer to: https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series.html
Appendix I - Use the USART for input and output.
This implementation supports input and output via the STM32’s serial interface.You can use a terminal application like PuTTY to interact with the device on the host.
➡️ or more details, see the separate document accessible:
─── ✦ ───