This code encapsulates the bit manipulation of STM32F2 series GPIO registers using unions and macros. The core design utilizes the RegSet union to facilitate convenient access to 32-bit registers and individual bits, while macros simplify the register address mapping for different GPIO ports (A-I). Below is the formatted code and an analysis of the calling methods:
Formatted Code (with Comments)
#ifndef __BIT_H
#define __BIT_H
#include "stm32f2xx_hal.h"
// Register bit manipulation union: mapping 32-bit register to 16 independent bits
typedef union _RegSet {
unsigned long int Reg; // Access the 32-bit register as a whole
struct { // Bit fields: low 16 bits decomposed into independent bits (v00-v0f corresponding to bit0-bit15)
unsigned v00:1, v01:1, v02:1, v03:1, v04:1, v05:1, v06:1, v07:1,
v08:1, v09:1, v0a:1, v0b:1, v0c:1, v0d:1, v0e:1, v0f:1;
} BitIO;
/* Expandable for other bit field structures (e.g., high 16 bits) */
} RegSet;
/* GPIO register address mapping macros (X represents port number, e.g., A/B/C) */
#define P_I(X) (*(volatile RegSet *)(GPIO##X##_BASE + 0x10)) // Input Data Register (GPIOx_IDR)
#define P_O(X) (*(volatile RegSet *)(GPIO##X##_BASE + 0x14)) // Output Data Register (GPIOx_ODR)
#define P_O_H(X) (*(volatile uint16_t *)(GPIO##X##_BASE + 0x18)) // Set Register (GPIOx_BSRRL)
#define P_O_L(X) (*(volatile uint16_t *)(GPIO##X##_BASE + 0x1A)) // Reset Register (GPIOx_BSRRH)
#define P_O_V(X) (*(volatile uint32_t *)(GPIO##X##_BASE + 0x18)) // Atomic Operation Register (GPIOx_BSRR)
#endif // __BIT_H
Core Design Analysis
-
RegSet Union:
-
Provides two access methods: the Reg member allows reading and writing the register as a 32-bit whole, while the BitIO member allows individual manipulation of each bit in the low 16 bits (bit0-bit15) through bit fields (v00-v0f).
-
Applicable to the 16-bit pins of STM32 GPIO (e.g., PA0-PA15), with support for high 16 bits through an expandable bit field structure (reserved in the code as /*Other bit struct*/).
GPIO Register Macro Definitions:
-
P_I(X): Input Data Register (IDR, offset 0x10), used to read pin levels.
-
P_O(X): Output Data Register (ODR, offset 0x14), used to read/write output levels (note atomicity).
-
P_O_H/L(X): Set/Reset Register (BSRRL/BSRRH, offset 0x18/0x1A), atomically operate on a specific bit (writing 1 is effective, does not affect other bits).
-
P_O_V(X): 32-bit BSRR register (offset 0x18), sets the low 16 bits and resets the high 16 bits (recommended for atomic operations).
-
Based on the base address of the GPIO peripheral for STM32F2 (GPIOX_BASE, e.g., GPIOA_BASE), key registers are mapped through offsets:
Minimal Calling Example
Assuming GPIOA pin 0 (output) and pin 1 (input) have been configured, here are the minimal calls for common operations:
1. Read Input Pin (e.g., read PA1 level)
uint8_t pa1_level = P_I(A).BitIO.v01; // v01 corresponds to bit1 (PA1), returns 0 or 1
2. Control Output Pin (e.g., set/clear PA0)
P_O(A).BitIO.v00 = 1; // Set PA0 high via ODR (non-atomic operation, may be interrupted)
P_O(A).BitIO.v00 = 0; // Set PA0 low via ODR
// Recommended: Use BSRR register for atomic operations (no interrupt interference)
P_O_H(A) = 1 << 0; // BSRRL: Set PA0 high (only bit0=1 is effective)
P_O_L(A) = 1 << 0; // BSRRH: Set PA0 low (only bit0=1 is effective)
P_O_V(A) = (1 << (0 + 16)); // Equivalent to P_O_L(A) = 1 << 0 (high 16 bits control reset)
3. Overall Operation on Register (e.g., batch setting output values)
P_O(A).Reg = 0x000F; // Overall assignment to ODR: PA0-PA3 high, PA4-PA15 low
uint16_t port_val = P_I(A).Reg; // Read the 16-bit value of the IDR register (PA0-PA15 levels)
Notes
-
Bit Field Naming: v00-v0f correspond to bit0-bit15; it is recommended to rename according to pin functions (e.g., led_bit instead of v00) to enhance readability.
-
Atomic Operations: When modifying outputs, prioritize using P_O_H/L/V(X) (BSRR register) to avoid read/write tearing that may occur with P_O(X).Reg or P_O(X).BitIO in multi-interrupt scenarios.
-
Port Range: Supports GPIOA-GPIOI (STM32F2 series typically has 9 GPIO ports); exceeding this range will cause compilation errors (ensure GPIOX_BASE macro exists).
This encapsulation balances code simplicity with hardware operation efficiency, suitable for embedded scenarios with high GPIO operation frequency.