什么是閉包
閉包這個詞語由來已久,自上世紀 60 年代就由 Scheme 語言引進之后,被廣泛用于函數式編程語言中,進入 21 世紀后,各種現代化的編程語言也都不約而同地把閉包作為核心特性納入到語言設計中來。那么到底何為閉包?
閉包是一種匿名函數,它可以賦值給變量也可以作為參數傳遞給其它函數,不同于函數的是,它允許捕獲調用者作用域中的值,例如:
fn main() {
let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));
}
上面的代碼展示了非常簡單的閉包 sum
,它擁有一個入參 y,同時捕獲了作用域中的 x 的值,因此調用 sum(2) 意味著將 2(參數 y)跟 1(x)進行相加,最終返回它們的和:3。
可以看到 sum
非常符合閉包的定義:可以賦值給變量,允許捕獲調用者作用域中的值。
使用閉包優雅處理中斷
本篇文章將介紹如何使用閉包優雅處理中斷。Rust 使用閉包處理中斷的設計模式,目的是讓中斷處理更加及時、同時讓 Rust 中的所有權檢查能夠順利通過,也讓API的使用更加簡潔,符合人體工學設計,同時保證上層應用與底層邏輯的分離。
crate倉庫:https://crates.io/crates/py32f030_hal
代碼示例:examples/adc_block_interrupt_closure.rs
#![no_std]
#![no_main]
externcrate alloc;
use defmt::Debug2Format;
use hal::adc::{AdcChannel, AnyAdc, ChannelConfig, Config, Event, SampleCycles, TrigleSignal};
use py32f030_hal::adc::ConversionMode;
use py32f030_hal::clock::peripheral::PeripheralInterrupt;
use py32f030_hal::clock::sys_core_clock;
use py32f030_hal::{selfas hal, mode::Blocking};
use {defmt_rtt as _, panic_probe as _};
#[cortex_m_rt::entry]
fn main() -> ! {
// -------- Setup Allocator --------
const HEAP_SIZE: usize = 128;
staticmut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
#[global_allocator]
static ALLOCATOR: alloc_cortex_m::CortexMHeap = alloc_cortex_m::CortexMHeap::empty();
unsafe {
ALLOCATOR.init(
&mut HEAP as *constu8asusize,
core::mem::size_of_val(&HEAP),
)
}
let p = hal::init(Default::default());
defmt::info!("{}", sys_core_clock());
letmut adc: AnyAdc<_, Blocking> = AnyAdc::new(
p.ADC,
Config::default().sample(SampleCycles::Cycle_239_5),
ChannelConfig::default()
.over_write(false)
.wait(true) // 轉換完成后等待讀取完畢再開始轉換
.singal(TrigleSignal::Soft)
.mode(ConversionMode::Continuous),
&[AdcChannel::Channel11, AdcChannel::Channel12],
// &[AdcChannel::Channel11],
)
.unwrap();
// 使用閉包的方式在中斷中調用閉包處理函數
// 兼顧友好型 api
staticmut QUEUE: [u16; 16] = [0; 16];
adc.on_interrupt(
Event::EOC.into(), /* EOC 中斷 */
alloc::boxed::Box::new(move |adc| {
/* 中斷自動調用的閉包 */
staticmut CNT: usize = 0;
unsafe {
QUEUE[CNT] = adc;
CNT += 1;
if QUEUE.len() == CNT {
CNT = 0;
}
}
// 打印轉換成功的adc, 打印耗時會導致打印完畢后直接再次進入中斷哦,
// 導致很難看到loop里面的打印
// defmt::info!("adc: {}", adc);
}),
);
// 開啟 EOC 中斷
adc.event_config(Event::EOC, true);
adc.id().enable_interrupt();
adc.start();
loop {
cortex_m::asm::wfi();
defmt::info!(
"adc {:?} sum: {} avrage: {}",
Debug2Format(unsafe { &QUEUE }),
unsafe { QUEUE.iter().sum::() },
unsafe { QUEUE.iter().sum::() / QUEUE.len() asu16 }
);
}
}
測試代碼中使用接口來注入閉包函數,讓上下文能夠輕松地進入中斷處理函數中,中斷發生時能被調用。
pub fn on_interrupt(
&mut self,
events: EnumSet,
callback: alloc::boxed::Box,
)
那么如何實現這種設計模式呢? 在ADC 模塊的驅動中可以閱讀 src/adc/interrupt.rs
以及 src/adc/mod.rs
// src/adc/interrupt.rs
use crate::pac::interrupt;
pub(super) staticmut CLOSURE: Option<*constdynFn()> = None;
pubfn dispatch() {
unsafe {
ifletSome(func) = CLOSURE {
(*func)()
}
}
}
// ADC 中斷服務函數
#[interrupt]
fn ADC_COMP() {
// ADC1 的中斷 eoc
critical_section::with(|_cs| {
dispatch();
})
}
// src/adc/mod.rs
impl<'d, T: Instance, M: Mode> AnyAdc<'d, T, M> {
pubfn on_interrupt(
&mutself,
events: EnumSet,
callback: alloc::boxed::Box,
) {
crate::interrupt::register(
#[allow(static_mut_refs)]
unsafe {
&mut CLOSURE
},
alloc::boxed::Box::new(move || {
callback(T::data_read());
for e in events {
T::event_flag(e);
}
}),
);
for e in events {
self.event_config(e, true);
}
}
}
ADC 的中斷服務函數為:fn ADC_COMP()
, 中斷發生時,進入 fn dispatch()
函數,fn dispatch()
內部檢查存儲ADC中斷專用的閉包全局變量,當設置了 Option
的值后,即注冊了閉包,則執行閉包的邏輯。
on_interrupt
函數為 ADC 內部成員函數,負責提供給上層應用接口注入閉包邏輯。 crate::interrupt::register(...)
函數實現如下:
// src/interrupt.rs
use alloc::boxed::Box;
pubfn register(closure: &'staticmutOption<*constdynFn()>, f: Box) {
unsafe {
ifletSome(old) = *closure {
*closure = None;
let _ = alloc::boxed::Box::from_raw(old as *mutdynFn());
}
let raw = alloc::boxed::Box::into_raw(f);
*closure = Some(raw)
}
}
注冊函數將會使用 Box 申請堆空間用于存儲閉包邏輯,因此可在測試的 main 函數中看到設置堆區的初始化邏輯。要求堆的大小必須保證大于上層接口中的閉包所占用的空間大小。在注冊新的閉包時,register
內部會管理舊的閉包,即釋放舊的堆空間,讀者可以嘗試注釋let _ = alloc::boxed::Box::from_raw(old as *mut dyn Fn());
后,多次調用 on_interrupt
即可。
測試結果
運行:cargo r --example adc_block_interrupt_closure --no-default-features -F "example"
注意需要關閉默認的宏embassy
, 在 cargo r
命令后添加 --no-default-features
即可
Erasing ? [00:00:00] [#######################################################################] 16.00 KiB/16.00 KiB @ 193.12 KiB/s (eta 0s )
Programming ? [00:00:01] [#######################################################################] 13.75 KiB/13.75 KiB @ 8.18 KiB/s (eta 0s ) Finished in 1.784s
INFO 8000000
└─ adc_block_interrupt_closure::__cortex_m_rt_main::{closure#0} @ examples/adc_block_interrupt_closure.rs:35
INFO adc "[0, 1441, 2170, 1394, 2243, 1446, 2172, 1399, 2251, 1442, 2182, 1412, 2261, 0, 0, 0]" sum: 1441 avrage: 1133
└─ adc_block_interrupt_closure::__cortex_m_rt_main::{closure#0} @ examples/adc_block_interrupt_closure.rs:78
INFO adc "[2210, 1426, 2278, 1370, 2220, 1429, 2258, 1380, 2228, 1435, 2182, 1412, 2261, 1428, 2278, 1362]" sum: 26881 avrage: 1820
└─ adc_block_interrupt_closure::__cortex_m_rt_main::{closure#0} @ examples/adc_block_interrupt_closure.rs:78
INFO adc "[2268, 1456, 2180, 1402, 2256, 1408, 2202, 1418, 2274, 1367, 2203, 1419, 2278, 1363, 2212, 1430]" sum: 29056 avrage: 1819
└─ adc_block_interrupt_closure::__cortex_m_rt_main::{closure#0} @ examples/adc_block_interrupt_closure.rs:78
引用
Rust語言圣經(Rust Course):https://course.rs/advance/functional-programing/closure.html
py32f030-hal:https://github.com/hysonglet/py32f030-hal