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

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

[复制链接]
STMCU小助手 发布时间:2023-1-1 21:00
至于中断的什么优先级,什么优先级分组,使能之类的原理,就不再赘述。这里主要是记载以下如何使用中断,以及中断配置函数的实现过程,其中并叙述我曾经的疑惑和感悟。
! e& D$ r7 n# Y% Y
  我的开发板里的中断例程是用按键控制一个灯亮和灭的两个状态。

- T; t) s# H. J% z2 I- \6 T
  这个例程的实现过程如下描述:

6 H: o& b: V, ~3 o5 s
第一步,将一个I/O口配置成中断输入模式。
+ C6 m; V2 D. ?- @
  这里需要注意的是,GPIO本身是没有中断功能神马的。如果硬要使他产生中断输入方式,就得将相应的端口映射到相应的外部事件上去。而其他外设是有中断功能的,直接使能/失能其中断即可,比如USART,直接开启其发送/接收中断,那么USART也就相应的采取中断方式进行工作了。
5 ~8 ?# ]1 b  V* o, W5 g7 w) J
  而这一点,是我开始很疑惑的:为啥GPIO口使用中断方式进行工作的时候就必须要映射到外部事件上去,而其他就不呢?百度网友的解惑是:比如USART产生的中断,是没有经过EXTI,而是直接将中断放入了NVIC;但是GPIO它作为中断源,是要经过EXTI的。仔细参看下面两个图,其实就会恍然大悟:
# N* `" s& O% v, L# E8 \
12220833-15331e0b20104519978a596626e2c248.jpg

6 Y+ j  k5 g, W5 g
12220855-fccdc6e73b3649bea37705918f876b00.jpg
' |. n0 [% I2 x
这第一步就是作为输入中断源的I/O口的相关配置,例程库函数如下:

% Q; \3 c) b- i; q, _7 B
  1. void BUTTON_Configuration(void)
    7 p9 P- {1 k6 v6 g2 n2 }
  2. {
    * X7 |# w8 V) w4 h
  3.     GPIO_InitTypeDef    GPIO_InitStructure;7 g3 b1 G8 S- h9 Q' }6 X  x
  4.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;; \; Q2 `0 ]' y5 h8 T- U1 o: J
  5.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
    : G, d- h7 q2 p
  6.     GPIO_Init(GPIOD, &GPIO_InitStructure);
    9 Z' i+ g$ N6 p5 ~9 u8 U
  7. $ _9 W0 C: B/ y) a9 W2 D0 p7 g+ Y
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);' o9 F* j  ]  M0 _& F
  9.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);& x' t/ _7 I- E' T
  10.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12);0 s1 n2 v& V0 z5 U4 h
  11. }
复制代码
$ w3 r/ v4 L4 p8 a
因为我板子上的例程是按键输入中断,所以函数名字就写的按键配置吧;
# l& P7 _' F4 A# f
3~5行,就是gpio口的普通配置,学习单片机开天辟地,就先是gpio口,这个没啥稀奇的了,没啥可说的了;我的板子上是PD^11,PD^12两个端口作为中断输入的。
1 Y' Y* d5 j" d3 S, C5 a
8行,注意这个时候,要使能GPIO口的复用时钟功能。

! j6 x; W; M# ^0 r; T. f+ M
9~10行,就是将PD^11,PD^12映射到外部事件线上去。在keil中跳转到其函数实现中:
/ B8 L6 V( m" l4 D
  1. void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
    6 ^+ |4 ?/ i8 c9 }# M
  2. {: E* ?2 }9 f9 A$ }# Z. @- F
  3.   uint32_t tmp = 0x00;5 X5 }0 r/ z( P# _1 V& x
  4.   /* Check the parameters */9 N% ?$ P5 e: }) M; G3 E
  5.   assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
    ' b6 {$ D3 f6 s  t( L
  6.   assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));4 A% G7 O& l. w9 j3 y
  7. ; w- V- c! N7 K
  8.   tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
    9 b- G) u- ~# p1 _3 s7 t
  9.   AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
    & M' i( V+ e9 U! ?  g9 J
  10.   AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));3 X! b7 _7 v" g( n$ h% g8 j
  11. }
复制代码

& o, T& }, _* e1 D3 G+ w
5~6行,用库函数的都知道,就是两个宏定义,起到的作用是对相关的数据神马的进行正确性检查。
& o8 J& {8 Z2 r* _$ s) |$ A
8行,GPIO_PinSource这个是外面BUTTON_Configuration()调用GPIO_EXTILineConfig()时传的参数。可能都不知道8行这个式子为啥要这么写。先看看我例程中是如何传的参:
1 h# h( W0 |# B0 v+ K! I
  1. GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
复制代码

- ~2 a  f& B$ z$ C( W+ F& n
也即是GPIO_PinSource <==>GPIO_PinSource11;那么GPIO_PinSource11是个什么东西呢:官方库已经这样定义了:
  1. #define GPIO_PinSource11           ((uint8_t)0x0B)
复制代码
2 k6 V6 B1 R* `
如果按照我的例程,8行这个式子中,tmp == 0x0F << (0x04 *(0x0B & 0x03)==>tmp = 0x0F000;先不管这个数字是个啥意思,反正它就是个数字,它其实是为了第9行寄存器的值服务的-->既然如此,如果用寄存器写的话,我可以直接给寄存器某个值,何必要山路十八弯呢?当然,库函数,是有通用性的,就像一个数学公式的作用。

- D0 Z8 f3 I+ S, `4 j# H0 E
9行:AFIO_EXTICR:外部事件控制<配置>寄存器。在数据手册中显示,有4个,它们分别对应的是各个外部事件exit_x<x = 0~15>。每个寄存器对应4个外部事件,于是4x4 = 16。,注意数据手册中的编号是从1开始的,而不是0开始的;但是,MDK中是0~3的。于是这里把相关值带进来一看,第九行其实就变成了如下式子:
) x; U- w0 F5 `
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开始的;
% n* [8 j3 r: D7 ]8 R
这个样,9行以前的一切的操作,就是为了给该寄存器的某个位进行清零嘛,至于具体清哪一位,还得看你映射到哪一位。
10行:引脚选择了,现在就选择这个引脚是哪个端口的,我的是D端口,那么按照官方对D端口的定义如下:
  1. #define GPIO_PortSourceGPIOD       ((uint8_t)0x03)
复制代码
. S- j# `: C& Q0 M) N- }7 R4 ^
AFIO->EXTICR[2]  |= 0x03 << 12;查看数据手册,恰好是设置成了D口的第11号端子上嘛。
  K% _3 Y+ f* u) l' l
于是第一步总结是:
" t7 x: p8 p, N$ e
1)外部事件寄存器相关位清零;

' F0 J" L; Y0 v( u9 L0 ^
2)设置输入端子的编号

5 q; V5 o2 y  \) Q4 j5 b4 o% [$ ]
3)设置端口编号

  g9 J# \' |) a+ v+ w
注:一共有A~G个端口嘛,而每个端口上又有N多端子,这个参看GPIo那章数据手册。
: Q& f9 y% N, U2 `. J9 q( `
悟出:库函数确实方便,具有公式效应,但山路十八弯,在这里,该例程中,要实现该功能,用寄存器,就两条语句嘛:
  1. 1 AFIO->EXTICR[2] &= 0x0FFF;0 h* L' H; C: n" v+ j9 i
  2. 2 AFIO->EXTICR[2] |= 0x03 << 12;
复制代码
1行:12~15清零
2行:0x03:表示是PD端口 ;0x03 << 12:表示在AFIO_EXTICR寄存器中的第12位开始写入0x03这个值,而括号中的2,说明是第三个寄存器,这样一组合,恰好就是PD^11了.描述起来一长串,如果看对照个看数据手册的话,就一目了然了。而这里唯一会让人凌乱的是:这个寄存器在数据手册上的编号和MDK中的编号不一致。自己在细读了<<stm32不完全手册>>才发现这个问题,开始可是百思不得其解呀。

( c/ g+ ?- F/ c2 h
总结下第一步要做的事情:
6 {  ]+ y7 l. j
1)初始化I/O口为输入;
& `1 Y* b6 @! v
2)开启I/O复用时钟,并设置外部事件映射关联。
4 ~0 y' r& u: y) n4 ?. O; [* N; H

0 P/ V. q) z& g: c! r! h* m
接下来是第二步:
  第一步是将外部GPIO口映射到某外部事件上去。那么接下来,就该对该外部事件进行配置了,包括外部事件线路的选择、触发条件、使能。这里需要理解清楚的是,GPIO口和外部事件是各自独立的,它们并不是一体的---详细理解第一步,将GPIO口映射到某外部事件,可以看出GPIO和外部事件这个东西是两个不同的东西,在这里,GPIO的映射,无非就是GPIO口搭了外部事件的一趟顺风车。也所以,外部事件依然是要配置和使能的,不能说,将GPIO口映射到外部事件就可以产生中断了。

. x! Q$ P  j. o0 r  i! E7 ?
  接下来看看例程中外部事件的配置函数:
  1. void EXTI_Configuration(void), {7 w1 Y: g7 j
  2. {- r. a8 R( S5 E* }: }3 b" J5 f
  3.     EXTI_InitTypeDef EXTI_InitStructure;! g* W' ]! P1 V1 X2 e9 s
  4.     /*PD11外部中断输入*/: i! @& `' U* r3 k- |) u& z! i4 g
  5.     EXTI_InitStructure.EXTI_Line = EXTI_Line11;! B! l( y6 \; J- H7 K
  6.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;0 Q! T5 m7 P% m
  7.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    % C; }" V7 m3 f: B- c$ k4 O( [
  8.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;
    0 `) A6 n% e& |
  9.     EXTI_Init(&EXTI_InitStructure);
    : n% F" i# p, Y( L2 e* J9 z
  10. + i# H5 Z% n# g
  11.     /*PD12外部中断输入*/3 y: o3 j2 G5 w! f6 Z) C
  12.     EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    " Z( ?! p8 f3 q9 }6 w6 }# V
  13.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    0 f# E4 V$ n9 N. D0 m- ]
  14.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    5 D7 Z! F# G% m- K2 ]2 r8 U
  15.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;7 k5 T, i5 V" d5 r: \! M: o( H/ X
  16.     EXTI_Init(&EXTI_InitStructure);
    ( w- b& K- G- b9 q$ Q
  17. }
复制代码
' _: ^& x: i9 j; j: [; @
可以看出,5~8;12~15行,无非就是在填充一个接头体。而真正实在的是9、16行:它们是外部事件初始化函数,把前面填充的结构体地址作为参数,传进EXTI_INit()。

$ n4 ?+ Z4 ~9 B+ O, u7 d
在MDK中右键EXTI_Init()跳转到该函数的实现中去,代码如下:
  1. void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)7 |8 B; `# N' B! r% t& [6 L
  2. {
    8 E5 B: ^2 U' V# y6 D3 u8 I/ `, T7 L0 s
  3.   uint32_t tmp = 0;
    % ?, v1 o  t+ W% C% M+ Y
  4. $ ]7 A$ g& }, E, K" `2 R4 D; A
  5.   /* Check the parameters */
    + M  V! H" X  `
  6.   assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
    # B# |% b  M, h# E
  7.   assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));2 M! Q" n( Y% r+ x, O+ k, \% |
  8.   assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));, w4 ]6 Q1 P7 s# S* Z
  9.   assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));" p5 j+ V6 O9 Q5 n# f
  10. : e; ~! t" x4 P1 Z. T
  11.   tmp = (uint32_t)EXTI_BASE;
    ; X1 r! ~8 B, L- |
  12. / i# V" ~- o2 \! u
  13.   if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)
    - }4 P4 G0 D0 K7 _; z& C+ ?
  14.   {% D( F5 r9 T6 g0 j8 z+ Q% s
  15.     /* Clear EXTI line configuration */
    4 M) M5 s) b0 p& V& e4 n7 o
  16.     EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;, H5 }' v( j) F7 c! T: I8 V5 v/ ?+ i
  17.     EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;
    . e* S1 v" e% w5 j* o
  18. 3 C5 a7 W! ^  r1 t4 Q
  19.     tmp += EXTI_InitStruct->EXTI_Mode;
    $ h* I- g$ H& s- s
  20. 6 l' o# d$ @4 l7 ?
  21.     *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    # q- }3 J& h& K1 l2 d8 \6 N

  22. 7 M4 A0 [: }- O1 C
  23.     /* Clear Rising Falling edge configuration */
    8 g0 i, {+ F8 n& Z- L2 M
  24.     EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;
    ) ~$ Q8 r! y7 [% \, ~9 Y
  25.     EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;
      T6 O. M" L! B2 K7 Z
  26. # u( u+ g6 f0 w7 h- Q! M; M$ y1 V
  27.     /* Select the trigger for the selected external interrupts */
    1 k; A' _9 q, ^9 G, [# v
  28.     if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)# J% B3 p" Z, S$ j9 ~# P0 v* E+ q
  29.     {. ]0 N$ G3 }+ s' z6 e6 z: a/ \4 T  P
  30.       /* Rising Falling edge */
    ; T8 K& D7 i4 x( f
  31.       EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
    # H$ W/ x! O6 u
  32.       EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;* W4 n* z, h3 B% |, o& U/ ~' r: ~; |
  33.     }
    & `& z* S; R9 u
  34.     else5 z9 ^7 W5 ]1 V, @0 i0 r) _, `! X. e
  35.     {" M  Q8 i# J6 y- P5 e8 a
  36.       tmp = (uint32_t)EXTI_BASE;
    , P0 [- g# z& F% l! w
  37.       tmp += EXTI_InitStruct->EXTI_Trigger;
    9 G& {. ^: S" x# Y

  38. 5 k  x8 I* R, x. A6 y) ]
  39.       *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;8 a# f, C- v9 I& ]: z: Y  }! H: b! v
  40.     }
    ( ~0 S. G! C* P7 \& W
  41.   }
    5 [+ @, s9 a& t7 P
  42.   else
    3 x( _5 w. j+ r5 m+ B- ^2 A  B
  43.   {% W; P1 D" m+ C. h
  44.     tmp += EXTI_InitStruct->EXTI_Mode;! C' p& `% E9 D/ \7 i
  45. 0 ^4 A- i: R6 i. u4 _
  46.     /* Disable the selected external lines */+ z0 N2 v* \' ~% {. m
  47.     *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;7 F/ y# H  [- s! V
  48.   }3 f8 w/ s7 y+ l; N/ b
  49. }
复制代码
! j1 A+ J- C8 q) E! f
第11行,就是存储了一个地址值。可以先看看它们是怎么定义的:
  1. 1 #define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */3 C) x) f. S! z4 s
  2. 2 #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
    ! E$ Z9 m6 g) }# [" x0 F' b& K6 {4 u
  3. 3 #define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
复制代码

3 }$ s5 S$ ^. ?+ M
如果对stm32框架掌握的足够熟悉,这里一眼便知端倪:GPIO口是挂在APB2上的,APB2是连在AHB总线上的,AHB再连到总线矩阵上的,环环相套,牵一发而动全身。附图如下:

* T. F% |2 I$ Y: T2 d% u1 [
13212932-0f749bf04f4f4399b93997b2e4089589.jpg

. M  w5 `" ~2 m  H2 N
第16行、17行,分别是EXTI_IMR中断事件屏蔽寄存器和外部事件屏蔽寄存器,相应的位为0的时候,它们屏蔽相应线上的中断/事件请求。带入例程中的值算算
  1. 1 #define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */
    ' `6 k. D/ u1 {. i( Q/ e2 J- |+ y
  2. 2 #define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
复制代码
* E& W# T% p' X
先将事件11的值带入下面两个式子,换算如下:
  1. 1     EXTI->IMR &= ~0x00800;+ M/ {5 q4 \3 C+ Z2 Q* T4 b5 b+ ?7 n5 S# ]
  2. 2     EXTI->EMR &= ~0x00800;
复制代码
表示,将寄存器EXTI_IMR的11位清零,将EXTI_EMR的11位清零<下标从0开始>--意思是,将先关闭11号线上的中断/事件请求功能:有个原则是,在配置某一线上的中断或者事件之前,先将该线上的中断/事件清零,官方库写的比较严谨,所以这里先清零。那意思是,接下来就该对该线上的中断/事件进行配置了。

+ p& K. h# W  i$ T* U& x! p5 O5 \
且看上面外部事件初始化函数中的31行和32行,EXTI_RTSR是上升沿边沿触发寄存器,EXTI_FTSR是下降沿边沿触发器,这里一看便知,是在配置边沿触发模式:边沿触发分3类--上、下、随便。当然,在配置边沿触发的时候,边沿触发器相应的位也应该清零,这在该函数的24、25行已经体现出来了。
2 `# F* u) j9 t) E' v1 u, t; p
另外注意模式的配置,看该函数中的19行:
  1. 1 tmp += EXTI_InitStruct->EXTI_Mode;
复制代码
这个式子的表达的意思,接上文是:
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
司马昭知心,路人皆知:就是某个地址 加上一个值后,这个地址也就变成了另外一个地址了。但为啥要这么做呢?
$ \( D* f3 V1 m/ ?
而外部事件中断模式的值定义为如下:
  1. 1 typedef enum( a. c1 u9 e1 E' g4 a9 c1 F* x
  2. 2 {. ]" a" w" O1 }6 z( ]) s
  3. 3   EXTI_Mode_Interrupt = 0x00,
    2 C2 q: F5 ^7 v( x
  4. 4   EXTI_Mode_Event = 0x045 c" v+ y" \( w' ~
  5. 5 }EXTIMode_TypeDef;
复制代码

0 |# ]" ^  x4 J% A
可能不熟悉的<包括开始的我>,会发现,tmp在开始就只是对寄存器清零使用了下,就没再使用了啊?可是在该函数中,为啥后面还有一串关于tmp的代码呢?
如果细读开始处赋值的意思就该明白了:是将某一个地址写入了该变量,那么在一定的条件下,该变量就相当于内存地址了嘛。注意是在一定的条件下,而函数中,也给出了这个条件,那就是类型强制转换。
请看39行或者47行:
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    8 P2 S9 b1 k" l/ K
  2. 2
    & p1 t+ C. V; c/ i& M, S
  3. 3 $ ~( s) K/ D! Y/ D) l1 v) P
  4. 4 -----------------9 t& J0 f, S! L) b
  5. 5 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
复制代码

4 I5 b& L% \/ _! k7 F* h$ h
__IO 表示volatile关键字;*(volatile unsigned int *) 表示了什么?指针的前面加*号,表示一个具体的值,也即是某个地址上具体的数据。
0 O* y" S+ e7 R( Z- C9 e% |
于是这里表示的是对某个内存地址进行操作,也就是向某个内存空间放入某个值,放入的是什么值呢:中断时间线的值;放入什么地址呢?
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码

! S* P1 E& t, [" U1 L
注意,如果边沿触发不是随机的话,还要加上边沿触发寄存器的值。
: q2 m9 ^$ Y! T2 w0 ~
由于以上几步开始没有理解,特别是我最后没有把中断事件线的值写入内存空间,导致我的第一次按键中断实验失败了,而且还不知道错在哪,在分析了官方代码后,方才知晓。

- @0 Z# E: w1 @' b/ u- T: B6 R
到这里,外部事件配置就完成了,可能细心的人会发现并没有使能外部事件/中断,是怎么回事呢?请看看上面tmp所代表的的内存空间地址是多少,然后对照着数据手册上查看下事件/中断屏蔽寄存器的地址是多少,再看看下面这句代码最后的值是多少,就一目了然了。
; a) U" @+ D) l
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
复制代码

3 S3 b1 u* t; ?& w
总结下第二步:就是配置外部事件的模式、触发条件、使能外部触发。而在这个过程中,注意先将某功能寄存器相关位清零后再写入值。

6 m6 a' F5 Y# _! ~; \
这个过程用不写成通用的库函数的话,那么可以用下面代码代替:
  1. #define tmp  (*(volatile unsigned int*))0x40010400
    0 t+ e1 Q" m3 c6 o( _9 R5 a

  2. ; a1 z' H8 _9 {# s; I9 d
  3. EXTI->IMR &= ~0x00800;' G8 ?2 X: U  L0 z! k5 R
  4. EXTI->EMR &= ~0x00800;
    - o% K; g6 `: S$ u# V2 S
  5. EXTI->RTSR &= ~0x00800;
    ! _4 D4 A) v! t( ~( s
  6. EXTI->FTSR &= ~0x00800;: w6 U% |& V& h1 p8 ?, D- X; C
  7. EXTI->RTSR |= 0x00800;//如果配置成上升沿触发的话
    ; a: U' b9 t2 @/ L
  8. tmp |= 0x00800;//使能中断/事件
复制代码
6 i9 }2 K* U! M% G7 }8 _
第三步,现在就该配置中断了。也即是配置中断分组,以及中断优先级。当然,这并不是最后的工作。

3 _! ]8 }, r- C0 M: _
中断配置函数如下:
  1. void NVIC_Configuration(void); o" z; D+ N2 P$ ^: B
  2. {/ q5 n- i2 X( G  [: Z, Q0 p
  3.     NVIC_InitTypeDef NVIC_InitStructure;
    - M0 `* o8 S  h8 m2 |! U
  4.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);4 \! b. w, k$ z* Y# B
  5. ) M7 ^7 |3 \# r" ~. O
  6.     /*外部中断线*// u0 }. ~# H, a- Z9 A
  7.     NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ;1 u1 Q6 a6 O% d, P
  8.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ;( a9 F9 S$ J! n7 e2 y
  9.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    : ~3 w1 q6 |; {; ]# o% z6 h- @
  10.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ;
    + \: o. _& ]7 T4 a
  11.     NVIC_Init(&NVIC_InitStructure);% s: X$ T0 C+ A0 s
  12. }
复制代码

" C9 t! E7 Q6 l5 D6 a& k
4行,是一个中断分组函数,跳转,看实现:
  1. void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup), ~! g, a0 X( m' }. o( u) n
  2. {
    - o( g% s- J& n  W* p" r
  3.   /* Check the parameters */
      Z2 F( R1 d- ~  E" o0 X
  4.   assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
    8 F4 u+ j* R3 {( e4 a' N: T

  5. ) t. w$ ?  J' i% d: }" b
  6.   /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
    + h+ ?" W& `+ t' a1 _% K- O2 v
  7.   SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;$ h9 E& i' T2 c5 k) u
  8. }
复制代码

8 d9 `/ I% {+ c2 A( c' @" ?/ ?
要查的SCB_AIRCR应用程序及复位控制寄存器的话,手头就必须备有<<xxxM3编程手册>>,<<xxxstm32数据手册>>中是没有这个寄存器的,当然还有许多都没有,比如滴答定时器等等。
$ L& L- h. q. H4 W0 v: \6 `  _
其中 AIRCR_VECTKEY_MASK 就是一个钥匙,在改写SCB_AIRCR中的值的时候,必须填入这个值,否则修改无效。AIRCR_VECTKEY_MASK = 0x05FA0000;

+ r4 o  S  m3 ~
而后面就跟着组的编号,注意这个编号不是简单的就是0,1,2,3,4.不能就这么简单的写入这个寄存器了。
' X$ O: r/ J% c6 o$ D# g1 `
注意这个寄存器的名字,它本身并不叫中断分组寄存器,而是借用了这个寄存器的某几位来进行中断分组--换句话说,这个寄存器可能要实现多种控制功能,而中断分组功能是在其中的某几位:当然,编程手册上说明了,是该寄存器的8~10位来进行中断分组控制;所以,组号需要进行位移到8~10位上来。如图:
4 t8 h% I; p4 s- t* m
13232035-61d308ebf03a4d3d992a918904467d45.jpg

6 S$ J5 }7 q& d/ a% E0 s, v
<存疑部分>
; C, ~9 {( E% ]4 h3 o' z$ p: k6 Z4 n
当然,该函数中的值,也就影响到下面中断优先级的配置。在MDK中跳入NVIC_Init()中,看其实现过程:
  1. void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)3 c) ~0 [) t8 I+ X% _' T
  2. {
    ) ~( D- w: _- F0 {2 y/ F; D( w: @
  3.   uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
    : A2 Q' }/ c3 B) P) w
  4. / y! \$ X7 O# z! H" R; P. f2 y
  5.   /* Check the parameters */. g8 ~! F1 Y" W% I" M. T* V' k
  6.   assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));- r1 a5 W: _7 ^+ @& i
  7.   assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));
    ) e$ u3 W( N- C  ?5 {4 B, y, d
  8.   assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));# @' W! a: I4 w% D6 @

  9. % C8 f8 y" s# w8 ?4 ~
  10.   if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
    5 p, |3 h3 v1 x2 \$ ]; N
  11.   {
    , A4 N. [' O# ^4 H
  12.     /* Compute the Corresponding IRQ Priority --------------------------------*/; R1 L& m" K% @# B
  13.     tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    5 ?4 l4 l; k. b% }
  14.     tmppre = (0x4 - tmppriority);
    4 M- F1 u) s9 p, o3 o8 M
  15.     tmpsub = tmpsub >> tmppriority;
    + O8 n' L% d* b0 g2 D2 c% ^" O" ^

  16. 8 k- g" h8 G& Y
  17.     tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;( `6 t; L; U( K2 ]- T
  18.     tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    6 w5 D6 C4 a; C" f
  19.     tmppriority = tmppriority << 0x04;# K  w1 l8 @8 X. [

  20. 2 ]( b% q) |- k% D0 ~
  21.     NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;  p7 S  n) b9 w- e4 H3 E

  22. * x# f$ w% X+ s2 b6 J. A8 P
  23.     /* Enable the Selected IRQ Channels --------------------------------------*/
    ' }2 b& j0 c* K+ P
  24.     NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
    . X7 O: \! q+ n9 n
  25.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);: l: R# O8 ]4 Y! ^; p6 T$ c. N
  26.   }
    $ c% g- q6 u2 q0 P
  27.   else9 l" |& r7 `# v1 x& b
  28.   {
    0 c. j3 N) c3 @9 A+ H" V. i  ]
  29.     /* Disable the Selected IRQ Channels -------------------------------------*/6 f7 s0 ~" i1 @4 ?2 ?" ~
  30.     NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
    0 R# _$ t$ l! C0 w
  31.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
    * R6 c! E9 x( R* m' V! h
  32.   }/ C$ {+ \6 v0 s! I' `7 {" b& }
  33. }
复制代码

  X' I7 p/ z0 r
该函数中的第3行,有3个临时变量,分别是:优先级组--这个组的意思就是中断分组的那个意思,至于是否是那个值,看下文解释;抢断优先级;亚优先级。注意亚优先级初始值是0x0F--具体是啥原因呢?且看下面代码。

# N7 r9 K$ G3 h9 U1 z+ A
第13行,先取出中断控制复位神马神马寄存器的8~10 这3个位上的值< SCB->AIRCR& 0x07>;然后经过一定的算法得出中断组号:假设SCB_AIRCR的8~10位为0x07,按照上面图来说,也就是第0组,那么按照它里面的这个式子来算,恰好结果tmppriority = 0;至于具体为啥会ST会想到用这么个式子来得出组号,我现在只可意会。

/ R4 v, @8 {+ M' m7 l  B9 y# o
那么,假设是第0组,也就是tmppriority = 0;那么按照中断分组中,第0组的抢断优先级和亚优先级的规定来说,是全4位亚优先级。那么14和15行就很容易明白了,它就是在设置抢断优先级和亚优先级的比例位数。
5 l- y3 ?! c8 o' S) r( O0 v
那么17行,就是像临时变量中写入抢断优先级的值;

5 r+ `2 T" R- [) M3 S
18行,就是向临时变量中写入亚优先级的值;注意抢断优先级和亚优先级一个在先一个在后,一旦分组一定,那么它们是会乖乖呆在自己位置上,不会去占用别人的位置的。

$ o; x7 Y! t" N3 g; p9 h3 ]
21行,就是把优先级配置值写入NVIC_IP寄存器,它是NVIC中断优先级配置寄存器,其定义在<<xxx编程手册>>中,同时可参看<<xx不完全手册>>。
5 F: L# \  n4 D5 U6 M
在我例程版本库中,对该寄存器的定义方法是:
6 e* j9 |1 U5 h4 C: Q
  1. 1  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
复制代码

0 K) g" x( `+ k9 x
如果带入例程中的值来算:EXTI15_10_IRQn 的中断编号为40,即是:

8 j6 u& t* v+ u# U" q
  1. NVIC->IP[40] = tmppriority;
复制代码

% |' x. V: m( R6 W3 ]5 K( m
意思就是向相应的中断上赋予你的优先级配置值,这里特别要注意19行,优先级的值还向左移动了4位,这个是为啥呢?无法回答,带入例程中的值算算:我例程中是,分在中断2组,抢断为0,亚优先级有1,则带入:
7 S. e( p, I+ O' T4 z2 N3 \& k
  1. 1  tmppriority = 0x10;
    5 E, [* v  d+ H/ K( l4 f% f
  2. 2  NVIC->IP[40] =0x10;
复制代码

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

7 m+ P% T- `0 a& g
最后,补充一点就是怎么查看EXTI15_10_IRQn 的中断编号,这个数据手册上有,当然,官方也在库中给定义了。数据手册部分截图如下:
( k6 k( `/ x3 M
14225347-87ca63c19bd744138d5eac4912b5b536.jpg
+ a1 W& i3 y) ^7 z4 X
14225324-690e8471b9a84e59ba7ada9ef0a55e73.jpg
$ t. O6 g# m4 N+ ~! S  p6 |
其中EXTI15_10中断的位置在该向量表的第40号位置,所以刚才上面的优先级寄存器引脚的值就是40,至于具体为啥要写成IP[40],而不是直接IP,这里有个小小编程技巧,是数组的妙用,属于C语言范畴了。

4 c' F9 T# I5 Q) ?
接下来的第24行和30行,是第一对相反作用的寄存器,即中断使能/失能,注意,别以为写0就可以失能,stm32不认为那样有效,必须是向失能寄存器中写1才可以的。同时注意一点是:这个寄存器定义成了数组,那么数组中就应该有N个元素。它这里就是某个元素相应的位,管理一个区域的中断。这里说复杂了,编程手册上已经讲的非常详细了。另外,它的定义方式如同<<xx不完全手册>>上所讲解的那样,但是定义的模式并不一定就完全相同:世界是变动的。
( @. D! L/ W! S5 x2 |
好吧,中断也使能了,总结下第三步:

& m# T, d* j# P8 {3 b5 J; ^
1)中断分组:注意是在SCB_AIRCR寄存器的8~10.分组的同时,也就影响到了后面优先级的分配。
. z6 I& c- }# M2 O9 h; B
2) 配置优先级:其实质还是在SCB_AIRCR寄存器的4~7位,前面8~10位是什么值,那么这里4~7位就该怎么分了;当然,最后的配置值是写入了NVIC_IP的寄存器中了;

+ `( K4 R9 i' H) N: f
3)使能中断:在NVIC_ISER寄存器中,注意该寄存器定义的方式是数组,而非普通的变量,理解的时候,要理解一个数组是由N个变量组成即可<非严谨>。
* ]  Y& _# G6 a1 Z6 w. L6 s6 _
第四步:中断服务函数:

6 @9 ^( w( Z" O* @3 I: l  \; K
  九九归一,终于来到了最后一步,也是所有中断必须要经历的一步。

& P+ t8 B% d; p; ~/ I* t4 h
  这里有个重点必须注意:所有中断服务函数的名字,ST官方已经取好了,而且还放在了中断向量表中了<也即是启动文件里>,如果你不自己写启动文件的话,那么你的中断服务函数的名字必须和ST官方的一样,不然,一个中断来了,找不到负责任的函数,它就只有悲剧去了。

! }, r5 j3 N  k& t5 a
看看例程吧:
  1. void EXTI15_10_IRQHandler(void)
    # o3 R% o& t1 Y3 I2 y1 c* b0 @& A
  2. {) U0 m  G" n1 W. e. I/ M
  3.     if(EXTI_GetITStatus(EXTI_Line11)!= RESET)
    0 w7 |; q4 t5 S. o9 A4 }
  4.     {/ [+ r- K: K% ~% p
  5.         EXTI_ClearITPendingBit(EXTI_Line11);2 P& S2 |  f7 N
  6.         Flag = 0x01;! u2 _( k" w9 H$ q, Z9 Q
  7.     }
    ( u8 V$ e6 X6 {  \2 p# i( ^; ~

  8. 1 X: b$ z- R$ w( x
  9.     if(EXTI_GetITStatus(EXTI_Line12)!= RESET)
    : r9 m( D" J* Q. g( t* `7 |
  10.     {" w/ K6 q6 y& W& M8 f8 t
  11.         EXTI_ClearITPendingBit(EXTI_Line12);% N! J* `- t, p
  12.         Flag = 0x02;0 O1 k( I! r0 s, ?5 N) z
  13.     }0 A. p- |1 D. G$ B
  14. }
复制代码

5 t. l+ E& A  _, U
  1. int main(void)$ I$ ^  [1 i) J  o
  2. {
    % u( m! a1 y0 Y2 z
  3.     /* Add your application code here& Y4 A. T5 s$ W; N1 I$ M$ B
  4.        */
    7 [$ m* W( B  s6 R, U
  5.     SystemInit();              /*系统初始化*/
    / }' D4 I' P; j) ?
  6.     LED_Configuration();3 y. U% g% r4 s. O/ v1 a1 |
  7.     BUTTON_Configuration();
    ( l1 M2 p2 F) T' h- Q
  8.     NVIC_Configuration();
    8 ]9 M- A8 F) `0 P3 |1 a3 ?+ O
  9.     EXTI_Configuration();0 T& P& S) T9 U9 r) V1 @! K' ~
  10.     /* Infinite loop */- c5 V$ @. q# u* a. o
  11.     while (1)0 e# G# \0 X: R7 U' u( `' k# @
  12.     {
    ( y, O: B; Q: F
  13.         switch(Flag)5 _/ g: v. ], ]/ S1 E0 J
  14.         {
    4 \5 K, @2 Y# L) i; `
  15.             case 0x01:
    3 S9 J3 X. ^$ R6 N& c- U
  16.             {! B2 U# h4 X8 @( o
  17.                 LED2(1);2 \4 O- \, j/ i' H
  18.                 Delay();* u: w% l! a/ {- y- [5 _1 R& H5 ?
  19.                 LED2(0);$ p) c  [* ]" u: J% H
  20.                 Delay();
    : T4 a" q# Y$ l! P' U3 y
  21.                 break;9 |! p2 t3 c: Y1 [. g' d. @( `
  22.             }
    : ?8 P9 m+ l9 [2 n; L
  23.             case 0x02:
    ) a5 i% q2 y2 q7 b; o
  24.             {9 b5 Q# E8 S  N/ z* H+ ?
  25.                 LED3(1);
    0 B5 Z4 i" i, M- G% H* M
  26.                 Delay();
    & U/ q3 p% k0 X, R; C% m  M, ]3 D
  27.                 LED3(0);0 H+ X3 Z  W/ s1 r: e. [
  28.                 Delay();
    ( n( G, s) ~$ |6 Y; U7 ?  m
  29.                 break;0 N$ H) q8 }8 v" C" j4 L
  30.             }3 x* w+ c& ?+ w; s. D( o
  31.             default   :
    7 O% H% G: Y- g. L2 t
  32.             {
    0 _  s1 H; e3 j* n# Q% d
  33.                  LED1(1);9 ~# V5 {% v2 |& e
  34.                 Delay();
    + x; }; @6 M! f* N* Q
  35.                 LED1(0);
    ! y+ W3 B" F; k6 p
  36.                 Delay();
    7 Z/ n. m" [+ k  Q( b
  37.                 break;. l; V. r$ d. Z8 P; l" N6 }6 v' E" t4 b
  38.             }: I! x' u! Y3 H( s) H! \
  39.         }
    6 r% H) H' @( ]2 M: H8 @4 C7 E. X: H
  40.     }
    8 P' J$ h0 d6 f% h  g  {
  41. }
复制代码
5 ^" J1 o! V% \# d3 D7 n: V; G
没啥说的,就是定义了一个全局变量Flag,每次中断,都影响Flag的值,然后main函数判断该值,就这么简单。完了。
' k; V5 p! _4 J
转载自:鱼竿的传说

4 d* M/ m$ i5 w6 q8 ^1 ?
收藏 评论0 发布时间:2023-1-1 21:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版