STM32 Programming. Part 5: GPIO I/O Ports
Posted: 16 Oct 2023, 02:14
Introduction
General-purpose input/output (GPIO) is an important component of any microcontroller through which it interacts with the world around it. In STM32 microcontrollers the ports are named with letters A, B, C and so on: GPIOA, GPIOB, GPIOC.... Each GPIO has 16 I/O lines, and each line can be configured independently of the others. Here are the configuration options:
- Input floating - input with pull-up resistors disabled
- Input pull-up - input with pull-up to logic one
- Input-pull-down - input with pull-up to logic zero
- Analog - analog input (e.g. for ADC)
- Output open-drain - open-collector output (write 1 - output in high impedance state, write 0 - output is pressed to ground by internal transistor).
- Output push-pull - output push-pull (write 1 - output is in log. 1, write 0 - output is in log. 0).
- Alternate function push-pull - alternate function in push-pull mode.
- Alternate function open-drain - alternate function in the open-collector mode
Analog mode. Inside the microcontroller there are analog-to-digital converters, which, as you know, must have analog inputs. So, in Analog mode, the microcontroller leg is connected to the analog input of the ADC inside the microcontroller. In addition, all digital pipelining of this leg is disabled to reduce digital noise and power consumption.
Alternate function. In this mode, the microcontroller leg is controlled by internal digital peripherals such as the USART module.
GPIO registers
Let's take a look at the GPIO port registers.
Port configuration register low (GPIOx_CRL)
This is a configuration register for port pins numbered 0 through 7. Each pin is provided with 4 configuration bits: 2 MODEy bits and 2 CNFy bits.
MODEy[1:0]: Mode of the port pin, input or output. In the output mode you should select the maximum switching frequency of this leg, as far as I understand it is to optimize the power consumption of the port.
- 00: Input (value after reset)
- 01: Output, maximum frequency 10 MHz.
- 10: Output, maximum frequency 2 MHz.
- 11: Output, maximum frequency 50 MHz.
In input mode (MODEy[1:0]=00):
- 00: Analog mode - analog mode (connected to ADC or DAC)
- 01: Floating input - input with pull-up resistors disabled (value after reset)
- 10: Input with pull-up / pull-down - input with pull-up or pull-down.
- 11: Reserved - not used
- 00: General purpose output push-pull - output in pull/push mode
- 01: General purpose output Open-drain - open-collector output
- 10: Alternate function output Push-pull - Alternate function output push/pull mode
- 11: Alternate function output Open-drain - Alternate function output with open collector
This is a configuration register for port pins numbered 8 through 15. This is the same as the GPIOx_CRL register.
Port input data register (GPIOx_IDR)
IDRy: these bits contain the input value of the corresponding I/O port.
Port output data register (GPIOx_ODR)
ODRy: port output data.
Port bit set/reset register (GPIOx_BSRR)
This register can be used to reset or set any bit of the ODR register without read-modify-write operations.
BRy: Reset a bit of the I/O port ODR register (y= 0 ... 15)
- 0: Does not affect the corresponding bit of ODRx
- 1: Resets the corresponding ODRx bit to zero.
- 0: Does not affect the corresponding bit of ODRx
- 1: Sets the corresponding ODRx bit to one
This register can be used to reset any bit of the ODR register without read-modify-write operations.
BRy: Reset a bit in the ODR register of the I/O port (y= 0 ... 15)
- 0: No effect on the corresponding ODRx bit
- 1: Resets the corresponding ODRx bit to zero
This register is used to lock the port configuration bits after writing a valid sequence to 16 bits (LCKK) of the register. The bit values [15:0] are used to lock the GPIO configuration. During the locking sequence in LCKK, the LCKR values [15: 0] must not be changed. When the locking sequence has been written, the configuration of the selected I/O ports can only be changed after resetting the microcontroller. Each LCKy bit blocks the ability to change the four port configuration bits (CRL, CRH).
LCKK[16]: Lock key.
- 0: Port configuration lock is not active.
- 1: Port configuration lock is active. GPIOx_LCKR is locked until the next microcontroller reset.
- Write 1
- Write 0
- Write 1
- Read 0
- Read 1 (this read operation is not mandatory, but merely confirms that the lock has been successfully set).
LCKy: These bits can be read and written, but can only be written if the LCKK bit is zero.
- 0: The configuration of pin number y is not inhibited.
- 1: The configuration of pin number y is inhibited.
So, the registers have been dealt with, now it's time to practice. All examples in this article are for STM32F103C8 microcontroller. I have at my disposal such a debug board:
It has an 8 MHz quartz resonator and an LED on the PB12 port. With the help of this LED we will organize Hello, World!
The task is clear: we set PB12 to output in push-pull mode and use the ODR register to pull pin 12 of the GPIOB port back and forth! But we forgot about one small detail: RCC. The point is that by default after resetting the microcontroller all peripheral modules are disconnected from the clock signal source, including GPIO. And clocking can be supplied using RCC registers. I talked about it in Part 3. First of all, we need to determine which bus GPIOB is connected to. Open the datasheet of the microcontroller and look for this table:
GPIOB is connected to the APB2 bus. Go to Reference manual, open the section about RCC, go to paragraph 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). Using this register you can send a clock signal to the APB2 bus devices:
The RCC_APB2ENR register contains many flags for different peripherals, including our GPIOB, the flag is called IOPBEN. Before we start initializing PB12 we need to set this bit to one.
Let's go programming! Let's take the project from Part 2 as a basis: Let's create a function to initialize the port:
Code: Select all
void PortInit(void)
{
}
Code: Select all
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Turn on GPIOB port clocking.
Code: Select all
GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //reset everything to zero at first
//MODE: output with a maximum frequency of 2 MHz
//CNF: push-pull mode
GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);
Code: Select all
void PortInit(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Turn on GPIOB port clocking
GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //reset everything to zero for starters
//MODE: output with a maximum frequency of 2 MHz
//CNF: push-pull mode
GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);
}
Now the control. For this we need the ODR register (Figure 4). Here is the function to set a high level on PB12:
void PortSetHi(void)
{
GPIOB->ODR |= (1<<12);
}
Code: Select all
void PortSetHi(void)
{
GPIOB->ODR |= (1<<12);
}
Code: Select all
void PortSetLow(void)
{
GPIOB->ODR &= ~(1<<12);
}
The constructs (1<<12) are turned into a 0x1000 bitmask at compile time, and we get only one write operation to the BSRR or BRRR register (see Figures 5, 6).
Ni and a simple main() to check:
Code: Select all
void main()
{
int i;
PortInit();
for(;;)
{
PortSetHi();
for(i=0; i<0x40000; i++)
;
PortSetLow();
for(i=0; i<0x40000; i++)
;
}
}
Let's now configure some port pin, like PB15, to be a pull-up input to the power supply. When we connect PB15 to minus, we will have an LED light up. The task is clear, let's proceed to implementation. Let's add a couple of lines to PortInit():
Code: Select all
/// Configure PB15 to be a pull-up input ///
GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_CNF15);
//MODE: input, leave at zero
//CNF: input with pull-up / pull-down
GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos);
GPIOB->ODR |= (1<<15); //Enable pull-up.
The PortInit() function takes the following form:
void PortInit(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Turn on GPIOB port clocking
/// Set PB12 to output ///
GPIOB->CRH &= ~(GPIO_CRH_MODE12 | GPIO_CRH_CNF12); //reset everything to zero for starters
//MODE: output with a maximum frequency of 2 MHz
//CNF: push-pull mode
GPIOB->CRH |= (0x02 << GPIO_CRH_MODE12_Pos) | (0x00 << GPIO_CRH_CNF12_Pos);
/// Set PB15 to a pull-up input ///
GPIOB->CRH &= ~(GPIO_CRH_MODE15 | GPIO_CRH_CNF15);
//MODE: input, leave at zero
//CNF: input with pull-up / pull-down
GPIOB->CRH |= (0x00 << GPIO_CRH_MODE15_Pos) | (0x02 << GPIO_CRH_CNF15_Pos);
GPIOB->ODR |= (1<<15); //Enable pull-up
}
Let's move on to reading the state of PB15. There is an IDR register for this purpose (Fig. 3):
Code: Select all
int ReadPort(void)
{
if(GPIOB->IDR & (1<<15))
return 1;
return 0;
}
In this case main() will look like this:
Code: Select all
void main()
{
PortInit();
for(;;)
{
if(ReadPort())
PortSetHi();
else
PortSetLow();
}
}