如何配置GPIO的外部中断
$ ?2 S# C3 B! i8 i6 B) M/ t* c以STM32F103为例,记录配置GPIO外部中断的一般方法与流程。/ B6 ?2 C6 P# R6 ~6 B6 j
! V i( p. F+ H) j" w7 U \
配置RCC时钟! |/ o9 b7 I, I$ u
RCC时钟配置是STM32MCU顺利运行的必备步骤,笔者使用的参考代码如下:2 K" ]9 M( k; t
3 M$ M& y z( e: k- void RCC_Config(): v: |4 D7 t8 P# l8 }4 `+ b
- {4 u4 _1 K- V: y0 u7 p2 `- T7 v
- RCC_DeInit();
4 D, c) O% q, T - //使能HSE,并等待 HSE 稳定
" G) E R# h' k - RCC_HSEConfig(RCC_HSE_ON);
: r4 V) u0 L% Z) S* T - ErrorStatus HSEStartUpStatus;9 v% i s! z$ R0 r+ E
- HSEStartUpStatus = RCC_WaitForHSEStartUp();
/ G( h( d1 r1 q - //HSE启动成功1 e" D- L* L* S6 D, s, m7 s
- if (HSEStartUpStatus == SUCCESS)
$ k( y: r' `0 I2 L) r( s+ V: b" { - {
: r$ T2 X- A- P - //使能FLASH预存取缓冲区
' Q8 g P4 b( B - FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);: c" W; c, c* w- u4 d
- //SYSCLK周期与闪存访问时间的比例设置
% O, q$ c3 H: V/ K' p0 n8 Y - FLASH_SetLatency(FLASH_Latency_2);
! ~, |- M7 o8 x$ d2 G0 F% R - //Configures the AHB clock (HCLK): AHB clock = SYSCLK
1 ]% {# l% F% | - RCC_HCLKConfig(RCC_SYSCLK_Div1);1 {0 N/ Z. n& F9 z f& @
- //Configures the High Speed APB clock (PCLK2): APB2 clock = HCLK
" H$ m/ b3 O" J$ f - RCC_PCLK2Config(RCC_HCLK_Div1);
5 z* E, H9 w* D5 s, P# y - //Configures the High Speed APB clock (PCLK2): APB1 clock = HCLK/2 t* N" X% L! p8 T) t1 J+ f
- RCC_PCLK1Config(RCC_HCLK_Div2);
! ?0 X% n+ ?/ ], e1 H: E - //Configures the PLL clock source and multiplication factor: 5 _* E c, C, u) R1 x/ U# ]
- //HSE oscillator clock selected as PLL clock entry
5 T5 _ M1 B1 h; z- v% @ - //The PLL multiplication factor is 95 I( ^8 `7 x. |+ Q) x# ~4 v
- RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);& y. u8 W" s/ W3 |! p
- //Enables the PLL.
% p' n3 {5 ^& J" d F h( l - RCC_PLLCmd(ENABLE);
$ P9 L: _+ H! I" m0 [# q - //等待PLL时钟稳定7 L( I4 b. {# ?0 ?! G, O
- while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
: | p& Z0 f8 d) ~$ q( Q! V - ;
4 G3 O6 y; V, p$ U2 ^ - //Configures the system clock (SYSCLK): PLL selected as system clock3 L3 K! u" r. r* ]( D
- RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
8 x) p' Q2 |) t$ y/ O' q. ~# t - //读取时钟切换状态位,确保PLLCLK被选为系统时钟
7 O) t% `- `# u# P- I - while (RCC_GetSYSCLKSource() != 0x08)6 Z+ I% K2 b* `8 x( P: A: L3 q3 c. U' u
- ;
K$ y$ R1 x% a0 v) W - }
- |5 O7 A1 a+ Q4 S6 V3 d - //HSE启动失败
& g8 i- H* l% \" [2 L# V - else
) i* @& l9 y# D% L% \, B - {
2 c, N9 H3 G- D- ]" D- ^2 b& G - while (1)1 h5 K. c& s" @; A& P
- ;& ^+ y( h4 i: N$ ^
- }- p; Y0 F/ C8 D, w
- }
复制代码
& {% Y1 [, w9 Q) XGPIO配置# S2 l. m m( d5 G; I4 E
以PB10, PB11, PB12, PB13作为外部中断输入口为例:1 Y4 n) t/ F7 h6 P9 j2 G' u$ K
; _8 j! x! i7 }. c+ W, |9 Y2 O- void GPIO_Config()4 Q; H# e. X2 @- |' e7 m
- {
) B- I, Y: T8 ^8 w' u - GPIO_InitTypeDef GPIO_InitStructure;
/ B( X; V: w4 e. m2 M -
+ Z" x1 w) `# g0 _: d - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);% X" b: n& i2 E
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
% U, N* ~5 E v& g& W! A, a: P -
6 W {: V, S) F9 a- H! ]! E: T - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13;8 w: f0 q- O7 T, _
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;: m( i) W0 M+ V" }& i( O$ {
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;& r' O$ n( g' e, c! J1 p) s( A
- 3 H: V7 I& a+ M8 t: e0 d
- GPIO_Init(GPIOB, &GPIO_InitStructure);$ q% c5 _- V* j( L2 M' r, N. P' l
- //选择EXTI的信号源
4 S9 n, |3 T8 Q - GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);
\7 C: W) E } - GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11);6 r8 D% e* l# K4 a
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12);. P% M" d# t; ~. d+ I; i7 e
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);
3 Q \! j2 l& X# r- D0 i5 m
5 z+ }/ Y4 w# _$ J- o$ o) [$ n- \- }
复制代码
' F! R/ T1 N$ T: J( c4 I2 _1 EEXTI配置
; F4 C. t9 v: T1 \EXTI (External interrupt/event controller): 外部中断/事件控制器,管理了控制器的 20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。6 ?. o. @5 B* w1 r9 r3 @
& M; T: _( k. @8 C% H3 L9 |
- void EXTI_Config()
! v6 k' ?* t3 S' o& h& u - {9 X: Y( `& O" A+ u
- EXTI_InitTypeDef EXTI_InitStructure;
- r3 o; W, x, y$ m - # [1 D4 ?0 G: e# Z
- EXTI_InitStructure.EXTI_Line=EXTI_Line10 | EXTI_Line11 | EXTI_Line12 | EXTI_Line13;
6 C( q# c: k* w) G& a) K& j2 { - EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
( h' j L3 \1 q$ }9 [ - EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
: O7 ^' a- c7 h0 H, S - EXTI_InitStructure.EXTI_LineCmd=ENABLE;/ u( G# i+ D6 p) h0 K6 P
-
7 N8 [& e' G9 j: \/ N - EXTI_Init(&EXTI_InitStructure);
1 }9 a% M! M2 M- T& _- {+ E - }
复制代码 & b! p4 c5 @3 G3 Z
NVIC配置+ _6 |! @" @- Z0 l" R( i2 c
NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3内核里面的 NVIC进行裁剪,把不需要的部分去掉,所以说 STM32 的 NVIC 是 Cortex-M3 的 NVIC 的一个子集。
; K8 x. x% O1 Z J* w) Y7 U* B8 o6 g" f3 G9 T
在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。, c; U _$ ]3 N, Q
+ j2 {8 X* T/ c但是绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数减少,在F103中,只使用了高 4bit用于表达优先级的这 4bit,又被分组成抢占优先级和子优先级。
# G- u9 {9 O% ?) ?& p# @, X/ M
* o/ f# ^ n3 K# W: i如果有多个中断同时响应,抢占优先级高的就会 抢占 抢占优先级低的优先得到执行,如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
3 U, e" N* @: v, l
* J2 b) S! z- _; @3 [4 S$ \- void NVIC_Config()
+ @. n( S/ ~# f+ t4 q8 |4 e - {
- g3 }6 Y5 G& [+ ]3 B( ]" z/ n - NVIC_InitTypeDef NVIC_InitStructure;8 C& h5 |8 u& G, o1 G
- //NVIC_PriorityGroup_2: 2 bits for pre-emption priority & 2 bits for subpriority
! t3 t6 F7 f) d) y# _$ O( V. B2 ? - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
H6 J8 a# q' c \7 n R& A) Z - - \5 g, H4 p) O' M$ b# Z* T
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;9 [0 i: ^2 }% O$ q7 ^3 j# v0 T
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00; 8 Z% r4 ~7 C7 `; j
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;5 I- k4 D$ {7 z! I P# K
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;7 L3 l4 u* e$ z% }& C
- 9 W2 r: K; v* L0 p8 o
- NVIC_Init(&NVIC_InitStructure);
$ k/ ^" Q/ z9 O( ]$ H( |2 l5 e2 ] - }
复制代码 * _: Y4 y7 z) f, R: }' W4 |' l' i- u
中断服务函数 IRQ_Handler x* D! E6 @) h
在启动文件 startup_stm32f10x_xd.s 中,我们已经初始化了中断向量表。实际的中断服务函数都需要重新编写,为了方便管理,可以把中断服务函数统一写在 stm32f10x_it.c 这个源文件中。关于中断服务函数的函数名必须跟启动文件里面预先设置的一样,否则系统就在中断向量表中找不到中断服务函数的入口,实现不了中断。8 B6 Q( B3 I4 A; D2 J0 m) ]
6 F2 L6 f0 f. v2 \9 N, q0 s- void EXTI15_10_IRQHandler(void); t/ `* j" [( `# Y! U
- {2 ~$ d$ ^" A' b5 h$ S; o' t
- if(EXTI_GetITStatus(EXTI_Line10)!=RESET)& V6 k L; ?& z* H2 X4 U
- {
7 H3 v" K( N) P0 W4 i - DoSomething_Line10();
) {2 A) c6 _! F$ n4 R - EXTI_ClearITPendingBit(EXTI_Line10);
0 l7 ^* G$ [9 ~: ~, H! g - }2 a/ B; X$ h7 Z9 V" E: W
- else if(EXTI_GetITStatus(EXTI_Line11)!=RESET)
5 ]2 [5 p& o( l( R/ s - {1 Q( R6 Z. |; {4 U& R- ~
- DoSomething_Line11();
5 @! b- h& U1 } - EXTI_ClearITPendingBit(EXTI_Line11);
6 z, u0 ]" h7 n3 I5 J9 W. Q+ n" { - }
' n9 K P2 u7 o, L" N3 Y7 e - else if(EXTI_GetITStatus(EXTI_Line12)!=RESET)* N& w8 f7 a* r) `' C5 q; O' T
- {
! w2 l8 `! O7 m$ Y V+ I- A - DoSomething_Line12();
# X3 @" P% [9 s* G) W* n - EXTI_ClearITPendingBit(EXTI_Line12);$ Q0 E- M2 g8 A
- }& S& c0 O* Z, q/ y- w
- else if(EXTI_GetITStatus(EXTI_Line13)!=RESET)5 `! [7 U3 I6 o7 n: A U
- {# k. s2 q" w- L( u" f/ r
- DoSomething_Line13();0 X! x9 W& O4 M4 P- C: D+ k% {
- EXTI_ClearITPendingBit(EXTI_Line13);
) {. d0 x! k" ]% l( l8 p - }' S0 R. A' N+ X2 z
- else if(EXTI_GetITStatus(EXTI_Line14)!=RESET)
. {: q$ p; ?+ E) u - {) D! r( z6 k% ?" O; L6 A1 q* v) i
- DoSomething_Line14();
; f0 G' F7 A2 `7 p- B - EXTI_ClearITPendingBit(EXTI_Line14);+ @ K# L) p) w) d
- }
+ k V6 T! [4 X7 y - else if(EXTI_GetITStatus(EXTI_Line15)!=RESET)
3 j4 {. O J: x3 e ^ - {
6 N% ~; U2 ~1 \' I. n - DoSomething_Line15();
! @. n+ ^8 B) q9 O# L& i+ f' v. u - EXTI_ClearITPendingBit(EXTI_Line15);
0 R: F6 ^/ K8 O - }
, u% ?4 W4 J I - else g6 K4 Z9 E. M. {: @" }5 R: h# X% W
- {
# B# |5 N+ P* X; r - 2 N8 i: p1 D3 x" ^; h. @
- }! K% H3 J: E" j" v
- }
复制代码 $ C! s3 ?2 \% i/ o& B' p
3 \" c2 y6 t( N
% C* }( ~& E" X& f( w8 }
2 f" C$ h% n5 p. W( n! Z |