What Is STM32 GPIO Register Access?
Accessing GPIO through BSRR and IDR instead of HAL_GPIO_WritePin/ReadPin: why, when it's worth it, and how it applies to row-by-row matrix scanning on STM32.
HAL_GPIO_WritePin() and HAL_GPIO_ReadPin() are the standard way to work with GPIO in
the STM32 HAL. They work fine for most cases. But in certain specific situations, scanning
every row and reading every column of a keyboard matrix inside a tight loop, for example,
accessing registers directly lets you write shorter code, read several pins in a single
instruction, and cut out unnecessary overhead.
This note isn’t an argument for “never use HAL.” HAL is the right call for 90% of cases. The goal here is to explain exactly how BSRR and IDR work, so you can reach for them correctly when you genuinely need to.
1. GPIOB->BSRR: setting and resetting pins in one write
BSRR (Bit Set/Reset Register) is a 32-bit register split into two halves:
Bits [15:0] - Set field: writing 1 to bit N drives pin N HIGH
Bits [31:16] - Reset field: writing 1 to bit N+16 drives pin N LOW
Writing 0 to any bit: no effect
The important property here is that BSRR is atomic. You can set some pins and reset others in the same 32-bit write, with no intermediate glitch.
/* Drive PB3 LOW (reset field: bit 3+16 = bit 19) */GPIOB->BSRR = (uint32_t)(1U << 3) << 16U;/* Drive PB3 HIGH (set field: bit 3) */GPIOB->BSRR = (uint32_t)(1U << 3);/* Drive PB3 LOW and PB5 HIGH at the same time */GPIOB->BSRR = ((uint32_t)(1U << 3) << 16U) | (uint32_t)(1U << 5);Compared to HAL:
/* HAL, one call per pin */HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET);/* Register, can handle multiple pins in one write */GPIOB->BSRR = (uint32_t)GPIO_PIN_3 << 16U;For a keyboard matrix that needs to set and reset row pins on every scan loop iteration, the speed difference isn’t huge, but the code ends up shorter and clearer about intent.
2. GPIOB->IDR: reading every pin in one read
IDR (Input Data Register) is a 16-bit register, with each bit corresponding to one GPIO
pin. A single IDR read returns the state of every pin at once.
/* Read the state of every GPIOB pin */uint32_t idr = GPIOB->IDR;/* Check a specific pin */if ((idr & GPIO_PIN_4) == 0U) { /* PB4 is LOW */}/* Check several pins at once */uint32_t col_mask = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;uint32_t col_state = idr & col_mask;Compared to HAL:
/* HAL, one call per pin, four times over */if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4) == GPIO_PIN_RESET) { ... }if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_RESET) { ... }if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == GPIO_PIN_RESET) { ... }if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET) { ... }/* Register, one read, mask the result */uint32_t idr = GPIOB->IDR;/* process idr & pin mask */For matrix scanning, which needs to read 4 column pins after activating each row, reading IDR once and masking the result is cleaner than looping over HAL calls.
3. Applying it: keyboard matrix scanning
Here’s how the STM32 USB HID Keyboard project uses BSRR + IDR to scan a 4x4 matrix on GPIOB:
#define MATRIX_GPIO GPIOB/* Precomputed pin arrays */static const uint16_t gRowPins[4] = { GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3 };static const uint16_t gColPins[4] = { GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7 };uint16_t MatrixScan_ReadRaw(void){ uint16_t state = 0U; for (uint8_t row = 0; row < 4; row++) { /* Drive the row LOW */ MATRIX_GPIO->BSRR = (uint32_t)gRowPins[row] << 16U; /* A short delay to let the signal settle */ __NOP(); __NOP(); __NOP(); __NOP(); /* Read every column at once */ for (uint8_t col = 0; col < 4; col++) { if ((MATRIX_GPIO->IDR & gColPins[col]) == 0U) { state |= (uint16_t)(1U << (row * 4 + col)); } } /* Return the row to HIGH */ MATRIX_GPIO->BSRR = (uint32_t)gRowPins[row]; } return state; /* bit N = keyLoc N is pressed */}The function returns a 16-bit word, with each bit corresponding to a keyLoc, the key’s position in the matrix. The caller never needs to know anything about GPIO. It just works with this bitmask.
4. Counting set bits, Kernighan’s bit-counting trick
Once you have the 16-bit raw state, you need to count how many keys are currently pressed to detect a simultaneous press. Kernighan’s bit-counting trick is simple and needs no lookup table:
uint8_t MatrixScan_CountPressed(uint16_t rawState){ uint8_t count = 0U; uint16_t mask = rawState; while (mask != 0U) { mask &= (uint16_t)(mask - 1U); /* clear the lowest set bit */ count++; } return count;}Each iteration clears exactly one set bit. The number of iterations equals the number of set bits. With a 16-bit word, and a keyboard matrix that only ever has a few keys pressed at once, this loop runs at most 2-3 times in practice.
5. When to use registers, when to use HAL
| Criteria | Direct register access | HAL |
|---|---|---|
| Portability | Only valid for that specific STM32 family | Easier to port to a different line |
| Handling multiple pins | One write/read covers several pins | One call per pin |
| Atomic operation | BSRR guarantees atomicity | Need to check the HAL implementation |
| Code readability | Requires understanding the register map | Function names are self-explanatory |
| Debug friendliness | Need to check the datasheet while reading code | Easier for newcomers to read |
| Best fit for | Scan loops that need speed or multi-pin access | Ordinary GPIO in init code or callbacks |
The general rule: default to HAL, and only switch to registers when you have a specific reason, with a comment explaining why.
/* * Direct register access (BSRR/IDR) instead of HAL_GPIO for matrix scan: * - All matrix pins are on GPIOB, single IDR read covers all columns * - BSRR allows atomic set/reset of row pins * - Eliminates per-pin HAL call overhead in the tight scan loop */MATRIX_GPIO->BSRR = (uint32_t)gRowPins[row] << 16U;Related posts
- What Are Keyboard Matrix Fundamentals?
- What Is a Matrix Scanning Algorithm?
- What Is a Debounce Algorithm?
- Project: STM32G0 USB Lab: Key Input Pipeline
Public references
- STM32G0B1RE Reference Manual, Chapter: General-purpose I/Os (GPIO), BSRR and IDR register description
- STM32 HAL GPIO source,
see the
HAL_GPIO_WritePinimplementation to see how HAL uses BSRR internally
Found this article useful?
Share, give feedback, or support if you find this content valuable.
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
Designing the Input Pipeline Before the USB Layer
How input flows from a keypad through scanning, debounce, key policy, and an event queue, and the tradeoffs made before an HID report ever gets built.
What Is the HID Keyboard Report Format?
The 8-byte structure of an HID keyboard report: the modifier byte, the reserved byte, and 6 keycode slots. How to send a report from STM32 middleware.
What Is a USB Descriptor Overview?
The hierarchical structure of USB descriptors: Device, Configuration, Interface, Endpoint - wTotalLength, bDescriptorType, and what commonly breaks enumeration.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.