Introduction
Setting up a 32-bit ARM based MCU GPIO might be more tricky than you think. There are packages, such as the Hardware Abstraction Layer, one can use to quickly setup a GPIO as an output. But ā what really happens behind the scenes?
Iāll try to list down all the things Iāve learned using the STM32F030R8.
HAL
The easiest thing to do is to learn from examples. Luckily, ST provides a bunch! The default code generated from the STM32CubeIDE contains a few functions that group a few HAL calls:
So, what really happens above?
- A clock is setup per GPIO PORT (e.g GPIOA or GPIOC)
- Every GPIO has its own configuration
Letās start with the clock.
Reset and clock control (RCC)
Note the first thing done is to setup the clock for each PORT
used:
Thatās it. Easy, right? The clock part is done. Now, letās jump into the GPIO configuration.
Init
The HAL provides a struct of type GPIO_InitTypeDef
to ease the process of initializing a GPIO:
The Pull
, Speed
and Alternate
integers are variables we donāt need to pay attention now. On the other hand, both Pin
and Mode
integers are important. The two integers are quite explicit for what they serve.
To initialize, a struct of type GPIO_InitTypeDef
is created and is filled with the desired data. Since we want to set up the GPIO as an output, we need to have something (more or less) like the snippet below:
The GPIO_PIN_5
is defined by the HAL (stm32f0xx_hal_gpio.h).
The mode is defined as MODE_OUTPUT
. This enables a few things internally. The mode also specifies OUTPUT_PP
, which sets the GPIO in a push-pull mode (sources Vcc when 1 and Gnd when 0).
Write
To write to a GPIO Pin, one simply needs to use one of the HAL available functions:
Thatās all folks!ā¦
If thatās what you were looking for, you are good to go! However, if you want to learn more, Iād recommend to keep reading! š
Underneath the HAL
The HAL is great and most of the times is enough. However, it is important to know how to do it manually, register by register, so that one can understand better how things work and develop away from any vendor tool.
Reset and clock control (RCC)
Digging __HAL_RCC_GPIOC_CLK_ENABLE
, we can check a define as such:
Which translates to:
The RCC
points to a memory mapped address register, the Reset and Clock Control (RCC). In this case, we deal with the AHB peripheral clock enable register (RCC_AHBENR 7.4.6).
The AHB peripheral, for those who donāt know, is a bus that connects the GPIOs to the Bus Matrix (which is also connected to the AHB2 bus).
The AHB peripheral clock enable register enables/disables the clock for all the GPIO ports.
Therefore, by previously setting the PORTC
bit to 1, we are actually enabling the clock.
Init
What registers need to be set to initialize a port? Letās check.
Inside HAL_GPIO_Init
:
Starting from the top to the bottom, the register OTYPER
stands for output type register:
The register PUPDR
stands for pull-up/pull-down register but doesnāt necessarly need to be set. The default value is 0: no pull-up or pull-down resistor.
And finally, the MODER
. It stands for mode register and is responsible for setting a GPIO pin to one of the four available options:
From the code example above, both MODER
and OTYPER
are dependant on GPIO_Init
, which is the same struct as seen before.
Simplifying a bit all the bitwise operations and the abstractions, the code may look like this:
After a port is correctly initialized as an output, there are a few things that happen on the hardware level:
Everything starts with the MODER
set as an output. The OTYPER
register controls how the MOSFETs are used and the PUPDR
enables or disables the resistors accordingly.
In contrast to what some may think, the input data register is still being sampled with data. However, there is one major diference:
- The input data register (IDR) has the I/O state (data presented at the GPIO)
- The output data register (ODR) has the last written value
Itās important to know the diference, as they are not exactly the same!
Write
Inside HAL_GPIO_WritePin
:
In this specific case, due to both, BSRR
and BRR
, having only access to write and being placed before ODR
, they can be used without bitwise operations:
The bottom
Every GPIO has its own address mapped into the MCU memory. For example, the AHB2 Bus has the adress of 0x48000000
.
GPIOA sits at its base address, GPIOB exists with the offset of 0x00000400
, so 0x48000400
.
Therefore, itās possible to define the GPIOs as such:
Maybe there are more ways (and even better) to setup a GPIO, but these options should cover most of the cases.