前言# I$ x* T q1 Z! l8 q* D
有些工程师非常的小心,小心到甚至在程序中对一个外设配置完一次还不放心,还要再配置一次。这本身看起来没有什么问题,但是在特定的外设中,反而会不小心造成一些小问题,比如这里所要说的 EXTI。
- z6 A+ A, J+ N: h
$ }) g3 s6 }$ {& M3 x3 n W: m问题
& {% f8 y8 c6 c& o, H) a某客户在其产品的设计中,使用了 STM32F302CCT6。客户在开发过程中,其所配置的 EXTI 外部中断,在外部没有中断信号的情况下,上电后运行程序,总是会进入 EXTI 中断程序一次。
) L/ `2 ~% m2 [, O9 @" R! D# h# f" s* j5 z6 h8 f+ \
调研
2 c* j: I3 e0 i# j& D2 m1.了解问题1 g+ W; O; `! y- Y
客户在开发中使用了 STM32F30x 的标准外设库 STM32F30x_DSP_StdPeriph_Lib_V1.2.3,在其程序设计中,参考了EXTI_Example 例程的代码,一开始在初始化过程中先执行了一次 EXTI15_10_Config()将 PC13 设置为外部中断口,设置为下降沿触发中断(PC13 外部有上拉电阻)。EXTI15_10_Config()函数原型如下:
: Y: U( G* T8 r( r2 Y' w# E- /**
; v& q0 I! p" H; f8 q
7 N. w& L4 _% L9 q+ [- * @brief Configure PC13 in interrupt mode
5 ? s4 A5 F, m, J$ Z# B2 R - * @param None
/ x' ^! y3 n) F; i8 | - * @retval None/ V$ c& w' h- o! w- Z
- */
' `: F$ I; R2 q3 D; y* s - static void EXTI15_10_Config(void)3 ~" g& f/ c: m: B3 n
- {8 A I# l; N, a/ N( D) W, E
- /* Enable GPIOC clock */
- y6 N* f( G( r' {1 L, Q# I - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);6 p- G& l$ p1 G9 ^) f2 a; z7 v
- 2 Z6 i9 C# y. u4 B# k
- /* Enable SYSCFG clock */
/ l2 K. `' |& k! J1 N5 H+ {7 @ - RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);- Q8 Y5 t3 G; o/ u e9 t
7 L1 U! Z) r7 L9 w3 m3 f9 E( f- /* Configure PC13 pin in input pull-down mode */
. d7 p% Z, o8 Y% z) E - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;: d2 h' D6 h7 @0 V+ C
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
2 l* a- Q' k+ P) y5 W5 k& C) Y - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;) c I) \ N7 `' a2 q8 Y& g, [
- GPIO_Init(GPIOC, &GPIO_InitStructure);1 n0 [7 R3 F, e" B$ m( L& S
# V$ p) I3 t( ~0 e4 I- /* Connect EXTI13 Line to PC13 pin */' G* R" G9 v* R$ d
- SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);( ?6 p) f( _6 n. o7 r2 j
0 C8 _6 `2 c, x2 h8 [- /* Configure EXTI13 line */
8 a/ W8 k: T# x5 F; Z6 n - EXTI_InitStructure.EXTI_Line = EXTI_Line13;
8 o# A' ?" W5 ^1 D! b, b4 C - EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
# R/ g; @, ` e& J6 X" l$ T/ ^/ ` - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;0 O9 l. w& A9 V, X$ P
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;% i. d- ^: d9 H1 a3 F. g
- EXTI_Init(&EXTI_InitStructure);
% ^1 J" X# s1 j - 5 E- P; I4 U0 b2 c b
- /* Enable and set EXTI15_10 Interrupt to the lowest priority */
; {; Z3 R9 J3 ~) X4 J - NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
/ R7 B5 z( A4 \- o - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
6 a3 t5 p7 n2 D( @, p - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
* I6 B3 a0 Y5 F - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;8 B" ~9 j5 M$ t8 D6 ~' a
- NVIC_Init(&NVIC_InitStructure);
3 w+ Q" m: e, G6 f# P - }
复制代码
! P* d5 q; y I客户在后面的程序中,在使用 EXTI 之前再调用了一次 EXTI15_10_Config()将 PC13 设置为外部中断口。. H* m" A6 g- z
调试运行的时候,发现 PC13 在没有外部触发下降沿信号的时候(经示波器确认),上电时总会进入外部中断服务程序EXTI15_10_IRQHandler 中。% T! n* `* b' J% j @) }. `0 g8 Q
( t& \! Q' ^- H$ L% [' @- @2.问题分析/ q5 l4 k3 T v* G" d6 b3 o3 X
如果仔细研究过程序,其实这个问题并不难知道。在执行第一次的 EXTI15_10_Config()之后,PC13 作为 EXTI13 外部中断已经开启。在这种情况下,如果再执行一次 EXTI15_10_Config(),我们来看看这里边究竟有什么情况?: S; O% x8 q- I
仔细查看 EXTI15_10_Config()的程序内容,注意到这一句:
& c2 T- d% A1 d# d q4 ]2 \- /* Connect EXTI13 Line to PC13 pin */
, p5 [+ s' L1 }" [ ] - SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13);
复制代码 % T D. t p. H8 D7 v; l: }$ _; _
也就是说,EXTI15_10_Config()调用了 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource13)来将 PC13配置为 EXTI13 中断源。来看一下 SYSCFG_EXTILineConfig 这个函数的原型,它存在于 stm32f30x_syscfg.c 文件中。5 ]0 ~7 y- S- l# E0 G; X
) l7 \# y1 I3 p7 w9 I- /**
& q3 {2 x4 x1 m9 ?! U2 E* N - * @brief Selects the GPIO pin used as EXTI Line.
+ y G' ~7 G+ N3 U+ B/ o - * @param EXTI_PortSourceGPIOx : selects the GPIO port to be used as source
2 w' ^7 ?$ s, v v0 W4 E/ P# ` - * for EXTI lines where x can be (A, B, C, D, E, F, G,7 s; X) P* {! R6 B: m* e
- H).
" o; k& w" H5 |- n - * @param EXTI_PinSourcex: specifies the EXTI line to be configured.
3 v! g# G j- d3 L7 Z. y - * This parameter can be EXTI_PinSourcex where x can be (0..15)# ^, q+ D9 n3 {
- * @retval None& V3 L' v6 _( w4 c/ b& W- q
- */
% Z; H+ a" N6 }$ g& z5 C/ D - void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex)
! b: Z, G) Z9 q - {
8 J( [1 |; ~5 b% C1 Q3 A - uint32_t tmp = 0x00;
7 p* z" ~0 n I) J, W4 i: W5 w - /* Check the parameters */
+ p. S7 G& e1 k/ Z( E; w - assert_param(IS_EXTI_PORT_SOURCE(EXTI_PortSourceGPIOx));
# h1 r" v" i( J - assert_param(IS_EXTI_PIN_SOURCE(EXTI_PinSourcex));. C! m/ X6 a- w4 e8 K& f; {
' f9 X/ |; Q8 X+ b1 w! G- tmp = ((uint32_t)0x0F) << (0x04 * (EXTI_PinSourcex & (uint8_t)0x03));
: u( _" `8 e8 a, w, e: g8 `% l - SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] &= ~tmp;' F! F4 n) o6 D
- SYSCFG->EXTICR[EXTI_PinSourcex >> 0x02] |= (((uint32_t)EXTI_PortSourceGPIOx) << (0x04 *
0 R4 e- Z2 ^0 e* |6 { - (EXTI_PinSourcex & (uint8_t)0x03)));
2 N" D* P/ U. j5 | - }
复制代码 SYSCFG_EXTILineConfig 这个函数的执行流程是这样的:先将 SYSCFG_EXTICR 外部中断配置寄存器中相对应的位清零,然后再写入新值。
r& s0 U- W k- p! [4 k5 b7 D3 ^: ?/ a8 v7 {
也就是说,在此 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。
) [( @7 ?1 G/ }) k) R
" |- j/ i5 r' A8 O1 D
* q9 D& L, b) H2 \) G9 ]4 k* C由此,产生中断的原因很清楚:2 h) W5 i8 z3 m6 [' Z& h
1. 第一次执行 EXTI15_10_Config(),使能了 EXTI13 的中断,中断源来自 PC13;
( [1 L. n, q0 n2. 第二次执行 EXTI15_10_Config(),在调用 SYSCFG_EXTILineConfig 配置 EXTI13 中断源时,先将中断源切回至PA13;由于失去了 PC13 引脚上的高电平,在内部产生了一个下降沿,因此触发了 EXTI13 中断(之前已被使能),进入中断服务程序执行代码;
. d4 h. l( H! b1 p f3. 从中断服务程序返回,再将 EXTI13 中断源重新配置到 PC13。: G/ j. r2 d7 j1 s; o* ~4 W
2 M9 @ _0 ~3 \9 t) Y& q
所以,误触发而进入中断服务程序的原因就是这样。$ |- ~; X4 `$ Z, F: p
) N- r) G0 F' m. C6 M" S( V
3.问题解决
$ q7 t3 n+ g0 W# w* @9 h& V问题解决很简单,EXTI13 既然已经配置好了,就不要再去重复进行配置了,没有必要,浪费效率且造成小问题。要使用和不要使用只要通过操作 EXTI_IMR 寄存器使能或禁用相应的中断就可以了。如果在特殊情况下非要重新配置的话,也要注意一下这个问题,先禁用中断。
3 x9 d' R+ i L ) Y% B& B C! g5 r+ J
结论+ i- F/ {7 b2 H! A& v: ~3 \1 i- A" m
由于 SYSCFG_EXTILineConfig()函数在配置 EXTI 中断源时,会先将中断源配置到默认中断源后再配置到实际要使用的中断源,这样重复执行 EXTI15_10_Config()就存在着误触发中断的风险,需加以注意。. \5 A$ I" K) b" g' A
3 @" _/ b) M/ ~! ^" s
处理% ^, z8 X8 Z' F
去掉重复配置的代码即可
( L3 W- W* \% x$ p q7 P- r: a" o1 @# W. Z; w/ A" |8 }8 D
/ T# a4 D% X, s& o( E |