General Purpose Input Output
The STM32 is equipped with an extremely flexible General Purpose Input Output (or GPIO) peripheral allowing to configure each Input/Output independently. The IO is the simplest interface between the STM32 and the outside world.
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 and reading many RM we can notice that GPIO peripheral has three hardware version:
- 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 STM32L4+.
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.
Despite its complexities, the GPIO can easily be addressed through a software driver from ChibiOS/HAL which offers full support for those functionalities 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).
In this article we will take a look to the GPIO peripheral explaining how to use it with PAL explaining its API and common use cases.
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 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 (from the right to the left):
- the pin itself, represented by the green square on the right;
- two protection diodes in dark 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 is 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 driver
The output driver 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 and this configuration is also called push-pull.
It is possible to replace the PMOS 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 like I2C.
Note that is not possible to create a wired NOR connecting push-pull gates together because this can cause under certain condition a short circuit (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.
This mode is used when we need to sample the logic level of a pin. When a pin is programmed as Input its logic level is sampled each clock cycle and can be retrieved accessing the Input Data Register (also known as GPIOx_IDR).
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.
This mode is used when we need to use a pin as digital output. 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.
The port state can be configured through the Output Data Register (also known as GPIOx_ODR) . Note that the GPIOx_IDR still report the status of the current Port status.
This mode is used when we need to generate an analog signal through the DAC peripheral or to sample it using an ADC peripheral. 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”.
We will discuss of to use ADC and DAC in a later dedicated article. Anyway note that to avoid any noise an unused pin should never be programmed as analog but 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.
This mode is used to assign a pin to a STM32’s peripheral. The STM32 is equipped with a big number of internal peripherals which are interconnected to GPIO pins through multiplexers: acting on these multiplexer it is possible reroute connections.
The Alternate mode requires also a multiplexer selector to choose rerouting. The selector could be changed acting on a couple of registers named Alternate Function High Register (GPIOx_AFHR) and Alternate Function Low Register (GPIOx_AFLR).
Rerouting are limited and it is not possible to assign any peripheral to any pin, Anyway each peripheral is mapped on more than a pin: this guarantees an higher flexibility during the hardware design phase.
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 electrical characteristics are reported in the Electrical Characteristics chapter of the STM32 Datasheet and this inlcude the range of acceptable values of VDD.
The GPIO is powered with VDD which represent the positive voltage which powers the whole STM32. VDD represents also the high logic level of the STM32. 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.
STM32 GPIOs are 5 V tolerant and they are compatible with the TTL logic levels.
Another important thing is the maximum current 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 with a restriction on the total current sourced/sunk by all the pins which must not exceed 120 mA. These currents are enough to drive 5 mm monochromatic LED which usually sink not more than 10 mA.
The ChibiOS PAL driver
Naming conventions and auto-completion
Each API of the PAL driver starts with the prefix “pal”. Function names are camel-case, pre-processor constants uppercase and variables lowercase.
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.
Auto completion related on Project Index, a list of symbols contained in our code like function names, global variables, macros and so on, which Eclipse creates automatically building the project.To use auto-completion the project must have been built at least once.
In case of problems, it is possible to force the index rebuild right-clicking on the project and choosing Index -> Rebuild.
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 a port and pad identifier or otherwise a line identifier. Each identification method has its own set of functions.
From here and out we will often rely on bit-wise operators to do bit-masking. If you are not used to these king of operators I suggest you to read Registers and bit masks.
Board level initialisations
Each pad must be properly configured before being used. The configuration for each Pad generally depends on our application but in certain cases it is bonded by the board schematics. 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 and to use them 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 (already provided or Pull down resistor and de-bouncing circuitry) 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 palInit() and 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 redefinition
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.
Common API are the Set/Clear/Toggle Pad/Line functions
The first couple set an output pin as PAL_HIGH the second set an output pin as PAL_LOW and the latest couple toggle the status of the pin which mean that if pin logic level is PAL_HIGH after a toggle function call it will change its level to PAL_LOW (and vice-versa).
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
The PAL driver offers some functions to handle pins set as input similar to those used to manage output. 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
where 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 to change the pins configuration during the code execution. To do that PAL offers 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 hardware dependent;
- PAL_MODE_UNCONNECTED, which represent a safe state for unconnected pads and is hardware dependant;
- 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.
For example, we have an additional mode to deal with alternate functions:
For port speed:
For pull-up\down configuration:
Further readings and Hands-on
The PAL driver is very simple and it is used often joint with other driver. The best way to do some experiment with it is to modify the main demo for your development board. There are also some article I suggest you to read:
- Dealing with LEDs using anSTM32 which is a collection of examples and exercises related to PAL Driver.
- Dealing with push-buttons using an STM32 (Examples and Exercises), which is another collection of examples that show how to use PAL driver to sample digital inputs detecting edges;
- How to use an HD44780 based Liquid Crystal Display which explains how to use an LCD display which communication protocol is parallel and entirely based on PAL Driver
- ChibiOS/HAL design: an object-oriented approach which is a further reading about ChibiOS/HAL design and contains information about its design which can be useful to deal with it.
Previous and next
This article is part of a series of articles which are meant to be tutorials. I have composed them to be read in sequence. Here the previous and next article of this series:
- A close look to ChibiOS demos for STM32 (Previous)
- Using STM32’s USART with ChibiOS Serial Driver (Next)
- General Purpose Input Output
- Functional description
- The ChibiOS PAL driver
- Further readings and Hands-on
- Previous and next