japaric/f3

Question: How to handle `EXTI` interrupts

nordmoen opened this issue · 1 comments

I have started testing some interrupt handling from the built in l3gd20 and gotten it half way to work. However, I don't quite understand how to process the interrupt and stop the line from continuing to be high. I have tested with the following simple example:

//! This example uses the [Real Time For the Masses framework](https://docs.rs/cortex-m-rtfm/~0.3)
#![deny(warnings)]
#![feature(proc_macro)]
#![no_std]

extern crate cortex_m;
extern crate cortex_m_rtfm as rtfm;
extern crate f3;

use cortex_m::asm;
use f3::hal::prelude::*;
use f3::hal::spi::Spi;
use f3::hal::stm32f30x;
use f3::{L3gd20, l3gd20};
use rtfm::{app, Threshold};

app! {
    device: stm32f30x,

    resources: {
        static GYRO: L3gd20;
        static COUNT: usize = 0;
    },

    tasks: {
        EXTI1: {
            path: sensor_interrupt,
            resources: [GYRO, COUNT],
        }
    },
}

fn init(p: init::Peripherals, _: init::Resources) -> init::LateResources {
    let mut flash = p.device.FLASH.constrain();
    // Enable SYSCFG so that we can change EXTI pin configuration (see below)
    p.device.RCC.apb2enr.write(|w| w.syscfgen().enabled());
    let mut rcc = p.device.RCC.constrain();

    let clocks = rcc.cfgr.freeze(&mut flash.acr);
    let mut gpioa = p.device.GPIOA.split(&mut rcc.ahb);
    let mut gpioe = p.device.GPIOE.split(&mut rcc.ahb);

    let mut nss = gpioe
        .pe3
        .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);
    nss.set_high();

    // The `L3gd20` abstraction exposed by the `f3` crate requires a specific pin configuration to
    // be used and won't accept any configuration other than the one used here. Trying to use a
    // different pin configuration will result in a compiler error.
    let sck = gpioa.pa5.into_af5(&mut gpioa.moder, &mut gpioa.afrl);
    let miso = gpioa.pa6.into_af5(&mut gpioa.moder, &mut gpioa.afrl);
    let mosi = gpioa.pa7.into_af5(&mut gpioa.moder, &mut gpioa.afrl);

    let spi = Spi::spi1(
        p.device.SPI1,
        (sck, miso, mosi),
        l3gd20::MODE,
        1.mhz(),
        clocks,
        &mut rcc.apb2,
    );

    // Setup interrupt from sensor. The first thing to do is to change EXIT1
    // configuration to trap on pin `E`
    p.device.SYSCFG.exticr1.write(|w| unsafe {
        w.exti1().bits(0x04)
    });
    // Configure EXTI1 to enable interrupts
    //p.device.EXTI.emr1.write(|w| w.mr1().set_bit());
    p.device.EXTI.imr1.write(|w| w.mr1().set_bit());
    //p.device.EXTI.ftsr1.write(|w| w.tr1().set_bit());
    p.device.EXTI.rtsr1.write(|w| w.tr1().set_bit());

    let mut l3gd20 = L3gd20::new(spi, nss).unwrap();
    l3gd20.drdy_interrupt(true).unwrap();

    // Need to read gyro to enable interrupt?
    l3gd20.gyro().unwrap();

    init::LateResources {
        GYRO: l3gd20,
    }
}

fn idle() -> ! {
    // Sleep
    loop {
        rtfm::wfi();
    }
}

fn sensor_interrupt(_: &mut Threshold, mut r: EXTI1::Resources) {
    let status = r.GYRO.status().unwrap();
    let _g = r.GYRO.gyro().unwrap();
    if !status.new_data {
        let _c = *r.COUNT;
        asm::bkpt();
    } else {
        *r.COUNT += 1;
    }
}

With this simple addition to the l3gd20 crate:

/// Enable or disable interrupt on `DRDY`/`INT2`
pub fn drdy_interrupt(&mut self, enable: bool) -> Result<&mut Self, E> {
    let bits = if enable { 1 << 3 } else { 0 };
    let mask = 0b0000_1000;
    self.change_config(Register::CTRL_REG3, mask, bits)
}

My question is, how do I mark the interrupt as processed in the sensor_interrupt so that the function is not called continuous?

Soooo you may not care anymore, but heck, I'll put the answer down because I spent all night doing this and finally conquered it. First of all - thanks so much for this question. I got pretty far using your code as an example.

The one thing you forgot to do is set the pending register bit to 1 for your external interrupt. The documentation for my stm32f103 says:

When the selected edge occurs on the external interrupt line, an interrupt request is generated. The pending bit corresponding to the interrupt line is also set. This request is reset by writing a ‘1’ in the pending register

(my emphasis added)

If you don't set the pending register, the interrupt will get called in a loop. I did it with this code. Not sure if I'm doing it the recommended way, but it worked for me!

let exti = unsafe { &*hal::stm32f103xx::EXTI::ptr() }; // Better way to do this?
exti.pr.write(|w| w.pr10().set_bit());

This code won't work verbatim for you; you'll have to replace some registers to your application.