STM32 编译期检查EXTI 中断函数是否正确定义

检查中断函数的目的是避免被低级错误浪费时间,比如,一开始用PA0 引脚输入中断信号,中断函数写的是EXTI0_IRQHandler,后来引脚换成PA2 了,但是中断函数忘记改。

原理

引脚一般都是用宏或者常量定义的,比如:

//宏 PA0
#define INPUT_PIN_SOURCE GPIO_PinSource0
#deine INPUT_PORT_SOURCE GPIO_PortSourceGPIOA

// 或者常量
constexpr auto INPUT_PIN_SOURCE = GPIO_PinSource0;
constexpr auto INPUT_PORT_SOURCE = GPIO_PortSourceGPIOA;

拿着定义好的常量去调用EXTI 的函数,再定义好对应的中断处理函数:

void main() {
	GPIO_EXTILineConfig(INPUT_PORT_SOURCE, INPUT_PIN_SOURCE);
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line0;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	
	// ...
}

void EXTI0_IRQHandler(void) {
	// ...
}

像一开始说的,这里至少有两个容易错的地方,一个是INPUT_PIN_SOURCE 可能会和EXTI_Line0 不匹配,用了PA2 结果还是选择了Line0,之类的,拿一个函数包起来加一个参数检查就能解决,比较简单;另一个就是中断函数的名字也可以不匹配,而且就算写错了编译也能通过,只能在运行时感觉不对劲之后才能发现这里错了。

检查中断函数的原理也很简单,如果想用line0,就检查EXTI0_IRQHandler 有没有定义,方法就是在函数内原地引用一下这个函数的名字:

void main() {
	// ...
	extern void EXTI0_IRQHandler(void);
	EXTI0_IRQHandler;
	// ...
}

这行代码实际没干任何事,开启链接时优化-flto 就会被优化掉,但如果EXTI0_IRQHandler 没定义,会链接出错,提示:

Error: L6218E: Undefined symbol EXTI0_IRQHandler() (referred from main.o).

于是你就第一时间知道你写岔了。

宏实现

这种原理只能通过宏实现,先写一个宏函数:

#define init_pin_interrupt(pin_source, exti_line)                                   \
    static_assert(pin_source == exti_line, "mismatch between pin and exti line.");   \
    _MATCH_EXTI_PIN_INTERRUPT_HANDLER_CHECKER(exti_line); \
    // ...

// 用法
init_pin_interrupt(INPUT_PIN_SOURCE, 0)

输入这两个参数,表示希望用exti 的line0,对应的引脚常量是INPUT_PIN_SOURCE。在宏内部,先检查INPUT_PIN_SOURCE 是否等于exti_line,保证两边是匹配的,然后可以把EXTI 的一部分初始化代码写在这个宏里面。

检查中断函数的功能调用了另一个宏函数来实现,原理是根据exti_line 的值,匹配具体负责引用中断函数的宏:

#define _MATCH_EXTI_PIN_INTERRUPT_HANDLER_CHECKER(exti_line) _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_##exti_line()

根据exti_line 的值,如果值是0,就会进一步调用_EXTI_PIN_INTERRUPT_HANDLER_CHECKER_0 这个宏,其他的同理:

#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_0()                \
    extern void EXTI0_IRQHandler(void);                        \
    _Pragma("clang diagnostic push")                           \
        _Pragma("clang diagnostic ignored \"-Wunused-value\"") \
            EXTI0_IRQHandler;                                  \
    _Pragma("clang diagnostic pop")

实际的检查代码放在这个宏里,就是把引用中断函数的两行代码就地插入,如果EXTI0_IRQHandler 没定义,就引起一个链接错误。上面说过,EXTI0_IRQHandler; 这行代码实际不会做任何事,所以clang 编译器会给一个警告,说表达式的值没有被使用,宏里面额外加的三行代码就是用来临时关闭编译器的这个警告,只影响这一行代码,其他地方有没用的表达式的话编译器还是会照常警告。

因为EXTI 的通道5 至9、10 至15 是放在一起的,代码上可以简化一下,比如,对这两组只定义一个宏来检查:

#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9_5()              \
    extern void EXTI9_5_IRQHandler(void);                      \
    _Pragma("clang diagnostic push")                           \
        _Pragma("clang diagnostic ignored \"-Wunused-value\"") \
            EXTI9_5_IRQHandler;                                \
    _Pragma("clang diagnostic pop")


#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()            \
    extern void EXTI15_10_IRQHandler(void);                    \
    _Pragma("clang diagnostic push")                           \
        _Pragma("clang diagnostic ignored \"-Wunused-value\"") \
            EXTI15_10_IRQHandler;                              \
    _Pragma("clang diagnostic pop")

然后再做一层映射:

#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_5()  _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9_5()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_6()  _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9_5()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_7()  _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9_5()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_8()  _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9_5()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9()  _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_9_5()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_10() _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_11() _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_12() _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_13() _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_14() _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()
#define _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15() _EXTI_PIN_INTERRUPT_HANDLER_CHECKER_15_10()

实际测试过了,原理是没问题的。

兼容性

  1. 只适用于clang 编译器,确切的说是keil 的AC6 编译器,AC5 等其他的没测试;
  2. 只适用stm32f103 单片机,因为依赖官方的固件库,原理上其他单片机也能用;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值