你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

stm32 中断几个库函数实现过程分析

[复制链接]
STMCU小助手 发布时间:2023-1-1 21:00
至于中断的什么优先级,什么优先级分组,使能之类的原理,就不再赘述。这里主要是记载以下如何使用中断,以及中断配置函数的实现过程,其中并叙述我曾经的疑惑和感悟。
: k; I( P+ w; Z! E
  我的开发板里的中断例程是用按键控制一个灯亮和灭的两个状态。
/ q# n% \4 f# G% `% v3 F3 d# J1 j
  这个例程的实现过程如下描述:

0 f$ S$ f0 j0 O" f# f4 n
第一步,将一个I/O口配置成中断输入模式。
& v4 G3 U, W. U6 @7 u" ^
  这里需要注意的是,GPIO本身是没有中断功能神马的。如果硬要使他产生中断输入方式,就得将相应的端口映射到相应的外部事件上去。而其他外设是有中断功能的,直接使能/失能其中断即可,比如USART,直接开启其发送/接收中断,那么USART也就相应的采取中断方式进行工作了。

+ R0 W, [$ E1 {* b+ y
  而这一点,是我开始很疑惑的:为啥GPIO口使用中断方式进行工作的时候就必须要映射到外部事件上去,而其他就不呢?百度网友的解惑是:比如USART产生的中断,是没有经过EXTI,而是直接将中断放入了NVIC;但是GPIO它作为中断源,是要经过EXTI的。仔细参看下面两个图,其实就会恍然大悟:
- X- d; f" J% J6 I$ N  d) S1 c* }
12220833-15331e0b20104519978a596626e2c248.jpg
+ z& z0 {; R( J2 _
12220855-fccdc6e73b3649bea37705918f876b00.jpg

5 d, p7 ?9 L% _! F
这第一步就是作为输入中断源的I/O口的相关配置,例程库函数如下:
) M) F6 P2 B+ k: b
  1. void BUTTON_Configuration(void)1 K( r7 `6 i; `1 ^$ V8 f
  2. {% ^: j: ]& Z! b- I3 u6 O
  3.     GPIO_InitTypeDef    GPIO_InitStructure;$ e1 x/ c" ?$ ?' n. q0 l; _0 g
  4.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;' I! x7 N7 y  q9 `
  5.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
    4 Z; X' d1 m, t/ U. d
  6.     GPIO_Init(GPIOD, &GPIO_InitStructure);
    # k4 e( r2 T6 P& U! A* Y
  7. ( C# H/ t& K/ [- ]" x$ [8 y% t3 o
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);5 }* D+ k( u4 C, v) k( w1 g
  9.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);8 P% ]& r( c6 V1 j
  10.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12);5 p5 [! H& P! z6 X
  11. }
复制代码

* H$ ?7 @" E+ i2 W8 @/ P
因为我板子上的例程是按键输入中断,所以函数名字就写的按键配置吧;

3 o/ c# \( j0 p4 ?. g
3~5行,就是gpio口的普通配置,学习单片机开天辟地,就先是gpio口,这个没啥稀奇的了,没啥可说的了;我的板子上是PD^11,PD^12两个端口作为中断输入的。

( \& e0 f" k" k" P* w1 r2 B
8行,注意这个时候,要使能GPIO口的复用时钟功能。

: a/ }$ t3 v0 \, J- ?# o, u
9~10行,就是将PD^11,PD^12映射到外部事件线上去。在keil中跳转到其函数实现中:
' Q+ @9 U  M  `: ]; i6 d9 H
  1. void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
    9 g2 p  T( x/ T( m& R7 `5 A
  2. {  |0 ~. X) G0 S9 [7 ]  |6 j# A
  3.   uint32_t tmp = 0x00;
    2 l* y5 |1 p" N  {% p1 ~$ b
  4.   /* Check the parameters */
    1 |0 G2 [+ P7 S" y) D! [7 C4 `
  5.   assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
    5 n! p) h! Q2 {8 ?
  6.   assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));) P6 r5 P, o3 M) b; V: s2 Q* K6 _
  7. 2 F( E  h8 z/ M' g( ~8 h
  8.   tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));6 v: {+ K/ U2 E6 L) H& U
  9.   AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;+ ?6 J1 @- O5 ?$ a# R+ \
  10.   AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
    . [- L0 _- q  B- y
  11. }
复制代码
9 z' o6 L2 F: ^& s+ _: V
5~6行,用库函数的都知道,就是两个宏定义,起到的作用是对相关的数据神马的进行正确性检查。
3 B, n# R0 v1 [& n; e+ d+ T
8行,GPIO_PinSource这个是外面BUTTON_Configuration()调用GPIO_EXTILineConfig()时传的参数。可能都不知道8行这个式子为啥要这么写。先看看我例程中是如何传的参:

4 W2 @5 h4 y$ P
  1. GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
复制代码
  v" E; Z. V4 @  j$ U# R; z
也即是GPIO_PinSource <==>GPIO_PinSource11;那么GPIO_PinSource11是个什么东西呢:官方库已经这样定义了:
  1. #define GPIO_PinSource11           ((uint8_t)0x0B)
复制代码

4 c3 Y  G, T% K: f1 I% U
如果按照我的例程,8行这个式子中,tmp == 0x0F << (0x04 *(0x0B & 0x03)==>tmp = 0x0F000;先不管这个数字是个啥意思,反正它就是个数字,它其实是为了第9行寄存器的值服务的-->既然如此,如果用寄存器写的话,我可以直接给寄存器某个值,何必要山路十八弯呢?当然,库函数,是有通用性的,就像一个数学公式的作用。

2 |& j% x* q& j6 ?3 P6 ~9 p$ o' a
9行:AFIO_EXTICR:外部事件控制<配置>寄存器。在数据手册中显示,有4个,它们分别对应的是各个外部事件exit_x<x = 0~15>。每个寄存器对应4个外部事件,于是4x4 = 16。,注意数据手册中的编号是从1开始的,而不是0开始的;但是,MDK中是0~3的。于是这里把相关值带进来一看,第九行其实就变成了如下式子:
. p7 Q8 K/ {$ i+ H- t- C
AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
AFIO->EXTICR[2] &= ~0xF000;==>AFIO->EXTICR[2] &= 0x0FFF;
什么意思?不就是将这个寄存器的第12~15清零吗?不就是将数据手册中第AFIO_EXTICR3寄存器的12~15清零么?再次注意:该寄存器在MDK中是0~3的,数据手册中的编号是从1开始的,而不是0开始的;
0 n/ O: j# N' ]
这个样,9行以前的一切的操作,就是为了给该寄存器的某个位进行清零嘛,至于具体清哪一位,还得看你映射到哪一位。
10行:引脚选择了,现在就选择这个引脚是哪个端口的,我的是D端口,那么按照官方对D端口的定义如下:
  1. #define GPIO_PortSourceGPIOD       ((uint8_t)0x03)
复制代码
( ^1 ]! L- y& N9 V8 ?; }$ q% W
AFIO->EXTICR[2]  |= 0x03 << 12;查看数据手册,恰好是设置成了D口的第11号端子上嘛。

* k5 L7 |. Q$ ?' C- F' H( @/ Y: C
于是第一步总结是:
% H9 E4 G4 \1 P2 I: @
1)外部事件寄存器相关位清零;
* ]# P, \2 z0 |$ y! _
2)设置输入端子的编号
. ^' C1 \( {6 w
3)设置端口编号
7 _$ Z3 O0 \$ }$ o+ ]
注:一共有A~G个端口嘛,而每个端口上又有N多端子,这个参看GPIo那章数据手册。
9 }2 w. h+ |0 ]2 y
悟出:库函数确实方便,具有公式效应,但山路十八弯,在这里,该例程中,要实现该功能,用寄存器,就两条语句嘛:
  1. 1 AFIO->EXTICR[2] &= 0x0FFF;' w1 t0 a3 {4 n9 m7 i& r
  2. 2 AFIO->EXTICR[2] |= 0x03 << 12;
复制代码
1行:12~15清零
2行:0x03:表示是PD端口 ;0x03 << 12:表示在AFIO_EXTICR寄存器中的第12位开始写入0x03这个值,而括号中的2,说明是第三个寄存器,这样一组合,恰好就是PD^11了.描述起来一长串,如果看对照个看数据手册的话,就一目了然了。而这里唯一会让人凌乱的是:这个寄存器在数据手册上的编号和MDK中的编号不一致。自己在细读了<<stm32不完全手册>>才发现这个问题,开始可是百思不得其解呀。

; V) ~0 N' K6 g* c" i" X7 G
总结下第一步要做的事情:

! g( H" R* N4 F
1)初始化I/O口为输入;
( l9 y& F; G- b. y
2)开启I/O复用时钟,并设置外部事件映射关联。
) L! C4 O+ q! o( ^& C/ W
' k' y( G1 E$ H5 F7 [' a: }5 O# x
接下来是第二步:
  第一步是将外部GPIO口映射到某外部事件上去。那么接下来,就该对该外部事件进行配置了,包括外部事件线路的选择、触发条件、使能。这里需要理解清楚的是,GPIO口和外部事件是各自独立的,它们并不是一体的---详细理解第一步,将GPIO口映射到某外部事件,可以看出GPIO和外部事件这个东西是两个不同的东西,在这里,GPIO的映射,无非就是GPIO口搭了外部事件的一趟顺风车。也所以,外部事件依然是要配置和使能的,不能说,将GPIO口映射到外部事件就可以产生中断了。

4 F% }8 f: G/ N/ ^6 ^
  接下来看看例程中外部事件的配置函数:
  1. void EXTI_Configuration(void)
    0 `: ~* r6 E7 H# A# o' ~
  2. {! ?3 x+ B9 C7 g: Q
  3.     EXTI_InitTypeDef EXTI_InitStructure;
    0 C' ^+ R: @5 e0 ]* k0 X/ Q
  4.     /*PD11外部中断输入*/
    7 `, E* O% j6 W' C; S0 K( t
  5.     EXTI_InitStructure.EXTI_Line = EXTI_Line11;8 A+ V4 ^% H6 m7 Z0 J- P) B! P7 }
  6.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    : b( [" N% k% T* [
  7.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    & ?/ }& E0 t: c1 y3 ^. w
  8.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;8 I/ O6 X" X1 h% R5 E& j2 j
  9.     EXTI_Init(&EXTI_InitStructure);* z4 E. }% \! ]" R4 c- @
  10. 2 R1 D3 m2 @$ c+ M) S
  11.     /*PD12外部中断输入*/  ]0 W1 V: ~3 h2 J8 j9 @, ~* y
  12.     EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    + o2 f! M6 h- j3 t( u8 T5 R
  13.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    1 n9 g; r5 V- [' l
  14.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    ( _8 \1 G1 L' Z. m. ~
  15.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;
    % g: I: v8 Q2 j2 M4 V
  16.     EXTI_Init(&EXTI_InitStructure);
    5 w/ @8 x. O, c
  17. }
复制代码
/ ^+ Z; J! J& U0 y7 X3 w) e
可以看出,5~8;12~15行,无非就是在填充一个接头体。而真正实在的是9、16行:它们是外部事件初始化函数,把前面填充的结构体地址作为参数,传进EXTI_INit()。
2 E& n4 b) _1 _, b
在MDK中右键EXTI_Init()跳转到该函数的实现中去,代码如下:
  1. void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
    2 S% {5 l* P6 q( Z% j4 l& Y. I$ o
  2. {
    7 \2 W+ s9 X6 `" O# j. E
  3.   uint32_t tmp = 0;
    ) C8 p5 I. ~1 J" D/ V6 C; l
  4. : H7 b$ E: z# ^
  5.   /* Check the parameters */
    : ]5 }. f4 ^: _
  6.   assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
    / Z9 p5 f" q$ z
  7.   assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));
    0 y9 \7 m* w' O! e) G% W, r/ h
  8.   assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));6 W) E: O- E% J# M4 E6 o6 f
  9.   assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));
    4 M8 g9 E& R% V: d0 H
  10. ; ]1 @0 p6 u; S6 W1 r
  11.   tmp = (uint32_t)EXTI_BASE;
      _- E+ `, }8 y- M. h

  12. 5 k0 s. y8 B- q/ v
  13.   if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)
    % p0 \) z) j5 q4 x
  14.   {
    * s8 _" G* J8 E; H
  15.     /* Clear EXTI line configuration */! a6 ~0 R9 k' t: w9 u3 h
  16.     EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;
      R$ s, K* E0 U2 N4 h1 l
  17.     EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;
    3 o7 h% z! O  M# h

  18. . l/ ?+ _: j$ v+ a$ l# d/ r
  19.     tmp += EXTI_InitStruct->EXTI_Mode;) S- H" ]/ O" a3 \, _& ^/ x0 W
  20. $ U4 j1 X& a) f5 |" D7 F* M* ?# c! {8 e
  21.     *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;! b0 ]* r8 B8 D( Z+ I# X$ B

  22. 3 T  {' M: ]1 L0 _$ l
  23.     /* Clear Rising Falling edge configuration */( c3 Z7 J. I2 d  L
  24.     EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;& \+ F. {! o' m) \5 s8 M
  25.     EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;- g6 @) p+ d6 J. |8 p" a

  26.   A  J$ |" {' a3 J0 @( e+ G" ]- R
  27.     /* Select the trigger for the selected external interrupts */% N7 \! L% Q2 @7 D& ~, N( I& q* i8 R6 G
  28.     if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)! \* |3 F+ D* k5 N
  29.     {7 J) k: e2 e0 H; i/ S+ |8 p
  30.       /* Rising Falling edge */
      Y+ ~( ?- s( G. }
  31.       EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
    5 A7 C- f* p  U. W) l! a% b, R
  32.       EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;6 r7 F$ l+ \7 O1 ~
  33.     }/ }! U! v. @7 B/ K# m4 }6 j8 a4 V
  34.     else. d1 a* g+ j6 ^) K+ V
  35.     {
    % P1 T8 s6 W# U. g
  36.       tmp = (uint32_t)EXTI_BASE;
    $ P6 n7 T; t2 j% k6 X, Y8 }
  37.       tmp += EXTI_InitStruct->EXTI_Trigger;( P2 Q2 E7 M8 C! O" y) E
  38. , X0 z0 G* \- x$ h
  39.       *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    6 U& U# F2 f2 X% Y6 ?1 T9 z9 K
  40.     }
    8 [$ b' P$ h5 g3 }1 i8 k5 T- X
  41.   }
    ' g# o' c5 l' R, d" F* R
  42.   else
    ! r# ~7 r% I9 I) {2 J/ s( |
  43.   {
    8 r$ B8 U! u- a, z$ l
  44.     tmp += EXTI_InitStruct->EXTI_Mode;# i% L8 S& n1 F& J
  45. 7 k* _$ a1 F6 F+ B% i/ w& m
  46.     /* Disable the selected external lines */! S6 P  n- ~6 F8 w. o3 D4 {
  47.     *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;' P, ]1 v, S, t) K( z' U
  48.   }8 y- B% I  A- n. y; \
  49. }
复制代码

5 y( C( p# W, B+ b  k' }2 M
第11行,就是存储了一个地址值。可以先看看它们是怎么定义的:
  1. 1 #define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
    ' [" o: S0 H+ M
  2. 2 #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
    7 y4 J5 z$ B5 J9 y; V. M& w  [
  3. 3 #define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
复制代码
3 N& U0 Z, p& j/ h
如果对stm32框架掌握的足够熟悉,这里一眼便知端倪:GPIO口是挂在APB2上的,APB2是连在AHB总线上的,AHB再连到总线矩阵上的,环环相套,牵一发而动全身。附图如下:

. p6 W1 R) I& V+ ^, [2 H, O
13212932-0f749bf04f4f4399b93997b2e4089589.jpg

% e  R7 T4 z8 X, v
第16行、17行,分别是EXTI_IMR中断事件屏蔽寄存器和外部事件屏蔽寄存器,相应的位为0的时候,它们屏蔽相应线上的中断/事件请求。带入例程中的值算算
  1. 1 #define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */, \! O' |1 ]  n5 B2 y
  2. 2 #define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
复制代码
; H# w+ q0 s/ R1 D( B; h: O; [
先将事件11的值带入下面两个式子,换算如下:
  1. 1     EXTI->IMR &= ~0x00800;
    ) _  ^9 ]' m+ r, Z/ U3 o- A
  2. 2     EXTI->EMR &= ~0x00800;
复制代码
表示,将寄存器EXTI_IMR的11位清零,将EXTI_EMR的11位清零<下标从0开始>--意思是,将先关闭11号线上的中断/事件请求功能:有个原则是,在配置某一线上的中断或者事件之前,先将该线上的中断/事件清零,官方库写的比较严谨,所以这里先清零。那意思是,接下来就该对该线上的中断/事件进行配置了。

( d5 L2 V& L% H  F
且看上面外部事件初始化函数中的31行和32行,EXTI_RTSR是上升沿边沿触发寄存器,EXTI_FTSR是下降沿边沿触发器,这里一看便知,是在配置边沿触发模式:边沿触发分3类--上、下、随便。当然,在配置边沿触发的时候,边沿触发器相应的位也应该清零,这在该函数的24、25行已经体现出来了。
* M0 J( E/ {, b0 u/ x
另外注意模式的配置,看该函数中的19行:
  1. 1 tmp += EXTI_InitStruct->EXTI_Mode;
复制代码
这个式子的表达的意思,接上文是:
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
司马昭知心,路人皆知:就是某个地址 加上一个值后,这个地址也就变成了另外一个地址了。但为啥要这么做呢?

1 ^$ U6 ]9 _, e' y- u1 l2 d# ]
而外部事件中断模式的值定义为如下:
  1. 1 typedef enum1 k/ x# e5 o2 x4 G& D2 \+ v9 ]/ z
  2. 2 {0 r+ g- W1 Q! T2 W1 q: x8 _9 i
  3. 3   EXTI_Mode_Interrupt = 0x00,$ {& y3 {' @+ \% w0 D
  4. 4   EXTI_Mode_Event = 0x04
    , }6 Y- ]$ R! E3 m+ Q4 W, E" U
  5. 5 }EXTIMode_TypeDef;
复制代码

7 @" Z1 I) N9 t
可能不熟悉的<包括开始的我>,会发现,tmp在开始就只是对寄存器清零使用了下,就没再使用了啊?可是在该函数中,为啥后面还有一串关于tmp的代码呢?
如果细读开始处赋值的意思就该明白了:是将某一个地址写入了该变量,那么在一定的条件下,该变量就相当于内存地址了嘛。注意是在一定的条件下,而函数中,也给出了这个条件,那就是类型强制转换。
请看39行或者47行:
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    7 y( [$ U, F% l4 r+ {5 a% t
  2. 2
    + t" x; u4 i( \: m% R8 `
  3. 3
    4 B% O* T+ I: H- p# S. I/ i
  4. 4 -----------------
    " n" f% N7 C) ?% \7 E5 I
  5. 5 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
复制代码

# V! G# [. O  \% E0 P
__IO 表示volatile关键字;*(volatile unsigned int *) 表示了什么?指针的前面加*号,表示一个具体的值,也即是某个地址上具体的数据。

' ~2 t1 r  z9 P
于是这里表示的是对某个内存地址进行操作,也就是向某个内存空间放入某个值,放入的是什么值呢:中断时间线的值;放入什么地址呢?
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
. G  C! _* ?% N( R0 C
注意,如果边沿触发不是随机的话,还要加上边沿触发寄存器的值。
. S+ i' |; P- e/ b- S( @7 }, w% F6 \
由于以上几步开始没有理解,特别是我最后没有把中断事件线的值写入内存空间,导致我的第一次按键中断实验失败了,而且还不知道错在哪,在分析了官方代码后,方才知晓。

) T% {- Q% \4 i  Y3 O
到这里,外部事件配置就完成了,可能细心的人会发现并没有使能外部事件/中断,是怎么回事呢?请看看上面tmp所代表的的内存空间地址是多少,然后对照着数据手册上查看下事件/中断屏蔽寄存器的地址是多少,再看看下面这句代码最后的值是多少,就一目了然了。

  y: a$ {: p* E/ e0 |8 R( D9 x; i
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
复制代码
9 `9 p" C6 u9 y9 M4 x# `
总结下第二步:就是配置外部事件的模式、触发条件、使能外部触发。而在这个过程中,注意先将某功能寄存器相关位清零后再写入值。
5 v) ~2 Q$ _4 u' D% _
这个过程用不写成通用的库函数的话,那么可以用下面代码代替:
  1. #define tmp  (*(volatile unsigned int*))0x40010400
    ' U8 q- k) H9 B& o
  2. 1 W' {* S  r3 y: [0 A% h2 O: l
  3. EXTI->IMR &= ~0x00800;( q' N  F9 w7 t1 l4 R4 c
  4. EXTI->EMR &= ~0x00800;* v. G& ]+ J* ]
  5. EXTI->RTSR &= ~0x00800;
    2 ?- i9 M0 o/ l" }* f
  6. EXTI->FTSR &= ~0x00800;. u5 G3 @* {% F) y. G
  7. EXTI->RTSR |= 0x00800;//如果配置成上升沿触发的话
    0 i, B0 h5 \; p3 s: B
  8. tmp |= 0x00800;//使能中断/事件
复制代码

; }& [! D) r7 O4 j  d% T% F
第三步,现在就该配置中断了。也即是配置中断分组,以及中断优先级。当然,这并不是最后的工作。

8 w: f4 I' J% U. A+ P  `
中断配置函数如下:
  1. void NVIC_Configuration(void)
    * |. `+ [, B- E3 Q
  2. {
    & z; J0 ^: Y: J" }2 T7 \* _
  3.     NVIC_InitTypeDef NVIC_InitStructure;
    " B: x: J. O( ^- i5 F5 L
  4.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);0 _3 y3 v4 x0 {( l7 H

  5. / V" m- p& ^, `6 \' Y3 B
  6.     /*外部中断线*/
    ; V/ w1 D; ~% J+ j8 \" |( V3 S  ~
  7.     NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ;
    ' X+ A) k) p9 G( o  A( V3 j
  8.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ;
    2 {  Z% `* b7 r: f3 t
  9.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    ; m; \' L# A2 j. O$ A; q
  10.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ;/ I3 e* [' z: S* M0 U6 \" P
  11.     NVIC_Init(&NVIC_InitStructure);2 P( l5 Z  h; R- I8 |
  12. }
复制代码
) a# P! d, u0 y* Y
4行,是一个中断分组函数,跳转,看实现:
  1. void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)1 @$ W$ E9 H  W3 J
  2. {
    , _" X( e# @' w3 e: k$ m7 ~
  3.   /* Check the parameters */1 x0 i3 q  _) V8 M
  4.   assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));2 L6 O, b+ S" `/ T9 x) [+ l% Q

  5. + o4 j1 v+ [7 t0 x# g
  6.   /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
    # `& B- L5 R1 ~( Q
  7.   SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;' ~" |: g' x: k- b$ t, y1 S- ?
  8. }
复制代码

+ G6 v5 P2 d& b6 o8 b% G1 W/ T
要查的SCB_AIRCR应用程序及复位控制寄存器的话,手头就必须备有<<xxxM3编程手册>>,<<xxxstm32数据手册>>中是没有这个寄存器的,当然还有许多都没有,比如滴答定时器等等。
% J/ J6 Q$ @9 z4 m
其中 AIRCR_VECTKEY_MASK 就是一个钥匙,在改写SCB_AIRCR中的值的时候,必须填入这个值,否则修改无效。AIRCR_VECTKEY_MASK = 0x05FA0000;
  z( i$ n1 m% x+ k; V) M
而后面就跟着组的编号,注意这个编号不是简单的就是0,1,2,3,4.不能就这么简单的写入这个寄存器了。
( ]+ x( g7 ?+ _8 ]% h; p6 b6 w* U
注意这个寄存器的名字,它本身并不叫中断分组寄存器,而是借用了这个寄存器的某几位来进行中断分组--换句话说,这个寄存器可能要实现多种控制功能,而中断分组功能是在其中的某几位:当然,编程手册上说明了,是该寄存器的8~10位来进行中断分组控制;所以,组号需要进行位移到8~10位上来。如图:

4 p; H, Q7 l* S
13232035-61d308ebf03a4d3d992a918904467d45.jpg
1 |! J, w" @" Q; K# Z! u* \
<存疑部分>

$ @; M" m& O& x+ D  z
当然,该函数中的值,也就影响到下面中断优先级的配置。在MDK中跳入NVIC_Init()中,看其实现过程:
  1. void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)/ z8 G. k- A! z& p
  2. {
    8 Y  A7 Q/ c+ X2 @
  3.   uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
    2 X9 L5 L, Z# X, R; H7 ]
  4. + D5 _: `. A( F# a# z
  5.   /* Check the parameters */
    1 s& c9 a: c7 j1 n  O( W* O
  6.   assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));3 q* ]1 x8 E6 }4 w" S
  7.   assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));
    ) V$ }8 j& \" ^3 M5 ^& |: L$ L
  8.   assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
    * r8 I6 j3 ?, v; F$ N$ ?

  9. 5 z% K7 ^$ S: p
  10.   if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)  M# x" w  K' u6 w" g" J4 ^. ^/ [
  11.   {
    " Y8 Z" L  D7 @+ @! k+ @3 M9 J, B+ p
  12.     /* Compute the Corresponding IRQ Priority --------------------------------*/
    $ \& q! X  e6 ]+ {7 B/ {
  13.     tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    9 F3 ]6 A! ?: A: l0 W
  14.     tmppre = (0x4 - tmppriority);
    6 Y# ?9 c% ^2 u( F! `6 m# I
  15.     tmpsub = tmpsub >> tmppriority;
    1 \1 T3 K1 q" E% B; h

  16. ) T. x7 v2 o4 R
  17.     tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
    : {8 H1 a$ }5 D1 z4 R7 U4 X
  18.     tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    ; X* K# o! a: G' `' Z2 b
  19.     tmppriority = tmppriority << 0x04;5 \( W7 z1 U4 Z8 c) c  y7 }

  20. 2 ]5 @5 n% q: X' M, a( o
  21.     NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;6 S9 f% G+ X, h

  22. 0 M- T, C: @9 ?  X& ^
  23.     /* Enable the Selected IRQ Channels --------------------------------------*/5 G0 K, E2 g( I! ]
  24.     NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =$ L0 E4 a. M- R  z7 r( c
  25.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);! H# G8 k. }  G5 C7 U/ d
  26.   }1 n$ E7 d$ y. h- }! i
  27.   else
    % t+ a0 G/ g4 Q; b5 ?( `  x
  28.   {
    % ?$ V4 e' N+ x, Q* s
  29.     /* Disable the Selected IRQ Channels -------------------------------------*/
    9 O' I6 H9 x9 y- P! X
  30.     NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
    ( _: O5 h' r- M" j. I6 _
  31.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
    ) K- }% T6 a# \) A( v! W% n: l
  32.   }
    * S: W5 T5 p. x' ]( B# ~
  33. }
复制代码
2 u3 g  X* B4 }3 r$ q
该函数中的第3行,有3个临时变量,分别是:优先级组--这个组的意思就是中断分组的那个意思,至于是否是那个值,看下文解释;抢断优先级;亚优先级。注意亚优先级初始值是0x0F--具体是啥原因呢?且看下面代码。
* z3 S! Z' I. ]
第13行,先取出中断控制复位神马神马寄存器的8~10 这3个位上的值< SCB->AIRCR& 0x07>;然后经过一定的算法得出中断组号:假设SCB_AIRCR的8~10位为0x07,按照上面图来说,也就是第0组,那么按照它里面的这个式子来算,恰好结果tmppriority = 0;至于具体为啥会ST会想到用这么个式子来得出组号,我现在只可意会。

4 s* R- s% C3 J! Z
那么,假设是第0组,也就是tmppriority = 0;那么按照中断分组中,第0组的抢断优先级和亚优先级的规定来说,是全4位亚优先级。那么14和15行就很容易明白了,它就是在设置抢断优先级和亚优先级的比例位数。

/ c3 x; r3 Q9 P$ C8 ?- F' y
那么17行,就是像临时变量中写入抢断优先级的值;

3 M2 i1 _- x- @
18行,就是向临时变量中写入亚优先级的值;注意抢断优先级和亚优先级一个在先一个在后,一旦分组一定,那么它们是会乖乖呆在自己位置上,不会去占用别人的位置的。

( m# l# }, _# [% }7 z; j
21行,就是把优先级配置值写入NVIC_IP寄存器,它是NVIC中断优先级配置寄存器,其定义在<<xxx编程手册>>中,同时可参看<<xx不完全手册>>。

; V8 k2 T$ f# o% D
在我例程版本库中,对该寄存器的定义方法是:

2 w9 X( |8 q; ^3 V' [
  1. 1  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
复制代码
, `+ X& \* K2 I2 u! W
如果带入例程中的值来算:EXTI15_10_IRQn 的中断编号为40,即是:

3 E" V. @. J3 I' q9 s" l0 |
  1. NVIC->IP[40] = tmppriority;
复制代码

8 e3 B" v( U; I! ^
意思就是向相应的中断上赋予你的优先级配置值,这里特别要注意19行,优先级的值还向左移动了4位,这个是为啥呢?无法回答,带入例程中的值算算:我例程中是,分在中断2组,抢断为0,亚优先级有1,则带入:

7 q. Y" p8 ~) P( Z* Y
  1. 1  tmppriority = 0x10;
    9 c$ Q( ~( G+ o' G7 R/ R
  2. 2  NVIC->IP[40] =0x10;
复制代码
. ?; B# [+ g& C5 ]' N. ^0 s
也即是IP[40]的第5位置1,可能还是无法理解,再次参看正点原子的书,得知,中断占IP寄存器的8位,但是只是用到了其高4位,而我们在设值的时候,也即是设它高4位的值。那么就符合这里的情况了:抢断优先级为0,亚优先级为1,组号为2。 也退出,亚优先级和抢断优先级一共4位,抢断处于高位。这4位中,它俩怎么分,就是一个此消彼长的情况,视不同的组而定了。至于组合优先级之间的对应关系,上图已经清楚的解释了。也即是SCB_AIRCR 这个寄存器处理的事情了。当然,这些寄存器,在<<xxx数据手册>>中也许是找不到的,而在编程手册中去找。

) p  u; s3 X& {2 r5 M4 l$ {% b( _
最后,补充一点就是怎么查看EXTI15_10_IRQn 的中断编号,这个数据手册上有,当然,官方也在库中给定义了。数据手册部分截图如下:
) R$ G1 @/ D5 I9 Z
14225347-87ca63c19bd744138d5eac4912b5b536.jpg

! s; ]- p% O5 D$ {; U0 h
14225324-690e8471b9a84e59ba7ada9ef0a55e73.jpg

+ s- F8 l6 X) V, b
其中EXTI15_10中断的位置在该向量表的第40号位置,所以刚才上面的优先级寄存器引脚的值就是40,至于具体为啥要写成IP[40],而不是直接IP,这里有个小小编程技巧,是数组的妙用,属于C语言范畴了。

/ [) B. ^* d) N% X, A. [/ [
接下来的第24行和30行,是第一对相反作用的寄存器,即中断使能/失能,注意,别以为写0就可以失能,stm32不认为那样有效,必须是向失能寄存器中写1才可以的。同时注意一点是:这个寄存器定义成了数组,那么数组中就应该有N个元素。它这里就是某个元素相应的位,管理一个区域的中断。这里说复杂了,编程手册上已经讲的非常详细了。另外,它的定义方式如同<<xx不完全手册>>上所讲解的那样,但是定义的模式并不一定就完全相同:世界是变动的。
4 U* c& m* P% K& Q) D, f9 V2 }( T
好吧,中断也使能了,总结下第三步:
: F. ]) {3 j2 q$ A; r
1)中断分组:注意是在SCB_AIRCR寄存器的8~10.分组的同时,也就影响到了后面优先级的分配。

4 Y6 y. t- L$ J3 v, o5 Z2 \
2) 配置优先级:其实质还是在SCB_AIRCR寄存器的4~7位,前面8~10位是什么值,那么这里4~7位就该怎么分了;当然,最后的配置值是写入了NVIC_IP的寄存器中了;
" n" l1 q; l0 o
3)使能中断:在NVIC_ISER寄存器中,注意该寄存器定义的方式是数组,而非普通的变量,理解的时候,要理解一个数组是由N个变量组成即可<非严谨>。
/ ~0 ?1 C( M1 e! \
第四步:中断服务函数:
, {* H# u$ M" U6 b4 Z
  九九归一,终于来到了最后一步,也是所有中断必须要经历的一步。
# p$ L6 r) a/ H1 m$ T0 W
  这里有个重点必须注意:所有中断服务函数的名字,ST官方已经取好了,而且还放在了中断向量表中了<也即是启动文件里>,如果你不自己写启动文件的话,那么你的中断服务函数的名字必须和ST官方的一样,不然,一个中断来了,找不到负责任的函数,它就只有悲剧去了。
; N+ l0 n! g" S5 s5 D6 l
看看例程吧:
  1. void EXTI15_10_IRQHandler(void), |/ B. @; U6 v1 ^) q- w# D! L
  2. {* d- ~  m3 `; H. L, o
  3.     if(EXTI_GetITStatus(EXTI_Line11)!= RESET)
    % [5 ]" o: [( |0 C$ ~2 E( |
  4.     {
    4 J& [) d# Z( y* g2 k
  5.         EXTI_ClearITPendingBit(EXTI_Line11);
    9 T5 Q9 a( h8 N$ {0 o% W
  6.         Flag = 0x01;
    1 l0 A1 j1 f% j  R" T( G
  7.     }
      ^. R$ x4 K0 l0 C
  8. # O5 o% [/ |7 j* M: T; h
  9.     if(EXTI_GetITStatus(EXTI_Line12)!= RESET)- ^  a% a1 r3 c5 j& T! x* j9 C! }& b
  10.     {
    : n5 Q, w6 D9 }$ e& m; `
  11.         EXTI_ClearITPendingBit(EXTI_Line12);
    & a0 r7 X. e5 h$ J  p. N& _2 l
  12.         Flag = 0x02;
    9 x4 P; E3 l* a2 ?4 S$ \' ]' S5 p
  13.     }
    6 ?0 N* q! F/ A  m
  14. }
复制代码
9 U/ D) Q, ^% h& w: a
  1. int main(void)" \# O2 W7 A3 {* y* Z- q
  2. {' Y! s* I- F! y
  3.     /* Add your application code here5 q1 r% b4 E- x+ h
  4.        */; Q& \8 F- o2 `3 r0 K9 o( |
  5.     SystemInit();              /*系统初始化*/
    5 |5 b4 W1 Q# C) d, u( [
  6.     LED_Configuration();
    : q# b! _9 F7 N/ q/ ?/ U
  7.     BUTTON_Configuration();
    . T, k) g, A( \4 c: e
  8.     NVIC_Configuration();, i1 I3 U7 l' `, v" V
  9.     EXTI_Configuration();- U8 j) y+ R6 }1 a. k
  10.     /* Infinite loop */
    , U- ?0 _" `) \, _  y8 `# X; o2 a
  11.     while (1)
    ( @/ Z- ~- B& Q% {
  12.     {# e* U! z" _* q( u9 w7 n* ]
  13.         switch(Flag)
    ( T/ l0 g2 b  Y$ _: C
  14.         {
    / \% `% V' t" V2 f$ H
  15.             case 0x01:
    # d2 r2 z' o5 d- g
  16.             {1 i( l# n5 f3 |7 d5 q% M- ]! T4 p
  17.                 LED2(1);! @4 @7 J( q0 K2 x
  18.                 Delay();
    8 T/ x  u, l9 P; p8 _) K
  19.                 LED2(0);2 Z6 r' `% C: X
  20.                 Delay();
    0 P* T/ ]8 s/ \
  21.                 break;- E9 M6 n/ i5 @+ B: l, Q
  22.             }" ?9 s8 V' g5 u# {
  23.             case 0x02:# B# L, D. Y+ W+ I, E
  24.             {/ @, `" D, T2 ?/ K( y
  25.                 LED3(1);3 ~2 \! S9 |! R
  26.                 Delay();( E. x! J$ R( K2 J$ T  h, R; c
  27.                 LED3(0);* U8 G2 m- {% E7 A1 O1 O! J7 ~
  28.                 Delay();+ Q# _0 _. O" l
  29.                 break;
    $ m6 O3 s! i0 P7 A9 c9 B  j% G* m
  30.             }+ j: c8 W) \; x# x6 @1 u- @. T
  31.             default   :1 w6 m" @/ N3 b; D  a
  32.             {
    ) j! h3 b3 D7 p# p
  33.                  LED1(1);
    $ B. l( e, [# q% S& }5 a1 w
  34.                 Delay();# S& _5 N: S" R
  35.                 LED1(0);
    + g1 f; z' y- Y& p
  36.                 Delay();
    ; c/ b' w4 I! |5 f, b* X
  37.                 break;( i4 g$ E9 w1 U  O) Y0 l
  38.             }
    ! L: n6 r8 }  C* C+ T
  39.         }# w6 \5 H0 U$ T8 a
  40.     }
    & }! s3 @( g! ]% O! I) ~
  41. }
复制代码
% k6 t9 V2 m# c7 a. j5 p
没啥说的,就是定义了一个全局变量Flag,每次中断,都影响Flag的值,然后main函数判断该值,就这么简单。完了。

' m0 F+ b+ A' o+ |
转载自:鱼竿的传说
& X0 o, a: c( L; t3 R" x
赞 收藏 评论0 发布时间:2023-1-1 21:00

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版