ES: PY32 Peripherals

Configuring peripherals on the PY32F0xx requires following a consistent four-step pattern: enable clocks, configure GPIO pins, initialize the peripheral, and optionally configure interrupts. This guide covers the peripheral configuration workflow with code examples for GPIO, USART, and NVIC setup.

Every peripheral on the PY32F0xx — GPIO, USART, SPI, I2C, ADC, timers — follows the same fundamental configuration pattern. Understanding this pattern once means you can configure any peripheral on the device.


The PY32 Peripheral Configuration Pattern

md
Step 1: Enable Clock
         |
         v
Step 2: Configure GPIO pins
         |
         v
Step 3: Configure Peripheral (baud rate, word length, etc.)
         |
         v
Step 4: Configure Interrupts (optional)
         |
         v
Step 5: Enable the Peripheral

This flow is identical across USART, SPI, I2C, ADC, and timers. The details differ but the structure is always the same.


Step 1: Enable Clocks

Most peripherals on the PY32F0xx are clock-gated by default — they receive no clock signal until explicitly enabled. Attempting to configure a peripheral without enabling its clock first results in writes to configuration registers being silently ignored.

The RCC (Reset and Clock Control) module manages peripheral clocks:

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

// Enable clock for USART1 on APB2 bus
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

Each peripheral is connected to either the APB1 or APB2 bus:

PeripheralBusClock Enable Function
GPIO A, B, FAPB2RCC_APB2PeriphClockCmd
USART1APB2RCC_APB2PeriphClockCmd
SPI1APB2RCC_APB2PeriphClockCmd
I2C1APB1RCC_APB1PeriphClockCmd
TIM1APB2RCC_APB2PeriphClockCmd
TIM3APB1RCC_APB1PeriphClockCmd
ADC1APB2RCC_APB2PeriphClockCmd

Step 2: Configure GPIO Pins

Peripheral signals are multiplexed on GPIO pins. Before a peripheral can communicate through its pins, those GPIO pins must be configured in the correct mode.

For a peripheral transmit pin (TX, MOSI, SCL), configure as alternate function push-pull output:

cpp
GPIO_InitTypeDef GPIO_InitStructure;

// Configure PA9 as USART1 TX (alternate function output)
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;        // Alternate function
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// Configure PA10 as USART1 RX (floating input)
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // Input
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO modes available:

ModeConstantUse
Input floatingGPIO_Mode_IN_FLOATINGDigital input, no pull
Input pull-upGPIO_Mode_IPUInput with pull-up resistor
Input pull-downGPIO_Mode_IPDInput with pull-down resistor
Output push-pullGPIO_Mode_Out_PPStandard digital output
Output open-drainGPIO_Mode_Out_ODOpen-drain output (I2C)
Alternate functionGPIO_Mode_AFPeripheral pin (TX, MOSI, SCL)
AnalogGPIO_Mode_AINADC input

Step 3: Configure the Peripheral

Each peripheral has its own initialization structure. Here is USART1 as an example:

cpp
USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate            = 9600;
USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
USART_InitStructure.USART_StopBits            = USART_StopBits_1;
USART_InitStructure.USART_Parity              = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode                = USART_Mode_Tx | USART_Mode_Rx;

USART_Init(USART1, &USART_InitStructure);

Every peripheral initialization follows the same pattern:

  1. Declare and fill an XXX_InitTypeDef structure
  2. Call XXX_Init(peripheral_instance, &init_structure)

Step 4: Configure Interrupts (Optional)

For peripherals that need to signal events to the CPU (received byte, conversion complete, transfer done), interrupts are configured through the NVIC (Nested Vectored Interrupt Controller):

cpp
NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel                   = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // Highest priority
NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;

NVIC_Init(&NVIC_InitStructure);

Interrupt priorities on Cortex-M0:

PriorityPreemptionUse
0HighestCritical real-time operations
1MediumGeneral peripheral events
2LowNon-time-critical background tasks
3LowestLowest-priority background processing

After configuring the NVIC, enable the specific interrupt in the peripheral itself:

cpp
// Enable USART receive interrupt
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);


Step 5: Enable the Peripheral

After all configuration is complete, start the peripheral:

cpp
// Enable USART1
USART_Cmd(USART1, ENABLE);


Complete Peripheral Configuration Example: USART1

Putting all five steps together:

cpp
void USART1_Init(void) {

    // Step 1: Enable clocks
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,  ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    // Step 2: Configure GPIO pins
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;     // PA9 = TX
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;     // PA10 = RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // Step 3: Configure USART1
    USART_InitTypeDef USART_InitStructure;

    USART_InitStructure.USART_BaudRate            = 9600;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStructure);

    // Step 4: Configure NVIC interrupt (optional)
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel                   = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    // Step 5: Enable peripheral
    USART_Cmd(USART1, ENABLE);
}


Applying the Pattern to Other Peripherals

The same five-step structure applies to every peripheral:

md
SPI:     Enable RCC → Configure GPIO (AF mode) → SPI_Init() → NVIC (optional) → SPI_Cmd()
I2C:     Enable RCC → Configure GPIO (OD mode) → I2C_Init() → NVIC (optional) → I2C_Cmd()
ADC:     Enable RCC → Configure GPIO (AIN mode) → ADC_Init() → HAL_ADC_Start()
Timer:   Enable RCC → Configure GPIO (AF for PWM) → TIM_Init() → TIM_Cmd()

Learn the pattern once, apply it to every peripheral.


Final Thoughts

The PY32F0xx peripheral configuration pattern is consistent, predictable, and shared across the entire STM32-compatible HAL family. Once you understand the five-step flow — enable clock, configure GPIO, initialize peripheral, configure interrupts, enable peripheral — you can bring up any peripheral on the device without referring to the full reference manual for each one.

The peripheral configuration pattern is the master key to PY32 firmware development.