前言
- x9 t( F9 m! `3 h8 p' p有些工程师非常的小心,小心到甚至在程序中对一个外设配置完一次还不放心,还要再配置一次。这本身看起来没有什么问题,但是在特定的外设中,反而会不小心造成一些小问题,比如这里所要说的 EXTI。( U! g7 |1 w* y- {5 z) P5 }* c+ l
" u: ~5 A; y2 S1 Z7 T' k) d9 ?0 F问题
0 I0 I! b }9 P某客户在其产品的设计中,使用了 STM32F302CCT6。客户在开发过程中,其所配置的 EXTI 外部中断,在外部没有中断信号的情况下,上电后运行程序,总是会进入 EXTI 中断程序一次。4 Y1 p: u% }/ l3 d+ L. E$ f7 M
$ R1 a" ^" p. V调研: v2 D) i/ f1 A+ a: J
1.了解问题1 ]. e3 [! Q/ _ i
客户在开发中使用了 STM32F30x 的标准外设库 STM32F30x_DSP_StdPeriph_Lib_V1.2.3,在其程序设计中,参考了EXTI_Example 例程的代码,一开始在初始化过程中先执行了一次 EXTI15_10_Config()将 PC13 设置为外部中断口,设置为下降沿触发中断(PC13 外部有上拉电阻)。EXTI15_10_Config()函数原型如下:
+ U% _7 y5 c& n- /**
/ G$ U1 l* i( a - ; ~/ f9 r2 {0 s% J% {( \3 D0 s
- * @brief Configure PC13 in interrupt mode j- l# @& _$ C& f
- * @param None
2 \8 q# @( D( s - * @retval None
# v z( n) g# u. n - */+ C; k4 w! a9 p4 q
- static void EXTI15_10_Config(void)
! }8 `% Q1 j% W4 [" i, {1 }9 W - {+ Y7 K3 X4 b& d
- /* Enable GPIOC clock */& d& Z8 {0 ]: c$ O7 ^3 c
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);: M c- h" Q2 X+ d/ L9 I! v$ C
* t/ e' N, n. c- /* Enable SYSCFG clock */. w0 m! q3 o1 W: S
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);2 y# p4 b# `* i1 h* h# y* F
) t) Y! w) D, z2 @) E( G- /* Configure PC13 pin in input pull-down mode */
3 z0 G( J9 x) P/ j8 S: }7 C - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
7 S+ ]8 `' y e - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
% F1 [5 J$ T0 c* ]2 | - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
$ N& J) P: X. u2 e& W - GPIO_Init(GPIOC, &GPIO_InitStructure);
8 H5 {' Y1 }, U5 Z" V
% U" f/ }" R9 y' L1 S: o& q- /* Connect EXTI13 Line to PC13 pin */$ c/ P; z" u1 G- E& R% j4 h
- SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
# d3 a* P, A. V9 g7 ` - 1 W# r( D, F l7 t, D. _
- /* Configure EXTI13 line */
; a& l- Y0 D5 M - EXTI_InitStructure.EXTI_Line = EXTI_Line13;+ W* o! t; N5 z" R, @- z% a/ s
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
, e' z1 z9 h& ]( V- e8 O - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
' @: @0 W# \( G+ V% z) A: ~ - EXTI_InitStructure.EXTI_LineCmd = ENABLE;
. k0 M& A: T5 |0 o - EXTI_Init(&EXTI_InitStructure);8 f7 t* W6 Q- M3 d
- ( {& R/ S. ^, G0 _
- /* Enable and set EXTI15_10 Interrupt to the lowest priority */3 o y6 {+ Y% l7 ?
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;- r; ]7 Z; n+ m! p5 s
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
- R f! R. j! {; v, b - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
3 h, _) y. Y! w% U) I - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
! @- R) V# y5 f! b X. w% A# u - NVIC_Init(&NVIC_InitStructure);) u7 e0 Q" F. t! q7 b \& b
- }
复制代码 ; F. r8 x, ` t0 x8 ]
客户在后面的程序中,在使用 EXTI 之前再调用了一次 EXTI15_10_Config()将 PC13 设置为外部中断口。
6 t: R1 l1 Q* a9 w* C; l调试运行的时候,发现 PC13 在没有外部触发下降沿信号的时候(经示波器确认),上电时总会进入外部中断服务程序EXTI15_10_IRQHandler 中。
Q$ |+ x4 x/ A" z
9 v+ P1 j0 P! i8 v# p, Z% ^- ]+ C- X0 D2.问题分析& T* X% Z4 g4 T$ I
如果仔细研究过程序,其实这个问题并不难知道。在执行第一次的 EXTI15_10_Config()之后,PC13 作为 EXTI13 外部中断已经开启。在这种情况下,如果再执行一次 EXTI15_10_Config(),我们来看看这里边究竟有什么情况?
# C9 I) u+ J5 \2 v, M! l0 f3 ~仔细查看 EXTI15_10_Config()的程序内容,注意到这一句:
1 l" U' z0 X$ d) m% B- /* Connect EXTI13 Line to PC13 pin */2 b, d& S$ t# L' [! \
- SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
复制代码
2 }. r5 y. ]5 I$ s* C o9 Y% b" T也就是说,EXTI15_10_Config()调用了 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13)来将 PC13配置为 EXTI13 中断源。来看一下 SYSCFG_EXTILineConfig 这个函数的原型,它存在于 stm32f30x_syscfg.c 文件中。
3 N% u) S5 e* S H/ f! |- b! |" H
1 }. Y# i+ o& J) R) s" s* H- /**
3 t! C* r, `1 j/ Y; H W - * @brief Selects the GPIO pin used as EXTI Line./ @5 s4 \6 m% A
- * @param EXTI_PortSourceGPIOx : selects the GPIO port to be used as source6 V/ g/ v' ?+ }( G# b3 X, O4 d
- * for EXTI lines where x can be (A, B, C, D, E, F, G,) c8 ]; M- U( R
- H).! l0 I. d& q# S# o
- * @param EXTI_PinSourcex: specifies the EXTI line to be configured.
6 a: @3 r- d& N5 a- H/ u+ y1 }+ u% L - * This parameter can be EXTI_PinSourcex where x can be (0..15), [! _: I6 g. {/ Y
- * @retval None
$ P5 y' A; C. P& ^ W+ S, w - */! H# t& X% j4 d
- void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex)! T8 M- M4 P0 y7 d$ a9 ?6 G/ _
- {1 a) y, ?% W% d/ A/ P4 e$ Y" d
- uint32_t tmp = 0x00;2 i4 m; K/ W! N8 K
- /* Check the parameters */
+ F* u. W! N% T* G3 k/ T& V7 @9 n, P - assert_param(IS_EXTI_PORT_SOURCE(EXTI_PortSourceGPIOx));/ z$ q2 G* w9 c7 D' G3 @
- assert_param(IS_EXTI_PIN_SOURCE(EXTI_PinSourcex));
# q8 ~& \9 P3 F T- X - + j5 s! U2 {" U& f
- tmp = ((uint32_t)0x0F) << (0x04 * (EXTI_PinSourcex & (uint8_t)0x03));
3 T3 X, _& m$ F/ l9 @ - SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] &= ~tmp;
* k3 J! o( a0 ], n7 u - SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] |= (((uint32_t)EXTI_PortSourceGPIOx) << (0x04 *' F) R0 s# i7 a P& H5 s+ G
- (EXTI_PinSourcex & (uint8_t)0x03)));
* f$ a/ a+ M5 D, e; D - }
复制代码 SYSCFG_EXTILineConfig 这个函数的执行流程是这样的:先将 SYSCFG_EXTICR 外部中断配置寄存器中相对应的位清零,然后再写入新值。
6 D* }4 B5 G+ \! S, |
6 ]" {1 i8 \5 i) r6 a) }( ?7 b也就是说,在此 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。: w+ K- @8 }6 \0 g' H
2 S6 Y( x& @, A8 e8 x! o" h# d1 w& }; B$ ^4 j
由此,产生中断的原因很清楚:3 t) Z- {* N0 D" X, r
1. 第一次执行 EXTI15_10_Config(),使能了 EXTI13 的中断,中断源来自 PC13;
: t$ `8 J B* e' L( }% V2. 第二次执行 EXTI15_10_Config(),在调用 SYSCFG_EXTILineConfig 配置 EXTI13 中断源时,先将中断源切回至PA13;由于失去了 PC13 引脚上的高电平,在内部产生了一个下降沿,因此触发了 EXTI13 中断(之前已被使能),进入中断服务程序执行代码;2 J" q+ G3 |3 A* j& b+ {$ {) V8 a
3. 从中断服务程序返回,再将 EXTI13 中断源重新配置到 PC13。1 c0 B. E1 p9 K! B$ N7 o5 s
, X7 T- f4 p/ E/ j4 E" L+ j) s
所以,误触发而进入中断服务程序的原因就是这样。
+ g5 n2 A7 d; |$ _
+ x( A ^- K, W8 X+ u3 [1 L3.问题解决. L2 i3 z( x+ ?0 W3 m. K) Y
问题解决很简单,EXTI13 既然已经配置好了,就不要再去重复进行配置了,没有必要,浪费效率且造成小问题。要使用和不要使用只要通过操作 EXTI_IMR 寄存器使能或禁用相应的中断就可以了。如果在特殊情况下非要重新配置的话,也要注意一下这个问题,先禁用中断。
: q: r1 M& h8 r
) r% ]/ r" V9 E- p结论2 b2 M5 [, z% b4 C, E# U
由于 SYSCFG_EXTILineConfig()函数在配置 EXTI 中断源时,会先将中断源配置到默认中断源后再配置到实际要使用的中断源,这样重复执行 EXTI15_10_Config()就存在着误触发中断的风险,需加以注意。
6 w1 O& t, ~8 n$ [% Q' y 6 h& ~3 O( u3 d
处理
0 X' O! u9 e2 _3 H. S去掉重复配置的代码即可
+ D4 Y. D2 o$ Y+ I6 X
* V, ^' Q" b% m3 i' x; l& q4 f2 d |+ `# \3 B- p. Y0 t
|