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

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

[复制链接]
STMCU小助手 发布时间:2023-1-1 21:00
至于中断的什么优先级,什么优先级分组,使能之类的原理,就不再赘述。这里主要是记载以下如何使用中断,以及中断配置函数的实现过程,其中并叙述我曾经的疑惑和感悟。
7 d3 \  D$ V' O; B+ n: x3 J
  我的开发板里的中断例程是用按键控制一个灯亮和灭的两个状态。
: {: g1 b  u& x/ k* j0 @
  这个例程的实现过程如下描述:

2 D- e+ {/ V/ U+ N' r5 d
第一步,将一个I/O口配置成中断输入模式。
: L2 ?. m4 V' y
  这里需要注意的是,GPIO本身是没有中断功能神马的。如果硬要使他产生中断输入方式,就得将相应的端口映射到相应的外部事件上去。而其他外设是有中断功能的,直接使能/失能其中断即可,比如USART,直接开启其发送/接收中断,那么USART也就相应的采取中断方式进行工作了。

0 l- u/ K6 X! k# t! |
  而这一点,是我开始很疑惑的:为啥GPIO口使用中断方式进行工作的时候就必须要映射到外部事件上去,而其他就不呢?百度网友的解惑是:比如USART产生的中断,是没有经过EXTI,而是直接将中断放入了NVIC;但是GPIO它作为中断源,是要经过EXTI的。仔细参看下面两个图,其实就会恍然大悟:

) c/ w( q9 X: f" R1 e
12220833-15331e0b20104519978a596626e2c248.jpg

* O) b6 G% T( [2 {0 p+ P' ?" l) m
12220855-fccdc6e73b3649bea37705918f876b00.jpg
4 L9 ^* F5 i$ H$ S
这第一步就是作为输入中断源的I/O口的相关配置,例程库函数如下:
1 N* T& q$ G: d* Q
  1. void BUTTON_Configuration(void)
    ! Q- W. |" u) p9 F
  2. {" p% L4 P6 b6 U# f
  3.     GPIO_InitTypeDef    GPIO_InitStructure;
    & _* a! w& U5 R6 X# A
  4.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;( {  \4 P- O$ g9 N' U. Z- R6 ~1 Z
  5.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
    / H$ N) Q3 ~& \& x
  6.     GPIO_Init(GPIOD, &GPIO_InitStructure);
    3 ~* H7 S) L2 Q7 o) h
  7. 4 H3 J7 b. p8 T7 v7 L0 q) Q# K8 m
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);' o, a" l. K) x0 X
  9.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
    - {5 N; |) v. z0 H
  10.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12);+ r% W& f. x2 a- c0 S
  11. }
复制代码
; X9 F+ \3 }  p) f
因为我板子上的例程是按键输入中断,所以函数名字就写的按键配置吧;
0 J& b+ ?/ F0 H  ?$ V+ N: [
3~5行,就是gpio口的普通配置,学习单片机开天辟地,就先是gpio口,这个没啥稀奇的了,没啥可说的了;我的板子上是PD^11,PD^12两个端口作为中断输入的。
) g9 K7 Y. Y( _5 r4 c
8行,注意这个时候,要使能GPIO口的复用时钟功能。
* F0 `1 V1 W7 s! }0 b
9~10行,就是将PD^11,PD^12映射到外部事件线上去。在keil中跳转到其函数实现中:

6 v+ Z0 G$ `+ z/ M& |5 o  e7 N$ a
  1. void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)! n: L( z+ h# O
  2. {
    ( ~3 S# [. K# E! p+ o: T2 f; N
  3.   uint32_t tmp = 0x00;  z" f" ^5 c4 D/ z
  4.   /* Check the parameters */
    - f' r% s0 J9 ?+ G5 x) B
  5.   assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));1 i. j& y" @: L1 f; w" ?' t
  6.   assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));/ Z# [9 A. K3 o) q

  7. ; U( \, X+ J$ @
  8.   tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
    5 n$ j( v" n( J5 k3 j% Q
  9.   AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;: v& U5 E* p4 y  r& w9 L2 }+ g
  10.   AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
    # u, P& `6 ^* t9 [- O" ~1 p1 b
  11. }
复制代码
$ n5 u& A9 E& D4 v. G) d6 N
5~6行,用库函数的都知道,就是两个宏定义,起到的作用是对相关的数据神马的进行正确性检查。

8 s( z( q, D* K
8行,GPIO_PinSource这个是外面BUTTON_Configuration()调用GPIO_EXTILineConfig()时传的参数。可能都不知道8行这个式子为啥要这么写。先看看我例程中是如何传的参:
( ^/ z/ Y1 I& _$ F% T, B) ~' P
  1. GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
复制代码
; l. j/ V- F5 q' K' K3 G1 t& V3 ?
也即是GPIO_PinSource <==>GPIO_PinSource11;那么GPIO_PinSource11是个什么东西呢:官方库已经这样定义了:
  1. #define GPIO_PinSource11           ((uint8_t)0x0B)
复制代码

! F8 Y: x8 A; ^  I' ?
如果按照我的例程,8行这个式子中,tmp == 0x0F << (0x04 *(0x0B & 0x03)==>tmp = 0x0F000;先不管这个数字是个啥意思,反正它就是个数字,它其实是为了第9行寄存器的值服务的-->既然如此,如果用寄存器写的话,我可以直接给寄存器某个值,何必要山路十八弯呢?当然,库函数,是有通用性的,就像一个数学公式的作用。
+ U6 [1 p% x3 T2 a+ G$ u& F9 N3 ]
9行:AFIO_EXTICR:外部事件控制<配置>寄存器。在数据手册中显示,有4个,它们分别对应的是各个外部事件exit_x<x = 0~15>。每个寄存器对应4个外部事件,于是4x4 = 16。,注意数据手册中的编号是从1开始的,而不是0开始的;但是,MDK中是0~3的。于是这里把相关值带进来一看,第九行其实就变成了如下式子:

) X5 l& h0 @- a+ y
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 s9 f% G" B0 Q
这个样,9行以前的一切的操作,就是为了给该寄存器的某个位进行清零嘛,至于具体清哪一位,还得看你映射到哪一位。
10行:引脚选择了,现在就选择这个引脚是哪个端口的,我的是D端口,那么按照官方对D端口的定义如下:
  1. #define GPIO_PortSourceGPIOD       ((uint8_t)0x03)
复制代码
/ t1 Y$ ^$ W* m* }
AFIO->EXTICR[2]  |= 0x03 << 12;查看数据手册,恰好是设置成了D口的第11号端子上嘛。
- ]5 C* g5 c: Y+ W( s
于是第一步总结是:

; R5 H8 w) C& o% E3 a( q
1)外部事件寄存器相关位清零;

9 o. h7 ~7 x/ D8 b7 j
2)设置输入端子的编号

/ r5 V- ]3 ^8 O+ e- U: s( [% G0 J
3)设置端口编号
2 _. m& @+ h4 N5 H& u  g- t) P4 p/ P
注:一共有A~G个端口嘛,而每个端口上又有N多端子,这个参看GPIo那章数据手册。
2 o/ P# `2 r7 J
悟出:库函数确实方便,具有公式效应,但山路十八弯,在这里,该例程中,要实现该功能,用寄存器,就两条语句嘛:
  1. 1 AFIO->EXTICR[2] &= 0x0FFF;2 c: K: L. ^/ ]+ f* a; k
  2. 2 AFIO->EXTICR[2] |= 0x03 << 12;
复制代码
1行:12~15清零
2行:0x03:表示是PD端口 ;0x03 << 12:表示在AFIO_EXTICR寄存器中的第12位开始写入0x03这个值,而括号中的2,说明是第三个寄存器,这样一组合,恰好就是PD^11了.描述起来一长串,如果看对照个看数据手册的话,就一目了然了。而这里唯一会让人凌乱的是:这个寄存器在数据手册上的编号和MDK中的编号不一致。自己在细读了<<stm32不完全手册>>才发现这个问题,开始可是百思不得其解呀。
7 L( X# }9 o, ?3 H  X) o* b+ V
总结下第一步要做的事情:

0 S- d) ^5 }( m% O" W
1)初始化I/O口为输入;

' p" t* y" k" W) ]# o
2)开启I/O复用时钟,并设置外部事件映射关联。

4 A% v& M( Z( C3 W* Q2 E
6 [& d4 F$ d. x, a1 m
接下来是第二步:
  第一步是将外部GPIO口映射到某外部事件上去。那么接下来,就该对该外部事件进行配置了,包括外部事件线路的选择、触发条件、使能。这里需要理解清楚的是,GPIO口和外部事件是各自独立的,它们并不是一体的---详细理解第一步,将GPIO口映射到某外部事件,可以看出GPIO和外部事件这个东西是两个不同的东西,在这里,GPIO的映射,无非就是GPIO口搭了外部事件的一趟顺风车。也所以,外部事件依然是要配置和使能的,不能说,将GPIO口映射到外部事件就可以产生中断了。
" b; t/ c9 [! x: C+ z7 r
  接下来看看例程中外部事件的配置函数:
  1. void EXTI_Configuration(void)/ k- q0 P6 l" \6 h
  2. {: l7 k6 u! c8 `, ^
  3.     EXTI_InitTypeDef EXTI_InitStructure;
    * @: g/ D8 C" F# N: N/ w- R" ~: r3 u4 Z
  4.     /*PD11外部中断输入*/
    2 g" y9 e! S5 k
  5.     EXTI_InitStructure.EXTI_Line = EXTI_Line11;
    & G; S! K; J. T9 l7 G' S
  6.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;1 W: U$ F2 z/ ~7 C3 u$ N6 _+ ]
  7.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;& r$ e7 [' a* }. d) J5 m
  8.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;
    * u& X- B, K9 m; J' k5 d) B
  9.     EXTI_Init(&EXTI_InitStructure);: Y: `+ g+ w$ ^& x
  10. 7 d1 z0 `4 P& R
  11.     /*PD12外部中断输入*/
    ' c0 [% _# N4 B8 H5 l
  12.     EXTI_InitStructure.EXTI_Line = EXTI_Line12;3 D1 x. w- |/ K) u8 @3 M$ v
  13.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;2 l  R: H3 w: q: g1 M
  14.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    * x* S& [3 z: P
  15.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;& y3 @* I5 J4 e+ i) K* N' r- [
  16.     EXTI_Init(&EXTI_InitStructure);0 C# U! H% N' c- h% u
  17. }
复制代码

/ Q) H" T% V4 U% z2 L- i$ {( `. o
可以看出,5~8;12~15行,无非就是在填充一个接头体。而真正实在的是9、16行:它们是外部事件初始化函数,把前面填充的结构体地址作为参数,传进EXTI_INit()。
% |6 r, [& r/ y- r" a- Z6 z5 _
在MDK中右键EXTI_Init()跳转到该函数的实现中去,代码如下:
  1. void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
    7 q9 T% V9 q. c! k
  2. {
    # q) L% }: z' I2 ^
  3.   uint32_t tmp = 0;; g; l& S: x+ ~' p, e5 j
  4. 7 v8 h6 B, m& D9 q6 ]. n
  5.   /* Check the parameters */
      b) T7 @" |+ Q' q
  6.   assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
    6 l( e4 E' }# `6 f- p% F4 z
  7.   assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));3 @) W9 B% Y' U( q9 z+ n% F
  8.   assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));4 q3 J. ~1 M1 |$ J5 j, D5 }' h
  9.   assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));: R* A- Y2 _' k. f3 {( G  T

  10. ! L6 i% q' x- l- U' u
  11.   tmp = (uint32_t)EXTI_BASE;
    ! [. {. l. T# M
  12. ( M' C& z: J3 K: ^
  13.   if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)* d. s. A' I& B3 d$ t
  14.   {4 n. C% V: s/ |" n* V2 L2 G
  15.     /* Clear EXTI line configuration */3 Q3 V; V# X5 S& w1 w& q
  16.     EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;
    9 y' P$ Q  o2 c( s* M) d' l% G$ Q1 G5 O
  17.     EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;7 E& T% S. {3 M6 o6 N
  18. 1 a8 m8 l# [; u/ ~, \+ |" I
  19.     tmp += EXTI_InitStruct->EXTI_Mode;9 O: G1 e. ^! i+ i9 v' N

  20. 5 w$ O, G0 t7 p4 ]$ U- ^2 `* H- J' Z
  21.     *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    + w7 h9 b: }# l; j6 {

  22. ( R1 y: g  e$ p! ^2 q
  23.     /* Clear Rising Falling edge configuration */
    7 Z) A4 S4 I0 e' o8 T
  24.     EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;
    " r! q4 s. k2 S1 V5 J6 e  o
  25.     EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;
    + U# ^# B- ~' ?2 }
  26. % {5 F. v# ]' c! u1 b; E
  27.     /* Select the trigger for the selected external interrupts */
    + v* I0 y# x$ O) ~# Q
  28.     if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)
    7 C* b9 G, j8 K
  29.     {
    / d! U* M/ x: k" Z# @$ j; S
  30.       /* Rising Falling edge */
    * B( j0 y7 G! f
  31.       EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
    7 E3 Z; d. c1 I/ J+ T, ^' E
  32.       EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;3 i$ z* Q/ @/ i( h9 n. v
  33.     }8 L% A$ G- e$ K7 P8 R9 H
  34.     else8 g2 W, V6 ]' U8 v8 r
  35.     {
    * P: U' L1 G9 p6 S
  36.       tmp = (uint32_t)EXTI_BASE;
    3 s9 ]( J3 }# Z$ |: M& o* Q
  37.       tmp += EXTI_InitStruct->EXTI_Trigger;
    & ?& x& q5 l9 \. C' i' R

  38. * Q/ r4 F9 r7 X# c, O3 r
  39.       *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    3 X' E9 K0 O: x/ f0 y
  40.     }
    - U- J7 I  t) F0 @
  41.   }
    , K- g* [" a7 t  M# o* F4 r
  42.   else  B( l5 B8 \% G5 C1 ~1 E# `" \
  43.   {
    - K2 B  E7 k! H  I$ J
  44.     tmp += EXTI_InitStruct->EXTI_Mode;( Z1 d, o8 i, A& ]/ ^) e& A2 j

  45. 9 {3 x( W$ N  V
  46.     /* Disable the selected external lines */
    ; w$ ]& u8 n0 G- \* o. n/ `/ F
  47.     *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
    * H4 j% Y, l9 K$ ~  d$ ^4 W
  48.   }
    1 O& q  {3 @  B- {/ W  o
  49. }
复制代码
+ ~6 e& {: [8 V- A& u" n
第11行,就是存储了一个地址值。可以先看看它们是怎么定义的:
  1. 1 #define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */, N8 h) j( V3 Q0 f2 x8 G( j  c
  2. 2 #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)5 r1 l0 D1 F5 B, N3 U' `
  3. 3 #define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
复制代码

  F! n% a3 c4 [0 X5 [) Z
如果对stm32框架掌握的足够熟悉,这里一眼便知端倪:GPIO口是挂在APB2上的,APB2是连在AHB总线上的,AHB再连到总线矩阵上的,环环相套,牵一发而动全身。附图如下:

. K( v! v4 s4 p5 O6 F2 A. |+ N
13212932-0f749bf04f4f4399b93997b2e4089589.jpg
& X4 l$ `* k5 r! o$ U
第16行、17行,分别是EXTI_IMR中断事件屏蔽寄存器和外部事件屏蔽寄存器,相应的位为0的时候,它们屏蔽相应线上的中断/事件请求。带入例程中的值算算
  1. 1 #define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */, o% q; c4 k$ v2 e3 |6 v1 c- e; k  S3 y6 x
  2. 2 #define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
复制代码

- j9 v% x# s0 M- v: x( n
先将事件11的值带入下面两个式子,换算如下:
  1. 1     EXTI->IMR &= ~0x00800;4 l  i. d* X* Y' `! C- I+ ~$ `+ Q8 f
  2. 2     EXTI->EMR &= ~0x00800;
复制代码
表示,将寄存器EXTI_IMR的11位清零,将EXTI_EMR的11位清零<下标从0开始>--意思是,将先关闭11号线上的中断/事件请求功能:有个原则是,在配置某一线上的中断或者事件之前,先将该线上的中断/事件清零,官方库写的比较严谨,所以这里先清零。那意思是,接下来就该对该线上的中断/事件进行配置了。
; _1 j/ }- {" V( B( \& i" N
且看上面外部事件初始化函数中的31行和32行,EXTI_RTSR是上升沿边沿触发寄存器,EXTI_FTSR是下降沿边沿触发器,这里一看便知,是在配置边沿触发模式:边沿触发分3类--上、下、随便。当然,在配置边沿触发的时候,边沿触发器相应的位也应该清零,这在该函数的24、25行已经体现出来了。

2 u$ ^' T5 o- d6 q1 k" h9 [+ `
另外注意模式的配置,看该函数中的19行:
  1. 1 tmp += EXTI_InitStruct->EXTI_Mode;
复制代码
这个式子的表达的意思,接上文是:
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
司马昭知心,路人皆知:就是某个地址 加上一个值后,这个地址也就变成了另外一个地址了。但为啥要这么做呢?

3 c8 c) k, g- ~5 S- R7 }4 l. h
而外部事件中断模式的值定义为如下:
  1. 1 typedef enum- L3 o  o/ q4 |
  2. 2 {* }4 ?+ `! y5 m. C4 u
  3. 3   EXTI_Mode_Interrupt = 0x00,
    / U4 z- O% S/ r# E" }) v2 w
  4. 4   EXTI_Mode_Event = 0x04
    . i5 g2 b) \" B
  5. 5 }EXTIMode_TypeDef;
复制代码
9 {' J) @) \, S  F$ g
可能不熟悉的<包括开始的我>,会发现,tmp在开始就只是对寄存器清零使用了下,就没再使用了啊?可是在该函数中,为啥后面还有一串关于tmp的代码呢?
如果细读开始处赋值的意思就该明白了:是将某一个地址写入了该变量,那么在一定的条件下,该变量就相当于内存地址了嘛。注意是在一定的条件下,而函数中,也给出了这个条件,那就是类型强制转换。
请看39行或者47行:
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    % y0 V5 i8 v6 h! ~0 m. }6 Q% C' S
  2. 2
    9 b8 ?# r( K- C1 ~  i
  3. 3
    $ {: s" K. P! v3 Q. @  B
  4. 4 -----------------
    , H% A2 I" y. j0 m3 v$ [
  5. 5 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
复制代码
# ~0 t) ^, H! _$ `" N5 _
__IO 表示volatile关键字;*(volatile unsigned int *) 表示了什么?指针的前面加*号,表示一个具体的值,也即是某个地址上具体的数据。

! x1 G  f6 N5 s, ~4 I6 [
于是这里表示的是对某个内存地址进行操作,也就是向某个内存空间放入某个值,放入的是什么值呢:中断时间线的值;放入什么地址呢?
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
. a2 X" @* g& j+ y7 G  Q/ I- G% {
注意,如果边沿触发不是随机的话,还要加上边沿触发寄存器的值。

1 d' ~% D+ M% w4 I) ?
由于以上几步开始没有理解,特别是我最后没有把中断事件线的值写入内存空间,导致我的第一次按键中断实验失败了,而且还不知道错在哪,在分析了官方代码后,方才知晓。

$ R# A6 B" P" h
到这里,外部事件配置就完成了,可能细心的人会发现并没有使能外部事件/中断,是怎么回事呢?请看看上面tmp所代表的的内存空间地址是多少,然后对照着数据手册上查看下事件/中断屏蔽寄存器的地址是多少,再看看下面这句代码最后的值是多少,就一目了然了。

2 J1 ~# H8 [' S5 D$ J- x% o) I( r! y8 v0 O
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
复制代码

: F3 u. S5 ?7 I& c$ B6 m! Z8 n  n2 J
总结下第二步:就是配置外部事件的模式、触发条件、使能外部触发。而在这个过程中,注意先将某功能寄存器相关位清零后再写入值。
) E* W% W; M4 y4 }
这个过程用不写成通用的库函数的话,那么可以用下面代码代替:
  1. #define tmp  (*(volatile unsigned int*))0x40010400
    ) H; X3 ^' z& r0 _7 Y" }4 K

  2. ! I2 u' Y* K- t
  3. EXTI->IMR &= ~0x00800;
    5 S" K$ L+ q( T4 l- G1 w# }* u- b
  4. EXTI->EMR &= ~0x00800;
    5 D: D. R' B6 _( P) G7 E! A: B# M) Y
  5. EXTI->RTSR &= ~0x00800;
    4 _+ V* E4 Y8 k) P
  6. EXTI->FTSR &= ~0x00800;
    + e5 I2 f3 \, M+ M5 r! H9 g
  7. EXTI->RTSR |= 0x00800;//如果配置成上升沿触发的话
    - n: s! }* {8 g! p/ t6 Z
  8. tmp |= 0x00800;//使能中断/事件
复制代码
9 [' z# d0 Q5 `& i0 k
第三步,现在就该配置中断了。也即是配置中断分组,以及中断优先级。当然,这并不是最后的工作。
# @; g- y, ?$ N6 C
中断配置函数如下:
  1. void NVIC_Configuration(void)
    ' W# ?! s+ D$ F. H/ b3 a$ L
  2. {
    $ G9 \* q: H9 [7 A: V$ I. U
  3.     NVIC_InitTypeDef NVIC_InitStructure;: B" v- U# T  @
  4.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    * V2 e3 b, ~/ ?
  5. ' g9 M, t; n1 l8 P  }2 Q! m, D+ C( o
  6.     /*外部中断线*/
    ' `0 l  ]9 A3 u* x6 r! |* x
  7.     NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ;
    " E) b* A  l; J3 A
  8.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ;  s  l+ `/ Y, V& N$ e
  9.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    2 u4 V8 F: C  A6 U7 }
  10.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ;6 J$ O" A9 d+ |4 a9 N# m" t
  11.     NVIC_Init(&NVIC_InitStructure);) o2 [: D. k' w4 n& ^3 B+ `1 F
  12. }
复制代码

9 m7 [+ i9 }. N2 x5 u
4行,是一个中断分组函数,跳转,看实现:
  1. void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)3 x6 u. x0 w& p
  2. {
    7 p; s5 {0 w# K% R& h& a" [
  3.   /* Check the parameters */
    ; J+ O! G+ z2 K$ r9 y6 {
  4.   assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
    3 o1 x% J8 t/ C" R
  5. & U  z* S  ~! J
  6.   /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
    / n/ F& b8 M8 t+ ~4 H) i5 }/ B5 x) ~
  7.   SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
    1 C' S0 P+ M. [; a
  8. }
复制代码

% u* J, r0 o$ O" Z; i
要查的SCB_AIRCR应用程序及复位控制寄存器的话,手头就必须备有<<xxxM3编程手册>>,<<xxxstm32数据手册>>中是没有这个寄存器的,当然还有许多都没有,比如滴答定时器等等。

1 I8 M) ~  p$ T9 s7 E) T
其中 AIRCR_VECTKEY_MASK 就是一个钥匙,在改写SCB_AIRCR中的值的时候,必须填入这个值,否则修改无效。AIRCR_VECTKEY_MASK = 0x05FA0000;
% y5 u; s, y. _+ I
而后面就跟着组的编号,注意这个编号不是简单的就是0,1,2,3,4.不能就这么简单的写入这个寄存器了。
2 E2 z5 G* z, e: ~5 U% {
注意这个寄存器的名字,它本身并不叫中断分组寄存器,而是借用了这个寄存器的某几位来进行中断分组--换句话说,这个寄存器可能要实现多种控制功能,而中断分组功能是在其中的某几位:当然,编程手册上说明了,是该寄存器的8~10位来进行中断分组控制;所以,组号需要进行位移到8~10位上来。如图:

' |0 ?4 Z6 S! G6 J4 N7 W) B/ n
13232035-61d308ebf03a4d3d992a918904467d45.jpg

/ D. p/ u% {( O2 I4 Q
<存疑部分>

. X" m" f$ H. j2 A" _% x
当然,该函数中的值,也就影响到下面中断优先级的配置。在MDK中跳入NVIC_Init()中,看其实现过程:
  1. void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
    8 S- I0 o) J' N# [4 g3 V
  2. {0 ?1 S& t' Q' n% m0 y+ [
  3.   uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;# b9 @1 {! u; @
  4. ( b3 A4 Z7 O0 h2 \
  5.   /* Check the parameters */
    4 D9 O6 W  s, v; y$ @9 [
  6.   assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));. N1 `9 @% v0 K2 f- Z
  7.   assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));+ M+ u/ }& }5 w$ r* W* N- c
  8.   assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));/ Q- L3 [! q  `3 ^. W) W7 ^
  9. & {. w3 D+ M% y3 m+ C
  10.   if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
    5 w5 [' [8 C2 q/ }! P% ?
  11.   {
    7 i, K) ~, K+ x" R( M! q% |; r2 C$ a
  12.     /* Compute the Corresponding IRQ Priority --------------------------------*/
    $ F1 W# `5 g, S$ o1 I, u
  13.     tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    3 b3 A% u- P/ s6 T, N, {# E
  14.     tmppre = (0x4 - tmppriority);
    5 y' C  n' I: y/ ~) b: u
  15.     tmpsub = tmpsub >> tmppriority;7 w- u. R$ V5 e" k$ k

  16. , t9 [* L7 I' F; u9 E1 D; x0 S6 c
  17.     tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;/ m! C( N/ Y' _9 c1 D& \
  18.     tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    - m3 K2 m; W7 y" L/ I* {& K
  19.     tmppriority = tmppriority << 0x04;
    3 l+ P1 B0 E' @+ |2 Q1 L: S
  20. ; Z/ O( \8 h% B
  21.     NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;8 m$ O+ C3 P0 }4 y

  22. . ?& G1 [& O' U" k" N. G+ G! j6 J5 a
  23.     /* Enable the Selected IRQ Channels --------------------------------------*/' t1 @; A* o, Q8 L- `
  24.     NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =# C: b/ w+ [5 b2 D/ p5 u
  25.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
    4 A* l1 X9 _. P! Z  E% e6 x8 I
  26.   }
    5 ^1 s$ a9 o$ {+ j& `
  27.   else
    7 l0 p; W& D5 P( f3 \
  28.   {1 L$ H7 ~/ s6 n- ^$ y$ B
  29.     /* Disable the Selected IRQ Channels -------------------------------------*/1 K% u& p6 f' d1 p/ Q
  30.     NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =5 d* i$ F2 O" J$ a* i& Q
  31.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
    " A: }% b1 E; V2 c' a
  32.   }
    ; v5 H3 A, _1 K
  33. }
复制代码

2 B' Z' Q$ V! c2 n8 H* o: k
该函数中的第3行,有3个临时变量,分别是:优先级组--这个组的意思就是中断分组的那个意思,至于是否是那个值,看下文解释;抢断优先级;亚优先级。注意亚优先级初始值是0x0F--具体是啥原因呢?且看下面代码。

7 N6 u& h& s7 R- }8 l
第13行,先取出中断控制复位神马神马寄存器的8~10 这3个位上的值< SCB->AIRCR& 0x07>;然后经过一定的算法得出中断组号:假设SCB_AIRCR的8~10位为0x07,按照上面图来说,也就是第0组,那么按照它里面的这个式子来算,恰好结果tmppriority = 0;至于具体为啥会ST会想到用这么个式子来得出组号,我现在只可意会。
; B" Y1 s2 N) H; a  P
那么,假设是第0组,也就是tmppriority = 0;那么按照中断分组中,第0组的抢断优先级和亚优先级的规定来说,是全4位亚优先级。那么14和15行就很容易明白了,它就是在设置抢断优先级和亚优先级的比例位数。

0 b) g$ U" J* _; H
那么17行,就是像临时变量中写入抢断优先级的值;

) J2 p" n0 e1 i" d' J
18行,就是向临时变量中写入亚优先级的值;注意抢断优先级和亚优先级一个在先一个在后,一旦分组一定,那么它们是会乖乖呆在自己位置上,不会去占用别人的位置的。

* f" x% R' s) T& Z
21行,就是把优先级配置值写入NVIC_IP寄存器,它是NVIC中断优先级配置寄存器,其定义在<<xxx编程手册>>中,同时可参看<<xx不完全手册>>。
5 G/ _( Z' {2 i
在我例程版本库中,对该寄存器的定义方法是:

& C$ [! `' ~$ J
  1. 1  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
复制代码
3 k- {" h9 E5 V
如果带入例程中的值来算:EXTI15_10_IRQn 的中断编号为40,即是:

% f* q$ t/ |( [
  1. NVIC->IP[40] = tmppriority;
复制代码

# U+ i# b, y& g: |
意思就是向相应的中断上赋予你的优先级配置值,这里特别要注意19行,优先级的值还向左移动了4位,这个是为啥呢?无法回答,带入例程中的值算算:我例程中是,分在中断2组,抢断为0,亚优先级有1,则带入:
  ]& ~0 l0 c! v  C
  1. 1  tmppriority = 0x10;3 i' _$ D: Y* }3 X
  2. 2  NVIC->IP[40] =0x10;
复制代码

- }& J8 E) E  ~7 R
也即是IP[40]的第5位置1,可能还是无法理解,再次参看正点原子的书,得知,中断占IP寄存器的8位,但是只是用到了其高4位,而我们在设值的时候,也即是设它高4位的值。那么就符合这里的情况了:抢断优先级为0,亚优先级为1,组号为2。 也退出,亚优先级和抢断优先级一共4位,抢断处于高位。这4位中,它俩怎么分,就是一个此消彼长的情况,视不同的组而定了。至于组合优先级之间的对应关系,上图已经清楚的解释了。也即是SCB_AIRCR 这个寄存器处理的事情了。当然,这些寄存器,在<<xxx数据手册>>中也许是找不到的,而在编程手册中去找。

% S% B+ y( \* o2 r% [2 ^
最后,补充一点就是怎么查看EXTI15_10_IRQn 的中断编号,这个数据手册上有,当然,官方也在库中给定义了。数据手册部分截图如下:

, O2 d! _$ K5 z) }8 X3 d/ L
14225347-87ca63c19bd744138d5eac4912b5b536.jpg

# F( k+ n2 V1 ~! _. `$ v; X8 g
14225324-690e8471b9a84e59ba7ada9ef0a55e73.jpg
1 I  [/ F" I, J" y8 E5 R- c6 T# B
其中EXTI15_10中断的位置在该向量表的第40号位置,所以刚才上面的优先级寄存器引脚的值就是40,至于具体为啥要写成IP[40],而不是直接IP,这里有个小小编程技巧,是数组的妙用,属于C语言范畴了。
# j7 o3 v2 }" r0 d0 ^
接下来的第24行和30行,是第一对相反作用的寄存器,即中断使能/失能,注意,别以为写0就可以失能,stm32不认为那样有效,必须是向失能寄存器中写1才可以的。同时注意一点是:这个寄存器定义成了数组,那么数组中就应该有N个元素。它这里就是某个元素相应的位,管理一个区域的中断。这里说复杂了,编程手册上已经讲的非常详细了。另外,它的定义方式如同<<xx不完全手册>>上所讲解的那样,但是定义的模式并不一定就完全相同:世界是变动的。

8 j! b( d  O4 _: _" i3 {
好吧,中断也使能了,总结下第三步:
/ J5 p) P' M$ M- V
1)中断分组:注意是在SCB_AIRCR寄存器的8~10.分组的同时,也就影响到了后面优先级的分配。

8 g- S! F0 U. `6 w
2) 配置优先级:其实质还是在SCB_AIRCR寄存器的4~7位,前面8~10位是什么值,那么这里4~7位就该怎么分了;当然,最后的配置值是写入了NVIC_IP的寄存器中了;
% Y7 O0 K% s7 q& e. c$ O5 B
3)使能中断:在NVIC_ISER寄存器中,注意该寄存器定义的方式是数组,而非普通的变量,理解的时候,要理解一个数组是由N个变量组成即可<非严谨>。
  g2 f7 J3 b" W$ D# l
第四步:中断服务函数:
2 j. o- U' z1 y( w
  九九归一,终于来到了最后一步,也是所有中断必须要经历的一步。
% z/ X: K. d. [
  这里有个重点必须注意:所有中断服务函数的名字,ST官方已经取好了,而且还放在了中断向量表中了<也即是启动文件里>,如果你不自己写启动文件的话,那么你的中断服务函数的名字必须和ST官方的一样,不然,一个中断来了,找不到负责任的函数,它就只有悲剧去了。

* p2 {1 P, B2 x
看看例程吧:
  1. void EXTI15_10_IRQHandler(void)4 d, |4 a4 X2 g4 q3 }5 @) @" P3 t
  2. {
    6 ^* k, m2 s! t/ ?/ b- D
  3.     if(EXTI_GetITStatus(EXTI_Line11)!= RESET)1 U% i: z; b7 J) U$ w7 ?* P8 V
  4.     {
    2 {  r' y/ q/ @0 l
  5.         EXTI_ClearITPendingBit(EXTI_Line11);# e6 m/ r1 h- k3 y9 I: V
  6.         Flag = 0x01;! N3 \- ~( f- l2 c0 M
  7.     }3 O# l2 i4 J$ e' \; _

  8. 3 k, ^9 {( c. G" b' j; l. N
  9.     if(EXTI_GetITStatus(EXTI_Line12)!= RESET)
    : A8 Z" f% \  [
  10.     {& _+ Z# N% j  C* N0 g2 @+ U
  11.         EXTI_ClearITPendingBit(EXTI_Line12);
    2 X2 p8 B8 u; E, h/ B
  12.         Flag = 0x02;% T; e/ Q8 F' W! W0 r
  13.     }, M; d7 F4 J. U1 E1 O4 l
  14. }
复制代码
8 {) R' U6 g$ h, D3 Y- L
  1. int main(void)
    5 P* ~/ x* Q: ~6 s# R
  2. {& K& h7 x! f! L$ C; z1 e
  3.     /* Add your application code here3 O, ~7 r+ D! C8 w) g
  4.        */
    " d/ X9 q" e/ v, u
  5.     SystemInit();              /*系统初始化*/  v, ^6 Z. ?8 j& _% A
  6.     LED_Configuration();
    ! }4 A9 [1 \! T9 |# W4 }& @* v  \& Y
  7.     BUTTON_Configuration();
    5 Y* n$ D/ J; g# Y
  8.     NVIC_Configuration();
    : V! B% I* h) X0 K
  9.     EXTI_Configuration();) j4 N& H* @* @, X* F# R
  10.     /* Infinite loop */! ]: @  L2 ?. t, V8 S1 [
  11.     while (1)6 e8 a. z! }6 r  {. A
  12.     {- p/ U: W* X3 a# |9 x2 Z5 J6 r& x  G
  13.         switch(Flag)2 s- X; c, t, ^. ~. G
  14.         {
    ! `2 m  u* p: s+ Q
  15.             case 0x01:" d' ?: T0 \* |4 m
  16.             {0 h' a( O2 f, l; }; M# W- e
  17.                 LED2(1);
      m  G$ m3 C7 }7 ^9 l
  18.                 Delay();# l# a: X) Y- z& K
  19.                 LED2(0);
    ! v3 }8 u( C' {$ S; A
  20.                 Delay();
    0 O8 D1 x& J3 y% ?" Q* f9 w
  21.                 break;$ f# R: ]2 s, |1 U' B* [7 Q/ Q
  22.             }
    1 ]' E- l+ X+ r+ V$ z8 D
  23.             case 0x02:2 h2 m) ~* S% s% u
  24.             {9 u1 Q/ f% p' f4 c; ~% x0 b% D
  25.                 LED3(1);
    8 j# }6 |8 K6 l$ D8 y. n. m9 T! D
  26.                 Delay();
    3 {; m; q, m8 x6 z7 m
  27.                 LED3(0);; c0 h% z% b$ P2 e" O0 F
  28.                 Delay();% D% k3 o0 ~! y4 @# T4 m. b
  29.                 break;) C# |' \2 P" a
  30.             }
    ! X- K8 L' `% Z
  31.             default   :
    2 l  E8 O* ~8 J- t- [( h
  32.             {
    $ `; f4 ^0 [. K' Q  v- B% D" R
  33.                  LED1(1);
    ; z& N: I/ a* F
  34.                 Delay();
    4 G1 j% }1 G, T; a5 l
  35.                 LED1(0);: l: k9 X! Q) a9 z
  36.                 Delay();
    * s( {. K* `) k7 Y; t
  37.                 break;
    $ q( H. _& z4 S' [$ i3 E
  38.             }& W" i8 ~3 k5 {- G1 v1 e& i4 h
  39.         }8 n; \0 Q0 ?% N: G% b4 `1 f7 @) Q
  40.     }
    + X# v$ p2 g, Q$ X( T5 y2 A& }% z
  41. }
复制代码
: h, s4 {. l" n  t) e
没啥说的,就是定义了一个全局变量Flag,每次中断,都影响Flag的值,然后main函数判断该值,就这么简单。完了。

, n2 V  s9 c9 T; e5 F# k
转载自:鱼竿的传说
$ N  _/ p6 h: s% ?) [+ Y
收藏 评论0 发布时间:2023-1-1 21:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版