atsamd_hal/peripherals/eic.rs
1//! # External Interrupt Controller
2//!
3//! This module provides typesafe APIs for interacting with the EIC peripheral,
4//! which is used to generate interrupts based on the state of a GPIO.
5//!
6//! Each chip has a number of EXTINT channels:
7//!
8//! * SAMD11: 8 channels
9//! * SAMD21/SAMx5x: 16 channels
10//!
11//! Each channel can operate independently, and sense state changes for a single
12//! GPIO pin at a time. Refer to the datasheet for GPIO pin/EXTINT channel
13//! compatibility. In this module, an [`ExtInt`] represents an EXTINT channel
14//! which is tied to a GPIO [`Pin`], and is capable of sensing state changes.
15//!
16//! ## Steps to create an [`ExtInt`]
17//!
18//! 1. Start by creating an [`Eic`] struct, by calling [`Eic::new`]. This
19//! initializes the EIC peripheral and sets up the correct clocking.
20//!
21//! 1. Turn the [`Eic`] into a tuple of [`Channel`]s by calling [`Eic::split`].
22//! Each channel represents a single EXTINT channel.
23//!
24//! 1. Assign a pin to a channel by calling [`Channel::with_pin`]. This returns
25//! a fully configured and ready to use [`ExtInt`]. A [`Pin`] can also be
26//! directly converted into an [`ExtInt`] by calling one of the methods
27//! provided by the [`EicPin`] trait.
28//!
29//! ### Example setup
30//!
31//! ```no_run
32//! let eic_clock = clocks.eic(&gclk0).unwrap();
33//! // Initialize the EIC peripheral
34//! let eic = Eic::new(&mut peripherals.pm, eic_clock, peripherals.eic);
35//! // Split into channels
36//! let eic_channels = eic.split();
37//!
38//! // Take the pin that we want to use
39//! let button: Pin<_, PullUpInterrupt> = pins.d10.into();
40//!
41//! // Turn the EXTINT[2] channel into an ExtInt struct
42//! let mut extint = eic_channels.2.with_pin(button);
43//! ```
44//!
45//! ## `async` operation <span class="stab portability" title="Available on crate feature `async` only"><code>async</code></span>
46//!
47//! [`ExtInt`]s can be used for async operations. Configuring the [`Eic`] in
48//! async mode is relatively simple:
49//!
50//! * Bind the corresponding `EIC` interrupt source to the SPI
51//! [`InterruptHandler`] (refer to the module-level
52//! [`async_hal`](crate::async_hal) documentation for more information).
53//!
54//! * SAMD11/SAMD21: Turn an [`Eic`] into an async-enabled [`Eic`] by calling
55//! [`Eic::into_future`]. Since there is only a single interrupt handler for
56//! the EIC peripheral, all EXTINT channels must be turned into async channels
57//! at once.
58//! * SAMx5x: Turn an individuel [`ExtInt`] into an async-enabled [`ExtInt`] by
59//! calling [`ExtInt::into_future`]. Each channel has a dedicated interrupt
60//! source, therefore you must individually choose which channels to turn into
61//! async channels.
62//! * Use the provided [`wait`](ExtInt::wait) method. async-enabled [`ExtInt`]s
63//! also implement [`embedded_hal_async::digital::Wait`].
64
65use core::marker::PhantomData;
66
67use atsamd_hal_macros::{hal_cfg, hal_module};
68use seq_macro::seq;
69
70use crate::{
71 clock::EicClock,
72 gpio::{AnyPin, Pin},
73 pac,
74 typelevel::{NoneT, Sealed},
75};
76
77#[hal_module(
78 any("eic-d11", "eic-d21") => "eic/d11/mod.rs",
79 "eic-d5x" => "eic/d5x/mod.rs",
80)]
81mod impls {}
82#[cfg(feature = "async")]
83pub use impls::async_api::*;
84
85#[hal_cfg("eic-d5x")]
86use super::clock::v2::{self, gclk::GclkId, osculp32k::OscUlp32kId, pclk::Pclk, rtcosc::RtcOsc};
87
88pub type Sense = pac::eic::config::Sense0select;
89
90/// Trait representing an EXTINT channel ID.
91pub trait ChId {
92 const ID: usize;
93}
94
95/// Marker type that represents an EXTINT channel capable of doing async
96/// operations.
97#[cfg(feature = "async")]
98pub enum EicFuture {}
99
100/// Trait representing a GPIO pin which can be used as an external interrupt.
101pub trait EicPin: AnyPin + Sealed {
102 type Floating;
103 type PullUp;
104 type PullDown;
105
106 type ChId: ChId;
107
108 #[hal_cfg("eic-d5x")]
109 #[cfg(feature = "async")]
110 type InterruptSource: crate::async_hal::interrupts::InterruptSource;
111
112 /// Configure a pin as a floating external interrupt
113 fn into_floating_ei(self, chan: Channel<Self::ChId>) -> Self::Floating;
114
115 /// Configure a pin as pulled-up external interrupt
116 fn into_pull_up_ei(self, chan: Channel<Self::ChId>) -> Self::PullUp;
117
118 /// Configure a pin as pulled-down external interrupt
119 fn into_pull_down_ei(self, chan: Channel<Self::ChId>) -> Self::PullDown;
120}
121
122/// A numbered external interrupt, which can be used to sense state changes on
123/// its pin.
124pub struct ExtInt<P, Id, F = NoneT>
125where
126 P: EicPin,
127 Id: ChId,
128{
129 chan: Channel<Id, F>,
130 pin: Pin<P::Id, P::Mode>,
131}
132
133impl<P, Id, F> ExtInt<P, Id, F>
134where
135 P: EicPin,
136 Id: ChId,
137{
138 /// Release the underlying resources: [`Pin`] and [`Channel`].
139 pub fn free(self) -> (Pin<P::Id, P::Mode>, Channel<Id, F>) {
140 (self.pin, self.chan)
141 }
142
143 /// Construct pad from the appropriate pin in any mode.
144 /// You may find it more convenient to use the `into_pad` trait
145 /// and avoid referencing the pad type.
146 fn new(pin: P, chan: Channel<Id, F>) -> Self {
147 ExtInt {
148 pin: pin.into(),
149 chan,
150 }
151 }
152
153 #[cfg(all(doc, feature = "async"))]
154 #[hal_cfg(not("eic-d5x"))]
155 /// This method is not present with the selected feature set, defined for
156 /// documentation only
157 pub fn into_future(self) {
158 unimplemented!()
159 }
160}
161
162/// EIC channel.
163///
164/// Use this struct to create an [`ExtInt`] by calling
165/// [`with_pin`](Self::with_pin).
166pub struct Channel<Id: ChId, F = NoneT> {
167 eic: core::mem::ManuallyDrop<pac::Eic>,
168 _id: PhantomData<Id>,
169 _irqs: PhantomData<F>,
170}
171
172impl<Id: ChId, F> Channel<Id, F> {
173 /// Assign a pin to this [`Channel`], and turn it into an [`ExtInt`], which
174 /// is capable of sensing state changes on the pin.
175 pub fn with_pin<P: EicPin<ChId = Id>>(self, pin: P) -> ExtInt<P, Id, F> {
176 ExtInt::new(pin, self)
177 }
178
179 fn new(eic: pac::Eic) -> Self {
180 Self {
181 eic: core::mem::ManuallyDrop::new(eic),
182 _id: PhantomData,
183 _irqs: PhantomData,
184 }
185 }
186
187 #[hal_cfg("eic-d5x")]
188 #[cfg(feature = "async")]
189 fn change_mode<N>(self) -> Channel<Id, N> {
190 Channel {
191 eic: self.eic,
192 _id: self._id,
193 _irqs: PhantomData,
194 }
195 }
196}
197
198/// External Interrupt Controller.
199///
200/// Use [`split`](Self::split) to split the struct into individual channels,
201/// which can then be used to create [`ExtInt`]s, by calling
202/// [`Channel::with_pin`].
203pub struct Eic<I = NoneT> {
204 eic: pac::Eic,
205 _irqs: PhantomData<I>,
206}
207
208impl Eic {
209 /// Create a new [`Eic`] and initialize it.
210 #[hal_cfg(any("eic-d11", "eic-d21"))]
211 pub fn new(pm: &mut pac::Pm, _clock: EicClock, eic: pac::Eic) -> Self {
212 pm.apbamask().modify(|_, w| w.eic_().set_bit());
213
214 // Reset the EIC
215 eic.ctrl().modify(|_, w| w.swrst().set_bit());
216 while eic.ctrl().read().swrst().bit_is_set() {
217 core::hint::spin_loop();
218 }
219
220 eic.ctrl().modify(|_, w| w.enable().set_bit());
221 while eic.status().read().syncbusy().bit_is_set() {
222 cortex_m::asm::nop();
223 }
224 Self {
225 eic,
226 _irqs: PhantomData,
227 }
228 }
229
230 /// Create and initialize a new [`Eic`], and wire it up to the
231 /// ultra-low-power 32kHz clock source.
232 #[hal_cfg("eic-d5x")]
233 pub fn new(mclk: &mut pac::Mclk, _clock: &EicClock, eic: pac::Eic) -> Self {
234 mclk.apbamask().modify(|_, w| w.eic_().set_bit());
235
236 let mut eic = Self {
237 eic,
238 _irqs: PhantomData,
239 };
240
241 // Reset the EIC
242 eic.swreset();
243
244 // Use the low-power 32k clock and enable.
245 eic.eic.ctrla().modify(|_, w| {
246 w.cksel().set_bit();
247 w.enable().set_bit()
248 });
249
250 while eic.eic.syncbusy().read().enable().bit_is_set() {
251 core::hint::spin_loop();
252 }
253
254 eic
255 }
256
257 #[hal_cfg("eic-d5x")]
258 /// Switch the EIC to use the OSC32K clock
259 /// as a source. This enables it to run in deep-sleep
260 ///
261 /// In this mode, the maximum event frequency is limited to 16Khz
262 pub fn switch_to_osc32k(&mut self, _rtc: &RtcOsc<OscUlp32kId>) {
263 self.eic.ctrla().write(|w| w.enable().clear_bit());
264 self.sync();
265 self.eic.ctrla().write(|w| {
266 w.cksel().clk_ulp32k();
267 w.enable().set_bit()
268 });
269 self.sync();
270 }
271
272 #[hal_cfg("eic-d5x")]
273 /// Switch the EIC to use a GCLK source as its clock
274 /// source.
275 ///
276 /// In this mode, the peripheral cannot run in deep-sleep,
277 /// but its maximum event frequency is `F_GCLK/2`
278 pub fn switch_to_gclk<S: GclkId>(&mut self, _gclk: &Pclk<v2::pclk::ids::Eic, S>) {
279 self.eic.ctrla().write(|w| w.enable().clear_bit());
280 self.sync();
281 self.eic.ctrla().write(|w| {
282 w.cksel().clk_gclk();
283 w.enable().set_bit()
284 });
285 self.sync();
286 }
287
288 #[hal_cfg("eic-d5x")]
289 fn sync(&self) {
290 while self.eic.syncbusy().read().enable().bit_is_set() {
291 core::hint::spin_loop();
292 }
293 }
294
295 #[cfg(all(doc, feature = "async"))]
296 #[hal_cfg(not(any("eic-d11", "eic-d21")))]
297 /// This method is not present with the selected feature set, defined for
298 /// documentation only
299 pub fn into_future(self) {
300 unimplemented!()
301 }
302
303 /// Release the EIC and return the register block.
304 ///
305 /// **Note**: The [`Channels`] struct is consumed by this method. This means
306 /// that any [`Channel`] obtained by [`split`](Eic::split) must be
307 /// moved back into the [`Channels`] struct before being able to pass it
308 /// into [`free`](Eic::free).
309 pub fn free(mut self, _channels: Channels) -> pac::Eic {
310 self.swreset();
311
312 self.eic
313 }
314}
315
316impl<F> Eic<F> {
317 /// Reset the EIC
318 #[atsamd_hal_macros::hal_macro_helper]
319 fn swreset(&mut self) {
320 #[hal_cfg(any("eic-d11", "eic-d21"))]
321 let ctrl = self.eic.ctrl();
322
323 #[hal_cfg("eic-d5x")]
324 let ctrl = self.eic.ctrla();
325
326 ctrl.modify(|_, w| w.swrst().set_bit());
327 while ctrl.read().swrst().bit_is_set() {
328 core::hint::spin_loop();
329 }
330 }
331}
332
333#[cfg(feature = "async")]
334impl Eic<EicFuture> {
335 /// Release the EIC and return the register block.
336 ///
337 /// **Note**: The [`Channels`] struct is consumed by this method. This means
338 /// that any [`Channel`] obtained by [`split`](Eic::split) must be
339 /// moved back into the [`Channels`] struct before being able to pass it
340 /// into [`free`](Eic::free).
341 pub fn free(mut self, _channels: FutureChannels) -> pac::Eic {
342 self.swreset();
343 self.eic
344 }
345}
346
347#[hal_cfg("eic-d11")]
348macro_rules! with_num_channels {
349 ($some_macro:ident) => {
350 $some_macro! {8}
351 };
352}
353
354#[hal_cfg(any("eic-d5x", "eic-d21"))]
355macro_rules! with_num_channels {
356 ($some_macro:ident) => {
357 $some_macro! {16}
358 };
359}
360
361macro_rules! get {
362 ($literal:literal) => {
363 $literal
364 };
365}
366
367/// The number of EXTINT channels on this chip.
368pub const NUM_CHANNELS: usize = with_num_channels!(get);
369
370macro_rules! define_channels_struct {
371 ($num_channels:literal) => {
372 seq!(N in 0..$num_channels {
373 #(
374 /// Type alias for a channel number
375 pub enum Ch~N {}
376
377 impl ChId for Ch~N {
378 const ID: usize = N;
379 }
380 )*
381
382 /// Struct generating individual handles to each EXTINT channel
383 pub struct Channels(
384 #(
385 pub Channel<Ch~N>,
386 )*
387 );
388 });
389 };
390}
391
392with_num_channels!(define_channels_struct);
393
394#[cfg(feature = "async")]
395macro_rules! define_channels_struct_future {
396 ($num_channels:literal) => {
397 seq!(N in 0..$num_channels {
398 /// Struct generating individual handles to each EXTINT channel for `async` operation
399 pub struct FutureChannels(
400 #(
401 pub Channel<Ch~N, EicFuture>,
402 )*
403 );
404 });
405 };
406}
407
408#[cfg(feature = "async")]
409with_num_channels!(define_channels_struct_future);
410
411macro_rules! define_split {
412 ($num_channels:literal) => {
413 seq!(N in 0..$num_channels {
414 /// Split the EIC into individual channels.
415 #[inline]
416 pub fn split(self) -> Channels {
417 Channels(
418 #(
419 unsafe { Channel::new(core::ptr::read(&self.eic as *const _)) },
420 )*
421 )
422 }
423
424 });
425 };
426}
427
428impl Eic {
429 with_num_channels!(define_split);
430}
431
432#[cfg(feature = "async")]
433macro_rules! define_split_future {
434 ($num_channels:literal) => {
435 seq!(N in 0..$num_channels {
436 /// Split the EIC into individual channels
437 #[inline]
438 pub fn split(self) -> FutureChannels {
439 FutureChannels(
440 #(
441 unsafe { Channel::new(core::ptr::read(&self.eic as *const _)) },
442 )*
443 )
444 }
445 });
446 };
447}
448
449#[cfg(feature = "async")]
450impl Eic<EicFuture> {
451 with_num_channels!(define_split_future);
452}