Friday, 25 April 2025

Automating GPIO Interrupts — From Register Math to Clean ISR Calls

Mastering GPIO interrupts on an Arm-Cortex-M MCU means touching three worlds at once:

  1. EXTI―where each pin’s edge is detected and pushed onto an interrupt line;

  2. SYSCFG―which multiplexes those lines to the right GPIO port; and

  3. NVIC―the processor-side arbiter that enables, masks and prioritises every IRQ.

Below we turn last lecture’s bare-metal notes into repeatable code patterns: decode the pin, pick the right EXTI register, unlock its IRQ in NVIC, assign a priority, and finish with a lean ISR hook.


1. Deciding the Pin’s “Interrupt Mode”

When GPIO_Init() spots a mode constant ≥ GPIO_MODE_IT_FT, it knows we’re in interrupt land.

  • For falling-edge detection we set the FTSR bit and clear its twin in RTSR.

  • For rising-edge we do the opposite.

  • For both edges we set both bits.

2. Routing the Pin through SYSCFG

Each EXTI line can belong to exactly one GPIO port. Which one? Four EXTICR registers (0-3) hold four-bit fields that encode ports A…K.

iprx = pinNumber / 4; // which EXTICR register slot = pinNumber % 4; // which 4-bit slot portCode = GPIO_BASE_TO_CODE(pGPIOx); // e.g., A→0, B→1, C→2… SYSCFG->EXTICR[iprx] &= ~(0xF << (slot*4)); // clear old mapping SYSCFG->EXTICR[iprx] |= (portCode << (slot*4));

ST’s reference manual RM0090 shows the field layout and the default mapping to port A.

3. Un-masking the Line in EXTI

Enable delivery with the Interrupt Mask Register:

EXTI->IMR |= 1U << pinNumber;

Clearing the same bit later blocks further IRQs.


4. NVIC: Enable / Disable Irqs (ISER & ICER)

NVIC subdivides 0-239 IRQs across eight Interrupt-Set-Enable registers (ISER0…7). The formula is:


reg = IRQn / 32; // 0-7 bit = IRQn % 32; // 0-31 NVIC->ISER[reg] = 1U << bit; // enable NVIC->ICER[reg] = 1U << bit; // disable

ARM’s Generic User Guide lists the full register map.


5. NVIC Priority Math (IPR Registers)

Only the upper four bits of each 8-bit priority field are implemented in STM32F4, so we shift left by (8-NUM_PR_BITS) (=4):

iprx = IRQn / 4; // select IPR register section = IRQn % 4; // select 8-bit field shift = (section * 8) + 4; // skip reserved bits NVIC->IPR[iprx] |= (prio << shift);

The same layout is documented in the ARM and ST manuals.


6. Writing a Minimal ISR

Startup code ships “weak” stubs for every handler; just re-declare the one you need and call the driver helper:

void EXTI0_IRQHandler(void) /* overrides weak stub */ { GPIO_IRQHandling(GPIO_PIN_NO_0); // clear pending bit userButtonCallback(); // your app logic }

Clearing the pending bit is as simple as

if(EXTI->PR & (1U<<pin)) EXTI->PR = 1U<<pin;

which the helper function performs.


7. Putting It All Together

  1. Enable the SYSCFG clock.

  2. Call GPIO_PCLKControl() for the target port.

  3. Fill a GPIO_Handle_t with pin, mode (GPIO_MODE_IT_FT/RT/RFT), speed, pull-ups.

  4. Invoke GPIO_Init(&button).

  5. Use GPIO_IRQConfig(EXTI0_IRQn, 1, ENABLE) followed by GPIO_IRQPriorityConfig(EXTI0_IRQn, 3) to finalise NVIC.

  6. Implement EXTI0_IRQHandler() and keep it short.

With those steps your button, sensor, or encoder line now raises an interrupt in < 100 ns instead of waiting for a polling loop.


Conclusion

Adding full interrupt support to your GPIO driver means bridging peripheral space and processor core. Configure EXTI for edge detection, map the line with SYSCFG, un-mask it, and finish inside NVIC with enable-plus-priority writes. A tiny ISR—usually just clear pending & call user logic—completes the path. Once this scaffolding is in place the same template works for every edge-triggered pin on the device, freeing your main loop for more important work.

Written By: Musaab Taha


This article was improved with the assistance of AI.

No comments:

Post a Comment