A nice tutorial on getting started with STM32 and Eclipse, created by Mohammed Shalaby, can be found here.
Historically, and as also covered in that tutorial, development on an STM32 chip consists of first configuring the chip according to your specific PCB in CubeMX, followed by setting up a configuration within the Eclipse IDE to build and debug the project. However, it turns out that Eclipse is simply just generating a makefile
and running a make
command to build the project. To upload and debug, the fundamental tool involved is actually openocd
, and GDB is the debugger.
In this branch, we will be using all those tools directly. That is, we will build, upload, and debug the code without involving any editor, doing it all through the terminal. Then, we can use any editor we want to view and edit the code, as well as getting it to run the terminal commands for us. We will still be using CubeMX to generate the HAL code, and as it turns out, the makefile as well!
The benefits of this editor-independent approach consist of a much more fundamental understanding of what is happening, as well as the ability for each developer to use whatever IDE they want on the same code base.
If you would like to start from the absolute beginning, switch to the blank
branch, which contains nothing other than the config_stm32f4.ioc
file as well as this README.
In order to program and debug directly on the chip, we need to use the ST-LINK interface provided by our Discovery board. There are two ST-LINK versions that require two different sets of commands, both of which are provided in this document. In order to identify which version your board is running, I would recommend trying out both and identifying which version works, then running the commands associated with that version.
Assuming you are on the blank
branch, you will have only the following in your directory
uwb_firmware
βββ config_stm32f4.ioc
βββ README.md
- Open the CubeMX software.
- Use File > Load Project... to load the
config_stm32f4.ioc
file. - Under the Project Manager tab, in the Project section, you should see a field called
Toolchain / IDE
. Here, you can choose Makefile! - Leave everything else as-is, and click on GENERATE CODE.
This should populate the current directory with the following files.
uwb_firmware
βββ config_stm32f4.ioc
βββ config_stm32f4.xml
βββ Drivers
βΒ Β βββ CMSIS
βΒ Β βΒ Β βββ Device
βΒ Β βΒ Β βΒ Β βββ ST
βΒ Β βΒ Β βΒ Β βββ STM32F4xx
βΒ Β βΒ Β βΒ Β βββ Include
βΒ Β βΒ Β βΒ Β βΒ Β βββ stm32f405xx.h
βΒ Β βΒ Β βΒ Β βΒ Β βββ stm32f4xx.h
βΒ Β βΒ Β βΒ Β βΒ Β βββ system_stm32f4xx.h
βΒ Β βΒ Β βΒ Β βββ Source
βΒ Β βΒ Β βΒ Β βββ Templates
βΒ Β βΒ Β βββ Include
βΒ Β βΒ Β βββ cmsis_armcc.h
βΒ Β βΒ Β βββ cmsis_armclang.h
βΒ Β βΒ Β βββ cmsis_compiler.h
βΒ Β βΒ Β βββ cmsis_gcc.h
βΒ Β βΒ Β βββ cmsis_iccarm.h
βΒ Β βΒ Β βββ cmsis_version.h
βΒ Β βΒ Β βββ core_armv8mbl.h
βΒ Β βΒ Β βββ core_armv8mml.h
βΒ Β βΒ Β βββ core_cm0.h
βΒ Β βΒ Β βββ core_cm0plus.h
βΒ Β βΒ Β βββ core_cm1.h
βΒ Β βΒ Β βββ core_cm23.h
βΒ Β βΒ Β βββ core_cm33.h
βΒ Β βΒ Β βββ core_cm3.h
βΒ Β βΒ Β βββ core_cm4.h
βΒ Β βΒ Β βββ core_cm7.h
βΒ Β βΒ Β βββ core_sc000.h
βΒ Β βΒ Β βββ core_sc300.h
βΒ Β βΒ Β βββ mpu_armv7.h
βΒ Β βΒ Β βββ mpu_armv8.h
βΒ Β βΒ Β βββ tz_context.h
βΒ Β βββ STM32F4xx_HAL_Driver
βΒ Β βββ Inc
βΒ Β βΒ Β βββ Legacy
βΒ Β βΒ Β βΒ Β βββ stm32_hal_legacy.h
βΒ Β βΒ Β βββ stm32f4xx_hal_cortex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_def.h
βΒ Β βΒ Β βββ stm32f4xx_hal_dma_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_dma.h
βΒ Β βΒ Β βββ stm32f4xx_hal_exti.h
βΒ Β βΒ Β βββ stm32f4xx_hal_flash_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_flash.h
βΒ Β βΒ Β βββ stm32f4xx_hal_flash_ramfunc.h
βΒ Β βΒ Β βββ stm32f4xx_hal_gpio_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_gpio.h
βΒ Β βΒ Β βββ stm32f4xx_hal.h
βΒ Β βΒ Β βββ stm32f4xx_hal_i2c_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_i2c.h
βΒ Β βΒ Β βββ stm32f4xx_hal_pcd_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_pcd.h
βΒ Β βΒ Β βββ stm32f4xx_hal_pwr_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_pwr.h
βΒ Β βΒ Β βββ stm32f4xx_hal_rcc_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_rcc.h
βΒ Β βΒ Β βββ stm32f4xx_hal_spi.h
βΒ Β βΒ Β βββ stm32f4xx_hal_tim_ex.h
βΒ Β βΒ Β βββ stm32f4xx_hal_tim.h
βΒ Β βΒ Β βββ stm32f4xx_ll_usb.h
βΒ Β βββ Src
βΒ Β βββ stm32f4xx_hal.c
βΒ Β βββ stm32f4xx_hal_cortex.c
βΒ Β βββ stm32f4xx_hal_dma.c
βΒ Β βββ stm32f4xx_hal_dma_ex.c
βΒ Β βββ stm32f4xx_hal_exti.c
βΒ Β βββ stm32f4xx_hal_flash.c
βΒ Β βββ stm32f4xx_hal_flash_ex.c
βΒ Β βββ stm32f4xx_hal_flash_ramfunc.c
βΒ Β βββ stm32f4xx_hal_gpio.c
βΒ Β βββ stm32f4xx_hal_i2c.c
βΒ Β βββ stm32f4xx_hal_i2c_ex.c
βΒ Β βββ stm32f4xx_hal_pcd.c
βΒ Β βββ stm32f4xx_hal_pcd_ex.c
βΒ Β βββ stm32f4xx_hal_pwr.c
βΒ Β βββ stm32f4xx_hal_pwr_ex.c
βΒ Β βββ stm32f4xx_hal_rcc.c
βΒ Β βββ stm32f4xx_hal_rcc_ex.c
βΒ Β βββ stm32f4xx_hal_spi.c
βΒ Β βββ stm32f4xx_hal_tim.c
βΒ Β βββ stm32f4xx_hal_tim_ex.c
βΒ Β βββ stm32f4xx_ll_usb.c
βββ Inc
βΒ Β βββ FreeRTOSConfig.h
βΒ Β βββ main.h
βΒ Β βββ stm32f4xx_hal_conf.h
βΒ Β βββ stm32f4xx_it.h
βΒ Β βββ usbd_cdc_if.h
βΒ Β βββ usbd_conf.h
βΒ Β βββ usbd_desc.h
βΒ Β βββ usb_device.h
βββ Makefile
βββ Middlewares
βΒ Β βββ ST
βΒ Β βΒ Β βββ STM32_USB_Device_Library
βΒ Β βΒ Β βββ Class
βΒ Β βΒ Β βΒ Β βββ CDC
βΒ Β βΒ Β βΒ Β βββ Inc
βΒ Β βΒ Β βΒ Β βΒ Β βββ usbd_cdc.h
βΒ Β βΒ Β βΒ Β βββ Src
βΒ Β βΒ Β βΒ Β βββ usbd_cdc.c
βΒ Β βΒ Β βββ Core
βΒ Β βΒ Β βββ Inc
βΒ Β βΒ Β βΒ Β βββ usbd_core.h
βΒ Β βΒ Β βΒ Β βββ usbd_ctlreq.h
βΒ Β βΒ Β βΒ Β βββ usbd_def.h
βΒ Β βΒ Β βΒ Β βββ usbd_ioreq.h
βΒ Β βΒ Β βββ Src
βΒ Β βΒ Β βββ usbd_core.c
βΒ Β βΒ Β βββ usbd_ctlreq.c
βΒ Β βΒ Β βββ usbd_ioreq.c
βΒ Β βββ Third_Party
βΒ Β βββ FreeRTOS
βΒ Β βββ Source
βΒ Β βββ CMSIS_RTOS
βΒ Β βΒ Β βββ cmsis_os.c
βΒ Β βΒ Β βββ cmsis_os.h
βΒ Β βββ croutine.c
βΒ Β βββ event_groups.c
βΒ Β βββ include
βΒ Β βΒ Β βββ atomic.h
βΒ Β βΒ Β βββ croutine.h
βΒ Β βΒ Β βββ deprecated_definitions.h
βΒ Β βΒ Β βββ event_groups.h
βΒ Β βΒ Β βββ FreeRTOS.h
βΒ Β βΒ Β βββ list.h
βΒ Β βΒ Β βββ message_buffer.h
βΒ Β βΒ Β βββ mpu_prototypes.h
βΒ Β βΒ Β βββ mpu_wrappers.h
βΒ Β βΒ Β βββ portable.h
βΒ Β βΒ Β βββ projdefs.h
βΒ Β βΒ Β βββ queue.h
βΒ Β βΒ Β βββ semphr.h
βΒ Β βΒ Β βββ StackMacros.h
βΒ Β βΒ Β βββ stack_macros.h
βΒ Β βΒ Β βββ stream_buffer.h
βΒ Β βΒ Β βββ task.h
βΒ Β βΒ Β βββ timers.h
βΒ Β βββ list.c
βΒ Β βββ portable
βΒ Β βΒ Β βββ GCC
βΒ Β βΒ Β βΒ Β βββ ARM_CM4F
βΒ Β βΒ Β βΒ Β βββ port.c
βΒ Β βΒ Β βΒ Β βββ portmacro.h
βΒ Β βΒ Β βββ MemMang
βΒ Β βΒ Β βββ heap_4.c
βΒ Β βββ queue.c
βΒ Β βββ stream_buffer.c
βΒ Β βββ tasks.c
βΒ Β βββ timers.c
βββ README.md
βββ Src
βΒ Β βββ freertos.c
βΒ Β βββ main.c
βΒ Β βββ stm32f4xx_hal_msp.c
βΒ Β βββ stm32f4xx_hal_timebase_tim.c
βΒ Β βββ stm32f4xx_it.c
βΒ Β βββ syscalls.c
βΒ Β βββ system_stm32f4xx.c
βΒ Β βββ usbd_cdc_if.c
βΒ Β βββ usbd_conf.c
βΒ Β βββ usbd_desc.c
βΒ Β βββ usb_device.c
βββ startup
βΒ Β βββ startup_stm32f405xx.s
βββ startup_stm32f405xx.s
βββ STM32F405RGTx_FLASH.ld
Notice the presence of a Makefile
. Feel free to open this file and check it out.
In the project directory,
make all
and thats it. This will create a ./build
directory, with a bunch of stuff, including a .elf
file, which is the compiled firmware that we will be uploading to our board.
Steven suggests the following very basic tutorial on using make
: https://cs.colby.edu/maxwell/courses/tutorials/maketutor/.
Although OpenOCD can be downloaded explicitly, it is also possible to install it as a regular package
sudo apt-get install openocd
This will allow take care of creating a symbolic link to the openocd
command, allowing us to just use the openocd
command from any directory. Assuming you have built the code with make
, that you are still in the uwb_firmware
directory, and that the discovery board is plugged in with the appropriate jumpers removed, we can upload our firmware in one command. If your debugger is using ST-LINK V1, the command is
openocd -f board/stm32f4discovery.cfg -c "program ./build/config_stm32f4.elf verify reset exit"
If the debugger is using ST-LINK V2, then
openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg -c "program ./build/config_stm32f4.elf verify reset exit"
First, make sure the board is connected by USB and start OpenOCD in V1
openocd -f board/stm32f4discovery.cfg
or in V2
openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg
In a new terminal in the current uwb_firmware
directory
arm-none-eabi-gdb ./build/config_stm32f4.elf
and you will enter a GDB command line. The above step assumes that you have installed the arm-none-eabi-gcc
toolchain as per Mohammed's tutorial. To connect to the openocd server
(gdb) target remote localhost:3333
Then at this point you can use whatever GDB commands. You can directly load the firmware from here
(gdb) load
You can then use list
to see where you are in the code, as well as continue
or step
. Theres a way to set breakpoints from the GDB terminal, but at this point, we will move to using the VSCode editor for debugging.
At a minimum, you just need to open the uwb_firmware
folder in VS Code and you can start editting the source code with some basic syntax highlighting already. However, it will probably be full of red warnings as a result of Intellisense not finding all the files. We will need to configure intellisense properly.
Create a ./.vscode/
subdirectory. Create a c_cpp_properties.json
file and insert the following
{
"version": 4,
"configurations":
[
{
"name": "Linux: Embedded Development",
"intelliSenseMode": "gcc-arm",
"cStandard": "c99",
"cppStandard": "c++17",
"compilerPath": "/opt/gcc-arm-none-eabi-9-2020-q2-update/bin/arm-none-eabi-gcc"
,
"defines":
[
"USE_HAL_DRIVER",
"STM32F405xx"
],
"includePath":
[
"${workspaceFolder}/**",
"/opt/gcc-arm-none-eabi-9-2020-q2-update/lib/gcc/arm-none-eabi/9.3.1/include",
"Inc",
"Drivers/STM32F4xx_HAL_Driver/Inc",
"Drivers/STM32F4xx_HAL_Driver/Inc/Legacy",
"Middlewares/Third_Party/FreeRTOS/Source/include",
"Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS",
"Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
"Middlewares/ST/STM32_USB_Device_Library/Core/Inc",
"Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc",
"Drivers/CMSIS/Device/ST/STM32F4xx/Include",
"Drivers/CMSIS/Include"
]
}
]
}
Now you should have the full amazing code navigation/editting functionality of VS Code, including syntax highlighting, Go to Definition, Go to References, code peeking and more.
VS Code provides functionality to run whatever terminal command as a "task" from within the editor. In the ./.vscode/
folder, create a tasks.json
file with the following
{
"version": "2.0.0",
"tasks":
[
{
"label": "Build Firmware",
"group":
{
"kind": "build",
"isDefault": true
},
"type": "shell",
"command": "make all",
"args":
[
],
"problemMatcher":
[
"$gcc"
],
"presentation":
{
"focus": true
}
}
]
}
The line "isDefault": true
sets this task is the default build task. This means that all we need to do is press CTRL + SHIFT + B
to run the same make command as before.
Alternatively, press CTRL + SHIFT + P
to open the command palette, and select Run Task, it will then ask you which one to choose.
Just as before, we just need to create a VS Code task to run the upload command for us. Open tasks.json
and add the following task just after the build task (seperated by a comma)
{
"label": "Upload Firmware V2",
"type": "shell",
"command": "openocd",
"args":
[
"-f","interface/stlink-v2-1.cfg",
"-f","target/stm32f4x.cfg",
"-c","'program build/config_stm32f4.elf verify reset exit'"
]
},
{
"label": "Upload Firmware V1",
"type": "shell",
"command": "openocd",
"args":
[
"-f","board/stm32f4discovery.cfg",
"-c","'program build/config_stm32f4.elf verify reset exit'"
]
}
And thats it! You can run this from the command palette by pressing CTRL + SHIFT + P
and typing in Run Task, after which the suggestions will prompt you for which task to choose.
For this step, the easiest thing to do is to install the Cortex-Debug extension for VS Code.
Then, create a launch.json
file inside the .vscode
folder with the following contents.
{
"version": "0.2.0",
"configurations":
[
{
"name": "Build and Debug V2",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceFolder}",
"executable": "./build/config_stm32f4.elf",
"device": "STM32F405RGT6",
"svdFile": "${workspaceFolder}/.vscode/STM32F405.svd",
"configFiles":
[
"/usr/share/openocd/scripts/interface/stlink-v2-1.cfg",
"/usr/share/openocd/scripts/target/stm32f4x.cfg"
],
"preLaunchTask": "Build Firmware",
"overrideGDBServerStartedRegex": "Info\\s:\\s([^\\n\\.]*)\\.cpu([^\\n]*)"
},
{
"name": "Build and Debug V1",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceFolder}",
"executable": "build/config_stm32f4.elf",
"device": "STM32F405RGT6",
"svdFile": "${workspaceFolder}/.vscode/STM32F405.svd",
"configFiles":
[
"/usr/share/openocd/scripts/board/stm32f4discovery.cfg"
],
"preLaunchTask": "Build Firmware",
"overrideGDBServerStartedRegex": "Info\\s:\\s([^\\n\\.]*)\\.cpu([^\\n]*)"
}
]
},
Create a settings.json
file inside the .vscode
folder with the following contents.
{
"cortex-debug.armToolchainPath": "",
"cortex-debug.openocdPath": "/usr/bin/openocd"
}
You should now be able to go to the debug tab in VS Code and see a Build and Debug
option.
Makefile tutorial: https://cs.colby.edu/maxwell/courses/tutorials/maketutor/
FreeRTOS tutorial: https://freertos.org/fr-content-src/uploads/2018/07/161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf