embedded-hal引脚定义提取优化:寻求替代繁琐宏的简洁方案
问题:简化嵌入式Rust项目的引脚配置方案
我希望将项目的引脚布局配置化,把所有引脚编号定义提取到单独模块中,但又不能完全移除引脚定义——因为还要借助embedded-hal的自动引脚检查功能(比如配置SPI/定时器引脚时)。目前我用宏实现了方案,但宏定义太冗长,想找更简洁的实现方式。
当前实现代码:
硬件配置模块(hardware_config.rs)
pub struct AllGpio { pub a: hal::gpio::gpioa::Parts, pub b: hal::gpio::gpiob::Parts, pub c: hal::gpio::gpioc::Parts, } #[macro_export] macro_rules! display_rst_pin { ($gpio:ident) => { $gpio.a.pa11 }; } #[macro_export] macro_rules! display_sclk_pin { ($gpio:ident) => { $gpio.a.pa5 }; } // ... 更多宏定义
业务代码中使用
let gpio = AllGpio { a: dp.GPIOA.split(), b: dp.GPIOB.split(), c: dp.GPIOC.split(), }; let mut rst_pin = hw::display_rst_pin!(gpio).into_push_pull_output(); let mut sclk_pin = hw::display_sclk_pin!(gpio).into_alternate(); // ... 更多引脚初始化
解决方案
方法1:用关联函数封装引脚获取逻辑
把引脚定义封装成AllGpio的关联函数,既保留完整类型信息,又避免重复写宏:
// hardware_config.rs pub struct AllGpio { pub a: hal::gpio::gpioa::Parts, pub b: hal::gpio::gpiob::Parts, pub c: hal::gpio::gpioc::Parts, } impl AllGpio { // 显示屏复位引脚 pub fn display_rst(&mut self) -> &mut hal::gpio::gpioa::PA11<hal::gpio::Input> { &mut self.a.pa11 } // 显示屏SCLK引脚 pub fn display_sclk(&mut self) -> &mut hal::gpio::gpioa::PA5<hal::gpio::Input> { &mut self.a.pa5 } // ... 其他引脚的关联函数 }
使用时直接调用方法,embedded-hal的类型检查完全保留:
let mut gpio = AllGpio { a: dp.GPIOA.split(), b: dp.GPIOB.split(), c: dp.GPIOC.split(), }; let mut rst_pin = gpio.display_rst().into_push_pull_output(); let mut sclk_pin = gpio.display_sclk().into_alternate();
方法2:用自定义宏批量生成引脚访问器
如果引脚数量极多,写关联函数仍有重复,可以用一个宏批量生成所有引脚的访问函数,大幅减少冗余代码:
// hardware_config.rs pub struct AllGpio { pub a: hal::gpio::gpioa::Parts, pub b: hal::gpio::gpiob::Parts, pub c: hal::gpio::gpioc::Parts, } macro_rules! define_pin_accessors { ($($name:ident: $port:ident.$pin:ident),* $(,)?) => { impl AllGpio { $( pub fn $name(&mut self) -> &mut hal::gpio::$port::$pin<hal::gpio::Input> { &mut self.$port.$pin } )* } }; } // 批量定义所有引脚,格式:函数名: 端口.引脚名 define_pin_accessors! { display_rst: a.pa11, display_sclk: a.pa5, uart_tx: b.pb6, led_pin: c.pc13 // ... 其他引脚 }
使用方式和方法1完全一致,但只需要一次宏调用就能生成所有访问函数,维护成本更低。
方法3:类型别名+简化路径宏(适合类型敏感场景)
如果需要明确标注引脚类型,同时简化访问路径,可以结合类型别名和轻量宏:
// hardware_config.rs pub struct AllGpio { pub a: hal::gpio::gpioa::Parts, pub b: hal::gpio::gpiob::Parts, pub c: hal::gpio::gpioc::Parts, } // 类型别名,保留embedded-hal需要的完整类型信息 pub type DisplayRstPin = hal::gpio::gpioa::PA11<hal::gpio::Input>; pub type DisplaySclkPin = hal::gpio::gpioa::PA5<hal::gpio::Input>; // 轻量宏简化引脚访问路径 #[macro_export] macro_rules! display_rst { ($gpio:expr) => { &mut $gpio.a.pa11 }; } #[macro_export] macro_rules! display_sclk { ($gpio:expr) => { &mut $gpio.a.pa5 }; }
使用时:
let mut gpio = AllGpio { a: dp.GPIOA.split(), b: dp.GPIOB.split(), c: dp.GPIOC.split(), }; let mut rst_pin: DisplayRstPin = hw::display_rst!(gpio).into_push_pull_output(); let mut sclk_pin: DisplaySclkPin = hw::display_sclk!(gpio).into_alternate();
方案对比
- 关联函数方案:最直观,类型安全,适合引脚数量不多的小型项目。
- 批量宏生成方案:适合大量引脚的复杂项目,一次定义批量生成,长期维护成本最低。
- 类型别名方案:灵活性高,适合对类型控制要求严格的场景,但需要手动标注类型。
所有方案均保留embedded-hal的自动引脚检查功能,因为最终操作的都是原始引脚类型,未丢失任何类型信息。
内容的提问来源于stack exchange,提问作者Eugene
相关产品推荐
相关产品推荐

