This is the Series of tutorials on STM32 Microcontroller. The aim of this series is to provide easy and practical examples that anyone can understand. Basically you can write GPIO codes in multiple ways (Using HAL, GPIO driver). Using that HAL you can finish your job in one line of code. But I would suggest you to learn programming using bare-metal code (without any HAL or driver) initially. This is the STM32 GPIO Tutorial without HAL.
Post Contents
Prerequisites
Before starting this, Please go through the below tutorials.
STM32 GPIO Tutorial
Introduction
GPIO stands for “General Purpose Input/Output.” We are using STM32F401VE for our examples. STM32F401VE has five ports mentioned below.
- PORT A
- PORT B
- PORT C
- PORT D
- PORT E
Each port has 16 GPIO pins.
GPIO main features
- Up to 16 I/Os under control
- Output states: push-pull or open-drain + pull-up/down
- Output data from output data register (
GPIOx_ODR
) or peripheral (alternate function output) - Speed selection for each I/O
- Input states: floating, pull-up/down, analog
- Input data to input data register (
GPIOx_IDR
) or peripheral (alternate function input) - Bit set and reset register (
GPIOx_BSRR
) for bitwise write access toGPIOx_ODR
- Locking mechanism (
GPIOx_LCKR
) provided to freeze the I/O configuration - Analog function
- Alternate function input/output selection registers (at most 16 AFs per I/O)
- Fast toggle capable of changing every two clock cycles
- Highly flexible pin multiplexing allows the use of I/O pins as GPIOs or as one of several peripheral functions
Registers used in STM32 GPIO
There are a couple of registers used in GPIO. I have classified these register into 4 types based on its operation.
Control Registers
Before looking into the control register, we will see the Clock Register (RCC_AHB1ENR
) which will enable the AHB clock to the GPIO ports.
RCC_AHB1ENR
This is called as RCC AHB1 peripheral clock enable register. The register is given below.
Bit [0] – GPIOAEN: IO port A clock enable
- 0 – IO port A clock disabled
- 1 – IO port A clock enabled
Bit [1] – GPIOBEN: IO port B clock enable
- 0 – IO port B clock disabled
- 1 – IO port B clock enabled
Bit [2] – GPIOBEN: IO port C clock enable
- 0 – IO port C clock disabled
- 1 – IO port C clock enabled
Bit [3] – GPIOBEN: IO port D clock enable
- 0 – IO port D clock disabled
- 1 – IO port D clock enabled
Bit [4] – GPIOBEN: IO port E clock enable
- 0 – IO port E clock disabled
- 1 – IO port E clock enabled
We don’t need the rest of the bits as we are only working on GPIO.
Example
//These are a couple of ways of enabling AHB clock for Port A SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN); RCC->AHB1ENR |= (1UL << 0U);
The below control registers are used to configure the GPIOs.
- GPIO Port mode register (
GPIOx_MODER
) - GPIO Port output type register (
GPIOx_OTYPER
) - GPIO Port output speed register (
GPIOx_OSPEEDR
) - GPIO Port Pullup/Pulldown register (
GPIOx_PUPDR
)
GPIOx_MODER
This GPIO port mode register is used to select the I/O direction. Please find the below image of the GPIOx_MODER
register.
Here 2-bits are combined for one particular GPIO pin.
Bits [31:0] – MODERy : Direction selection for port X and bit Y, (y = 0 … 15)
MODERy Direction Selection:
00: Input (reset state)
01: General purpose output mode
10: Alternate Function mode
11: Analog mode
In this tutorial, we are using only I/O operation. So, we will use either Input mode or output mode.
Example
//makes Port A0 as output GPIOA->MODER = 0x00000001; //makes Port A5 as output GPIOA->MODER = 0x00000400; //makes Port A as output GPIOA->MODER = 0x55555555; //makes Port A as input GPIOA->MODER = 0x00000000;
GPIOx_OTYPER
This is the GPIO output type register which is used to select the output type (Push-Pull or Open Drain). First, we need to know what is push-pull and open drain.
Open-drain output type
I think most of them are aware of this. If you have worked on I2C you must have heard this. But still, I will put my words 😛 . In open-drain mode, inside the microcontroller one switch (transistor/MOSFET) is connected to the GPIO pin and the ground. So If you write high to the GPIO pin using software, it will be connected to the ground through the switch. Which means the original output is low. If you write low to the GPIO pin, it will be left floating since the switch will be turned off. That’s why we are using a pullup resistor to the open-drain pins.
Push-Pull output type
Whereas in push-pull mode, two switches (transistor/MOSFET) will be there inside of the microcontroller. One switch is connected to Vcc/Vdd and another switch is connected to the ground. So when you write High to the GPIO pin, the switch will be connected to the Vcc/Vdd. The resulting output will be high (1). And if you write low to the GPIO, then the switch will be connected to the ground. The resulting output will be low (0).
Got some idea about both output modes? Okay, let’s go to the register now. Please find the below image of the GPIOx_OTYPER
register.
Here,
Bits [15:0] – OTy : Port output type, (y = 0 … 15)
- 0 – Output Push-Pull (reset state)
- 1 – Output open-drain
Bits [31:16] – Reserved (Must be kept at reset value).
GPIOx_OSPEEDR
This GPIO Output speed register is used to set the speed of the GPIO pin. Please find the below image of the GPIOx_OSPEEDR
register.
Here 2-bits are combined for one particular GPIO pin.
Bits [31:0] – OSPEEDRy : Speed selection for port X and bit Y, (y = 0 … 15)
OSPEEDRy Selection:
00: Low Speed
01: Medium speed
10: High speed
11: Very high speed
GPIOx_PUPDR
This is the GPIO port pullup/pulldown register which is used to configure the GPIO pin into Pullup or pulldown mode. Please find the below image of the GPIOx_PUPDR
register.
Here 2-bits are combined for one particular GPIO pin.
Bits [31:0] – PUPDRy : pullup/pulldown selection for port X and bit Y, (y = 0 … 15)
PUPDRy Selection:
00: No pullup or pulldown
01: Pullup
10: Pulldown
11: Reserved
Example
//Enable Pullup on PA0 GPIOA->PUPDR = 0x00000001; //Enable Pullup on PA GPIOA->PUPDR = 0x55555555;
Data Registers
These data registers are used to make the store the data to be output/input. The below registers are used for output/input.
- Input data register (
GPIOx_IDR
) - Output data register (
GPIOx_ODR
) - Bit Set/Reset register (
GPIOx_BSRR
)
where, x = A, B, C, D, and E.
GPIOx_IDR
This is the Input Data Register. When you configure the GPIO ports as input using GPIOx_MODER
register, this register is used to get the value from the GPIO pin. This register is a read-only register. So you cannot write into it. Please find the below image of the GPIOx_IDR
register.
Bits [15:0] – IDRy : Port Input Data, (y = 0 … 15)
This will be containing the corresponding value of the corresponding I/O port. And It can be accessed in 32-bit word mode only. Which means you cannot read a single bit. You have to read the whole register.
Bits [31:16] – Reserved (Must be kept at reset value).
Example
Let’s assume that I have configured PORT B as input, using the GPIOB_MODER
register and other control registers. Now we can read the GPIO pins like below.
//Reading PB0 bit if( ( GPIOB->IDR & 0x01) == 0 ) { //PORT B's 0th bit is 0 } else { //PORT B's 0th bit is 1 } //Reading full PB uint32_t value = GPIOB->IDR; //Reading PB15 bool value = (( GPIOB->IDR >> 15 ) & 0x1);
GPIOx_ODR
This is the Output Data Register. When you have configured GPIO Port as output using GPIOx_MODER
register, this register is used to set the value to the GPIO pin. We can read and write to the register. Please find the below image of the GPIOx_ODR
register.
Bits [15:0] – ODRy : Port Output Data, (y = 0 … 15)
We can write to the corresponding value of the corresponding I/O port.
Bits [31:16] – Reserved (Must be kept at reset value).
Note: When you read the output data register, it will give you the last written value.
Example
Let’s assume that I have configured PORT B as output, using the GPIOB_MODER
register and other control registers. Now we can write the GPIO pins like below.
//Write 1 to the full Port B GPIOB->ODR = 0x0000FFFF; //Write 0 to the full Port B GPIOB->ODR = 0x00000000;
You have to be careful when you are writing the GPIO port using this GPIOx_ODR
. Because you may disturb the other Pins (bits) of the register which you don’t want to. Then what if I want to write a single bit without disturbing others? There is a way to do that. Just keep reading.
GPIOx_BSRR
This is GPIO Bit Set/Reset Register. When you want to set or reset the particular bit or pin, you can use this register. This is a write-only register. This register will do the atomic set/reset. So we don’t worry about the interrupts that causing problems during set/reset. And in this register, the lower 16-bits are used to set any of the 16 pins and the higher 16-bits to clear/reset any of the 16 pins of a particular IO port. Please find the below image of the GPIOx_BSRR
register.
Bits [15:0] – BSy : Port Set Bit, (y = 0 … 15)
These bits are write-only and accessed in word, half-word, or byte mode. If you read this register you will get 0x00000000. If you write 1 to any bit [0 to 15], it will set the corresponding bit in GPIOx_ODR
register [0 to 15]. If you write 0 to any bit [0 to 15], no action will be performed to the corresponding bit in GPIOx_ODR
register [0 to 15].
Bits [31:16] – BRy : Port Reset Bit, (y = 0 … 15)
These bits are write-only and accessed in word, half-word, or byte mode. If you read this register you will get 0x00000000. If you write 1 to any bit [16 to 31], it will set the corresponding bit in GPIOx_ODR
register [0 to 15]. If you write 0 to any bit [0 to 15], no action will be performed to the corresponding bit in GPIOx_ODR
register [0 to 15].
If you set both BSx
and BRx
, BSx
has the priority. So BRx
will be ignored. Where, x = 0…15.
Example
Let’s assume that I have configured PORT B as output, using the GPIOB_MODER
register and other control registers. Now we can write the GPIO pins like below.
//set all the bits of Port B GPIOB->BSRR = 0x0000FFFF; //Clear all the bits of Port B GPIOB->BSRR = 0xFFFF0000; //Set the 5th bit of Port B GPIOB->BSRR = (1U << 5); //Clear the 5th bit of Port B GPIOB->BSRR = (1U << 21);
Locking Registers
This register is used to lock the configuration of the port bits. The below register is used to do that.
GPIOx_LCKR
Using this register, you can freeze the GPIO configurations. Once you do the proper lock key write sequence, it will lock the GPIOx_MODER
, GPIOx_OTYPER
, GPIOx_OSPEEDR
, GPIOx_PUPDR
, GPIOx_AFRL
, and GPIOx_AFRH
registers. Before we see how to do that, let’s see that register. Please find the below image of the GPIOx_LCKR
register.
This can be accessed 32-bit word only and you can perform both read and write.
Bits [15:0] – LCKy : Port Set Bit, (y = 0 … 15)
- 0 – Port configuration is not locked
- 1 – Port configuration is locked
These bits can be written when the LCKK (16th bit) is 0.
Bits [16] – LCKK : Lock Key
- 0 – Port configuration lock key is not active
- 1 – Port configuration lock key is not active
This bit can be read at any time. But if we want to modify the bit, we have to follow the Lock key write sequence. Once you have locked the GPIO, then it will be locked until an MCU reset or a peripheral reset occurs.
Bits [31:17] – Reserved (Must be kept at reset value).
Lock key write sequence
As per the datasheet, the below is the lock key write sequence.
WR LCKR[16] = ‘1’ + LCKR[15:0]
WR LCKR[16] = ‘0’ + LCKR[15:0]
WR LCKR[16] = ‘1’ + LCKR[15:0]
RD LCKR
RD LCKR[16] = ‘1’ (this read operation is optional but it confirms that the lock is active)
Note: During the Lock key write sequence, the value of LCK[15:0] should not change. And in some other STM32, this GPIOx_LCKR
is not available for all the GPIO ports. So you should check with the datasheet before doing this. But in the STM32F401VE, GPIOx_LCKR register is available for all the GPIO ports.
Any error in the lock key write sequence aborts the lock. Once you have done the lock key write sequence properly on any bit of the port, any read access on the LCKK bit will return ‘1’ until the next CPU reset.
If you get confused, please go through the below example.
/* ** The below full code snippet, locks the GPIO configuration of Port B.5 (PB5) */ #define GPIO_PIN_POS (5U) //I want to lock PB5. So setting 5th bit #define LCKK_BIT_POS (16U) //Position of LCKK bit volatile uint32_t lock_gpio = 0; /* Lock key write sequence */ /* WR LCKR[16] = ‘1’ + LCKR[5] = ‘1’ */ lock_gpio = (( 1UL << LCKK_BIT_POS) | ( 1UL << GPIO_PIN_POS )); GPIOB->LCKR = lock_gpio; /* WR LCKR[16] = ‘0’ + LCKR[5] should not change*/ GPIOB->LCKR = ( 1UL << GPIO_PIN_POS ); /* WR LCKR[16] = ‘1’ + LCKR[5] should not change*/ GPIOB->LCKR = lock_gpio; /* RD LCKR */ lock_gpio = GPIOB->LCKR; if((GPIOB->LCKR & ( 1UL << LCKK_BIT_POS)) != 0) { //PB5 configuration has been locked } else { //PB5 configuration has not been locked }
Alternate Function Registers
Each GPIO pin has around sixteen alternative functions like SPI, I2C, UART, etc. So we can tell the STM32 to use our required functions.
The below mentioned two registers are used to select the one function out of sixteen alternative function inputs/outputs available for each I/O.
GPIOx_AFRL
This 32-bit register is grouped by 4bits. So This GPIOx_AFRL
register is used to select the alternate functions of Pin 0 to Pin 7. Please find the below image of the GPIOx_AFRL
register.
Bits [31:0] – AFRLy : Alternate function selection for port X and bit Y, (y = 0 … 7)
AFRLy Selection:
0000: AF0 (Alternate Function 0)
0001: AF1 (Alternate Function 1)
0010: AF2 (Alternate Function 2)
0011: AF3 (Alternate Function 3)
0100: AF4 (Alternate Function 4)
0101: AF5 (Alternate Function 5)
0110: AF6 (Alternate Function 6)
0111: AF7 (Alternate Function 7)
1000: AF8 (Alternate Function 8)
1001: AF9 (Alternate Function 9)
1010: AF10 (Alternate Function 10)
1011: AF11 (Alternate Function 11)
1100: AF12 (Alternate Function 12)
1101: AF13 (Alternate Function 13)
1110: AF14 (Alternate Function 14)
1111: AF15 (Alternate Function 15)
GPIOx_AFRH
This 32-bit register is grouped by 4bits. So This GPIOx_AFRH
register is used to select the alternate functions of Pin 8 to Pin 15. Please find the below image of the GPIOx_AFRH
register.
Bits [31:0] – AFRHy : Alternate function selection for port X and bit Y, (y = 0 … 7)
AFRHy Selection:
0000: AF0 (Alternate Function 0)
0001: AF1 (Alternate Function 1)
0010: AF2 (Alternate Function 2)
0011: AF3 (Alternate Function 3)
0100: AF4 (Alternate Function 4)
0101: AF5 (Alternate Function 5)
0110: AF6 (Alternate Function 6)
0111: AF7 (Alternate Function 7)
1000: AF8 (Alternate Function 8)
1001: AF9 (Alternate Function 9)
1010: AF10 (Alternate Function 10)
1011: AF11 (Alternate Function 11)
1100: AF12 (Alternate Function 12)
1101: AF13 (Alternate Function 13)
1110: AF14 (Alternate Function 14)
1111: AF15 (Alternate Function 15)
As of now, we are using these pins as a GPIO and we are not selecting other functions than Input/Output. So in our future post, we will discuss these GPIOx_AFRL
and GPIOx_AFRH
.
I think we have covered almost all the registers. Now we will just put them all together and make our hands dirty by playing with the LEDs. Let’s dive into the programming part.
LED Interfacing with STM32
In the below example, I have enabled all the Ports (A, B, C, D, and E) as an output. and toggling them with some delay. You can also find the complete project in GitHub.
Code
/***************************************************************************//** * \file main.c * * \details Setting all the Ports (A, B, C, D, and E) as output * and toggling them with some random delay * * \author EmbeTronicX * * \This code is verified with proteus simulation * *******************************************************************************/ #include "stm32f4xx.h" #define DELAY_COUNT ( 30000 ) //delay count /***************************************************************************//** \details Providing Delay by running empty for loop \return void \retval none *******************************************************************************/ static void delay( void ) { uint32_t i = 0; for( i=0; i<=DELAY_COUNT; i++ ); } /***************************************************************************//** \details The main function. It should not return. \return void \retval none *******************************************************************************/ int main(void){ //Enable the AHB clock all GPIO ports SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN); SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN); SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN); SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN); SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOEEN); //set all Port A to Port E as output GPIOA->MODER = 0x55555555; GPIOB->MODER = 0x55555555; GPIOC->MODER = 0x55555555; GPIOD->MODER = 0x55555555; GPIOE->MODER = 0x55555555; //Endless loop while(1){ //Turn ON the LED of all the ports GPIOA->BSRR = 0x0000FFFF; GPIOB->BSRR = 0x0000FFFF; GPIOC->BSRR = 0x0000FFFF; GPIOD->BSRR = 0x0000FFFF; GPIOE->BSRR = 0x0000FFFF; delay(); //Turn OFF the LED of all the ports GPIOA->BSRR = 0xFFFF0000; GPIOB->BSRR = 0xFFFF0000; GPIOC->BSRR = 0xFFFF0000; GPIOD->BSRR = 0xFFFF0000; GPIOE->BSRR = 0xFFFF0000; delay(); } }
Output
Please find the output of the example below.
Switch/Button interfacing with STM32
I have connected the button to the PA0 (Port A.0) and LEDs to the PD0 to PD3. You can also find the project in GitHub.
Code
/***************************************************************************//** * \file main.c * * \details Setting PORT D0 to D3 as output and PORT A0 as input. * When we press the Port A0, we will turn on the PD0-PD3. * * \author EmbeTronicX * * \This code is verified with proteus simulation * *******************************************************************************/ #include "stm32f4xx.h" /***************************************************************************//** \details The main function. It should not return. \return void \retval none *******************************************************************************/ int main(void){ //Enable the AHB clock all GPIO PORT A and PORT D SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN); SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN); //set Port A as input GPIOA->MODER = 0x00000000; //Enable Pullup on PA0 GPIOA->PUPDR = 0x00000001; //set PORTD0 to PORTD3 as output GPIOD->MODER = 0x00000055; //Turn OFF the LEDs of PORTD GPIOD->BSRR = 0xFFFF0000; //Endless loop while(1){ //Button is connected to PA0. So we need to check bit 0 of IDR register. if( ( GPIOA->IDR & 0x01) == 0 ) { //Turn ON the LEDs GPIOD->BSRR = 0x0000000F; } else { //Turn OFF the LEDs GPIOD->BSRR = 0x000F0000; } } }
Output
Please find the output of the example below.
Note: The code is tested with the Proteus simulation. Not with the real hardware. If you have figured out any problems and issues with code while testing with the hardware, Please inform us and provide us as the workaround. So that, We will update here also. This will help others to learn.