ES: PY32 GPIO

GPIO on the PY32F0xx supports input, output, alternate function, and analog modes with optional interrupt triggering. This guide covers the complete GPIO configuration API, reading inputs, driving outputs, and setting up external interrupts using the PY32 HAL library.

GPIO is the foundation of all embedded hardware interaction on the PY32F0xx. Before configuring USART, SPI, or I2C, those peripheral pins must be set up as GPIO in the correct mode. Understanding GPIO configuration on PY32 also means understanding the HAL API that applies to every other peripheral.


GPIO Features on PY32F0xx

The GPIO module in the PY32F0xx family supports:

FeatureDescription
Input modesFloating, pull-up, pull-down
Output modesPush-pull, open-drain
Alternate functionRoute peripheral signals (UART, SPI, I2C) through GPIO pins
Analog modeADC and DAC operation
Interrupt handlingRising, falling, or both edge triggers on any GPIO pin

Required Headers

cpp
#include "py32f0xx.h"        // Main device header
#include "py32f0xx_gpio.h"   // GPIO configuration functions
#include "py32f0xx_rcc.h"    // RCC for enabling GPIO clocks


Step 1: Enable GPIO Clock

Each GPIO port (GPIOA, GPIOB, GPIOF) requires its clock to be enabled before pins can be configured. This is done through the RCC (Reset and Clock Control) module:

cpp
// Enable clock for GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// Enable clock for GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

Forgetting to enable the GPIO clock is one of the most common mistakes in PY32/STM32-style HAL development. Without the clock, all register writes to the GPIO peripheral are silently ignored.


Step 2: Initialize GPIO Pins

Use the GPIO_InitTypeDef structure to configure pin mode, speed, and other parameters:

cpp
GPIO_InitTypeDef GPIO_InitStructure;

// Configure PA0 as floating digital input
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// Configure PA1 as push-pull output at 50 MHz
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO Mode Options

Mode ConstantTypeDescription
GPIO_Mode_IN_FLOATINGInputNo internal pull resistor
GPIO_Mode_IPUInputInternal pull-up resistor
GPIO_Mode_IPDInputInternal pull-down resistor
GPIO_Mode_Out_PPOutputPush-pull (drives HIGH and LOW)
GPIO_Mode_Out_ODOutputOpen-drain (drives LOW, floats HIGH)
GPIO_Mode_AFAlternateRoutes peripheral signal through pin
GPIO_Mode_AINAnalogADC/DAC input — disables digital functions

GPIO Speed Options

Speed ConstantMaximum Frequency
GPIO_Speed_2MHz2 MHz
GPIO_Speed_10MHz10 MHz
GPIO_Speed_50MHz50 MHz

For GPIO toggling, GPIO_Speed_50MHz is the standard choice. For I2C (open-drain) pins, lower speeds reduce EMI.


Step 3: Set or Read GPIO Pins

Set Output HIGH

cpp
GPIO_SetBits(GPIOA, GPIO_Pin_1);    // Set PA1 to HIGH (3.3V)

Set Output LOW

cpp
GPIO_ResetBits(GPIOA, GPIO_Pin_1);  // Set PA1 to LOW (0V)

Toggle Output

cpp
// Toggle PA1 state (read-modify-write on ODR)
GPIOA->ODR ^= GPIO_Pin_1;

Read Input State

cpp
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) {
    // PA0 is HIGH
} else {
    // PA0 is LOW
}

Read Entire Port

cpp
uint16_t port_value = GPIO_ReadInputData(GPIOA);
// Check individual bits with bitwise AND
if (port_value & GPIO_Pin_3) {
    // PA3 is HIGH
}


Complete GPIO Example: LED and Button

cpp
#include "py32f0xx.h"
#include "py32f0xx_gpio.h"
#include "py32f0xx_rcc.h"

void GPIO_Config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    // Enable clocks
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // Configure PA0 as input with pull-up (button: active LOW)
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // Pull-up
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // Configure PB5 as push-pull output (LED)
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

int main(void) {
    GPIO_Config();

    while (1) {
        if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) {
            // Button is pressed (active LOW with pull-up)
            GPIO_SetBits(GPIOB, GPIO_Pin_5);    // LED ON
        } else {
            GPIO_ResetBits(GPIOB, GPIO_Pin_5);  // LED OFF
        }
    }
}


Step 4: Configure GPIO Interrupts (External Interrupts)

GPIO pins on the PY32F0xx can trigger interrupts through the EXTI (External Interrupt) controller. This enables event-driven firmware rather than polling in a loop.

Required additional headers:

cpp
#include "py32f0xx_exti.h"   // External interrupt configuration
#include "py32f0xx_nvic.h"   // NVIC interrupt controller

Configure EXTI and NVIC:

cpp
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

// Connect EXTI Line0 to PA0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

// Configure EXTI Line0 for rising edge trigger
EXTI_InitStructure.EXTI_Line    = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

// Enable EXTI Line0 interrupt in NVIC
NVIC_InitStructure.NVIC_IRQChannel                   = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
NVIC_Init(&NVIC_InitStructure);

Implement the interrupt service routine:

cpp
void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // Handle the interrupt event
        GPIO_ToggleBits(GPIOB, GPIO_Pin_5);  // Toggle LED on button press

        // Clear the interrupt flag
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

Always clear the interrupt pending bit inside the ISR, or the interrupt will immediately trigger again.


GPIO API Summary

FunctionDescription
GPIO_Init(port, &init)Initialize GPIO pin(s)
GPIO_SetBits(port, pins)Set specified pins HIGH
GPIO_ResetBits(port, pins)Set specified pins LOW
GPIO_ReadInputDataBit(port, pin)Read single input pin state
GPIO_ReadInputData(port)Read all input pins on a port
GPIO_ReadOutputDataBit(port, pin)Read output register bit
GPIO_EXTILineConfig(port, pin)Connect GPIO pin to EXTI line
EXTI_Init(&init)Configure external interrupt
EXTI_GetITStatus(line)Check if interrupt is pending
EXTI_ClearITPendingBit(line)Clear interrupt flag

Final Thoughts

GPIO configuration on PY32F0xx follows the STM32-compatible HAL pattern precisely. The same GPIO_InitTypeDef structure, GPIO_Init() function, and EXTI/NVIC configuration flow used here applies to virtually every STM32 family microcontroller as well.

Mastering this pattern:

  • Enables you to configure any GPIO pin for any purpose
  • Gives you the foundation to configure peripheral pins (USART, SPI, I2C)
  • Provides interrupt-driven input handling without polling

GPIO on PY32 is the starting point for every firmware project on this platform.