GPIO - theory version

Contains lots of ELEC 3300 materials that you might not understand yet

Authors

Joseph Lam, Binay Gurung, Anshuman Medhi (Grand grand seniors)

Modified by: Alex Chan, Leo Wong, Christopher Kwan

What is GPIO?

A general-purpose input/output (GPIO) is an digital signal pin on an integrated circuit or electronic circuit board whose behavior — including whether it acts as an input or output — is controllable by the user at run time.

In short, GPIO is for outputting and reading HIGH [1] and LOW [0] values. (Sometimes we will use GPIO for analogue values but don't worry about that for now)

GPIO Configuration

  • Every pin on the MCU can be used as GPIO, they are divided into a blocks of 16 pins due to internal structure

    • Each block is named by letters: GPIOA, GPIOB, GPIOC...

      • Each pin within the block is numbered from 0: GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2...

  • So every pin is referred to by a pair of values: the port and the pin: GPIOE, GPIO_PIN_3 or when humans are talking we say PE3

Macros for GPIO

We will often use #define to give more meaningful names to the ports and pins, for ease of use:

/* main.h */
#define UP_BTN_GPIO_Port     GPIOE
#define UP_BTN_Pin           GPIO_PIN_3
#define LED1_GPIO_Port       GPIOE
#define LED1_Pin             GPIO_PIN_4
#define LED2_GPIO_Port       GPIOE
#define LED2_PIN             GPIO_PIN_5
#define LED3_GPIO_Port       GPIOE
#define LED3_PIN             GPIO_PIN_6
  • These defines are generated when you generate the HAL library from STM32CubeMX. You may change it at anytime to suit your need.

Initialization of GPIO (HAL Library Style)

Any hardware that you want to use must be first initialized. This is basically setting it up so it can work they way you want it to.

  • HAL library help you initialize the GPIO when the code is generated from STM32CudeMX. Inside the function MX_GPIO_Init

/* gpio.c */
// ...

/*Configure GPIO pin Output Level */
/*
 * This initialized 4 LED pins to LOW 0
 *
 * Notice that all pins need to be in the SAME port in order
 * to use the | bitwise-or operator to chain up the pins.
 */
HAL_GPIO_WritePin(GPIOA, LED4_Pin|LED3_Pin|LED2_Pin|LED1_Pin, GPIO_PIN_RESET);

/*Configure GPIO pins : PAPin PAPin PAPin PAPin */
  GPIO_InitStruct.Pin = LED4_Pin|LED3_Pin|LED2_Pin|LED1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // Output Push-pull mode
  GPIO_InitStruct.Pull = GPIO_NOPULL;             // No pull-up or pull-down
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    // Let's not worry that for now
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/*Configure GPIO pins : PAPin PAPin */
  GPIO_InitStruct.Pin = UP_BTN_Pin|DOWN_BTN_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;         // Input mode
  GPIO_InitStruct.Pull = GPIO_PULLUP;             // Pull-up
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

* Various GPIO Mode and GPIO Pull options

/* stm32f4xx_hal_gpio.h */
// Modes
#define  GPIO_MODE_INPUT        MODE_INPUT                /*!< Input Floating Mode                   */
#define  GPIO_MODE_OUTPUT_PP    (MODE_OUTPUT | OUTPUT_PP) /*!< Output Push Pull Mode                 */
#define  GPIO_MODE_OUTPUT_OD    (MODE_OUTPUT | OUTPUT_OD) /*!< Output Open Drain Mode                */
#define  GPIO_MODE_AF_PP        (MODE_AF | OUTPUT_PP)     /*!< Alternate Function Push Pull Mode     */
#define  GPIO_MODE_AF_OD        (MODE_AF | OUTPUT_OD)     /*!< Alternate Function Open Drain Mode    */

#define  GPIO_MODE_ANALOG       MODE_ANALOG

// Pull
#define  GPIO_NOPULL        0x00000000U   /*!< No Pull-up or Pull-down activation  */
#define  GPIO_PULLUP        0x00000001U   /*!< Pull-up activation                  */
#define  GPIO_PULLDOWN      0x00000002U   /*!< Pull-down activation                */
  • It seems there are various modes and pulls in the initialization, right? In Gerneal, you actually only need to know 4 of them - GPIO_MODE_INPUT, GPIO_MODE_OUTPUT_PP, GPIO_PULLUP, GPIO_PULLDOWN.

Input : GPIO_MODE_INPUT

  • GPIO_PULLUP and GPIO_PULLDOWN- The GPIO Pin can be used to read the logical value of the pin

    • The point is to deal with the secret 3rd state of binary digital signals: floating pins.

    • What happens when a pin is connected to nothing at all?

      • Noise will cause you to read a mostly random value

      • Therefore, The pull-up or pull-down gives a "weak" connection from the pin to either a high or low voltage. It gives a defined value to a floating pin while being weak enough to be easily overridden by any external signal.

Pull-up & Pull-down

One of the two set-ups here will read a high voltage when the button is on and low voltage when the button is off, while the other one is the opposite, can you tell which is which and why?

  • keywords : short circuit, potential divider

Output : GPIO_MODE_OUPUT_OD and GPIO_MODE_OUTPUT_PP

  • GPIO_MODE_OUPUT_OD and GPIO_MODE_OUTPUT_PP - The GPIO Pin can be used to output a digital signal using a pair of switches (see figure below)

    • Push-pull(PP) uses the 2 switches to connect the pin to either high voltage or low voltage, it pushes or pulls the voltage to the level assigned

    • Open-drain(OD) is similar but does not use the upper switch, thus it outputs a low voltage or completely disconnects the pin

  • GPIO_MODE_AF_PP & OD are the same but are for when the pin output is to be controlled by another bit of hardware, don't worry about it for now

Reading GPIO Input in the Program

The HAL_GPIO_ReadPin function reads the GPIO input.

/* stm32f4xx_hal_gpio.h */
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

// usage
uint8_t state = HAL_GPIO_ReadPin(BTN1_GPIO_Port, BTN1_Pin); // returns 0 or 1
  • The HAL_GPIO_ReadPin functions returns 1 or 0 of that respective pin of the parameter.

Readability:

  • Writing HAL_GPIO_ReadPin(XXX_GPIO_Port, XXX_Pin) every time may seem tidious. However with the help of magical c prepocessor, we can make it easier

/* main.h */
#define gpio_read(gpio) HAL_GPIO_ReadPin(gpio##_GPIO_Port, gpio##_Pin)

// usage
uint8_t state = gpio_read(BTN1);

Writing GPIO Output in the Program

The following macros can be found in main.h

  • The gpio_set(gpio) macro sets the GPIO pin to be 1.

  • The gpio_reset(gpio) macro resets the GPIO pin to 0.

  • The gpio_toggle(gpio) macro toggles the GPIO pin. (i.e. changes the GPIO pin state to 1 if it was originally 0 and vice versa)

Examples:

/* main.h */
#define gpio_set(gpio) HAL_GPIO_WritePin(gpio##_GPIO_Port, gpio##_Pin, GPIO_PIN_SET)
#define gpio_reset(gpio) HAL_GPIO_WritePin(gpio##_GPIO_Port, gpio##_Pin, GPIO_PIN_RESET)
#define gpio_toggle(gpio) HAL_GPIO_TogglePin(gpio##_GPIO_Port, gpio##_Pin)

// usage
// Turns off the LED
gpio_set(LED1);

// Turns on the LED
gpio_reset(LED1);

// Toggles the LED
gpio_toggle(LED1);

Full Example (Flickering the LED1 for every 500 ticks)

/* main.c */
int main(void) {

    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    volatile uint32_t last_ticks = HAL_GetTick();

    while (1) {

        if (HAL_GetTick() - last_ticks > 500) {
            last_ticks = HAL_GetTick();
            gpio_toggle(LED1); // flickering the LED1
        }
    }
    return 0;
}

Even More Readability:

Writing gpio_set(LED1) is still not very readable because you don't know if it is turning on/off the LED1.

Writing gpio_read(BTN1) is also not very readable because you don't know if gpio_read(BTN1) == 1 is pressed or gpio_read(BTN1) == 0is pressed.

So, we defined some more macro functions in main.h for more readability:

/* main.h */
#define led_on(led) gpio_reset(led) // notice that reset the pin turns the led on
#define led_off(led) gpio_set(led)
#define led_toggle(led) gpio_toggle(led)
#define btn_read(btn) gpio_read(btn)

If one day your hardware groupmate accidentally swapped the wires of all LEDs or buttons, you can simply change the defines above. You don't need to read through your hundreds/thousands of lines of code and change every gpio_set() gpio_reset() you wrote.

Simplest uses for GPIO

  • There are 3 LEDs and 2 Buttons on the board.

Thinking Time:

What should the GPIO Mode and Pull be for each of the above and why?

There are few step before using GPIO pins in your program:

  1. Find which GPIO you want to use in main.h (if you can't find one, define it yourself)

  2. Initialize the GPIO Pin once in gpio.h. Remember which mode and pull you need for the pin

  3. Use the GPIO Functions to read or write the digital signals

Button Example

// Initialising all gpio
MX_GPIO_Init();
// Remember the onboard button must be used with a pull up resistor

uint8_t pressed = btn_read(BTN1);
// Remember the onboard button will give a low signal when clicked, and a high signal otherwise

Last updated