During all the training we are going to focus on preparing your skills to know several features of the microcontrollers like gpio, timers, uart, interrupts and etc. It is important to know the resources and more important to know the target, in this case, will be the ST stm32g0b1re, a 32bit microcontroller with an ARM Cortex-M0+ core, do not forget to download the following documentation: datasheet and reference manual.

Prior to anything you will need to read and install the tools from the way we work
đź’ˇ
Although all the material is based on the G0 family, you can use it as a reference for the rest of the families since the HAL libraries share many similarities. While they are not identical—because the families themselves are different—it still provides a useful starting point. Be sure to read the corresponding microcontroller's user manual and the HAL library manual for more specific details.
đź’ˇ
Are you using a different stm32, take a look at our template migration guide

The template

For every newbie, the best way to start is through a working out-of-the-box project. Here is my advice, create a specific directory where you gonna put all your projects, and be disciplined with all the projects you create, the last thing you want is to have directories all over your computer. For instance:

$ mkdir Workspace
$ cd Workspace
$ git clone https://github.com/ModularMX/template-g0.git myNewProject
$ code -r myNewProject

The template is basically a simple blinky led, the hello world of microcontrollers, to flash and run the program type ( do not forget the terminal shall be in your project directory ). This is the project base you will use for every program you make across the entire training.

$ make
....
arm-none-eabi-objcopy -Oihex Build/temp.elf Build/temp.hex
arm-none-eabi-objdump -S Build/temp.elf > Build/temp.lst
arm-none-eabi-size --format=berkeley Build/temp.elf
   text    data     bss     dec     hex filename
   2244      20    1572    3836     efc Build/temp.elf

Connect and flash your board

$ make flash
openocd -f board/st_nucleo_g0.cfg -c "program Build/temp.hex verify reset" -c shutdown
Open On-Chip Debugger 0.11.0+dev-00715-g480d4e177-dirty (2022-06-22-18:51)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
srst_only separate srst_nogate srst_open_drain connect_deassert_srst

Info : clock speed 2000 kHz
Info : STLINK V2J40M27 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.253088
Info : [stm32g0x.cpu] Cortex-M0+ r0p1 processor detected
Info : [stm32g0x.cpu] target has 4 breakpoints, 2 watchpoints
Info : starting gdb server for stm32g0x.cpu on 3333
Info : Listening on port 3333 for gdb connections
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread 
xPSR: 0xf1000000 pc: 0x0800023c msp: 0x20024000
** Programming Started **
Info : device idcode = 0x10006467 (STM32G0B/G0Cx - Rev A : 0x1000)
Info : RDP level 0 (0xAA)
Info : flash size = 512kbytes
Info : flash mode : dual-bank
Warn : Adding extra erase range, 0x080008d8 .. 0x08000fff
** Programming Finished **
** Verify Started **
** Verified OK **
** Resetting Target **
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
shutdown command invoked.

Enjoy watching your led blinking, and if you want to know more about all the target you can use with the project makefile take a look at

The stm32 makefile
The template-g0 project uses a makefile to compile and link out of the box, it is not limited to build the project, also comes with several extra targets that helps you to run some others tasks like flashing or linting the project. But first let me explain how to add

The msps file

Before jumping into the examples and exercises, I think it's a good idea to talk about the MspInit functions and how they work. Essentially, any Init function from nearly every HAL driver calls its respective HAL_xxx_MspInit function (the GPIO_Init is an exception). But let's look at an example with the serial port. Below, you can see an example of its initialization routine

UART_HandleTypeDef UartHandle; /*uart handler structure*/
/*uart configuration options for module USART2, 9600 baudrate,
8bits, 1 stop bit, no parity, no flow control, and 8 bit lenght */
UartHandle.Instance         = USART2;
UartHandle.Init.BaudRate    = 9600;
UartHandle.Init.WordLength  = UART_WORDLENGTH_8B;
UartHandle.Init.StopBits    = UART_STOPBITS_1;
UartHandle.Init.Parity      = UART_PARITY_NONE;
UartHandle.Init.Mode        = UART_MODE_TX_RX;
/*init uart2 with previous paramters*/
HAL_UART_Init( &UartHandle );

The previous code looks perfectly normal, but something is missing. We didn’t specify the pins the serial port will use, nor have we written a single line about its configuration. If we look inside the HAL_UART_Init, we will see that its respective HAL_UART_MspInit is called almost at the beginning, or pretty much before configuring any registers. This Init function is defined inside the HAL_UART driver but with a weak qualifier, meaning we can overwrite the function with our own content

HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
    ... 
    /* Init the low level hardware : GPIO, CLOCK */
    HAL_UART_MspInit(huart);
 
    ...
}

Below is an example where we place the code necessary to configure pins A2 and A3 as Tx and Rx, and we also enable the UART and PORTA clock. You must keep in mind a few things: the pin configuration code is not mandatory to be placed in these functions, and you can define this function pretty much anywhere you like. The reason we place this kind of code here is simply to maintain structure, and that is the same reason why we place this function in the msps.c file.

void HAL_UART_MspInit( UART_HandleTypeDef *huart )
{
    GPIO_InitTypeDef GPIO_InitStruct; /*gpios init structure*/
    
    __HAL_RCC_USART2_CLK_ENABLE();    /*enable usart2 clock*/
    __HAL_RCC_GPIOA_CLK_ENABLE();     /*eneable port A clock */
    
    /*set pin 2(tx) and pin 3(rx) from port A in altern mode usart2
    the altern mode info can be found in device datasheet*/
    GPIO_InitStruct.Pin   = GPIO_PIN_2 | GPIO_PIN_3;
    GPIO_InitStruct.Mode  = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Alternate = GPIO_AF1_USART2;
    /*apply configuration configuracion*/
    HAL_GPIO_Init( GPIOA, &GPIO_InitStruct );
}

Now you know the main reason why you will see the pin initialization code in the msps.c file in all our examples. We follow a similar approach with interrupt vectors in the ints.c file, but that is something we will explain in its own article,

An here is a nice video introduction from ST