Map-Based Method for GPIO Alternate Function Configuration

Nowadays, most microcontrollers have two or more features integrated into one pin. This means a single pin can have more than one functionality. Such a pin is known as a multifunction pin.

All microcontroller manufacturers include a table in the device’s datasheet. This table lists each pin’s features and provides simple instructions to set up the desired function.

Consider the below-mentioned table for a better understanding See the table.

GPIO Alternate functions STM32

 

In this blog post, I will use the STM32H5XXX microcontroller to explain the concept of alternate functions in GPIO pins. Before diving into the coding, let’s first understand how the GPIO pin alternate function multiplexer and mapping work.

Microcontroller GPIO pins connect to onboard peripherals or modules through a multiplexer. This design ensures that only one peripheral’s alternate function (AF) remains active on a pin at a time, avoiding conflicts between peripherals that share the same pin.

In STM32 each I/O pin has a multiplexer with up to 16 alternate function inputs (AF0 to AF15), allowing flexible configuration for various applications.

 

How to Configure Alternate Functions for GPIO Pins:

Let’s understand this with an example. Suppose you want to use PA2 and PA3 as the TX and RX pins for USART2 on the STM32H5 microcontroller. In that case, you must configure them to use AF7 (Alternate Function 7), the alternate function for USART2 communication. See the above given image.

  • PA2: USART2_TX (Transmit)
  • PA3: USART2_RX (Receive)

Below is an example of the STM32 HAL code to configure PA2 and PA3 for USART2:

// Enable GPIOA clock
__HAL_RCC_GPIOA_CLK_ENABLE();

// Configure PA2 as USART2_TX and PA3 as USART2_RX
GPIO_InitTypeDef GPIO_InitStruct = {0};

// PA2 for USART2_TX (AF7)
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;  // Set as Alternate Function, Push-Pull mode
GPIO_InitStruct.Pull = GPIO_NOPULL;      // No pull-up or pull-down
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;  // High-speed configuration
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;   // Set Alternate Function 7 for USART2
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// PA3 for USART2_RX (AF7)
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;  // Set Alternate Function 7 for USART2
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

In the code above, you can see that I used GPIO_AF7_USART2 for both the RX and TX pins in the GPIO_InitStruct.Alternate field. This configuration sets PA2 as the USART2 TX pin and PA3 as the USART2 RX pin.

Typically, you would need to repeat this configuration for each pin, which can lead to many duplicate codes. This not only increases the amount of code but also makes it harder to maintain.

Don’t worry in this blog post I will give you a solution to this problem.

Instead of writing separate code for each pin, we can create a general function. This function would handle the configuration of any GPIO pin, setting it up with the correct alternate function based on its intended use. This way, we can avoid duplicating code and make the program more modular and easier to maintain.

But don’t worry! In this blog post, I will show you a simple solution.

Instead of repeating the same code for each pin, we can create a general function that handles the configuration for any GPIO pin. This function will automatically set the pin to the correct alternate function based on how it’s used. By doing this, we eliminate code duplication and make our program more modular and easier to maintain.

But how do we achieve this?

The answer lies in a customized map-based approach. This approach offers greater flexibility, scalability, and efficiency. It’s essentially a Table-Driven Design for managing pin alternate functions. Let’s take a look at the steps involved in implementing this customized map-based solution.

 

Steps to Implement a Customized Map-Based Approach:

A customized map-based approach simplifies GPIO alternate function configuration. This approach involves creating a centralized table that maps GPIO ports and pins to their respective alternate functions. Below are the steps, explained with a practical example:

 

Step 1: Create a Mapping Table

You need to define a table that holds the pin-to-alternate-function mapping. See the below example,

typedef struct
{
   GPIO_TypeDef* port; // GPIO Port (e.g., GPIOA, GPIOB)

   uint16_t pin; // GPIO Pin (e.g., GPIO_PIN_2)

   uint8_t altFunctionValue; // Alternate Function (e.g., AF7)
} GPIO_PinConfig;

// Define the mapping table
const GPIO_PinConfig gpio_pin_map[] = {
      {GPIOA, GPIO_PIN_2, GPIO_AF7_USART2}, // PA2 -> USART2_TX (AF7)
      {GPIOA, GPIO_PIN_3, GPIO_AF7_USART2}, // PA3 -> USART2_RX (AF7)
      {GPIOB, GPIO_PIN_6, GPIO_AF4_I2C1},   // PB6 -> I2C1_SCL (AF4)
      {GPIOB, GPIO_PIN_7, GPIO_AF4_I2C1},   // PB7 -> I2C1_SDA (AF4)
                                            // Add more mappings as needed
};

This table allows you to manage all pin configurations in one place, making it easier to update and scale. You just need to change the value as per your requirement.

 

Step 2: Create a Search Function

Now it is time to implement a function that searches the table using the port and pin as inputs. This function will either return the corresponding alternate function value or an error code if the pin is not found.

uint8_t getAlternateFunction(GPIO_TypeDef* port, uint16_t pin)
{
   // Invalid Alternate Function
   const uint8_t INVALID_AF = 0xFF;
   uint8_t altFunValue = INVALID_AF;
   for (size_t i = 0; i < sizeof(gpio_pin_map) / sizeof(gpio_pin_map[0]); i++)
   {
      if (gpio_pin_map[i].port == port && gpio_pin_map[i].pin == pin)
      {
         // Return the alternate function
         altFunValue = gpio_pin_map[i].altFunctionValue;
         break;
      }
   }
   return altFunValue;
}

 

Step 3: General Gpio Configuration Function:

In this step, we will create a function to simplify GPIO configuration, specifically for setting the alternate function of GPIO pins. This function will take the port and pin as input, look up the pin in the mapping table, retrieve the corresponding alternate function value, and apply the required configuration.

By using this approach, we make GPIO setup easier to manage and reduce the need to write separate code for each pin configuration. This makes the code cleaner and easier to understand, even for beginners.

void configureGpioAlternateFunction(GPIO_TypeDef* port, uint16_t pin)
{
   // Get the alternate function for the given port and pin
   uint8_t altFunValue = getAlternateFunction(port, pin);

   // Check if the pin was found
   if (altFunValue != 0xFF)
   {
      // Enable the GPIO clock (example for GPIOA, GPIOB, GPIOC and GPIOD )
      // You can add as per your requirement
      if (port == GPIOA)
      {
         __HAL_RCC_GPIOA_CLK_ENABLE();
      }
      else if (port == GPIOB)
      {
         __HAL_RCC_GPIOB_CLK_ENABLE();
      }
      else if (port == GPIOC)
      {
         __HAL_RCC_GPIOC_CLK_ENABLE();
      }
      else if (port == GPIOD)
      {
         __HAL_RCC_GPIOD_CLK_ENABLE();
      }

      // Configure the GPIO pin
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      GPIO_InitStruct.Pin =  (1U << pin);
      GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;       // Alternate Function, Push-Pull
      GPIO_InitStruct.Pull = GPIO_NOPULL;           // No Pull-up or Pull-down
      GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // High Speed
      GPIO_InitStruct.Alternate = altFunValue;      // Set Alternate Function
      HAL_GPIO_Init(port, &GPIO_InitStruct);
   }

   return (altFunValue != 0xFFU);
}

 

In this blog post, we learned how to configure GPIO pins with alternate functions on STM32 microcontrollers in a simple and organized way.

I hope this approach helps you in your STM32 projects. If you have any questions or need further explanation, feel free to leave a comment below. I will be happy to assist you! Don’t hesitate to share your thoughts or any challenges you’ve faced while working with GPIO pins.

Happy Coding.🤩