Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

STM32 GPIO Output: Fundamentals and Practical Implementation

Tech May 9 3

GPIO Fundamentals

GPIO (General Purpose Input Output) is a common peripheral that can be configured into 8 distinct input/output modes. Pin output voltage ranges from 0V to 3.3V, and pins marked with FT are 5V-tolerant, meaning they accept 5V input while still outputting 3.3V. In output mode, GPIO can generate high/low levels to drive loads like LEDs and buzzers, or produce timing sequences for communication protocols. In input mode, GPIO reads pin levels for button input, external module signals, ADC analog sampling, and communication data reception.

Basic GPIO Structure

All GPIO peripherals on STM32 MCUs are mounted on the APB2 peripheral bus, named sequentially as GPIOA, GPIOB, GPIOC, etc. Each GPIO module has 16 pins, numbered 0 through 15 (e.g. PA0, PA1, ..., PA15). Each GPIO core consists of two main parts: control registers and drive circuitry.

Registers are special memory regions that the CPU accesses via the APB2 bus to set output levels and read input states. Each bit in a GPIO register corresponds to one physical pin: writing 1 to an output register bit sets the corresponding pin high, writing 0 sets it low. Reading 1 from an input register indicates the pin is currently high, 0 indicates low. STM32 is a 32-bit MCU, so all internal registers are 32 bits wide, but only the lower 16 bits are used for GPIO pins, the upper 16 bits are unused.

Drive circuitry amplifies the signal to increase driving capability, since registers only store data and cannot supply enough current for external loads.

GPIO Bit-level Structure

Input Path

The IO pin is protected by two clamping diodes connected to 3.3V VDD and 0V VSS respectively: if input voltage exceeds 3.3V, the upper diode conducts to divert excess current directly to VDD, protecting internal circuits; if input voltage drops below 0V, the lower diode conducts to divert current from VSS, avoiding damage from reverse current. Both diodes remain off when input is between 0 and 3.3V.

Next comes the configurable pull-up and pull-down resistor network, controlled by software:

  • Pull-up connected, pull-down disconnected: pull-up input mode, default high when pin is floating
  • Pull-down connected, pull-up disconnected: pull-down input mode, default low when pin is floating
  • Both disconnected: floating input mode

After the resistors is a Schmitt trigger, which shapes the input signal: it switches output instantly to high when input exceeds the upper threshold, and switches to low instantly when input drops below the lower threshold, converting noisy input to clean digital levels. The shaped signal is then latched into the input data register for the CPU to read.

Two additional input paths are provided for on-chip peripherals:

  • Analog input: connects directly to the ADC before the Schmitt trigger, to pass raw analog signals
  • Alternate function input: connects after the Schmitt trigger, to pass clean digital signals to other peripherals like UART RX

Output Path

Digital output can be controlled either by the output data register (for general GPIO use) or by on-chip peripherals (for alternate function use), selected via a hardware multiplexer. GPIO provides dedicated bit set/clear registers to allow modifying individual output bits without affecting other pins, since the full output data register can only be accessed as a whole. There are 3 common methods to modify individual output bits:

  1. Read the full register, modify the target bit with bitwise operations, then write back: simple but inefficient
  2. Use bit set/clear registers: write 1 to the corresponding position in the set register to set a bit high, write 1 to the corresponding position in the clear register to set it low, other bits remain unchanged automatically
  3. Use the STM32 bit-band region: each bit of the peripheral register is mapped to a separate word address, so writing directly to the mapped address modifies the target bit

After output control, the signal drives two MOSFET switches that connect the IO pin to VDD or VSS. Three output configurations are available:

  1. Push-pull output: Both MOSFETs are active. When output is 1, the upper P-MOS conducts and lower N-MOS cuts off, connecting the pin to VDD (high output). When output is 0, P-MOS cuts off and N-MOS conducts, connecting to VSS (low output). Both levels have strong driving capability, so STM32 has full control over the output.
  2. Open-drain output: Only the N-MOS is active, P-MOS is disabled. When output is 1, N-MOS cuts off leaving the pin in high-impedance state. When output is 0, N-MOS connects the pin to VSS (low output). Only low output has driving capability. This mode is commonly used for multi-device bus communication like I2C to avoid interference, and can generate 5V output with an external pull-up resistor.
  3. Output disabled: When the pin is configured as input, both MOSFETs are disabled, output is turned off, and pin level is controlled by external circuitry.

GPIO Modes

By configuring the GPIO mode control register, pins can be set to 8 different modes:

Mode Name Type Characteristics
Floating Input Digital Input Reads pin level, level is undefined if pin is floating
Pull-up Input Digital Input Reads pin level, internal pull-up connected, default high when floating
Pull-down Input Digital Input Reads pin level, internal pull-down connected, default low when floating
Analog Input Analog Input Digital GPIO circuitry disabled, pin directly connected to internal ADC
Open-drain Output Digital Output Can output levels, high is high-impedance (no driving), low connected to VSS
Push-pull Output Digital Output Can output levels, high connected to VDD, low connected to VSS, both have driving capability
Alternate Open-drain Output Digital Output Controlled by on-chip peripherals, same output characteristics as general open-drain
Alternate Push-pull Output Digital Output Controlled by on-chip peripherals, same output characteristics as general push-pull

Addisional notes:

  • For input modes: only the pull resistor configuration differs. Always connect input pins to a valid drive source, do not leave floating inputs unconnected. Except analog input, all other modes keep digital input enabled.
  • For output modes: even when configured for output, input functionality remains available: a pin can only have one output but supports multiple input readers. Alternate function modes have the same output characteristics as general output modes, just output control is handed to on-chip peripherals.

Common Load Introduction

  • LED: Light-emitting diode, lights when forward biased, off when reverse biased. Most common designs use low-active (low level turns on LED).
  • Active Buzzer: Has an internal oscillation circuit, just apply DC voltage to produce a fixed frequency continuous tone.
  • Passive Buzzer: No internal oscillator, requires external oscillation pulses to produce sound, can generate different tones by adjusting pulse frequency.

Most designs use a transistor as a switch to drive buzzers and LEDs, for example a common configuration where pulling the control pin low turns the buzzer on, high turns it off.


Blinking LED Example

To use GPIO on STM32, follow 3 core steps: enable GPIO clock via RCC, initialize GPIO parameters, then control output level.

Common RCC clock control functions in the standard peripheral library:

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

// Enable GPIOA clock example
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

Common GPIO library functions:

// Initialization
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* init_struct);
void GPIO_StructInit(GPIO_InitTypeDef* init_struct);

// Read operations
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

// Write operations
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t pin);    // Set pin high
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t pin);  // Set pin low
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t pin, BitAction value);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t port_val); // Write all 16 pins

GPIO mode definitions:

typedef enum
{
  GPIO_Mode_AIN = 0x0,                // Analog input
  GPIO_Mode_IN_FLOATING = 0x04,         // Floating input
  GPIO_Mode_IPD = 0x28,                 // Pull-down input
  GPIO_Mode_IPU = 0x48,                 // Pull-up input
  GPIO_Mode_Out_OD = 0x14,              // Open-drain output
  GPIO_Mode_Out_PP = 0x10,              // Push-pull output
  GPIO_Mode_AF_OD = 0x1C,               // Alternate open-drain
  GPIO_Mode_AF_PP = 0x18                // Alternate push-pull
} GPIOMode_TypeDef;

We implement delay functions using the SysTick system timer: Delay.h:

#ifndef DELAY_H
#define DELAY_H

void delay_us(uint32_t us);
void delay_ms(uint32_t ms);
void delay_s(uint32_t s);

#endif

Delay.c:

#include "stm32f10x.h"

void delay_us(uint32_t us)
{
  SysTick->LOAD = 72 * us;
  SysTick->VAL = 0x00;
  SysTick->CTRL = 0x00000005;
  while(!(SysTick->CTRL & (1 << 16)));
  SysTick->CTRL &= ~(1 << 0);
}

void delay_ms(uint32_t ms)
{
  while(ms--) delay_us(1000);
}

void delay_s(uint32_t s)
{
  while(s--) delay_ms(1000);
}

Full blinking LED code (low-active LED on PA0):

#include "stm32f10x.h"
#include "Delay.h"

int main(void)
{
  // Enable GPIOA clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

  // Initialize PA0 as 50MHz push-pull output
  GPIO_InitTypeDef gpio_init;
  gpio_init.GPIO_Pin = GPIO_Pin_0;
  gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
  gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOA, &gpio_init);

  while(1)
  {
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
    delay_ms(500);
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
    delay_ms(500);
  }
}

LED Running Light Example

Running light works by turning on each LED in sequence with a delay. The example below uses 8 low-active LEDs connected to the lower 8 pins of GPIOA:

#include "stm32f10x.h"
#include "Delay.h"

int main(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

  GPIO_InitTypeDef gpio_init;
  gpio_init.GPIO_Pin = GPIO_Pin_All;
  gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
  gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOA, &gpio_init);

  while(1)
  {
    for(uint8_t i = 0; i < 8; i++)
    {
      GPIO_Write(GPIOA, ~(1U << i));
      delay_ms(500);
    }
  }
}

Active Buzzer Control Example

This example uses an active low-trigger buzzer connected to PB12. Note that PA15, PB3, PB4 are reserved for JTAG debugging by default, avoid using them unless you reconfigure the debug pins.

#include "stm32f10x.h"
#include "Delay.h"

int main(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

  GPIO_InitTypeDef gpio_init;
  gpio_init.GPIO_Pin = GPIO_Pin_12;
  gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
  gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOB, &gpio_init);

  while(1)
  {
    // Short beep
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    delay_ms(100);
    GPIO_SetBits(GPIOB, GPIO_Pin_12);
    delay_ms(100);

    // Long beep
    GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    delay_ms(500);
    GPIO_SetBits(GPIOB, GPIO_Pin_12);
    delay_ms(500);
  }
}
Tags: STM32GPIO

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.