前言
J/ h" Y* K6 A9 l& F1 l2 b( F6 V有些工程师非常的小心,小心到甚至在程序中对一个外设配置完一次还不放心,还要再配置一次。这本身看起来没有什么问题,但是在特定的外设中,反而会不小心造成一些小问题,比如这里所要说的 EXTI。- P0 t( y4 E0 V- [( H4 T8 U$ M
9 d$ i; ~: K6 x6 G9 E问题3 o, d, f' X! t) x6 f
某客户在其产品的设计中,使用了 STM32F302CCT6。客户在开发过程中,其所配置的 EXTI 外部中断,在外部没有中断信号的情况下,上电后运行程序,总是会进入 EXTI 中断程序一次。
8 @$ `/ b0 r2 g* y& K; J
9 W. x& z5 `2 ~5 M2 O x调研0 Q: H$ A( Y \+ k% {* [& A
1.了解问题 W5 { h' X1 z# [' Z7 ?
客户在开发中使用了 STM32F30x 的标准外设库 STM32F30x_DSP_StdPeriph_Lib_V1.2.3,在其程序设计中,参考了EXTI_Example 例程的代码,一开始在初始化过程中先执行了一次 EXTI15_10_Config()将 PC13 设置为外部中断口,设置为下降沿触发中断(PC13 外部有上拉电阻)。EXTI15_10_Config()函数原型如下:
" C/ C! ~4 z5 G! s) @- /**
' | A1 ^3 e: A- L }& c - ' n- I" c N3 N9 R: }; x. t
- * @brief Configure PC13 in interrupt mode; F! c4 e( e/ ^" F
- * @param None" R. ^ a: |/ c) U C( o( z' B
- * @retval None
! u; v/ r. Z/ x - */; v% `/ y9 t0 d; y* k, B4 n1 U
- static void EXTI15_10_Config(void)
3 e( M! y1 j- D2 g$ ^: l$ M - {$ t* t& n0 j* A$ E# x+ w S
- /* Enable GPIOC clock */
8 ]7 ?8 G6 ?' D& g6 m0 { - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);+ l) R* d& ?3 B5 q( g
- " s* e( B/ ]4 I) Z0 z$ k. L
- /* Enable SYSCFG clock */
& e& O' M% L6 u - RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);! [' ^% E. i) i' b5 V9 H0 C2 I
- % h, [3 c- o) f/ }
- /* Configure PC13 pin in input pull-down mode */
% U4 c% v$ B) t0 u - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
5 }* z9 x7 c4 @5 t9 k5 W - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;$ ?( \, i0 ?9 J0 B% ^
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
. z t, f/ G7 w, e4 k6 P3 s - GPIO_Init(GPIOC, &GPIO_InitStructure);3 _+ G$ F' Z9 a/ m' n8 d
* _6 B! P( q# M# ~+ V! v- /* Connect EXTI13 Line to PC13 pin */
' N9 M4 O" _! |3 [ - SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13); ]" L: W! _- K* H, i8 ~
- * E* c! c: W! _$ ` o
- /* Configure EXTI13 line */
. T" o; T- H' N/ R9 _- a! }5 T - EXTI_InitStructure.EXTI_Line = EXTI_Line13;* R. n, V' N3 ]+ S
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/ E+ P8 w9 Q, d% V! F E1 s4 U - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;9 L" _: m @( [
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
' L0 T; ]3 a# c$ I! p9 |! Q - EXTI_Init(&EXTI_InitStructure);% M$ ?. l7 s8 H: V a
" s4 P) d5 N; m, F* J r- /* Enable and set EXTI15_10 Interrupt to the lowest priority */
1 h2 Q8 p* e; ?% h" R( x) `( y2 ^ - NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;0 @2 r( J; M( R# Z
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
3 R, |& i7 b, }+ {! e i, N - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
5 B" m; e1 D8 u* F& \. j - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
S# H4 [7 F1 B* a - NVIC_Init(&NVIC_InitStructure);
# C. L- O7 C! w! W - }
复制代码 - I) W' ~/ F$ I1 S0 L
客户在后面的程序中,在使用 EXTI 之前再调用了一次 EXTI15_10_Config()将 PC13 设置为外部中断口。" E& S* i v* R W# P) Z
调试运行的时候,发现 PC13 在没有外部触发下降沿信号的时候(经示波器确认),上电时总会进入外部中断服务程序EXTI15_10_IRQHandler 中。
! I3 u8 |9 ^1 @9 z4 J2 K
8 P2 S8 V! f& Y2.问题分析% y4 a, Y: T, n+ V0 H5 f
如果仔细研究过程序,其实这个问题并不难知道。在执行第一次的 EXTI15_10_Config()之后,PC13 作为 EXTI13 外部中断已经开启。在这种情况下,如果再执行一次 EXTI15_10_Config(),我们来看看这里边究竟有什么情况?( M- U& k1 w; {3 G }& v
仔细查看 EXTI15_10_Config()的程序内容,注意到这一句:
5 D- D1 B1 H; I8 g+ j- /* Connect EXTI13 Line to PC13 pin */
6 @% |% ~0 ?- [8 | - SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
复制代码
* S# @/ Q" q! U5 E也就是说,EXTI15_10_Config()调用了 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13)来将 PC13配置为 EXTI13 中断源。来看一下 SYSCFG_EXTILineConfig 这个函数的原型,它存在于 stm32f30x_syscfg.c 文件中。& p* I4 o) D" d0 O% h+ D) F1 U9 ]6 u4 ?
& q, Z* x/ [0 n# u6 B# m m
- /**
! ?3 [ n" D- J+ _' ? - * @brief Selects the GPIO pin used as EXTI Line.
Q* x& \2 ?* c# v g5 ] - * @param EXTI_PortSourceGPIOx : selects the GPIO port to be used as source
) A/ v. F" X- h0 D4 h3 i* d5 L5 c" x - * for EXTI lines where x can be (A, B, C, D, E, F, G,
0 B' G1 c5 A4 I. c - H).: L, x" K& `! r0 R$ t
- * @param EXTI_PinSourcex: specifies the EXTI line to be configured.
7 G( R8 E# }: ` - * This parameter can be EXTI_PinSourcex where x can be (0..15)
& T$ |& \8 o3 D1 |. [ - * @retval None
. u H, v: t) O+ f, b - */8 [' t2 c' y2 n8 {. S
- void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex)3 C- z) Z- O1 z0 p/ m& f% t+ W
- {
+ i0 k6 X' O6 Q5 T/ m+ G - uint32_t tmp = 0x00;" p+ Z5 J1 q Z
- /* Check the parameters */ Y2 O. E# g% l- J n1 W" w# D8 p
- assert_param(IS_EXTI_PORT_SOURCE(EXTI_PortSourceGPIOx));
$ F9 d1 R8 n* k( Y1 E - assert_param(IS_EXTI_PIN_SOURCE(EXTI_PinSourcex));
4 h p; `3 z, l4 k
0 F2 G9 N0 ?7 T" J8 q3 _- tmp = ((uint32_t)0x0F) << (0x04 * (EXTI_PinSourcex & (uint8_t)0x03));
1 }5 f4 K# U( L3 C9 `* ] - SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] &= ~tmp;6 `# L( s0 R: g6 K" o& h Z' O8 f
- SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] |= (((uint32_t)EXTI_PortSourceGPIOx) << (0x04 *; k& d. f8 s' L4 c! @9 H; ?
- (EXTI_PinSourcex & (uint8_t)0x03)));6 e: p2 V) M: @6 p
- }
复制代码 SYSCFG_EXTILineConfig 这个函数的执行流程是这样的:先将 SYSCFG_EXTICR 外部中断配置寄存器中相对应的位清零,然后再写入新值。
8 j% K/ {1 u4 e. W' S; b) P: O4 P( k$ n+ j5 u5 x7 N
也就是说,在此 PC13 作为 EXTI13 中断的例子中,当执行“SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] &= ~tmp;”时,SYSCFG_EXTICR4 的 EXTI13[3:0]清零,将 EXTI13 外部中断的输入源设置为 PA13(默认);再执行“SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] |= (((uint32_t)EXTI_PortSourceGPIOx) << (0x04 * (EXTI_PinSourcex &(uint8_t)0x03)));”时,SYSCFG_EXTICR4 的 EXTI13[3:0]写入值 0x02,将 EXTI13 外部中断的输入源选择为 PC13。
* `3 ~, ~/ {, h% t* |# b1 |" A- g
+ J0 ?1 ?) P6 C; c由此,产生中断的原因很清楚:
. ^" O0 } V$ G5 I/ l, }1. 第一次执行 EXTI15_10_Config(),使能了 EXTI13 的中断,中断源来自 PC13;
$ p, _+ O* m+ ?3 C2. 第二次执行 EXTI15_10_Config(),在调用 SYSCFG_EXTILineConfig 配置 EXTI13 中断源时,先将中断源切回至PA13;由于失去了 PC13 引脚上的高电平,在内部产生了一个下降沿,因此触发了 EXTI13 中断(之前已被使能),进入中断服务程序执行代码;
2 G, J9 n" s" t4 [3. 从中断服务程序返回,再将 EXTI13 中断源重新配置到 PC13。2 T, w7 ?( l! c% ~7 U+ x) m& e7 Y
1 [7 L- q$ w! e# J所以,误触发而进入中断服务程序的原因就是这样。* L- Q$ Y+ x2 F+ z, T* H4 d
3 d9 C0 b E9 ]: y0 V" N: j& ]3.问题解决4 l" S. A% M' D. e: W
问题解决很简单,EXTI13 既然已经配置好了,就不要再去重复进行配置了,没有必要,浪费效率且造成小问题。要使用和不要使用只要通过操作 EXTI_IMR 寄存器使能或禁用相应的中断就可以了。如果在特殊情况下非要重新配置的话,也要注意一下这个问题,先禁用中断。
1 x$ b% l8 P0 r! q1 Y 3 B- G4 ]2 I: I
结论
/ C! S. G5 {( ?+ }: ?由于 SYSCFG_EXTILineConfig()函数在配置 EXTI 中断源时,会先将中断源配置到默认中断源后再配置到实际要使用的中断源,这样重复执行 EXTI15_10_Config()就存在着误触发中断的风险,需加以注意。3 u0 t7 ?! j' j0 m- b8 i
- W7 Y8 B- k) `) V4 D) B7 _
处理
/ u3 @8 g( P9 Q) v# K& x4 X去掉重复配置的代码即可
0 n9 J) N" B. r4 y( q+ m F8 K* x
7 G/ |) F$ P4 ?7 K2 P {! Z: e/ }5 D6 @5 R- |1 \! K8 P
|