A microcontroller is a small computer integrated in a small chip designed to control electronic circuits: control involves interaction! A MCU must be able to interact with the external circuitry in order to address or sense it. The MCU is equipped with different peripherals designed to interact with external circuits.
Anyway all the MCU interactions must necessarily go through those small wires coming out from the microcontroller package: these metal contacts are named pins and they are used to solder the chip on a Printed Circuit Board (PCB). The name pin come out from the shape of the metal contact which is similar to a needle.
They are also know as leads especially in case of through-hole electronic components or as pads especially in case of BGA package components because in that case contacts are very similar to PCBs’ metal pads.
According to its package, a microcontroller could have a different amount of pins; as example, in Fig.1 the chip is a LQFP64 having 64 pins. While some of them are used to supply the MCU or are ground reference, others can be driven or sampled. The MCU is therefore able to generate analog or digital waveform on them as well as read logic level or sample the voltage applied on them through an ADC. In this case that pin represent an Input/Output.
Usually each I/O can be configured to act in a specific mode: as example, an I/O must be configured in a different manner in case we need to use it as input or output and this requires to h registers. All the circuitry composed by analog front-end, registers and logic is usually part of a single entity named General Purpose Input Output (or GPIO).
GPIO functional description
As we said in the article “From 0 to STM32“, there are many versions of the same peripherals across the various STM32’s sub-families and this is way each sub-family usually has its own Reference Manual. In this document it is possible to find all the functional information about GPIO.
About GPIO peripheral, there are three hardware version of it:
- GPIOv1, the first version of the GPIO used on the first subfamily (the STM32F1);
- GPIOv2, the second version which has been used on almost every subfamily;
- GPIOv3, used on the STM32L4 (and maybe STM32H7) which was the latest sub-family previous the STM32H7.
The GPIOv3 is a GPIOv2 with a little of touch-up indeed the only difference is an additional feature and an additional register for the newer version. The GPIOv1 instead is like the ancient version of GPIOv2. I have to say (my personal opinion here) that the whole STM32F1 appears rudimentary in many ways. In its defence, debuting in 2007, this is the oldest STM32 subfamily (and it still kicks ass).
In this article we will mainly concentrate on GPIOv2 which is the most diffused GPIO but we will point out differences with other versions whereas it is possible.
The concept of port
STM32’s I/O pins are organised in groups of 16 elements numbered from 0 to 15. Each group is named Port and identified by a letter: so, we have GPIOA, GPIOB, GPIOC and so on. Pins are hence identified by the combination of the letter P, the port identifier (A, B, C,…) and the pin identifier (0, 1, 2,..). As example we have PA1, PB12, PF3, PH0, etc.
From here and out we will refer to the a single GPIO pin as pin and to the whole GPIO (the group of 16 Pad) as port.
Each port has many 32-bit registers some of which are dedicated to configurations: acting on these registers it possible to configure independently each pin changing its mode, output speed, output type and pull up/down resistor.
The following figure represent the principle diagram of a single pin
Looking the figure we can easily spot:
- the pin itself, represented by the green square on the left;
- two protection diodes in orange;
- a configurable Pull-up/Pull-down resistors in dark blue;
- the input driver block in azure;
- the output driver block in orange;
- the data and reset register in aquamarine.
Acting on configuration registers it is possible to choose whereas a pin must use the input or output driver, enable/disable pull up/down resistors, bypass the TTL Schmitt trigger, rearrange the output driver.
Note that GPIO registers are synchronous and clocked through the AHB (advanced high-performance bus). The refresh rate of the value of this registers depends on the clock speed of the AHB.
In electronic logic circuits, a pull–up resistor is a resistor connected between a signal conductor and a positive power supply voltage to ensure that the signal is in a high logic level state when external devices are disconnected or the circuit it-self in in a high-impedance condition.
Similarly, a pull-down resistor is a resistor connected between a signal conductor and a negative power supply voltage (or ground) to ensure that the signal is in a low logic level state when external devices are disconnected or high-impedance is introduced.
GPIO comes with configurable Pull-up/Pull-down resistors. Acting on the Pull up/Pull down Register (abbreviated as GPIOx_PUPDR whereas x is the identifier of the port) it is possible to configure the behaviour of the dark blue network of Fig.2 choosing between:
- Pull-up resistor;
- Pull-down resistor;
The output buffer
The output buffer could be schematised with a CMOS NOT gate. This gate inverts the input signal and its working principle is schematised in Fig.3.
It is composed by two mosfet: a PMOS on the high side and the NMOS on the low side. This drivers act as complementary switches: they are not closed simultaneously.
When the input is high the NMOS act as a short-circuit and the PMOS as a open-circuit: the output is pulled down to GND. When the input is low the PMOS act as short-circuit and the NMOS as an open-circuit: the output is pulled up to VCC.
For the previous reason the PMOS is also know as active pull-up network and the NMOS is also know as active pull-down network. This configuration is commonly know as push-pull because the two drivers are working alternatively.
It is possible to replace the NMOS with a weak pull-up network (i.e. a pull-up resistor) and this configuration is know as open-drain (see Fig.4).
This configuration is very interesting because it is possible to connect the output of two or more ports configured in that way obtaining a wired NOR: as long as one of the gates has the NMOS turned on the output is pulled down. This solution is often used in contended BUSes.
Note that is not possible to create a wired NOR connecting push-pull gates together.
Indeed, in this case the condition in which a NMOS and a PMOS of two different gates are simultaneously on there will be a short-circuit between VCC and GND (see Fig.5).
The output buffer can be configured to act as push-pull as well as open-drain. This configuration can be done acting on the Output Type register (also know as GPIOx_OTYPER).
The output buffer could be configured with different slew rate acting on the Output Speed register (also know as GPIOx_OSPEEDR).
Through this register is possible to set the speed of the output port in different manner as example on STM32F4 the available speed are:
- Low speed;
- Medium speed;
- High speed;
- Highest speed;
whereas the actual value of each speed in term of frequency is reported in Input Output AC Characteristic chapter of the STM32 Datasheet.
A GPIO can work in different modes. Acting on the GPIO Mode Register (also known as GPIOx_MODER) it is possible to program a pin in four different modes.
When a pin is programmed as Input its logic level is sampled each clock cycle. Accessing the Input Data Register (also known as GPIOx_IDR) it is possible to get the current status of each pin of the whole Port.
When a pin is programmed as Input, the output buffer is disabled and the TTL Schmitt trigger is turned on. The pull up\down resistors could be enabled\disabled acting on the GPIOx_PUPDR.
The Schmitt trigger is often used in digital circuits because its working principle provides signal robustness in case of noise on the input signal: due to its hysteresis, it is able to avoid multiple transition during port commutation commonly known as glitches.
General purpose output mode
When a pin is programmed as General Purpose Output, the output buffer is enabled and configured as push-pull or open-drain according to GPIOx_OTYPER and the TTL Schmitt trigger is turned on.
The pull-up\pull-down resistors could be enabled\disabled acting on the GPIOx_PUPDR. The output slew rate can be configured through the GPIOx_OSPEEDR.
It is possible to configure the state of the Port acting on the Output Data Register (also known as GPIOx_ODR) and accessing the GPIOx_IDR it is possible to check the current Port status.
When a pin is programmed as analog, the TTL Schmitt trigger is bypassed and the output buffer is disabled as well as pull-up\down resistors. A read access to the GPIOx_IDR gets a “0”.
In this configuration it is possible to use the ADC peripheral to sample signals or the DAC peripheral to generate signals on the pin.
We will discuss this point later but is important to notice that not every pin is internally connected to the ADC or DAC, moreover not every STM32 is equipped with DAC. To figure out which pin is connected to ADC/DAC you should refer to STM32 Datasheet.
To avoid any noise on the ADC channels, an unused pin should be programmed as Input with Pull-Up resistor.
In GPIOv3 there is an additional register named Analog Switch Control Register (also know as GPIOx_ASCR). To use the analog mode it is required to set an additional bit per each pin in this register.
The STM32 is equipped with a big number of internal peripherals which need of pins to interact with the external world. Internal peripherals are interconnected to GPIO pins through multiplexers: acting on these multiplexer it is possible reroute connections.
To use this feature a pin must be programmed as Alternate mode. The multiplexer selector could be changed acting on a couple of registers named Alternate Function High Register (GPIOx_AFHR) and Alternate Function Low Register (GPIOx_AFLR).
Internal peripherals can be mapped on GPIO pin. Each peripheral is mapped on more than a pin: this guarantees an higher flexibility during the hardware design phase.4
The alternate function map is reported in the STM32 Datasheet under the Chapter Pinouts and pin description in a table named Alternate function mapping (see Fig.6).
When a pin is programmed as alternate mode, the output buffer is enabled and configured as push-pull or open-drain according to GPIOx_OTYPER and the pin is driven by the signal coming from the peripheral.
The pull-up\down resistors could be enabled\disabled acting on the GPIOx_PUPDR. The output slew rate can be configured through the GPIOx_OSPEEDR.
The TTL Schmitt trigger is turned on and accessing the GPIOx_IDR it is possible to check the current port status.
GPIO absolute maximum rating
The GPIO is powered by VDD which represent the positive voltage which powers the whole STM32. VDD represents also the high logic level of the STM32.
All the information about the GPIO electrical characteristics are reported in the chapter Electrical Characteristics of the STM32 Datasheet. In this chapter there is information about the range of acceptable values of VDD. Most of the STM32 can operate between 1.7 V and 3.6 V. Anyway, the actual value of VDD depends on board schematics which can be found in the User Manual of the board in use.
Most of the STM32 development boards are equipped with a dual voltage LDO 5 V – 3.3 V. The 5 V is used for certain peripherals like the USB OTG. The STM32 is supplied with the 3.3 V and in this condition a logic “1” corresponds to 3.3 V while a logic “0” is 0 V.
Note that STM32 GPIOs are 5 V tolerant and they are compatible with the TTL logic levels.
Another important piece of information is about how many amperes a single pin is able to source/sink. Again this depends on the MCU but most of the STM32 are able to sink/source up to 25 mA per pin. Note that there is a restriction on the total current sourced/sunk by all the pins which must not exceed 120 mA. Even if we are talking of small currents, these currents are enough to drive 5 mm monochromatic LED which usually sink not more than 10 mA.
The ChibiOS PAL driver
ChibiOS/HAL offers full support for the GPIO so we do not have to act at register level to use this peripheral. The driver which manages the GPIO peripheral is named Port Abstraction Layer (also shortened PAL).
Naming conventions and auto-completion
Each function of the PAL driver starts with the prefix “pal” and like every ChibiOS function its name is camel-case.
The prefix is very useful for auto-completion purposes. As example, in Eclipse, starting to write a function and pressing CTRL + SPACE the IDE will show suggestions for auto-completion. In this case writing “pal” and pressing CTRL + SPACE we will see available API of the PAL driver as suggestions as shown in Fig.7.
This is true also for other ChibiOS/HAL drivers. As example the functions of the SPI Driver are prefixed by “spi”, the functions of the PWM Driver are prefixed by “pwm”, the functions of the ADC Driver are prefixed by “adc” and so on.
Note that to use auto-completion features the Project Index must be built. The index is a list of symbols contained in our code like function names, global variables, macros and so on; Eclipse creates it automatically building the project.
In case of problems, it is possible to force the index rebuild right-clicking on the project and choosing Index -> Rebuild.
Note that also pre-processor constants related to drivers are prefixed with the driver identifier but like every ChibiOS pre-processor constants their name is upper-case.
The logic levels of pin are defined in PAL as:
Port, pad and line
When we use the PAL driver we need to identify which I/O pins we are going to program. Basically there are four sets of function in this driver:
- Pad related which act on a single I/O. They need at least of two parameters: port and pad. The first one is a macro (GPIOA, GPIOB, GPIOC, GPIOD and so on), pad is an unsigned integer (from 0 to 15).
- Group related which act on a group of I/O. They need at least three parameters: port, mask and offset. Mask is a bit mask to identify which pins of the port must be programmed, offset is the offset of the mask (left shifting).
- Port related which act on the whole port. They need at least of one parameter: port.
- Line related which are an alternative to Pad related: the difference is that in these functions the pin identifier is represented by a single parameter instead of two: line.
Line is a smart way to encode the information of both port and pad in a single identifier. User can generate a line identifier using the macro
So as example with the code
We are defining a line identifier for the pin PA5.
To identify a single I/O we need for port and pad identifier or otherwise the line identifier only. Each identification method has its own set of functions.
As final note, If you are not used to bit-masking I suggest you to read Registers and bit masks.
Board level initialisations
At this point it should be clear that each pad must be properly configured previous being used. The configuration for each Pad generally depends on our application but in certain cases it is bonded by the board schematic. As example, let us consider the schematic in Fig.7: this figure represent a schematic from the STM32F3 Discovery User Manual.
Here it is possible to see that coloured LEDs are connected to pads from PE8 to PE15. Well, if we want to use these LEDs, these pads must be configured as Output Push-Pull with no Pull-up/Pull-down resistor.
Similarly, the pad PA0 is connected to the User Button and to use it we need to configure this pin as input floating. In this schematic are also visible a couple of MEMS which requires other pin configurations.
Well, we don’t have to worry too much about these configuration because when the PAL driver is initialised it programs all the GPIO according to a series of configuration which are stored in a header file named board.h. Each board has is own board header file. For ease of use, the board file are linked into the project into the linked folder named board.
The board files are a quick reference to understand how a pins are pre-configured. Recapping:
All the board-dependent configuration related to GPIO are already performed by ChibiOS PAL driver. These configuration are stored in the board files which could be explored as quick reference.
The initialisation is performed by the PAL API
This API is executed by halInit() whenever the PAL switch in halconf.h is enabled
To improve code readability in the board files pin identifiers are redefined as pre-processor constants. As example, the next code is from the STM32 Nucleo F401RE board.h file
Well this means that the following two statements are equivalent
but the second is much more clearer: we are acting on the LED_GREEN. The board file contains also line redefinitions
and this means that the following two statements are equivalent as well
Changing the logic level of output pins
The simplest use case of the PAL Driver is the LED toggling we have already seen in previous articles. When an I/O is configured as output is possible to switch on (and off) a LED just changing the logic level of the pin.
There are many function we can use to act on logic levels. To deal with a single pin we can use “Pad” related function or “Line” related functions.
As example these functions
set an output pin as PAL_HIGH (the only difference resides in the way used to identify the I/O pin). Similarly
set an output pin as PAL_LOW. These next function instead alternate the status of the pin
This mean that if pin logic level is PAL_HIGH, using a palTogglePad on that pin, it will change its level to PAL_LOW (and viceversa).
This last couple of functions is a little bit different because they receive an extra parameter
the parameter bit in this case represent the logic level we want to write on the targeted pin (PAL_HIGH or PAL_LOW). Just to make things more clearer the next statements are equivalent
Similarly it is possible to act on the whole port through the functions
Just note that here bits is a 16-bit unsigned representing the status of each pin. So as example
sets the PA0 and PA1 as PAL_HIGH while pads from PA2 to PA15 are set as PAL_LOW.
It is also possible to act on a group of pads or on the whole port using the functions
whereas port is the port identifier, mask is the pad selection mask, offset the group bit offset, bits represent bits to be written. This function acts on the GPIOx_ODR replacing the bits masked by
It should be almost immediate to understand what we have just said for those who know how bit-masking works. In any case you can take a look to Registers and bit masks.
Getting the logic level of input pins
Similarly to output functions PAL offers some functions to handle pins set as input. In this case these functions gets the logic level of pads. As example
return PAL_HIGH or PAL_LOW according to the status of the pin. It is also possible to read group and ports as well through the functions
the only difference is that here the returned value is an unsigned integer whereas each bit represent the status of the port/group.
Configuring pins on-the-fly
Some application requires that the configuration of some pins has to been changed during the code execution. This is possible since PAL has a specific set of functions to change pins configuration reprogramming their behaviour. Even in this case there are functions to configure a single pin, a group of pin or the whole port
Here we have to spend some words about mode. PAL is designed to work with many microcontrollers and comes with a set of generic modes which are suitable for every microcontroller. These modes are:
- PAL_MODE_RESET, which represent the after reset state. Note that this status is architecture dependent;
- PAL_MODE_UNCONNECTED, which represent a safe state for unconnected pads. Note that this status is architecture dependent;
- PAL_MODE_INPUT, which stands for input with no pull-up\down resistors;
- PAL_MODE_INPUT_PULLUP, which stands for input with pull-up resistor;
- PAL_MODE_INPUT_PULLDOWN, which stands for input with pull-down resistor;
- PAL_MODE_INPUT_ANALOG, which stands for input suitable for analog sampling;
- PAL_MODE_OUTPUT_PUSHPULL, which stands for output with a push-pull network;
- PAL_MODE_OUTPUT_OPENDRAIN, which for output with a open drain network;
Of course these modes are generic and cannot map all the modes of a STM32 microcontroller. So there are some additional specific mode for STM32 which can be mixed to previous to obtain all needed modes.
As example we have an additional mode to deal with alternate functions:
For port speed:
For pull-up\down configuration:
To conclude let us do a couple of example.
Let us consider we want to change the mode of a certain LINE to output open-drain type with pull-up resistor and lowest speed. The code will look in this way
Let us consider we want to use TIM5CH1 (an internal peripheral) on STM32 Nucleo-F401RE. We need to map TIM5CH1 on a certain pin.
Reading the STM32F401RE’s DataSheet (see Fig.6) we will figure out that this peripheral is mapped on PA0 as Alternate 2. The code will looks like
- GPIO functional description
- The ChibiOS PAL driver