GPIO Interrupt: Let Me Know When You Need Me
After the successful timer interrupt experiments, I worked on GPIO interrupts this week.
For STM32 microcontrollers, we use EXTI (external interrupt controller) and do these to configure GPIO interrupts:
- Enable peripheral clocks.
- Configure an input pin and set the edge detection.
- Unmask the interrupt mask register.
Once properly configured, an interrupt request is generated when the selected edge occurs on the external interrupt line.
In this post, I will show 3 example programs that demonstrate GPIO interrupt.
- GPIO interrupt with one button
- GPIO interrupt with two buttons and how to tell which one triggers an interrupt
- Interrupt with two buttons in a different way
The goal is to control LEDs when buttons are pressed. I used Nucleo-F429ZI and stm32f4xx-hal for all three of them.
Example 1: Interrupt with One Button
Enable peripheral clocks
First, we need to turn on SYSCFG
(System Configuration) peripheral.
let mut dp = stm32::Peripherals::take().unwrap();
dp.RCC.apb2enr.write(|w| w.syscfgen().enabled());
Configure a LED and a button
I need to access my button and LED in both ISR and user contexts. So, I wrap them with Mutex
. Note that the initial values are None
here. I will replace them with initialized instances later.
static BUTTON: Mutex<RefCell<Option<PC13<Input<PullDown>>>>> = Mutex::new(RefCell::new(None));
static LED: Mutex<RefCell<Option<PB7<Output<PushPull>>>>> = Mutex::new(RefCell::new(None));
Nucleo-F429ZI’s pin pc13
is connected to the user button. After setting it up as a pull down input, I set the edge detection to RISING
.
let gpioc = dp.GPIOC.split();
let mut user_button = gpioc.pc13.into_pull_down_input();
user_button.make_interrupt_source(&mut dp.SYSCFG);
user_button.enable_interrupt(&mut dp.EXTI);
user_button.trigger_on_edge(&mut dp.EXTI, Edge::RISING);
I also set up my LED.
let gpiob = dp.GPIOB.split();
let led = gpiob.pb7.into_push_pull_output();
I now move my initialized button and LED to Mutex. Moving the shared resources to Mutex makes it possible to access the LED and the button in both ISR and user contexts. I use critical section with cortex_m::interrupt::free
and replace the shared resources’ content (None
) with user_button
and led
.
free(|cs| {
BUTTON.borrow(cs).replace(Some(user_button));
LED.borrow(cs).replace(Some(led));
});
Unmask and enable interrupt
Interrupt can be enabled by unmasking NVIC’s register.
unsafe {
stm32::NVIC::unmask(stm32::interrupt::EXTI15_10);
}
EXTI15_10
means interrupt lines from 10 - 15. Whenever an interrupt is triggered on these lines (my button pc13
is on line 13), EXTI calls this handler.
#[interrupt]
fn EXTI15_10() {
free(|cs| {
if let Some(ref mut btn) = BUTTON.borrow(cs).borrow_mut().deref_mut() {
btn.clear_interrupt_pending_bit();
if let Some(ref mut led) = LED.borrow(cs).borrow_mut().deref_mut() {
led.toggle().unwrap();
}
}
});
}
Here, I clear the interrupt flag by calling btn.clear_interrupt_pending_bit()
and toggle the LED. (If I don’t clear the flag, it keeps calling the interrupt handler.)
Example 2: Interrupt with Two Buttons
Ok, that was pretty easy. But, as I said, EXTI15_10
means interrupt requests can be generated from any of six lines. In the example above, we know it is BUTTON
that triggers an interrupt because there are no other buttons. But, what if we have two buttons and want to call different functions depending on which button is pressed? My second example handles interrupt requests from two buttons.
Configure resources
This time, I declare two sets of button/LED.
static BUTTON: Mutex<RefCell<Option<PC13<Input<PullDown>>>>> = Mutex::new(RefCell::new(None));
static LED: Mutex<RefCell<Option<PB7<Output<PushPull>>>>> = Mutex::new(RefCell::new(None));
static ANOTHER_BUTTON: Mutex<RefCell<Option<PC10<Input<PullUp>>>>> = Mutex::new(RefCell::new(None));
static ANOTHER_LED: Mutex<RefCell<Option<PB14<Output<PushPull>>>>> = Mutex::new(RefCell::new(None));
I also declare a Mutex for the external interrupt controller.
static EXTI: Mutex<RefCell<Option<stm32::EXTI>>> = Mutex::new(RefCell::new(None));
Just like the first example, I configure buttons and LEDs. I then wrap my resources with Mutex
. This time, I wrap an instance of the external interrupt controller as well. I need EXTI
to determine where an interrupt request is generated in the ISR.
let exti = dp.EXTI; // External Interrupt Controller
free(|cs| {
BUTTON.borrow(cs).replace(Some(user_button));
LED.borrow(cs).replace(Some(led));
ANOTHER_BUTTON.borrow(cs).replace(Some(another_button));
ANOTHER_LED.borrow(cs).replace(Some(another_led));
EXTI.borrow(cs).replace(Some(exti));
});
Find out where the interrupt comes from
I now have two buttons: one on line 10 and the other on line 13. I want to find out which button is pressed and call a corresponding callback function. Here are my two callback functions.
fn user_button_cb() {
free(|cs| {
if let Some(ref mut btn) = BUTTON.borrow(cs).borrow_mut().deref_mut() {
// Clear the interrupt flag
btn.clear_interrupt_pending_bit();
if let Some(ref mut led) = LED.borrow(cs).borrow_mut().deref_mut() {
led.toggle().unwrap();
}
}
});
}
fn another_button_cb() {
free(|cs| {
if let Some(ref mut btn) = ANOTHER_BUTTON.borrow(cs).borrow_mut().deref_mut() {
// Clear the interrupt flag
btn.clear_interrupt_pending_bit();
if let Some(ref mut led) = ANOTHER_LED.borrow(cs).borrow_mut().deref_mut() {
led.toggle().unwrap();
}
}
});
}
And this is how my interrupt handler tells which button is pressed.
#[interrupt]
fn EXTI15_10() {
free(|cs| {
if let Some(ref mut exti) = EXTI.borrow(cs).borrow_mut().deref_mut() {
let pr = exti.pr.read();
// Interrupt on line 13?
if pr.pr13().bit_is_set() {
user_button_cb();
}
// Interrupt on line 10?
if pr.pr10().bit_is_set() {
another_button_cb();
}
}
});
}
When an interrupt is triggered, the pending bit corresponding to the interrupt line is set. In the interrupt handler, I evaluate the external interrupt controller’s PR register and call a corresponding callback.
Example 3: Another Way of Two Buttons
Example 2 works fine. But, if I touch EXTI
’s PR register, I can easily clear interrupt flags by directly writing to it. As we see in example 2, when an interrupt is triggered, the pending bit corresponding to the interrupt line is set. This request can be reset by writing a 1
in the pending register. Actually, stm32f4xx-hal
’s clear_interrupt_pending_bit()
method does exactly that:
fn clear_interrupt_pending_bit(&mut self) {
unsafe { (*EXTI::ptr()).pr.write(|w| w.bits(1 << self.i) ) };
}
I am accessing EXTI anyway. Why don’t I clear the flag by directly writing to the PR register?
#[interrupt]
fn EXTI15_10() {
free(|cs| {
if let Some(ref mut exti) = EXTI.borrow(cs).borrow_mut().deref_mut() {
let pr = exti.pr.read();
// Interrupt on line 13?
if pr.pr13().bit_is_set() {
// Clear the interrupt flag
exti.pr.write(|w| w.pr13().set_bit());
user_button_cb();
}
// Interrupt on line 10?
if pr.pr10().bit_is_set() {
// Clear the interrupt flag
exti.pr.write(|w| w.pr10().set_bit());
another_button_cb();
}
}
});
}
Since exti.pr.write(|w| w.pr13().set_bit());
clears the interrupt flag, I no longer need to call my button’s clear_interrupt_pending_bit()
in my callbacks.
fn user_button_cb() {
free(|cs| {
if let Some(ref mut led) = LED.borrow(cs).borrow_mut().deref_mut() {
led.toggle().unwrap();
}
});
}
fn another_button_cb() {
free(|cs| {
if let Some(ref mut led) = ANOTHER_LED.borrow(cs).borrow_mut().deref_mut() {
led.toggle().unwrap();
}
});
}
If I do it this way, I don’t even need Mutex for my buttons anymore. Once I configure the buttons and enable the interrupt, they generate interrupt requests and I can access them through EXTI. I don’t need my buttons in the interrupt handler. So, my shared resources are now just LEDs and EXTI.
static LED: Mutex<RefCell<Option<PB7<Output<PushPull>>>>> = Mutex::new(RefCell::new(None));
static ANOTHER_LED: Mutex<RefCell<Option<PB14<Output<PushPull>>>>> = Mutex::new(RefCell::new(None));
static EXTI: Mutex<RefCell<Option<stm32::EXTI>>> = Mutex::new(RefCell::new(None));