stm32l0xx-hal 0.10.0

Peripheral access API for STM32L0 series microcontrollers
Documentation
//! External interrupt controller (EXTI).
//!
//! For convenience, this module reexports the EXTI peripheral from the PAC.

use crate::pac::EXTI;
use crate::pwr::PowerMode;
use crate::syscfg::SYSCFG;
use crate::{gpio, pac};
use cortex_m::{interrupt, peripheral::NVIC};

/// Edges that can trigger a configurable interrupt line.
pub enum TriggerEdge {
    /// Trigger on rising edges only.
    Rising,
    /// Trigger on falling edges only.
    Falling,
    /// Trigger on both rising and falling edges.
    Both,
}

/// Higher-lever wrapper around the `EXTI` peripheral.
pub struct Exti {
    raw: EXTI,
}

impl Exti {
    /// Creates a new `Exti` wrapper from the raw `EXTI` peripheral.
    pub fn new(raw: EXTI) -> Self {
        Self { raw }
    }

    /// Destroys this `Exti` instance, returning the raw `EXTI` peripheral.
    pub fn release(self) -> EXTI {
        self.raw
    }

    /// Starts listening on a GPIO interrupt line.
    ///
    /// GPIO interrupt lines are "configurable" lines, meaning that the edges
    /// that should trigger the interrupt can be configured. However, they
    /// require more setup than ordinary "configurable" lines, which requires
    /// access to the `SYSCFG` peripheral.
    // `port` and `line` are almost always constants, so make sure they can get
    // constant-propagated by inlining the method. Saves ~600 Bytes in the
    // `lptim.rs` example.
    #[inline]
    pub fn listen_gpio(
        &mut self,
        syscfg: &mut SYSCFG,
        port: gpio::Port,
        line: GpioLine,
        edge: TriggerEdge,
    ) {
        let line = line.raw_line();

        // translate port into bit values for EXTIn registers
        let port_bm = match port {
            gpio::Port::PA => 0,
            gpio::Port::PB => 1,
            gpio::Port::PC => 2,
            gpio::Port::PD => 3,
            gpio::Port::PE => 4,
            gpio::Port::PH => {
                assert!((line < 2) | (line == 9) | (line == 10));
                5
            }
        };

        unsafe {
            match line {
                0 | 1 | 2 | 3 => {
                    syscfg.syscfg.exticr1.modify(|_, w| match line {
                        0 => w.exti0().bits(port_bm),
                        1 => w.exti1().bits(port_bm),
                        2 => w.exti2().bits(port_bm),
                        3 => w.exti3().bits(port_bm),
                        _ => w,
                    });
                }
                4 | 5 | 6 | 7 => {
                    // no need to assert that PH is not port,
                    // since line is assert on port above
                    syscfg.syscfg.exticr2.modify(|_, w| match line {
                        4 => w.exti4().bits(port_bm),
                        5 => w.exti5().bits(port_bm),
                        6 => w.exti6().bits(port_bm),
                        7 => w.exti7().bits(port_bm),
                        _ => w,
                    });
                }
                8 | 9 | 10 | 11 => {
                    syscfg.syscfg.exticr3.modify(|_, w| match line {
                        8 => w.exti8().bits(port_bm),
                        9 => w.exti9().bits(port_bm),
                        10 => w.exti10().bits(port_bm),
                        11 => w.exti11().bits(port_bm),
                        _ => w,
                    });
                }
                12 | 13 | 14 | 15 => {
                    syscfg.syscfg.exticr4.modify(|_, w| match line {
                        12 => w.exti12().bits(port_bm),
                        13 => w.exti13().bits(port_bm),
                        14 => w.exti14().bits(port_bm),
                        15 => w.exti15().bits(port_bm),
                        _ => w,
                    });
                }
                _ => (),
            };
        }

        let bm: u32 = 1 << line;

        unsafe {
            match edge {
                TriggerEdge::Rising => self.raw.rtsr.modify(|r, w| w.bits(r.bits() | bm)),
                TriggerEdge::Falling => self.raw.ftsr.modify(|r, w| w.bits(r.bits() | bm)),
                TriggerEdge::Both => {
                    self.raw.rtsr.modify(|r, w| w.bits(r.bits() | bm));
                    self.raw.ftsr.modify(|r, w| w.bits(r.bits() | bm));
                }
            }

            self.raw.imr.modify(|r, w| w.bits(r.bits() | bm));
        }
    }

    /// Starts listening on a configurable interrupt line.
    ///
    /// The edges that should trigger the interrupt can be configured with
    /// `edge`.
    #[inline]
    pub fn listen_configurable(&mut self, line: ConfigurableLine, edge: TriggerEdge) {
        let bm: u32 = 1 << line.raw_line();

        unsafe {
            match edge {
                TriggerEdge::Rising => self.raw.rtsr.modify(|r, w| w.bits(r.bits() | bm)),
                TriggerEdge::Falling => self.raw.ftsr.modify(|r, w| w.bits(r.bits() | bm)),
                TriggerEdge::Both => {
                    self.raw.rtsr.modify(|r, w| w.bits(r.bits() | bm));
                    self.raw.ftsr.modify(|r, w| w.bits(r.bits() | bm));
                }
            }

            self.raw.imr.modify(|r, w| w.bits(r.bits() | bm));
        }
    }

    /// Starts listening on a "direct" interrupt line.
    #[inline]
    pub fn listen_direct(&mut self, line: DirectLine) {
        let bm: u32 = 1 << line.raw_line();

        unsafe {
            self.raw.imr.modify(|r, w| w.bits(r.bits() | bm));
        }
    }

    /// Disables the interrupt on `line`.
    pub fn unlisten<L: ExtiLine>(&mut self, line: L) {
        let bm = 1 << line.raw_line();

        // Safety: We clear the correct bit and have unique ownership of the EXTI registers here.
        unsafe {
            self.raw.imr.modify(|r, w| w.bits(r.bits() & !bm));
            self.raw.rtsr.modify(|r, w| w.bits(r.bits() & !bm));
            self.raw.ftsr.modify(|r, w| w.bits(r.bits() & !bm));
        }
    }

    /// Marks `line` as "pending".
    ///
    /// This will cause an interrupt if the EXTI was previously configured to
    /// listen on `line`.
    ///
    /// If `line` is already pending, this does nothing.
    pub fn pend<L: ExtiLine>(line: L) {
        let line = line.raw_line();

        // Safety:
        // - We've ensured that the only 1-bit written is a valid line.
        // - This mirrors the `NVIC::pend` API and implementation, which is
        //   presumed safe.
        // - This is a "set by writing 1" register (ie. writing 0 does nothing),
        //   and this is a single write operation that cannot be interrupted.
        unsafe {
            (*EXTI::ptr()).swier.write(|w| w.bits(1 << line));
        }
    }

    /// Marks `line` as "not pending".
    ///
    /// This should be called from an interrupt handler to ensure that the
    /// interrupt doesn't continuously fire.
    pub fn unpend<L: ExtiLine>(line: L) {
        let line = line.raw_line();

        // Safety:
        // - We've ensured that the only 1-bit written is a valid line.
        // - This mirrors the `NVIC::pend` API and implementation, which is
        //   presumed safe.
        // - This is a "clear by writing 1" register, and this is a single write
        //   operation that cannot be interrupted.
        unsafe {
            (*EXTI::ptr()).pr.write(|w| w.bits(1 << line));
        }
    }

    /// Returns whether `line` is currently marked as pending.
    pub fn is_pending<L: ExtiLine>(line: L) -> bool {
        let bm: u32 = 1 << line.raw_line();

        // Safety: This is a read without side effects that cannot be
        // interrupted.
        let pr = unsafe { (*EXTI::ptr()).pr.read().bits() };

        pr & bm != 0
    }

    /// Enters a low-power mode until an interrupt occurs.
    ///
    /// Please note that this method will return after _any_ interrupt that can
    /// wake up the microcontroller from the given power mode.
    pub fn wait_for_irq<L, M>(&mut self, line: L, mut power_mode: M)
    where
        L: ExtiLine,
        M: PowerMode,
    {
        let interrupt = line.interrupt();

        // This construct allows us to wait for the interrupt without having to
        // define an interrupt handler.
        interrupt::free(|_| {
            // Safety: Interrupts are globally disabled, and we re-mask and unpend the interrupt
            // before reenabling interrupts and returning.
            unsafe {
                NVIC::unmask(interrupt);
            }

            power_mode.enter();

            Self::unpend(line);
            NVIC::unpend(interrupt);
            NVIC::mask(interrupt);
        });
    }
}

mod sealed {
    pub trait Sealed {}

    impl Sealed for super::GpioLine {}
    impl Sealed for super::ConfigurableLine {}
    impl Sealed for super::DirectLine {}
}

/// Trait implemented by all types representing EXTI interrupt lines.
pub trait ExtiLine: Sized + sealed::Sealed {
    /// Returns the line object corresponding to a raw EXTI line number.
    ///
    /// If `raw` is not a valid line for type `Self`, `None` is returned.
    fn from_raw_line(raw: u8) -> Option<Self>;

    /// Returns that raw EXTI line number corresponding to `self`.
    fn raw_line(&self) -> u8;

    /// Returns the NVIC interrupt corresponding to `self`.
    fn interrupt(&self) -> pac::Interrupt;
}

/// An EXTI interrupt line sourced by a GPIO.
///
/// All `GpioLine`s are *configurable*: They can be configured to listen for
/// rising or falling edges.
///
/// You can create a `GpioLine` by using the `ExtiLine::from_raw_line` method.
/// Lines `0..=15` are valid GPIO lines.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct GpioLine(u8);

impl ExtiLine for GpioLine {
    fn from_raw_line(line: u8) -> Option<Self> {
        match line {
            0..=15 => Some(GpioLine(line)),
            _ => None,
        }
    }

    fn raw_line(&self) -> u8 {
        self.0
    }

    fn interrupt(&self) -> pac::Interrupt {
        use pac::Interrupt::*;
        match self.0 {
            0..=1 => EXTI0_1,
            2..=3 => EXTI2_3,
            _ => EXTI4_15,
        }
    }
}

/// A configurable EXTI line that is not a GPIO-sourced line.
///
/// These lines can be configured to listen for rising edges, falling edges, or
/// both.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfigurableLine {
    Pvd = 16,
    RtcAlarm = 17,
    RtcTamper_CssLse = 19,
    RtcWakeup = 20,
    Comp1 = 21,
    Comp2 = 22,
}

impl ExtiLine for ConfigurableLine {
    fn from_raw_line(line: u8) -> Option<Self> {
        use ConfigurableLine::*;

        Some(match line {
            16 => Pvd,
            17 => RtcAlarm,
            // 18 = USB (or reserved)
            19 => RtcTamper_CssLse,
            20 => RtcWakeup,
            21 => Comp1,
            22 => Comp2,
            _ => return None,
        })
    }

    fn raw_line(&self) -> u8 {
        *self as u8
    }

    fn interrupt(&self) -> pac::Interrupt {
        use pac::Interrupt;
        use ConfigurableLine::*;

        match self {
            Pvd => Interrupt::PVD,
            RtcAlarm | RtcTamper_CssLse | RtcWakeup => Interrupt::RTC,
            Comp1 | Comp2 => Interrupt::ADC_COMP,
        }
    }
}

/// A non-configurable interrupt line.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum DirectLine {
    #[cfg(any(feature = "stm32l0x2", feature = "stm32l0x3"))]
    Usb = 18,
    I2C1 = 23,
    I2C3 = 24,
    Usart1 = 25,
    Usart2 = 26,
    // 27 = reserved
    Lpuart1 = 28,
    Lptim1 = 29,
}

impl ExtiLine for DirectLine {
    fn from_raw_line(line: u8) -> Option<Self> {
        use DirectLine::*;

        Some(match line {
            #[cfg(any(feature = "stm32l0x2", feature = "stm32l0x3"))]
            18 => Usb,
            23 => I2C1,
            24 => I2C3,
            25 => Usart1,
            26 => Usart2,
            // 27 = reserved
            28 => Lpuart1,
            29 => Lptim1,
            _ => return None,
        })
    }

    fn raw_line(&self) -> u8 {
        *self as u8
    }

    fn interrupt(&self) -> pac::Interrupt {
        use pac::Interrupt;
        use DirectLine::*;

        match self {
            #[cfg(any(feature = "stm32l0x2", feature = "stm32l0x3"))]
            Usb => Interrupt::USB,
            I2C1 => Interrupt::I2C1,
            I2C3 => Interrupt::I2C3,
            Usart1 => Interrupt::USART1,
            Usart2 => Interrupt::USART2,
            Lpuart1 => Interrupt::AES_RNG_LPUART1,
            Lptim1 => Interrupt::LPTIM1,
        }
    }
}