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

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

[复制链接]
STMCU小助手 发布时间:2023-1-1 21:00
至于中断的什么优先级,什么优先级分组,使能之类的原理,就不再赘述。这里主要是记载以下如何使用中断,以及中断配置函数的实现过程,其中并叙述我曾经的疑惑和感悟。

, `) Q2 K9 K7 b5 p: n8 v/ Y
  我的开发板里的中断例程是用按键控制一个灯亮和灭的两个状态。
  T/ [2 [( _: y9 b
  这个例程的实现过程如下描述:

1 o0 M9 L8 Y5 ?+ R' P
第一步,将一个I/O口配置成中断输入模式。
2 E2 }8 {) i& A! ^( A; M
  这里需要注意的是,GPIO本身是没有中断功能神马的。如果硬要使他产生中断输入方式,就得将相应的端口映射到相应的外部事件上去。而其他外设是有中断功能的,直接使能/失能其中断即可,比如USART,直接开启其发送/接收中断,那么USART也就相应的采取中断方式进行工作了。

3 q4 m1 }7 Q$ H% A5 y  p2 y
  而这一点,是我开始很疑惑的:为啥GPIO口使用中断方式进行工作的时候就必须要映射到外部事件上去,而其他就不呢?百度网友的解惑是:比如USART产生的中断,是没有经过EXTI,而是直接将中断放入了NVIC;但是GPIO它作为中断源,是要经过EXTI的。仔细参看下面两个图,其实就会恍然大悟:
) H) J! U) p8 U
12220833-15331e0b20104519978a596626e2c248.jpg
  M) q$ G4 l3 |
12220855-fccdc6e73b3649bea37705918f876b00.jpg
- G/ ~0 x2 X& c. U
这第一步就是作为输入中断源的I/O口的相关配置,例程库函数如下:

5 E  O( A/ Z' f! @6 v6 }/ ?% O
  1. void BUTTON_Configuration(void)
    ) H8 L) d$ D0 ]* P: z( F3 F: z
  2. {' i6 X( D. X/ c, G. C
  3.     GPIO_InitTypeDef    GPIO_InitStructure;
    ) y, s' O' j* f6 x6 s
  4.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;( E9 P+ S$ l# }9 e9 m5 {+ ^, h
  5.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
    % B+ Y- v$ O8 @" m( g. \
  6.     GPIO_Init(GPIOD, &GPIO_InitStructure);
    ) J+ s' w% O3 M
  7. ( O, q  f* q" P/ _: Y
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO,ENABLE);& x0 S1 e; r0 k+ t$ G3 c
  9.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
      K9 H* `3 J' ?% H1 j+ \
  10.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource12);
    ) z# S# a2 f- j3 \1 Z
  11. }
复制代码

# z( r  T2 D1 y: u8 U
因为我板子上的例程是按键输入中断,所以函数名字就写的按键配置吧;

& N9 t" W# J/ I# p+ p; L+ m
3~5行,就是gpio口的普通配置,学习单片机开天辟地,就先是gpio口,这个没啥稀奇的了,没啥可说的了;我的板子上是PD^11,PD^12两个端口作为中断输入的。

8 ~# H4 M0 x! Z( e
8行,注意这个时候,要使能GPIO口的复用时钟功能。
5 i$ f0 u* ~  V9 |5 ^2 B
9~10行,就是将PD^11,PD^12映射到外部事件线上去。在keil中跳转到其函数实现中:

* d, I0 D1 G7 {5 t. ~
  1. void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
    2 {; x- x! F9 |6 O( q2 H
  2. {
    # O4 @/ r, }. @+ ~4 Y, E9 x- Y' x
  3.   uint32_t tmp = 0x00;2 s2 k9 o/ L# h8 Y. Y, {
  4.   /* Check the parameters */
    & V- T4 q. q1 G/ T% D2 ?" D% ?
  5.   assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));0 v, j7 g* X2 S% x8 f/ f3 H
  6.   assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));, ~+ b- U$ W! E5 E' B
  7. ! p0 Z; k5 z) E8 R8 Y! H, c( A
  8.   tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
    $ F) e2 X! d5 S' B$ D
  9.   AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
    % E8 W( B! t. x& Q# l1 b
  10.   AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
    5 [+ S! K9 L2 a8 x% P
  11. }
复制代码
% `' U: L6 h1 u1 t7 b
5~6行,用库函数的都知道,就是两个宏定义,起到的作用是对相关的数据神马的进行正确性检查。
: g5 M8 M$ |$ F* F1 A8 }$ V
8行,GPIO_PinSource这个是外面BUTTON_Configuration()调用GPIO_EXTILineConfig()时传的参数。可能都不知道8行这个式子为啥要这么写。先看看我例程中是如何传的参:
7 F7 @6 k0 z! c- U9 Q. U
  1. GPIO_EXTILineConfig(GPIO_PortSourceGPIOD , GPIO_PinSource11);
复制代码

7 G6 K5 O8 Y' {9 |- X* g
也即是GPIO_PinSource <==>GPIO_PinSource11;那么GPIO_PinSource11是个什么东西呢:官方库已经这样定义了:
  1. #define GPIO_PinSource11           ((uint8_t)0x0B)
复制代码

$ }3 s! [5 @2 P
如果按照我的例程,8行这个式子中,tmp == 0x0F << (0x04 *(0x0B & 0x03)==>tmp = 0x0F000;先不管这个数字是个啥意思,反正它就是个数字,它其实是为了第9行寄存器的值服务的-->既然如此,如果用寄存器写的话,我可以直接给寄存器某个值,何必要山路十八弯呢?当然,库函数,是有通用性的,就像一个数学公式的作用。
! {2 l, `& |: h1 {) F
9行:AFIO_EXTICR:外部事件控制<配置>寄存器。在数据手册中显示,有4个,它们分别对应的是各个外部事件exit_x<x = 0~15>。每个寄存器对应4个外部事件,于是4x4 = 16。,注意数据手册中的编号是从1开始的,而不是0开始的;但是,MDK中是0~3的。于是这里把相关值带进来一看,第九行其实就变成了如下式子:
  {( w+ m/ d  s
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开始的;
# K5 G+ y; e; d. g+ m; ^7 @
这个样,9行以前的一切的操作,就是为了给该寄存器的某个位进行清零嘛,至于具体清哪一位,还得看你映射到哪一位。
10行:引脚选择了,现在就选择这个引脚是哪个端口的,我的是D端口,那么按照官方对D端口的定义如下:
  1. #define GPIO_PortSourceGPIOD       ((uint8_t)0x03)
复制代码
7 }7 ~' |9 K% {% ^3 f' @
AFIO->EXTICR[2]  |= 0x03 << 12;查看数据手册,恰好是设置成了D口的第11号端子上嘛。
% \" Q' l+ E! }5 y. l
于是第一步总结是:
2 c3 B7 V9 S9 U7 }! r, w5 ?. ~0 U
1)外部事件寄存器相关位清零;

6 Q( ], @. x" m$ d' ^+ p6 R; ~! [5 R- Y
2)设置输入端子的编号

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

4 A; ~* q: D1 i- n6 {8 `
总结下第一步要做的事情:
; E$ x( b# y) j; S( N
1)初始化I/O口为输入;

7 M7 F2 ^, \# B- ?) S5 f
2)开启I/O复用时钟,并设置外部事件映射关联。

0 G$ o3 [8 F- x$ H2 R
( M( D9 D/ o4 e6 a  l, u; z
接下来是第二步:
  第一步是将外部GPIO口映射到某外部事件上去。那么接下来,就该对该外部事件进行配置了,包括外部事件线路的选择、触发条件、使能。这里需要理解清楚的是,GPIO口和外部事件是各自独立的,它们并不是一体的---详细理解第一步,将GPIO口映射到某外部事件,可以看出GPIO和外部事件这个东西是两个不同的东西,在这里,GPIO的映射,无非就是GPIO口搭了外部事件的一趟顺风车。也所以,外部事件依然是要配置和使能的,不能说,将GPIO口映射到外部事件就可以产生中断了。

6 Q% Q( }1 S' @1 H7 ~
  接下来看看例程中外部事件的配置函数:
  1. void EXTI_Configuration(void)0 [* V2 i! O2 g' o+ h8 Q$ N+ `
  2. {
    5 l  Z2 t* b/ b5 m; ^
  3.     EXTI_InitTypeDef EXTI_InitStructure;  ^) G8 |: j. U  M! c
  4.     /*PD11外部中断输入*/3 Z& x0 H7 H$ ~$ ]- `0 H
  5.     EXTI_InitStructure.EXTI_Line = EXTI_Line11;" l( z" Z# X0 C! Y1 B
  6.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    * R+ O+ k  e8 G4 W% E
  7.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;! R* u$ j" }) V
  8.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;
    3 D/ V3 e6 ]" H( K( R
  9.     EXTI_Init(&EXTI_InitStructure);8 y4 d# J3 u; n
  10. 2 T4 ?) a7 j/ i6 i
  11.     /*PD12外部中断输入*/9 b4 }+ Q0 d  I! v& t, c
  12.     EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    7 N; q* m5 Y3 ~/ i+ j. I- A- V
  13.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;3 i/ {: U9 ~* U7 i' u# W
  14.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    " o- v) Z9 j. q4 W$ j' {% S+ K
  15.     EXTI_InitStructure.EXTI_LineCmd    = ENABLE;; A. P; B0 ^* O. Q) _9 o
  16.     EXTI_Init(&EXTI_InitStructure);
    ( M1 I* I5 w1 `" r
  17. }
复制代码

* y0 c, i' `3 k' J+ w, M
可以看出,5~8;12~15行,无非就是在填充一个接头体。而真正实在的是9、16行:它们是外部事件初始化函数,把前面填充的结构体地址作为参数,传进EXTI_INit()。

, F0 @, X! `0 x) P# X
在MDK中右键EXTI_Init()跳转到该函数的实现中去,代码如下:
  1. void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
      z, @- e& ~" X, X
  2. {: q2 c8 H" [0 c# Q( p
  3.   uint32_t tmp = 0;
    5 L3 x$ ]8 L4 A

  4. 4 T8 V- U/ K- t0 d
  5.   /* Check the parameters */
    ' {2 y2 G3 R: r8 _$ f  \' t7 i
  6.   assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));" Z# M4 [9 [# d& F
  7.   assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));
    & W; Q  A1 _/ ^% p' G7 q
  8.   assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));
    * T. D: }) B! v: j
  9.   assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));
    ' T7 F3 ^" N! U1 j9 G

  10. 7 P9 O6 M7 X1 q" C
  11.   tmp = (uint32_t)EXTI_BASE;% b$ i3 f9 x0 j# b: ?
  12. 3 k: f; x" h* p
  13.   if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)
      l4 N/ G8 E9 C  Z, _- b
  14.   {3 M. V8 \# t5 v7 @
  15.     /* Clear EXTI line configuration */
    % W% r7 k6 C8 b( ?$ |! F
  16.     EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;! M% q9 P( G+ i& O$ P9 o, O7 h' J+ l* K
  17.     EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;, F2 I8 p( I3 z& ]6 X7 k

  18. $ H  R- D* n5 {8 E7 p5 K: m
  19.     tmp += EXTI_InitStruct->EXTI_Mode;
    ! f9 U. o3 P# x6 N0 ^# Z8 z9 r. j
  20. ( w2 ~0 m; s3 C4 A( l
  21.     *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    # @+ v! \. h' t7 B. \% n* \

  22. 7 e2 f8 z) P4 A1 Q4 h+ m
  23.     /* Clear Rising Falling edge configuration */  H3 y1 `. p8 i6 y1 V% \. ^: c
  24.     EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;
    ! L7 U) c. c5 n2 C$ V7 B* _
  25.     EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;0 e! K- ]- U0 [' i" o: Z; s

  26. * r) m2 G) }# a! n6 ^+ K' B5 `
  27.     /* Select the trigger for the selected external interrupts */
    ' A  F) t+ J" {! \
  28.     if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)( K' i: E% x( D: R& G4 L
  29.     {+ ]. Y0 }" Q6 L0 y% X1 u& X) e
  30.       /* Rising Falling edge */: T- w+ l* B# x9 M+ |; M" h7 I
  31.       EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
    , ^6 R: u0 N6 S+ \' I* f
  32.       EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;. E6 \! Q+ ~& Z
  33.     }
    + E# O7 H8 F; |" J8 ~; R9 }
  34.     else5 }- P# H% w9 G
  35.     {
    # c6 z- x4 N$ o$ l/ i
  36.       tmp = (uint32_t)EXTI_BASE;  x" q0 J( U2 ^* U' P' y
  37.       tmp += EXTI_InitStruct->EXTI_Trigger;% X) [* L5 z* ^4 S5 ]
  38. 8 v* V$ W- f6 n3 ]$ B$ O, R& B
  39.       *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    / M* D. l4 L& V6 p( c: q
  40.     }
      {7 A; n: q* H  b% T+ f
  41.   }. ^+ h4 |1 _# m( y* F
  42.   else
    # U; D/ q9 |: ^: n4 k! f
  43.   {
    6 u( u2 r; n0 P4 J% U
  44.     tmp += EXTI_InitStruct->EXTI_Mode;/ o4 V" i1 B3 J& S
  45. : j% D$ K& F8 t% b9 ^
  46.     /* Disable the selected external lines */
    ) f* a1 H1 _& i& d8 Y; f2 e, K
  47.     *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
    0 Y) O2 T6 A) j) G$ n
  48.   }
    . e" G7 l# O1 o% z0 d7 ?) Z
  49. }
复制代码

& ^" }4 i% S) y/ R7 m
第11行,就是存储了一个地址值。可以先看看它们是怎么定义的:
  1. 1 #define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */2 u+ V+ `4 G/ x  X+ k* K" _! X
  2. 2 #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)5 p0 E# h) ]- G( x$ ]
  3. 3 #define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
复制代码

9 G. D( t( `; e8 S
如果对stm32框架掌握的足够熟悉,这里一眼便知端倪:GPIO口是挂在APB2上的,APB2是连在AHB总线上的,AHB再连到总线矩阵上的,环环相套,牵一发而动全身。附图如下:

9 Z7 i  k7 U9 y1 Q/ L" C
13212932-0f749bf04f4f4399b93997b2e4089589.jpg

+ o5 {' Z; ?3 j$ e6 ]* `
第16行、17行,分别是EXTI_IMR中断事件屏蔽寄存器和外部事件屏蔽寄存器,相应的位为0的时候,它们屏蔽相应线上的中断/事件请求。带入例程中的值算算
  1. 1 #define EXTI_Line11      ((uint32_t)0x00800)  /*!< External interrupt line 11 */
    2 Q$ g# D  r- q' K2 N+ A8 `- R# m3 {$ Z
  2. 2 #define EXTI_Line12      ((uint32_t)0x01000)  /*!< External interrupt line 12 */
复制代码
8 Y0 ]" O! e# ~4 b8 b) N
先将事件11的值带入下面两个式子,换算如下:
  1. 1     EXTI->IMR &= ~0x00800;
    9 c  o( c2 e( ^% f0 z- z8 u
  2. 2     EXTI->EMR &= ~0x00800;
复制代码
表示,将寄存器EXTI_IMR的11位清零,将EXTI_EMR的11位清零<下标从0开始>--意思是,将先关闭11号线上的中断/事件请求功能:有个原则是,在配置某一线上的中断或者事件之前,先将该线上的中断/事件清零,官方库写的比较严谨,所以这里先清零。那意思是,接下来就该对该线上的中断/事件进行配置了。

  M5 j; Z6 Q' W# ^. }0 m- u5 b
且看上面外部事件初始化函数中的31行和32行,EXTI_RTSR是上升沿边沿触发寄存器,EXTI_FTSR是下降沿边沿触发器,这里一看便知,是在配置边沿触发模式:边沿触发分3类--上、下、随便。当然,在配置边沿触发的时候,边沿触发器相应的位也应该清零,这在该函数的24、25行已经体现出来了。

, \; \7 \# m5 F) a
另外注意模式的配置,看该函数中的19行:
  1. 1 tmp += EXTI_InitStruct->EXTI_Mode;
复制代码
这个式子的表达的意思,接上文是:
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
司马昭知心,路人皆知:就是某个地址 加上一个值后,这个地址也就变成了另外一个地址了。但为啥要这么做呢?
7 G) O' U. u: G" _5 h) W9 F2 [  t& F
而外部事件中断模式的值定义为如下:
  1. 1 typedef enum4 a. I8 S6 B' d: M" A; Q& _. h
  2. 2 {
    1 b' P3 _" u1 ?- _
  3. 3   EXTI_Mode_Interrupt = 0x00," m; c0 t. D; H. Z' C# N$ R
  4. 4   EXTI_Mode_Event = 0x04
    3 i/ a5 G' y& Q% H" s+ d1 m' @
  5. 5 }EXTIMode_TypeDef;
复制代码
. g) Q# ~* P4 @8 h; f* O# V0 M
可能不熟悉的<包括开始的我>,会发现,tmp在开始就只是对寄存器清零使用了下,就没再使用了啊?可是在该函数中,为啥后面还有一串关于tmp的代码呢?
如果细读开始处赋值的意思就该明白了:是将某一个地址写入了该变量,那么在一定的条件下,该变量就相当于内存地址了嘛。注意是在一定的条件下,而函数中,也给出了这个条件,那就是类型强制转换。
请看39行或者47行:
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;" t% h4 H! N# A7 t
  2. 2
    0 P# t8 b4 `: C4 ~
  3. 3 ( Y; p* [6 @# Z
  4. 4 -----------------
    * x) \8 U+ t  q: N% c4 z3 @
  5. 5 *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
复制代码

- F; V7 f' q5 G7 g$ r( x! U
__IO 表示volatile关键字;*(volatile unsigned int *) 表示了什么?指针的前面加*号,表示一个具体的值,也即是某个地址上具体的数据。
5 |+ v: H! s- O% F; E0 }9 z+ d, X
于是这里表示的是对某个内存地址进行操作,也就是向某个内存空间放入某个值,放入的是什么值呢:中断时间线的值;放入什么地址呢?
  1. tmp = (uint32_t)EXTI_BASE + EXTI_InitStruct->EXTI_Mode;
复制代码
, s! n) a. J' `1 x+ [
注意,如果边沿触发不是随机的话,还要加上边沿触发寄存器的值。

$ ^8 m8 _2 m, n" u6 v% O
由于以上几步开始没有理解,特别是我最后没有把中断事件线的值写入内存空间,导致我的第一次按键中断实验失败了,而且还不知道错在哪,在分析了官方代码后,方才知晓。
$ {2 `- O- W2 _2 ?; \9 N
到这里,外部事件配置就完成了,可能细心的人会发现并没有使能外部事件/中断,是怎么回事呢?请看看上面tmp所代表的的内存空间地址是多少,然后对照着数据手册上查看下事件/中断屏蔽寄存器的地址是多少,再看看下面这句代码最后的值是多少,就一目了然了。

5 V. i0 Z9 l/ _; ]% F7 r
  1. 1 *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
复制代码

: Z" @- s+ I3 K* p8 t: e9 r: v
总结下第二步:就是配置外部事件的模式、触发条件、使能外部触发。而在这个过程中,注意先将某功能寄存器相关位清零后再写入值。

* t( ]/ ]. `/ z( ?
这个过程用不写成通用的库函数的话,那么可以用下面代码代替:
  1. #define tmp  (*(volatile unsigned int*))0x40010400
    % Q3 d* N: g: m7 p4 S8 }; }( f

  2. 2 q8 u1 _0 e6 K& H. h
  3. EXTI->IMR &= ~0x00800;; s$ E3 b% K6 V9 j' d1 F6 [
  4. EXTI->EMR &= ~0x00800;; n" t  N3 R8 A0 R1 s' C9 j
  5. EXTI->RTSR &= ~0x00800;
    # Z' s; e" p9 X9 I+ F) i
  6. EXTI->FTSR &= ~0x00800;/ t% R) v' {$ u6 M; S4 V; ]2 C% G
  7. EXTI->RTSR |= 0x00800;//如果配置成上升沿触发的话, K: m# c& x5 n. x5 P6 J) f8 I
  8. tmp |= 0x00800;//使能中断/事件
复制代码
7 p0 ~& p+ Y& M  A, H$ s
第三步,现在就该配置中断了。也即是配置中断分组,以及中断优先级。当然,这并不是最后的工作。
& ?2 j- c/ P" c1 {
中断配置函数如下:
  1. void NVIC_Configuration(void)
    " Z" P) m9 b3 y) K7 w
  2. {9 S# @/ R5 d. e& E* m
  3.     NVIC_InitTypeDef NVIC_InitStructure;
    : `& m/ E: W0 J' ^4 |
  4.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);0 c- ?) h5 o( d6 b9 \1 y
  5. * _+ B3 {2 m% Z
  6.     /*外部中断线*/
    1 n0 N; ^5 {* y$ O% S
  7.     NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn ;- X1 M! e0 d: s' b, U
  8.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0 ;
    $ C, C8 a2 e+ B7 U; O. J4 C& U
  9.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;. [1 e. _& ~8 s6 j4 X9 n' W
  10.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE ;
    9 F3 n1 o; Z& L. j; Y) D! r
  11.     NVIC_Init(&NVIC_InitStructure);
    3 ~9 p: i) {' h* c# n" D- N2 s
  12. }
复制代码

. Y+ t1 k9 ~: I" \- g- E4 Z& Y, w
4行,是一个中断分组函数,跳转,看实现:
  1. void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)8 I; }) h* |; W$ J( `
  2. {
    3 I& E1 [, _4 a  o6 r
  3.   /* Check the parameters */4 Z  X2 n2 |( p6 P
  4.   assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
    0 i+ h: p) r* I  l% R* p8 J
  5. & L( x- D8 M0 @  l6 m0 l
  6.   /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
    3 A3 Y0 O  c: \, {5 w
  7.   SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;4 b; N: d6 l2 H7 l
  8. }
复制代码

$ a8 |& [: ]" |+ Y" w4 X1 P
要查的SCB_AIRCR应用程序及复位控制寄存器的话,手头就必须备有<<xxxM3编程手册>>,<<xxxstm32数据手册>>中是没有这个寄存器的,当然还有许多都没有,比如滴答定时器等等。

- m# S# c2 Y: W5 `. V& v" B! V# f
其中 AIRCR_VECTKEY_MASK 就是一个钥匙,在改写SCB_AIRCR中的值的时候,必须填入这个值,否则修改无效。AIRCR_VECTKEY_MASK = 0x05FA0000;
: v# d" K- Y. o& h  o) D! w1 x* S
而后面就跟着组的编号,注意这个编号不是简单的就是0,1,2,3,4.不能就这么简单的写入这个寄存器了。
2 ^/ i8 [; A( ~! N( ~  j
注意这个寄存器的名字,它本身并不叫中断分组寄存器,而是借用了这个寄存器的某几位来进行中断分组--换句话说,这个寄存器可能要实现多种控制功能,而中断分组功能是在其中的某几位:当然,编程手册上说明了,是该寄存器的8~10位来进行中断分组控制;所以,组号需要进行位移到8~10位上来。如图:

# B, _7 N8 X" r: B6 n
13232035-61d308ebf03a4d3d992a918904467d45.jpg

( u2 R6 {; o' Y" }& n# Y
<存疑部分>
  U6 @# ]" ?" w, c7 y2 X9 O
当然,该函数中的值,也就影响到下面中断优先级的配置。在MDK中跳入NVIC_Init()中,看其实现过程:
  1. void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)% ?# }3 P: j# @' ?1 n. R/ b4 z& p
  2. {5 d8 H$ V1 [8 P# N
  3.   uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;9 y6 p+ g! b1 v: r+ [9 N$ j+ ~
  4. ! N' v. u+ C1 r1 W9 b" f
  5.   /* Check the parameters */) U% n) _4 P+ ~) M8 N" V* \" R0 N
  6.   assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));& i$ f! o; H1 Q  \
  7.   assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));0 F$ i1 d7 X5 }3 w
  8.   assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));3 e+ R7 X) t& v/ u

  9. - f+ y: y: V/ U* P0 j
  10.   if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
    ' u6 f% i- z  P& q
  11.   {
    : c9 G/ ^+ c, ]' S2 r; Z" v
  12.     /* Compute the Corresponding IRQ Priority --------------------------------*/7 ~' a# I  C% H) K6 ]
  13.     tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;" D2 X2 h, J2 d. ~; Y: S
  14.     tmppre = (0x4 - tmppriority);
    6 I* m0 B5 n6 S& Y. m4 Y+ m
  15.     tmpsub = tmpsub >> tmppriority;
    8 l0 L  U! f# O! N! i6 f( X

  16. ' W8 L; c/ x% P( Z% y$ A# W: X, t2 M1 @
  17.     tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;. M! r2 {$ O8 Y
  18.     tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    0 C8 D9 `, Z. z* r- A
  19.     tmppriority = tmppriority << 0x04;
      @4 H9 |8 a6 r) [

  20. * U, u6 M! i* B" `& \, F! \! N
  21.     NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
    8 F$ q& q& u- S1 v
  22. # b6 c! D& b8 s  A
  23.     /* Enable the Selected IRQ Channels --------------------------------------*/2 V9 d6 _# {- X5 v$ Z! I$ G* m9 U' l9 H
  24.     NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
    2 p2 Q2 ^" J* M- j. H
  25.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
    : k' c( A5 W; _% S2 z9 F. {
  26.   }6 p: Y* q1 ~- G, N0 [6 D$ X
  27.   else
    / p) e# \& I; q
  28.   {+ c+ B$ E& z% X/ ?2 p( }
  29.     /* Disable the Selected IRQ Channels -------------------------------------*/0 Z  U- M1 ^8 M
  30.     NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
    5 N* K0 M5 L$ e6 D* ^; f  I) x
  31.       (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
    1 S8 v* m" i5 H+ ]' A  p
  32.   }* t; w2 N; H" r! a, R
  33. }
复制代码
+ C+ Q7 H  l& ~: t: |3 r
该函数中的第3行,有3个临时变量,分别是:优先级组--这个组的意思就是中断分组的那个意思,至于是否是那个值,看下文解释;抢断优先级;亚优先级。注意亚优先级初始值是0x0F--具体是啥原因呢?且看下面代码。

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

" d( a4 U6 f9 [- X1 |) Q
那么,假设是第0组,也就是tmppriority = 0;那么按照中断分组中,第0组的抢断优先级和亚优先级的规定来说,是全4位亚优先级。那么14和15行就很容易明白了,它就是在设置抢断优先级和亚优先级的比例位数。
; r0 v$ z' A- }8 ^* H0 \9 u5 {- o
那么17行,就是像临时变量中写入抢断优先级的值;
  n* M- A2 p# p, r' t5 f, b4 [4 b
18行,就是向临时变量中写入亚优先级的值;注意抢断优先级和亚优先级一个在先一个在后,一旦分组一定,那么它们是会乖乖呆在自己位置上,不会去占用别人的位置的。
: g. Z8 ]3 f) n9 P* m0 x8 X( ?! K
21行,就是把优先级配置值写入NVIC_IP寄存器,它是NVIC中断优先级配置寄存器,其定义在<<xxx编程手册>>中,同时可参看<<xx不完全手册>>。
, _/ k1 D! F/ L) ?% w$ R
在我例程版本库中,对该寄存器的定义方法是:
) ~1 w: {* i; W0 D' P& \
  1. 1  __IO uint8_t  IP[240];                      /*!< Offset: 0x300  Interrupt Priority Register (8Bit wide) */
复制代码

( f( d' h/ S* q; ~1 o2 g
如果带入例程中的值来算:EXTI15_10_IRQn 的中断编号为40,即是:
& k/ E/ w) ^, [5 s" i9 F
  1. NVIC->IP[40] = tmppriority;
复制代码
2 h$ h% ^; o  n- v- P. E/ B$ N$ `
意思就是向相应的中断上赋予你的优先级配置值,这里特别要注意19行,优先级的值还向左移动了4位,这个是为啥呢?无法回答,带入例程中的值算算:我例程中是,分在中断2组,抢断为0,亚优先级有1,则带入:
$ Q  x/ W0 X- K5 _; @' Z# x
  1. 1  tmppriority = 0x10;
    . h8 S: A) h0 k# [1 I
  2. 2  NVIC->IP[40] =0x10;
复制代码
. C4 _8 Z8 F* `; t! O
也即是IP[40]的第5位置1,可能还是无法理解,再次参看正点原子的书,得知,中断占IP寄存器的8位,但是只是用到了其高4位,而我们在设值的时候,也即是设它高4位的值。那么就符合这里的情况了:抢断优先级为0,亚优先级为1,组号为2。 也退出,亚优先级和抢断优先级一共4位,抢断处于高位。这4位中,它俩怎么分,就是一个此消彼长的情况,视不同的组而定了。至于组合优先级之间的对应关系,上图已经清楚的解释了。也即是SCB_AIRCR 这个寄存器处理的事情了。当然,这些寄存器,在<<xxx数据手册>>中也许是找不到的,而在编程手册中去找。

& c: R* `8 r( ~( A1 P
最后,补充一点就是怎么查看EXTI15_10_IRQn 的中断编号,这个数据手册上有,当然,官方也在库中给定义了。数据手册部分截图如下:

. ?" c" K" P, q* I$ _7 j- @5 S  Q
14225347-87ca63c19bd744138d5eac4912b5b536.jpg
* Q# ~9 i. @) |( S
14225324-690e8471b9a84e59ba7ada9ef0a55e73.jpg

9 e( |2 T" i8 z9 W
其中EXTI15_10中断的位置在该向量表的第40号位置,所以刚才上面的优先级寄存器引脚的值就是40,至于具体为啥要写成IP[40],而不是直接IP,这里有个小小编程技巧,是数组的妙用,属于C语言范畴了。

2 Y( e8 j1 @+ |" K5 p4 c
接下来的第24行和30行,是第一对相反作用的寄存器,即中断使能/失能,注意,别以为写0就可以失能,stm32不认为那样有效,必须是向失能寄存器中写1才可以的。同时注意一点是:这个寄存器定义成了数组,那么数组中就应该有N个元素。它这里就是某个元素相应的位,管理一个区域的中断。这里说复杂了,编程手册上已经讲的非常详细了。另外,它的定义方式如同<<xx不完全手册>>上所讲解的那样,但是定义的模式并不一定就完全相同:世界是变动的。
) B" N# o4 `/ Q" ]% Q: L
好吧,中断也使能了,总结下第三步:
5 q/ E, {! Y* n( W; Q7 A
1)中断分组:注意是在SCB_AIRCR寄存器的8~10.分组的同时,也就影响到了后面优先级的分配。

# G6 r. J9 k' E: u! y! \
2) 配置优先级:其实质还是在SCB_AIRCR寄存器的4~7位,前面8~10位是什么值,那么这里4~7位就该怎么分了;当然,最后的配置值是写入了NVIC_IP的寄存器中了;
2 A- m6 a9 ?( O* t/ S
3)使能中断:在NVIC_ISER寄存器中,注意该寄存器定义的方式是数组,而非普通的变量,理解的时候,要理解一个数组是由N个变量组成即可<非严谨>。
' ?" U* w1 g7 F) X9 j+ U
第四步:中断服务函数:
3 N4 m; x0 D4 F3 x8 w' z0 i7 c1 a
  九九归一,终于来到了最后一步,也是所有中断必须要经历的一步。
5 l' {% x7 P$ Y, c* `9 v! p& P
  这里有个重点必须注意:所有中断服务函数的名字,ST官方已经取好了,而且还放在了中断向量表中了<也即是启动文件里>,如果你不自己写启动文件的话,那么你的中断服务函数的名字必须和ST官方的一样,不然,一个中断来了,找不到负责任的函数,它就只有悲剧去了。
% v6 \/ X+ f0 H& W* @5 Z
看看例程吧:
  1. void EXTI15_10_IRQHandler(void)  p! _, D& ?- a, H1 R. b
  2. {
    # `2 \  W8 G/ z, V$ v- i) o0 A
  3.     if(EXTI_GetITStatus(EXTI_Line11)!= RESET)
    ! T& y  q1 D/ b6 L) h" {. d1 o
  4.     {2 Z# k. w! d( X2 I
  5.         EXTI_ClearITPendingBit(EXTI_Line11);5 N, X3 |6 n7 f$ p6 R% H( d4 I
  6.         Flag = 0x01;; B; V: o4 O# @' J
  7.     }
    , w% {. c3 y2 Y( b  ]9 g

  8. 9 J! T+ Q, N$ C" [  S8 v
  9.     if(EXTI_GetITStatus(EXTI_Line12)!= RESET)
    # }5 u+ }! w" P' y: m2 B
  10.     {
    4 V5 e! f% `* A8 B0 w1 e
  11.         EXTI_ClearITPendingBit(EXTI_Line12);5 ~! @5 D% Y  _: O- A9 M7 ^4 q
  12.         Flag = 0x02;  |( i/ c: q& G; W0 S2 ^0 `7 [6 P, A, e
  13.     }! g9 u2 b* R0 E; C  w3 X
  14. }
复制代码
, @. j% E8 x2 z( L+ M
  1. int main(void)
    9 u$ i6 ~) `! r3 n& a
  2. {
    0 C, F% ~$ l/ c* ^1 b8 ^5 }
  3.     /* Add your application code here* X8 W" |& |0 Z* S, k' |! _
  4.        */$ f4 ^# [) R5 `9 o" m+ n
  5.     SystemInit();              /*系统初始化*/, j2 A) J+ m+ `3 b# c" O7 ]
  6.     LED_Configuration();' S: {4 j1 A+ ~
  7.     BUTTON_Configuration();
    5 G  H/ ?8 J) X; r5 a0 T# K
  8.     NVIC_Configuration();
    7 y: f6 \" G/ F( s3 W+ ]
  9.     EXTI_Configuration();
    4 g! i6 Q- U8 {# ]* Y
  10.     /* Infinite loop */7 K7 |) d( s. y$ s7 I) y
  11.     while (1)
    . b$ m# M/ v( j" E
  12.     {
    & ?) X" F6 k7 l! G1 F
  13.         switch(Flag)
    ; T  _: P3 N- ^5 A" v; g
  14.         {
    $ y6 L+ ]/ D0 K4 i/ q+ M
  15.             case 0x01:  G% K  M" K: q1 g0 [! c
  16.             {
    2 `" s) i$ t0 K
  17.                 LED2(1);. E1 f" }7 t1 k1 h. {6 P( u7 d9 H
  18.                 Delay();. D  v8 R* \$ m5 ~; }
  19.                 LED2(0);, K; |; m3 s+ O' h' g
  20.                 Delay();
    " \# k0 e) q4 y
  21.                 break;& P0 v( J  v2 p' Z9 H+ J
  22.             }% u# b: A9 |* l/ e
  23.             case 0x02:$ A6 Q5 `7 x  i
  24.             {
    0 R% d1 Q  ~+ K+ ~0 q2 K5 E
  25.                 LED3(1);% g9 j# D2 V4 d5 _4 v! V" N
  26.                 Delay();* j& }& j. B0 ~" y" l* h. k
  27.                 LED3(0);6 c2 w% t2 Z& _2 e' L
  28.                 Delay();
    , e9 x' u0 ~7 O# }3 r" F+ J: u
  29.                 break;6 w  b/ v' O9 h& Y2 W% w/ U" z
  30.             }) v6 g6 d% a6 f$ i: S: G( Q
  31.             default   :1 h  K9 ^3 p# G( l
  32.             {6 @; \' i$ V  M  `3 s& V# w  H. m5 T
  33.                  LED1(1);8 x& Y. n  |8 O" w
  34.                 Delay();. @3 d1 E8 z  U9 H) U0 A! O2 r
  35.                 LED1(0);: y2 o' ]2 ^3 W- S1 G$ K" b
  36.                 Delay();
    + z4 l7 q- o9 f7 f$ E6 k
  37.                 break;
    1 p5 m7 C/ g) |- D: T3 s4 a* P$ d
  38.             }+ j& M) Y2 n; `# S) e
  39.         }# [; z2 K; H! O) Y1 r5 l. J
  40.     }
    $ U. G0 |6 T) R7 X* F( ?  F! U8 w2 ^
  41. }
复制代码

( Q4 C6 Y4 F/ @. ^) q4 `& j9 k: l
没啥说的,就是定义了一个全局变量Flag,每次中断,都影响Flag的值,然后main函数判断该值,就这么简单。完了。

6 n/ `+ j4 v3 z( C. X
转载自:鱼竿的传说

) a8 A( d" O, Y8 Z: ~, u2 Z. N7 }
收藏 评论0 发布时间:2023-1-1 21:00

举报

0个回答

所属标签

相似技术帖

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