Page 1 of 1

STM32 Programming. Part 5: GPIO I/O Ports

Posted: 16 Oct 2023, 02:14
by Oleg
image.png
image.png (52.59 KiB)
Viewed 3554 times
In this part we will understand the GPIO I/O pores of the STM32F103C8 microcontroller and write "Hello, World!" with a blinking LED, as well as learn how to read the state of the microcontroller pins and use the built-in pull-up resistor.

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
I will give some explanations about the modes.

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)
Figure 1. CRL register
image.png (15.44 KiB)
Figure 1. CRL register Viewed 3554 times
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.
CNFy[1:0]: Mode Configuration.

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
In output mode (MODEy[1:0]>00):
  • 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
Port configuration register high (GPIOx_CRH)
Figure 2. CRH register
image.png (17.21 KiB)
Figure 2. CRH register Viewed 3554 times
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)
Figure 3. IDR register
image.png (11.84 KiB)
Figure 3. IDR register Viewed 3554 times
IDRy: these bits contain the input value of the corresponding I/O port.

Port output data register (GPIOx_ODR)
Figure 4. ODR register
image.png (12.36 KiB)
Figure 4. ODR register Viewed 3554 times
ODRy: port output data.

Port bit set/reset register (GPIOx_BSRR)
Figure 5. BSRR register
image.png (15.89 KiB)
Figure 5. BSRR register Viewed 3554 times
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.
BSy: Set bit at the ODR register of the I/O port (y= 0 ... 15)
  • 0: Does not affect the corresponding bit of ODRx
  • 1: Sets the corresponding ODRx bit to one
Port bit reset register (GPIOx_BRR)
Figure 6. BRR register
image.png (11.59 KiB)
Figure 6. BRR register Viewed 3554 times
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
Port configuration lock register (GPIOx_LCKR)
Figure 7. LCKR register
image.png (13.52 KiB)
Figure 7. LCKR register Viewed 3554 times
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.
The following sequence must be performed to lock:
  • 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.
Configuring GPIO port
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:
image.png
image.png (208.41 KiB)
Viewed 3554 times
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:
Figure 8. Table of buses and peripherals
image.png (52.63 KiB)
Figure 8. Table of buses and peripherals Viewed 3554 times
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:
Figure 9. RCC_APB2ENR register
image.png (18.33 KiB)
Figure 9. RCC_APB2ENR register Viewed 3554 times
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:
Downloaded 458 times
Let's create a function to initialize the port:

Code: Select all

void PortInit(void)
{
}
The first thing to do is to enable the GPIOB port clocking:

Code: Select all

RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //Turn on GPIOB port clocking.
Next, configure the port. We need pin PB12. Its configuration bits are in the CRH register:

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);
All Setup is complete! Here is the full code of the function:

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);
}
Now the control. For this we need the ODR register (Figure 4). Here is the function to set the high level on PB12:

Code: Select all

void PortSetHi(void)
{
  GPIOB->ODR |= (1<<12);
}
Here's a low one:

Code: Select all

void PortSetLow(void)
{
  GPIOB->ODR &= ~(1<<12);
}
Nothing complicated, almost like on AVRs! However, we have a more serious microcontroller and it has some tricks. Expressions like GPIOB->ODR |= (1<<12) are read-modify-write operations. This is long and has side effects when the same register is accessed simultaneously from different parts of the program (for example, from an interrupt and the main program thread). This is called violation of the atomicity of the operation. But in STM32 you can do it like this:

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++)
      ;
  }
}
That's it, the LED connected to BP12 is blinking! "Hello, World!" is ready!

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.
Everything here is almost the same as when setting PB12, only a different pin and MODE/CNF parameters. But the last line requires some explanations. The point is that the ODR register has a dual purpose. If a pin is configured for output, the corresponding bit in ODR is responsible for the value of the logic level on this pin. But if the pin is set to input, and CNF=10 (Input with pull-up / pull-down), then the corresponding bit in ODR controls the pull-up resistors of this pin. By the way, there is such a picture in the Reference manual:
Figure 10: Port Configuration Table
image.png (28.08 KiB)
Figure 10: Port Configuration Table Viewed 3554 times
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;
}
I think everything is clear here: if 15 bits in the IDR register are equal to 1, we return one, otherwise we return zero.

In this case main() will look like this:

Code: Select all

void main()
{
  PortInit();
  
  for(;;)
  {
    if(ReadPort())
      PortSetHi();
    else
      PortSetLow();
  }
}
On the debug board with the stm32f103c8 microcontroller, the LED is lit low on pin PB12. This means that when PB15 is shorted to ground, the zero value on this pin will be read and set to zero on PP12, and the LED will light up. If PB15 is disconnected from ground, the internal pull-up will push the pin to the plus side of the microcontroller power supply, the ReadPort() function will read a logical one on PB15, and a log. 1, which will cause the LED to turn off.