The "Holy Bible" for embedded engineers
Mastering General Purpose Input/Output for Embedded Systems
Understanding GPIO modes, configuration, and practical applications
GPIO (General Purpose Input/Output) is the foundation of embedded system I/O. Understanding GPIO configuration is essential for interfacing with external devices, sensors, and actuators.
Key Concepts:
GPIO Pin Configuration
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GPIO Pin β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Configuration Block β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β Mode β β Type β β Speed β β β
β β β (Input/ β β (Push-Pull/ β β (Low/Med/ β β β
β β β Output/ β β Open-Drain)β β High) β β β
β β β Alt Func) β β β β β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β Pull-Up/ β β Drive β β Interrupt β β β
β β β Pull-Down β β Strength β β Enable β β β
β β β (On/Off) β β (2/4/8/20mA)β β (Edge/Level)β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Input Mode Operation
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β External βββββΆβ Input βββββΆβ Input Data β
β Signal β β Buffer β β Register β
β (0V/3.3V) β β (High-Z) β β (Readable) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
Output Mode Operation
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Output Data βββββΆβ Output βββββΆβ External β
β Register β β Driver β β Load β
β (Writable) β β (Push-Pull) β β (LED/Relay) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
Interrupt Triggering Modes
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Rising Edge β
β βββββββ βββββββ βββββββ βββββββ β
β β β β β β β β β β
β β β β β β β β β β
β βββββββ βββββββ βββββββ βββββββ β
β β² β² β² β² β
β β β β β β
β Interrupt Interrupt Interrupt Interrupt β
β Triggered Triggered Triggered Triggered β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Falling Edge β
β βββββββ βββββββ βββββββ βββββββ β
β β β β β β β β β β
β β β β β β β β β β
β βββββββ βββββββ βββββββ βββββββ β
β βΌ βΌ βΌ βΌ β
β β β β β β
β Interrupt Interrupt Interrupt Interrupt β
β Triggered Triggered Triggered Triggered β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GPIO serves as the fundamental interface between the digital computational world and the physical world. Itβs the most basic building block that enables embedded systems to sense their environment and control external devices.
Key Characteristics:
Proper GPIO configuration is critical for system reliability and performance:
GPIO (General Purpose Input/Output) is a digital signal pin on a microcontroller or integrated circuit that can be configured as either an input or output. Itβs the most basic and fundamental way for embedded systems to interact with the external world.
Digital Signal Interface:
Hardware Interface:
Software Control:
GPIO vs. Analog I/O:
GPIO vs. Specialized I/O:
GPIO vs. PWM/ADC:
Hardware Interface:
System Control:
Real-time Requirements:
Hardware Control:
// LED control - simple but essential
void control_led(bool state) {
if (state) {
GPIO_SetPin(GPIOA, 5); // Turn on LED
} else {
GPIO_ClearPin(GPIOA, 5); // Turn off LED
}
}
// Button reading - user interface
bool read_button(void) {
return GPIO_ReadPin(GPIOB, 0); // Read button state
}
System Status:
// System status monitoring
void check_system_status(void) {
bool power_good = GPIO_ReadPin(GPIOA, 1);
bool temperature_ok = GPIO_ReadPin(GPIOA, 2);
bool communication_active = GPIO_ReadPin(GPIOA, 3);
if (!power_good || !temperature_ok || !communication_active) {
// Handle system fault
handle_system_fault();
}
}
Real-time Control:
// Real-time control example
void emergency_stop(void) {
// Immediate response to emergency stop button
if (GPIO_ReadPin(GPIOA, 4)) { // Emergency stop pressed
GPIO_ClearPin(GPIOA, 5); // Stop motor immediately
GPIO_SetPin(GPIOA, 6); // Activate alarm
}
}
High Impact Scenarios:
Low Impact Scenarios:
Why it matters: Understanding how GPIO pins operate electrically is crucial for reliable system design. Incorrect configuration can lead to signal degradation, noise issues, and even component damage.
The GPIO Electrical Model: GPIO pins operate as controlled switches that can either sense external signals or drive external loads. The key to reliable operation lies in understanding the electrical characteristics and matching them to your application requirements.
Key Electrical Considerations:
Minimal example:
// Basic GPIO configuration structure
typedef struct {
uint8_t mode; // Input/Output/Alternate/Analog
uint8_t type; // Push-pull/Open-drain
uint8_t speed; // Low/Medium/High speed
uint8_t pull_up_down; // No pull/Up/Down
} gpio_config_t;
// Simple GPIO configuration
void configure_gpio_pin(uint8_t pin, gpio_config_t *config) {
// Set pin mode
set_pin_mode(pin, config->mode);
// Configure output type and speed if output
if (config->mode == GPIO_MODE_OUTPUT) {
set_output_type(pin, config->type);
set_output_speed(pin, config->speed);
}
// Set pull-up/pull-down
set_pull_up_down(pin, config->pull_up_down);
}
Try it: Configure a GPIO pin for different load conditions and measure signal integrity.
Takeaways:
Why it matters: Understanding the internal structure of GPIO pins helps you make informed configuration decisions and troubleshoot issues effectively.
The GPIO Internal Structure: Each GPIO pin contains multiple functional blocks that work together to provide flexible I/O capabilities. The internal architecture determines the pinβs capabilities and limitations.
Key Architectural Components:
Minimal example:
// GPIO register access structure
typedef struct {
volatile uint32_t MODER; // Mode register
volatile uint32_t OTYPER; // Output type register
volatile uint32_t OSPEEDR; // Output speed register
volatile uint32_t PUPDR; // Pull-up/pull-down register
volatile uint32_t IDR; // Input data register
volatile uint32_t ODR; // Output data register
} GPIO_TypeDef;
// Configure pin mode using registers
void set_pin_mode_direct(GPIO_TypeDef *gpio, uint8_t pin, uint8_t mode) {
// Clear and set mode bits (2 bits per pin)
uint32_t mask = 3U << (pin * 2);
uint32_t value = mode << (pin * 2);
gpio->MODER = (gpio->MODER & ~mask) | value;
}
Try it: Examine the GPIO registers in a debugger to understand the configuration.
Takeaways:
Why it matters: Choosing the right GPIO mode is critical for system reliability and performance. Incorrect mode selection can cause signal integrity issues, excessive power consumption, or even component damage.
The Mode Selection Process: GPIO mode selection involves understanding your application requirements and matching them to the available configuration options. Each mode has specific electrical characteristics and use cases.
Mode Selection Considerations:
Minimal example:
// GPIO mode configuration with validation
typedef enum {
GPIO_MODE_INPUT = 0,
GPIO_MODE_OUTPUT = 1,
GPIO_MODE_ALTERNATE = 2,
GPIO_MODE_ANALOG = 3
} gpio_mode_t;
// Configure pin with mode validation
int configure_gpio_mode(uint8_t pin, gpio_mode_t mode, uint8_t pull_config) {
// Validate mode selection
if (mode > GPIO_MODE_ANALOG) {
return -1; // Invalid mode
}
// Set mode
set_pin_mode(pin, mode);
// Configure pull-up/pull-down for input mode
if (mode == GPIO_MODE_INPUT) {
set_pull_config(pin, pull_config);
}
return 0; // Success
}
Try it: Configure the same pin for different modes and measure the electrical characteristics.
Takeaways:
GPIO modes define how a pin operates - whether itβs an input, output, or connected to a special function. Each mode has specific electrical characteristics and behavior.
Mode Selection:
Mode Characteristics:
// Configure GPIO as digital input
void gpio_input_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Clear mode bits (00 = Input mode)
GPIOx->MODER &= ~(3U << (pin * 2));
// Configure as no pull-up/pull-down
GPIOx->PUPDR &= ~(3U << (pin * 2));
}
// Read digital input
uint8_t gpio_read_input(GPIO_TypeDef* GPIOx, uint16_t pin) {
return (GPIOx->IDR >> pin) & 0x01;
}
// Configure GPIO as digital output
void gpio_output_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set mode bits (01 = Output mode)
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (1U << (pin * 2));
// Configure as push-pull
GPIOx->OTYPER &= ~(1U << pin);
// Configure speed (11 = Very high speed)
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (3U << (pin * 2));
}
// Write digital output
void gpio_write_output(GPIO_TypeDef* GPIOx, uint16_t pin, uint8_t state) {
if (state) {
GPIOx->BSRR = (1U << pin); // Set bit
} else {
GPIOx->BSRR = (1U << (pin + 16)); // Reset bit
}
}
// Configure GPIO for alternate function
void gpio_alternate_config(GPIO_TypeDef* GPIOx, uint16_t pin, uint8_t alternate) {
// Set mode bits (10 = Alternate function mode)
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (2U << (pin * 2));
// Configure alternate function
if (pin < 8) {
GPIOx->AFR[0] &= ~(0xFU << (pin * 4));
GPIOx->AFR[0] |= (alternate << (pin * 4));
} else {
GPIOx->AFR[1] &= ~(0xFU << ((pin - 8) * 4));
GPIOx->AFR[1] |= (alternate << ((pin - 8) * 4));
}
}
Configuration registers control the behavior of GPIO pins. They determine the mode, electrical characteristics, and behavior of each pin.
Register Organization:
Register Types:
// Mode register bit definitions
#define GPIO_MODE_INPUT 0x00 // Input mode
#define GPIO_MODE_OUTPUT 0x01 // Output mode
#define GPIO_MODE_ALTERNATE 0x02 // Alternate function mode
#define GPIO_MODE_ANALOG 0x03 // Analog mode
// Configure pin mode
void gpio_set_mode(GPIO_TypeDef* GPIOx, uint16_t pin, uint8_t mode) {
// Clear existing mode bits
GPIOx->MODER &= ~(3U << (pin * 2));
// Set new mode bits
GPIOx->MODER |= (mode << (pin * 2));
}
// Output type definitions
#define GPIO_OTYPE_PUSH_PULL 0x00 // Push-pull output
#define GPIO_OTYPE_OPEN_DRAIN 0x01 // Open-drain output
// Configure output type
void gpio_set_output_type(GPIO_TypeDef* GPIOx, uint16_t pin, uint8_t type) {
if (type == GPIO_OTYPE_OPEN_DRAIN) {
GPIOx->OTYPER |= (1U << pin);
} else {
GPIOx->OTYPER &= ~(1U << pin);
}
}
// Speed definitions
#define GPIO_SPEED_LOW 0x00 // Low speed
#define GPIO_SPEED_MEDIUM 0x01 // Medium speed
#define GPIO_SPEED_HIGH 0x02 // High speed
#define GPIO_SPEED_VERY_HIGH 0x03 // Very high speed
// Configure output speed
void gpio_set_speed(GPIO_TypeDef* GPIOx, uint16_t pin, uint8_t speed) {
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (speed << (pin * 2));
}
Input configuration determines how a GPIO pin behaves when configured as an input. It includes pull-up/pull-down resistors, input filtering, and interrupt capabilities.
Input Characteristics:
Pull-up/Pull-down Resistors:
// Configure basic input
void gpio_input_basic_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set as input mode
GPIOx->MODER &= ~(3U << (pin * 2));
// No pull-up/pull-down
GPIOx->PUPDR &= ~(3U << (pin * 2));
}
// Configure input with pull-up
void gpio_input_pullup_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set as input mode
GPIOx->MODER &= ~(3U << (pin * 2));
// Enable pull-up
GPIOx->PUPDR &= ~(3U << (pin * 2));
GPIOx->PUPDR |= (1U << (pin * 2));
}
// Configure input with pull-down
void gpio_input_pulldown_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set as input mode
GPIOx->MODER &= ~(3U << (pin * 2));
// Enable pull-down
GPIOx->PUPDR &= ~(3U << (pin * 2));
GPIOx->PUPDR |= (2U << (pin * 2));
}
Output configuration determines how a GPIO pin behaves when configured as an output. It includes output type, drive strength, speed, and electrical characteristics.
Output Types:
Drive Characteristics:
// Configure push-pull output
void gpio_output_pushpull_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set as output mode
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (1U << (pin * 2));
// Configure as push-pull
GPIOx->OTYPER &= ~(1U << pin);
}
// Configure open-drain output
void gpio_output_opendrain_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set as output mode
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (1U << (pin * 2));
// Configure as open-drain
GPIOx->OTYPER |= (1U << pin);
}
// Configure high-speed output
void gpio_output_highspeed_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
// Set as output mode
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (1U << (pin * 2));
// Configure for high speed
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (3U << (pin * 2));
}
Alternate function configuration allows GPIO pins to be connected to hardware peripherals like UART, SPI, I2C, timers, and other specialized functions.
Peripheral Connection:
Common Alternate Functions:
// Configure GPIO for UART
void gpio_uart_config(GPIO_TypeDef* GPIOx, uint16_t tx_pin, uint16_t rx_pin) {
// Configure TX pin
gpio_alternate_config(GPIOx, tx_pin, 7); // AF7 for UART
// Configure RX pin
gpio_alternate_config(GPIOx, rx_pin, 7); // AF7 for UART
}
// Configure GPIO for SPI
void gpio_spi_config(GPIO_TypeDef* GPIOx, uint16_t sck_pin, uint16_t miso_pin, uint16_t mosi_pin) {
// Configure SCK pin
gpio_alternate_config(GPIOx, sck_pin, 5); // AF5 for SPI
// Configure MISO pin
gpio_alternate_config(GPIOx, miso_pin, 5); // AF5 for SPI
// Configure MOSI pin
gpio_alternate_config(GPIOx, mosi_pin, 5); // AF5 for SPI
}
Drive strength and slew rate determine how much current a GPIO pin can drive and how fast it can change state. These characteristics are crucial for driving different types of loads.
Drive Strength:
Slew Rate:
// Configure low drive strength
void gpio_low_drive_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (0U << (pin * 2)); // Low speed
}
// Configure high drive strength
void gpio_high_drive_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (3U << (pin * 2)); // Very high speed
}
Pull-up and pull-down resistors ensure that GPIO pins have a defined state when not actively driven. They prevent floating inputs and provide default logic levels.
Resistor Types:
Resistor Values:
// Configure internal pull-up
void gpio_pullup_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->PUPDR &= ~(3U << (pin * 2));
GPIOx->PUPDR |= (1U << (pin * 2));
}
// Configure internal pull-down
void gpio_pulldown_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->PUPDR &= ~(3U << (pin * 2));
GPIOx->PUPDR |= (2U << (pin * 2));
}
// Configure no pull-up/pull-down
void gpio_no_pull_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->PUPDR &= ~(3U << (pin * 2));
}
GPIO pins are used in countless applications in embedded systems. Understanding common applications helps in designing effective GPIO solutions.
User Interface:
Sensor Interface:
Actuator Control:
// LED control application
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
bool state;
} led_t;
void led_init(led_t* led, GPIO_TypeDef* port, uint16_t pin) {
led->port = port;
led->pin = pin;
led->state = false;
// Configure as output
gpio_output_config(port, pin);
gpio_write_output(port, pin, false);
}
void led_toggle(led_t* led) {
led->state = !led->state;
gpio_write_output(led->port, led->pin, led->state);
}
// Button interface application
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
bool last_state;
bool current_state;
} button_t;
void button_init(button_t* button, GPIO_TypeDef* port, uint16_t pin) {
button->port = port;
button->pin = pin;
button->last_state = false;
button->current_state = false;
// Configure as input with pull-up
gpio_input_pullup_config(port, pin);
}
bool button_read(button_t* button) {
button->last_state = button->current_state;
button->current_state = gpio_read_input(button->port, button->pin);
return button->current_state;
}
bool button_pressed(button_t* button) {
return !button->current_state && button->last_state; // Active low
}
#include <stdint.h>
#include <stdbool.h>
// GPIO configuration structure
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
uint8_t mode;
uint8_t type;
uint8_t speed;
uint8_t pull;
} gpio_config_t;
// GPIO mode definitions
#define GPIO_MODE_INPUT 0x00
#define GPIO_MODE_OUTPUT 0x01
#define GPIO_MODE_ALTERNATE 0x02
#define GPIO_MODE_ANALOG 0x03
// GPIO type definitions
#define GPIO_OTYPE_PUSH_PULL 0x00
#define GPIO_OTYPE_OPEN_DRAIN 0x01
// GPIO speed definitions
#define GPIO_SPEED_LOW 0x00
#define GPIO_SPEED_MEDIUM 0x01
#define GPIO_SPEED_HIGH 0x02
#define GPIO_SPEED_VERY_HIGH 0x03
// GPIO pull definitions
#define GPIO_PULL_NONE 0x00
#define GPIO_PULL_UP 0x01
#define GPIO_PULL_DOWN 0x02
// GPIO configuration function
void gpio_configure(const gpio_config_t* config) {
GPIO_TypeDef* GPIOx = config->port;
uint16_t pin = config->pin;
// Configure mode
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (config->mode << (pin * 2));
// Configure output type (only for output mode)
if (config->mode == GPIO_MODE_OUTPUT) {
if (config->type == GPIO_OTYPE_OPEN_DRAIN) {
GPIOx->OTYPER |= (1U << pin);
} else {
GPIOx->OTYPER &= ~(1U << pin);
}
}
// Configure speed (only for output mode)
if (config->mode == GPIO_MODE_OUTPUT) {
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (config->speed << (pin * 2));
}
// Configure pull-up/pull-down
GPIOx->PUPDR &= ~(3U << (pin * 2));
GPIOx->PUPDR |= (config->pull << (pin * 2));
}
// GPIO read function
bool gpio_read(GPIO_TypeDef* GPIOx, uint16_t pin) {
return (GPIOx->IDR >> pin) & 0x01;
}
// GPIO write function
void gpio_write(GPIO_TypeDef* GPIOx, uint16_t pin, bool state) {
if (state) {
GPIOx->BSRR = (1U << pin);
} else {
GPIOx->BSRR = (1U << (pin + 16));
}
}
// GPIO toggle function
void gpio_toggle(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->ODR ^= (1U << pin);
}
// LED control example
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
bool state;
} led_t;
void led_init(led_t* led, GPIO_TypeDef* port, uint16_t pin) {
led->port = port;
led->pin = pin;
led->state = false;
gpio_config_t config = {
.port = port,
.pin = pin,
.mode = GPIO_MODE_OUTPUT,
.type = GPIO_OTYPE_PUSH_PULL,
.speed = GPIO_SPEED_MEDIUM,
.pull = GPIO_PULL_NONE
};
gpio_configure(&config);
gpio_write(port, pin, false);
}
void led_on(led_t* led) {
led->state = true;
gpio_write(led->port, led->pin, true);
}
void led_off(led_t* led) {
led->state = false;
gpio_write(led->port, led->pin, false);
}
void led_toggle(led_t* led) {
led->state = !led->state;
gpio_write(led->port, led->pin, led->state);
}
// Button interface example
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
bool last_state;
bool current_state;
} button_t;
void button_init(button_t* button, GPIO_TypeDef* port, uint16_t pin) {
button->port = port;
button->pin = pin;
button->last_state = false;
button->current_state = false;
gpio_config_t config = {
.port = port,
.pin = pin,
.mode = GPIO_MODE_INPUT,
.type = GPIO_OTYPE_PUSH_PULL,
.speed = GPIO_SPEED_LOW,
.pull = GPIO_PULL_UP
};
gpio_configure(&config);
}
bool button_read(button_t* button) {
button->last_state = button->current_state;
button->current_state = gpio_read(button->port, button->pin);
return button->current_state;
}
bool button_pressed(button_t* button) {
return !button->current_state && button->last_state; // Active low
}
// Main function
int main(void) {
// Initialize LED
led_t led;
led_init(&led, GPIOA, 5);
// Initialize button
button_t button;
button_init(&button, GPIOB, 0);
// Main loop
while (1) {
if (button_pressed(&button)) {
led_toggle(&led);
}
}
return 0;
}
Problem: Input pins without pull-up/pull-down resistors Solution: Always configure pull-up/pull-down for inputs
// β Bad: Floating input
void bad_input_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->MODER &= ~(3U << (pin * 2)); // Input mode only
// No pull-up/pull-down - floating!
}
// β
Good: Input with pull-up
void good_input_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->MODER &= ~(3U << (pin * 2)); // Input mode
GPIOx->PUPDR |= (1U << (pin * 2)); // Pull-up enabled
}
Problem: Insufficient drive strength for load Solution: Choose appropriate drive strength
// β Bad: Low drive strength for high current load
void bad_drive_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (0U << (pin * 2)); // Low speed - may not drive load
}
// β
Good: High drive strength for high current load
void good_drive_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->OSPEEDR &= ~(3U << (pin * 2));
GPIOx->OSPEEDR |= (3U << (pin * 2)); // Very high speed
}
Problem: Not configuring all necessary registers Solution: Configure all relevant registers
// β Bad: Incomplete configuration
void bad_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->MODER |= (1U << (pin * 2)); // Output mode only
// Missing type, speed, and pull configuration
}
// β
Good: Complete configuration
void good_config(GPIO_TypeDef* GPIOx, uint16_t pin) {
GPIOx->MODER &= ~(3U << (pin * 2));
GPIOx->MODER |= (1U << (pin * 2)); // Output mode
GPIOx->OTYPER &= ~(1U << pin); // Push-pull
GPIOx->OSPEEDR |= (2U << (pin * 2)); // High speed
GPIOx->PUPDR &= ~(3U << (pin * 2)); // No pull
}
Problem: Race conditions in multi-threaded applications Solution: Use atomic operations or proper synchronization
// β Bad: Race condition
void bad_write(GPIO_TypeDef* GPIOx, uint16_t pin, bool state) {
if (state) {
GPIOx->ODR |= (1U << pin); // Non-atomic read-modify-write
} else {
GPIOx->ODR &= ~(1U << pin); // Non-atomic read-modify-write
}
}
// β
Good: Atomic operation
void good_write(GPIO_TypeDef* GPIOx, uint16_t pin, bool state) {
if (state) {
GPIOx->BSRR = (1U << pin); // Atomic set
} else {
GPIOx->BSRR = (1U << (pin + 16)); // Atomic reset
}
}
Next Steps: Explore Digital I/O Programming to understand digital input/output applications, or dive into Analog I/O for analog signal processing.