前言1 T" G- h- V& ?7 i; T
有些工程师非常的小心,小心到甚至在程序中对一个外设配置完一次还不放心,还要再配置一次。这本身看起来没有什么问题,但是在特定的外设中,反而会不小心造成一些小问题,比如这里所要说的 EXTI。- l, Y. o5 L0 l. i$ h
6 q: C/ _, S2 t8 q" j问题
0 l& ~7 i' s; p$ \( ~; u某客户在其产品的设计中,使用了 STM32F302CCT6。客户在开发过程中,其所配置的 EXTI 外部中断,在外部没有中断信号的情况下,上电后运行程序,总是会进入 EXTI 中断程序一次。
& B: r! Z# H* X6 N/ l7 n
- ^8 F6 n" g0 Z2 l5 S0 q调研
4 `) S& ^1 W8 n# q6 P: e6 {/ F1.了解问题. |( S6 R7 H2 X& {7 L0 W) w+ @
客户在开发中使用了 STM32F30x 的标准外设库 STM32F30x_DSP_StdPeriph_Lib_V1.2.3,在其程序设计中,参考了EXTI_Example 例程的代码,一开始在初始化过程中先执行了一次 EXTI15_10_Config()将 PC13 设置为外部中断口,设置为下降沿触发中断(PC13 外部有上拉电阻)。EXTI15_10_Config()函数原型如下:8 X" E' Z" M i; G1 @5 s/ y
- /**3 O5 C0 F9 l! A/ x7 w0 ?
- . h( v. R' T+ R( S9 H# y& N
- * @brief Configure PC13 in interrupt mode! U8 o- d" T8 f2 r* s! u
- * @param None. ^: @) s% `! {# N0 R: b) V
- * @retval None' G% A9 u& N7 B( c+ N
- */
0 w# S9 x8 K D$ W8 S8 ^! d - static void EXTI15_10_Config(void)
2 v+ o* O2 b& d* i) B; H1 B5 s - {$ N/ x$ _" _9 p
- /* Enable GPIOC clock */- h$ c, e& e+ |6 W0 v4 f
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);+ O. f1 f+ O. F, o( ]" ^
& P) T X, U5 c4 ^( M$ w- /* Enable SYSCFG clock */ p3 S# S" @; O) f
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
' c4 @# z$ Y+ n" a0 d3 h/ U1 i - 9 I( m6 M) ~7 y! N! N
- /* Configure PC13 pin in input pull-down mode */7 C" |: a4 g, M
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;5 r1 f; c- F+ J x
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
7 z# K& j. N. a O- i9 X5 i - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
/ W2 ?2 T- n/ L4 U0 Z - GPIO_Init(GPIOC, &GPIO_InitStructure);) u" H, D! `, m1 g
- * |( t) z7 A9 ?3 m& {/ P
- /* Connect EXTI13 Line to PC13 pin */
* p6 S, F* w5 b: O8 i3 A - SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
+ a4 o$ I3 w" C, E w O
! C# p5 Z. C* o9 t/ @: l2 H" U6 T2 J- /* Configure EXTI13 line */
; |3 y& ~% I7 {% v) U - EXTI_InitStructure.EXTI_Line = EXTI_Line13;8 g s& K$ y* N6 U' ~+ m! z
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;2 Z% g0 m/ l: q; E! W9 m
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;- H0 F8 n h7 z9 @. `+ w8 G8 F
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;/ s, b0 o+ F3 a+ o0 O2 x) @5 B" U7 v
- EXTI_Init(&EXTI_InitStructure);: h' Z$ L+ Q' k* {" P* O+ d
- 7 O/ u) X# x9 P! ]2 F; W
- /* Enable and set EXTI15_10 Interrupt to the lowest priority */7 O2 L+ P# `# {2 U
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;/ q8 M- A! e- ^5 Y5 |
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
8 g; l6 A/ C( l' G% v' ^ - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;: N2 _% L9 b$ P9 P: J0 ?
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;! W. Q- G% ~" Z/ c/ b
- NVIC_Init(&NVIC_InitStructure);
5 E! Q5 _( ]$ J4 k. { - }
复制代码 , F1 F) g* T- s) K3 M7 Z
客户在后面的程序中,在使用 EXTI 之前再调用了一次 EXTI15_10_Config()将 PC13 设置为外部中断口。
7 m# k& x7 x) d1 x; @7 F调试运行的时候,发现 PC13 在没有外部触发下降沿信号的时候(经示波器确认),上电时总会进入外部中断服务程序EXTI15_10_IRQHandler 中。
0 b" Q9 G- A2 ]4 ^. G2 d7 d+ J& x1 Z- s9 [
2.问题分析
9 G* P; [8 I/ ~7 D! l" @如果仔细研究过程序,其实这个问题并不难知道。在执行第一次的 EXTI15_10_Config()之后,PC13 作为 EXTI13 外部中断已经开启。在这种情况下,如果再执行一次 EXTI15_10_Config(),我们来看看这里边究竟有什么情况?' `; ^ N, l! Y' @& v% ?
仔细查看 EXTI15_10_Config()的程序内容,注意到这一句:1 X# P; I% l5 J0 S4 ^- p
- /* Connect EXTI13 Line to PC13 pin */
$ X; j' w0 @+ w( G - SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
复制代码
0 z; P$ K3 |8 L8 I+ o* K( K& J也就是说,EXTI15_10_Config()调用了 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13)来将 PC13配置为 EXTI13 中断源。来看一下 SYSCFG_EXTILineConfig 这个函数的原型,它存在于 stm32f30x_syscfg.c 文件中。
, a7 V" X; |6 L/ v7 M3 p3 S$ N9 ~7 @, I8 ]
- /**
: o6 g: T' V# C5 W0 J2 F- P9 u - * @brief Selects the GPIO pin used as EXTI Line.
" y% s' g4 F& m* |2 I6 G% O9 A" W - * @param EXTI_PortSourceGPIOx : selects the GPIO port to be used as source
6 V2 n( A' b1 L. D* P - * for EXTI lines where x can be (A, B, C, D, E, F, G,$ | |& N! h# X; x
- H).5 q6 m9 [ S6 X. h5 W& W6 H
- * @param EXTI_PinSourcex: specifies the EXTI line to be configured.. }: S9 v \6 U. [- z
- * This parameter can be EXTI_PinSourcex where x can be (0..15)
. Q/ _+ }; \4 f4 P# f' _; f - * @retval None a: g/ U6 U% B
- */
. G3 t Z* z# ]# ]1 s - void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex)
; i4 v: N. B# d) ` z- x0 b( P: C - {* o8 {) }4 C0 ]3 s
- uint32_t tmp = 0x00;
6 ^3 h8 d$ u0 |! s - /* Check the parameters */
% D* {) e# x+ V* Q5 h4 a* @ - assert_param(IS_EXTI_PORT_SOURCE(EXTI_PortSourceGPIOx));
3 q8 b" z1 D6 C6 L3 h9 J: @' ?4 e0 J B - assert_param(IS_EXTI_PIN_SOURCE(EXTI_PinSourcex));% t2 G% T& ?" K
- 0 L! b! j+ c4 c% L: r
- tmp = ((uint32_t)0x0F) << (0x04 * (EXTI_PinSourcex & (uint8_t)0x03));
+ ^7 p( w: {/ T+ X6 b8 J. P - SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] &= ~tmp;
- b! T2 Z; k& ]! w# q - SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] |= (((uint32_t)EXTI_PortSourceGPIOx) << (0x04 *" R3 v* e& ?3 T' p7 Y
- (EXTI_PinSourcex & (uint8_t)0x03)));
: I% O& v/ {, Z/ A1 m( M - }
复制代码 SYSCFG_EXTILineConfig 这个函数的执行流程是这样的:先将 SYSCFG_EXTICR 外部中断配置寄存器中相对应的位清零,然后再写入新值。% L1 o. G: q: e4 k: |% C, z
; i4 @ s; T. Y; y6 V% t2 }9 `3 w8 E也就是说,在此 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。
2 e# X& S' g1 \( T4 R) q0 p! ]- j4 A& b. z# v5 V
" j4 e _$ A) v% P$ k2 f由此,产生中断的原因很清楚:4 x! [7 }+ E6 g/ g1 M
1. 第一次执行 EXTI15_10_Config(),使能了 EXTI13 的中断,中断源来自 PC13;' F! k i7 i- x7 }
2. 第二次执行 EXTI15_10_Config(),在调用 SYSCFG_EXTILineConfig 配置 EXTI13 中断源时,先将中断源切回至PA13;由于失去了 PC13 引脚上的高电平,在内部产生了一个下降沿,因此触发了 EXTI13 中断(之前已被使能),进入中断服务程序执行代码;7 I" [- _. ]: w( ]( y
3. 从中断服务程序返回,再将 EXTI13 中断源重新配置到 PC13。
" ~2 O1 `; f& ^/ R/ U$ I! Y: G+ g& l0 P: Q& s; d
所以,误触发而进入中断服务程序的原因就是这样。5 y! o/ G# v4 Q* [/ P* b
3 [$ U6 y* F# I: O1 ?9 ~7 z3.问题解决
i9 T% `. N# R3 M, o问题解决很简单,EXTI13 既然已经配置好了,就不要再去重复进行配置了,没有必要,浪费效率且造成小问题。要使用和不要使用只要通过操作 EXTI_IMR 寄存器使能或禁用相应的中断就可以了。如果在特殊情况下非要重新配置的话,也要注意一下这个问题,先禁用中断。, R! h: J; P; U& {
! ^$ }4 S% I( R0 Q6 U6 Y结论+ H# x* l6 X1 G$ [
由于 SYSCFG_EXTILineConfig()函数在配置 EXTI 中断源时,会先将中断源配置到默认中断源后再配置到实际要使用的中断源,这样重复执行 EXTI15_10_Config()就存在着误触发中断的风险,需加以注意。" m! v# M) i9 y5 d/ ]9 M' e
, ~" F' ?" n' q& p; n处理
5 S) ?4 w" L) m1 r; B0 O去掉重复配置的代码即可% q+ j% _3 ]% f. T$ D! Q7 l
0 j0 T5 r4 m. L+ j. I) a
0 b* r! J j- C
|