Skip to main content

1.4. Project template (2026)


In this tutorial, let us create from scratch a minimal project that include everything we actually need to start programming STM32 devices. It is based on the initial blink demo project, but we will bring some additional headers and functions in, that makes the coding a bit more explicit.

With minor adaptations, you may consider this tutorial as a reference for starting new projects with any STM32 devices.

1. Create a New Project

Using the Project Explorer, close any projects that have been left open. 

The first steps are the same as for the 1.2. Hello World  tutorial. You can repeat the operations of section #1 "Creating a new project" without any changes, except the name you provide for the project. Let's replace "blink" by "my_project". 

When done, give a try building the project to make sure everything is in place so far.

You should end up with this project structure:

 

2. Edit the project structure (folders)

Using the contextual (right-click) menu, delete the /Inc folder.

 

And confirm you want to remove it from the filesystem (hard drive):

 

Do not worry for now if the include path generates a warning. Obviously, that path doesn't exist anymore. We'll fix that later.

Then, still using the contextual menu (right-click) within the Project Explorer and selecting:

  • New →
  • New →

create the following folder structure:

  • /app folder will be used to put application-level source files:
    • /app/inc for headers (.h)
    • /app/src for functions implementation (.c)
       
  • /bsp (board support package) folder will used to put driver-level source files used for peripherals initialization and board-related low-level functions:
    • /bsp/inc for headers (.h)
    • /bsp/src for functions implementation (.c)
       
  • /cmsis folder will be used to put STM32 device drivers:
    • /cmsis/core for CMSIS headers
    • /cmsis/device/inc for device headers
    • /cmsis/device/src for device startup source code

Now, within the project explorer:

  • Drag-n-drop (move) the 3 source files (main.c, syscalls.c, sysmem.c) from the /Src folder, to the /app/src folder

  • Delete the /Src folder

  • Drag-n-drop (move) the startup_stm32f072rbtx.s file from the /Startup folder, to the /cmsis/device/src folder

  • Delete the /Startup folder

The resulting project files and folder structure now is:

 

Finally, using again the contextual menu with New, add a new header main.h to the /app/inc folder. Select the Default C header template and click Finish.

 

Note that this folder structure is only a suggestion. If you know what you are doing, you can organize files the way you want and even put everything below root folder, although not recommended…

As your project grows, the number of source files can become really big. You need a clean file structure that you know and understand well to navigate comfortably between sources. The sooner you get familiar with your choice of folder structure, the better. Even for small projects.

 

3. Get the CMSIS files

A STM32 microcontroller, just like many others MCUs from several silicon vendors, is designed around an ARM Cortex-M core processor. All ARM-based microcontrollers come with a set of source files as defined by ARM under the Cortex Microcontroller Software Interface Standard (CMSIS) specification.

https://developer.arm.com/tools-and-software/embedded/cmsis

Practically, it is only few files you have to include in your project structure. These files are illustrated below :

Note that we already have the startup file.

In order to get up-to-date CMSIS source files, a good option is to download the latest release of STM32 Cube libraries for the targeted device familly (STM32F0 here). It comes as a pretty big package including HAL libraries, but do not worry, we will only pick-up few files from this package.

You can get the STM32F0 Cube library from ST website : https://www.st.com/en/embedded-software/stm32cubef0.html (Version 1.11.0 at time of writing).

 

Unzip the downloaded archive and open the folder:

 

In another window, open your project folder (located in your workspace folder):

 

Then copy/paste the following files, from the Cube library, into your project folders:

File(s) Source folderDestination folder
*.h\Drivers\CMSIS\Include\\cmsis\core\
system_stm32f0xx.h\Drivers\CMSIS\Device\ST\STM32F0xx\Include\cmsis\device\inc
system_stm32f0xx.c\Drivers\CMSIS\Device\ST\STM32F0xx\ Source\Templates\cmsis\device\src
stm32f0xx.h, stm32f072xb.h\Drivers\CMSIS\Device\ST\STM32F0xx\Include\cmsis\device\inc
stm32f0xx_it.h\Projects\STM32F072RB-Nucleo\Templates\Inc\app\inc
stm32f0xx_it.c\Projects\STM32F072RB-Nucleo\Templates\Src\app\src

If you are working with a device other than the STM32F072RB, just adapt the previous table to your needs… All ST's Cube libraries share the same file structure and naming convention.

Back into Eclipse, Refresh the Project Explorer (press F5). Your project structure now should be:

 

Some more explanations about files we've just added to the project:

  • The core headers are required to access dedicated CPU functionalities, which are not part of ST hardware. For instance, we use core CPU functions to configure the system timer (Systick), the Nested Vector Interrupt Controller (NVIC), and Low-Power modes.

  • STM32F0 headers (stm32f0xx.h, stm32f072xb.h) contain definitions (aliases) for all STM32 peripheral registers and their content. It is not a library, it is basically nothing more than a huge (≈10.000) list of #define. It allows calling a register and associated bit by names instead of addresses. For example, the code below we used to toggle the LED state (pin PA5) in previous labs:

*(int *)0x48000014 ^= 0x00000020U;

can now be written:

GPIOA->ODR ^= GPIO_ODR_5;

which is exactly same code, as there are just a #define behind GPIOA, ODR, GPIO_ODR_5 labels. Still, it makes code writing and reading way more comfortable. When hovering the mouse over a defined symbol, you get a bubble info that provides the definition:

 

These headers also include data types based on <stdint.h> that we will use instead of standard C types for integer numbers:

C typesEmbedded types
charint8_t
unsigned charuint8_t
shortint16_t
unsigned short uint16_t
intint32_t
unsigned intuint32_t
  • system_stm32.c and system_stm32.h provide few functions and macros you may want to use. In particular, the default clock settings are defined here and called from the startup routine.
     

  • stm32f0xx_it.c and stm32f0xx_it.h are there to implement interrupt handlers. This topic is addressed later.

 

4. Setting project build properties

At this moment, if you try the build button, it won't work. We need to configure the build first.

Right-click on the project folder and select → Properties

Select the C/C++ Build→Settings

  • In the MCU/MPU Settings section, review the MCU fields for your target device (just as we did in the "Hello World" tutorial):



     

  • In the MCU/MPU GCC Linker, General section, review the path to your Linker Script (just as we did in the "Hello World" tutorial):



     

  • In the MCU/MPU GCC Compiler, Include paths section, you must provide paths to all the header files (.h) in your project. In our example, we have (or will have) headers in:

    • /app/inc
    • /bsp/inc
    • /cmsis/core
    • /cmsis/device/inc

First, use the Delete button to remove the obsolete path to ..\Inc folder

Then use the Add button and then browse the Workspace to select folders. Doing so avoids mistakes and write portable paths.

 

Make sure all 4 paths are defined as follows:

 

When you're done, click the Apply & Close button of the Properties dialog.

Try the build button and watch the console. You should get several errors. Scroll up to the first one:

C:/STM/workspace_tuto/blink3/cmsis/device/inc/stm32f0xx.h:159:3: error: #error "Please select first the target STM32F0xx device used in your application (in stm32f0xx.h file)"
  159 |  #error "Please select first the target STM32F0xx device used in your application (in stm32f0xx.h file)"
      |   ^~~~~


This error is reported from within the stm32f0xx.h header:

#else
 #error "Please select first the target STM32F0xx device used in your application (in stm32f0xx.h file)"
#endif

 

You'll notice that device headers #include above that line are actually all grayed-out, meaning that no header is in fact included. The reason is that you must select which particular device you want to target in this project.

There are 2 ways to to that:

  • By editing the stm32f0xx.h header. You can either:

    • Add this

      #define STM32F072xB

      At the beginning of the header

    • Or simply un-comment the following line:

        /* #define STM32F072xB */  /*!< STM32F072x8, STM32F072xB Devices (STM32F072xx microcontrollers where the Flash memory ranges between 64 and 128 Kbytes)             */
  • By adding a preprocessor symbol in the build configuration. I definitely recommend this method because it leaves ST headers clean from any modification so that you can reuse those in another project without troubles.

 

Go back to project properties, under C/C++ Build→Settings, and open the  MCU/MPU GCC Compiler, Preprocessor section. In the Define symbols area, start by removing all of the previously defined symbols.

Then click the Add button and edit the symbol you want to add:

 

Make sure the symbol has been added, and then Apply & Close the Properties dialog.

 

You can notice immediate effect in the stm32f0xx.h header. The stm32f072xb.h header is no more grayed-out!

 

We're almost done... One last thing to do is to edit the stm32f0xx_it.c file. Comment out the call to HAL_IncTick() int the SysTick_Handler() function. We're not using HAL libraries.

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
  // HAL_IncTick();           // <- Comment this line
}

 Well done. Save all .

 

5. New blinking demo

Delete all the content of the main.c file (CRTL+A, Suppr) and replace it with this one:

/*
 * main.c
 *
 *  Created on: 15 mai 2021
 *      Author: Laurent
 */

#include "stm32f0xx.h"

int main()
{
	uint32_t i;
	
	// Start GPIOA clock
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
	
	// Configure PA5 as output
	GPIOA->MODER &= ~GPIO_MODER_MODER5_Msk;
	GPIOA->MODER |= (0x01 <<GPIO_MODER_MODER5_Pos);
	
	while(1)
	{
		// LED toggle
		GPIOA->ODR ^= GPIO_ODR_5;
		
		// Wait
		for(i=0; i<100000; i++);
	}	
}

 

6. Build and debug

Hit the build button and perform the usual checks:

  1. watch the Console. The build should pass without any warning or error:
     

    arm-none-eabi-size  my_project.elf 
    arm-none-eabi-objdump -h -S my_project.elf  > "my_project.list"
       text	   data	    bss	    dec	    hex	filename
        612	      0	   1568	   2180	    884	my_project.elf
    Finished building: default.size.stdout 
    Finished building: my_project.list
    11:35:31 Build Finished. 0 errors, 0 warnings. (took 3s.292ms)

     

  2. Have a look on the Build Analyzer and check memory levels:



     

  3. Make sure binaries and debug data has been updated in the Project Explorer:


     

     

Then, move on to the Debug Configuration . You did already, just setup the debug configuration:
 

 

 

And then make sure that the debugger session launches with no problems:

 

Finally try running the program with the usual debugger commands  (, , ), make sure the LED is still blinking...

 

... then exit the debug session . Well done!

 

7. Summary

In this tutorial, we've seen an approach to start new STM32 projects from scratch, using STM32CubeIDE. Such approach should be portable across other IDEs if you like.

The so-prepared project features:

  • Full CMSIS layer including:
    • Startup code
    • System initialization functions
    • Device headers enabling the use of embedded types and peripheral aliases (instead of magic numbers)
  • The LinkerScript