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

STM32从原理图PCB到移植RTOS GPIO

[复制链接]
STMCU小助手 发布时间:2023-2-23 22:10
我当时不理解,觉得领导挑人随心情,不体恤我们这些招聘的锻炼干部的辛苦。过第二天到了济南,老大跟我说:“这些人招聘进来,都是要给你做下属的,如果很难带出来,也是给你自己找麻烦。”
不过现在想来,觉得不无道理。
前期经典文章软硬兼修
0 g4 \0 R: ?" ?( p' g. a0 X
% G  d9 Z3 E; @- m& x" v
我们通过这篇文章,从芯片内部原理、电路设计、软件开发、软件运行,全流程看一下GPIO初始化、工作的全过程。帮助很多朋友只做MCU硬件的,又很想提升的朋友,进行学习和理解。

) U; B5 {9 H. K0 D
【1、熟悉芯片的内部结构和工作原理】
任何开发,我们都应该去看厂家的Datasheet和设计参考先,其他的教材、文档、书籍、网站、博客,都是基于原厂的资料进行编辑的。
所以我们应该下载并仔细阅读下面两个文档:

; h9 a1 x" ]" L  ^; o+ U5 t2 d" T
1.jpg

6 s$ `; b+ `! h* k; {
2.jpg

4 S6 O- ^6 _- g6 {. v  G! L
/ H# {' B9 m, u+ w* t1 z  X7 p& R2 ]1 A
STM32实物图:
   3.jpg
3 t$ Z0 T7 n" q8 f: U
STM32的144个管脚,除了为芯片供电的电源、GND、时钟、复位管脚之外,几乎所有管脚都可以用作GPIO
9 R6 z5 A3 ]1 O+ G& r
 2. STM32,LQFP144PIN的引脚分布图:
2 ~+ n3 v: P" L( ^; r1 w
4.jpg

- L; [! |, H  ?2 h$ r. h) x5 C% u' }
STM32F103ZET6:共144个引脚,7组IO口,每组16个IO口
  l. `: @8 U$ m+ G0 ~  X5 d7 Z
7*16=112个IO口(这7组IO口分别为GPIOA,GPIOB…GPIOG)( _" b  S* A6 I+ }& L, q
例如GIOA包含PA0,PA1,PA2…PA15,每组16个IO口。
从硬件上面,按照:A、B、C、D、E、F、G分为7组。
# G; v8 ]2 _. ]' o. D6 m: X
每组有16个管脚。

! ^0 P0 M" o$ c$ V, M
IO口的基本结构和工作方式
9 L! V/ w9 z( K
5.jpg
- w' l+ M9 ]% V; K9 [$ x
0 ^% n- p! C  r5 M5 L" J: P
stm32的GPIO的配置模式有好几种,包括:
4种输入模式
- p- T6 g4 F" r) w0 c   输入浮空
' y8 M4 q2 Y1 A   输入上拉
# x0 s$ _5 }" |* n! e   输入下拉
( M$ b- W  q/ M* T- n8 `; m" x& f   模拟输入 ) P: g1 w: {9 z2 B0 k8 U
4种输出模式
/ \7 c8 A; F2 b   开漏输出
- Z6 p$ E) T  @" l7 B   开漏复用功能% G6 A, }: R# N3 t0 l% C# x
   推挽输出  D  V6 j9 d! `
   推挽复用功能
; P' I  e" v2 h, d
可配置3种最大翻转速度
; ~5 I$ I3 Q* B! e   2MHz
8 v7 Z1 `, H2 s8 X   10MHz: M8 e0 S+ r+ X6 H
   50MHz0 ]" q: b5 |2 q  L* \4 g3 |0 x

! O4 e$ e3 o2 H+ ^) N- Z

1、模拟输入;

部分管脚可以用作ADC的输入管脚,需要通过软件进行配置。

当我们把对应的GPIO配置成ADC的功能。

5 y* l  W/ B9 v3 Q

6.jpg

# g- T& x- Q/ f/ W3 }/ \0 G

则信号接到GPIO的管脚,会被MCU内部集成的ADC进行检测。

. c/ m  k2 U3 Z/ g, {) k

7.jpg

) q3 _( W0 y. Z( [  n6 D

从上图我们可以看到,我觉得模拟输入最重要的一点就是,他不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。

8.jpg


  u) O; h: [' e$ Z

如果信号不作为模拟信号输入,可以作为数字信号输入。

数字信号输入时,可以配置上下拉电阻:高阻状态、无上下拉,为浮空输入。

上拉电阻打开,则为上拉输入、如果下拉电阻打开,则为下拉输入。

+ {3 C/ ^( F7 @4 T$ c( H+ H

9.jpg

  
上拉和下拉部分均为关闭状态(AD转换-模拟量转换为数字量)
# }3 d3 G- r9 G; m# b4 r施密特触发器为截止状态/ F) n  F% H4 ?0 k7 _
通过模拟输入通道输入到CPU; C" ^0 y- `# n1 k. P
IO口外部电压为模拟量(电压形式非电平形式),作为模拟输入范围一般为0~3.3V

2 p; y: Y+ {9 h" m

2、 浮空输入;

10.jpg

- W7 t) v; s& n- z$ A0 P

该输入状态,我的理解是,它的输入完全由外部决定,我觉得在数据通信中应该可以使用该模式。应为在数据通信中,我们直观的理解就是线路两端连接着发送端和接收断,他们都需要准确获取对方的信号电平,不需要外界的干预。所以我觉得这种情况适合浮空输入。比如我们熟悉的I2C通信的输入状态。

4 i6 m+ L. }8 \1 l& y
11.jpg

4 l+ }- d* r0 ~2 ^- E" ~1)外部通过IO口输入电平,外部电平通过上下拉部分(浮空模式下都关闭,既无上拉也无下拉电阻)( p$ K& w4 J5 u
2)传输到施密特触发器(此时施密特触发器为打开状态)
5 ^+ Z" ^. O* Z3)继续传输到输入数据寄存器IDR& S8 [9 R7 s# `4 A( C0 C6 v
4)CPU通过读输入数据寄存器IDR实现读取外部输入电平值。在输入浮空模式下可以读取外部输入电平


/ N4 {$ W, x) B; r) |$ l# ~2 r

3、上拉输入;

上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。


% I9 K# {# B" M* u- N- g: P
12.jpg

" X$ m. X7 z8 @$ M% k" ^0 S- w
13.jpg

% a/ a/ h* r) q和输入浮空模式相比较,不同之处在于内部有一个上拉电阻连接到VDD(输入上拉模式下,上拉电阻开关接通,阻值约30-50K)
7 g0 R& l  _# ?1 z+ x' U外部输入通过上拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取。


8 L: g7 y0 |/ T6 D5 q

4、下拉输入;

和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。


+ y2 q, R. D( r

14.jpg

9 }4 Y0 f& z; o4 ^
15.jpg

8 t  i" B4 X$ T) ^
  
        和输入浮空模式相比较,不同之处在于内部有一个下拉电阻连接到VSS(输入下拉模式下,下拉电阻开关接通,阻值约30-50K)( b/ S' y" E4 \7 j+ [- ?
        外部输入通过下拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取

7 ?3 {5 H$ ]1 j7 j4 f

5、开漏输出;

开漏输出,输出端相当于三极管的集电极,所以适合与做电流驱动的应用。要得到高电平,需要上拉电阻才可以。(例如模拟软件实现I2C的输出)


1 ]+ m2 G/ i0 Z0 S7 @3 ?
16.jpg

" z% K) Q# D' Z. ?/ w1 ~( D
17.jpg
  
1,CPU写入位设置/清楚寄存器BSRR,映射到输出数据寄存器ODR
7 q+ M) s$ k2 A  l% _  j2,联通到输出控制电路(也就是ODR的电平)* O9 [/ I( F) }, `: ~
3,ODR电平通过输出控制电路进入N-MOS管  & Q+ ^9 V/ j+ ?  [3 B
-ODR输出1:
1 I$ l( g) S5 \6 r8 R   N-MOS截止,IO端口电平不会由ODR输出决定,而由外部上拉/下拉决定。+ i2 s  V3 a/ Y& e# `
  在输出状态下,输出的电平可以被读取,数据存入输入数据寄存器,由CPU读取,实现CPU读取输出电平。2 L) S$ ~6 J9 Y# w8 K/ I. ^' b; d' F
  所以,当N-MOS截止时,如果读取到输出电平为1,不一定是我们输出的1,有可能是外部上拉产生的1。% ~7 {. H+ Y+ k1 t" c; t* G
0 |  j7 N' _2 ]1 q. s& N: s
  -ODR输出0:! X- N) ~7 C" Q: b9 Q! s* N
   N-MOS开启,IO端口电平被N-MOS管拉倒VSS,使IO输出低电平。& P' D5 ^+ s7 `8 H
   此时输出的低电平同样可以被CPU读取到。

: q; E# R: g5 n2 k' u$ n) g

6、 推挽输出;

推挽输出使用了推挽电路,结合推挽电路的特性,它是由两个MOSFET组成,一个导通的同时,另外一个截至,两个MOSFET分别连接高低电平,所以哪一个导通就会输出相应的电平。推挽电路速度快,输出能力强,直接输出高电平或者低电平。

5 L! y7 U$ ], a% h

18.jpg


3 h- |" j; P# }' b- C" ?

   与开漏输出模式唯一的区别在于输出控制电路之前电平的来源0 J7 ^. e1 y! Y( I( o, I
    开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的, C8 d" ]8 J1 ?3 Y1 O6 b9 b) }
    开漏推挽输出模式的输出电平是由复用功能外设输出决定的
. J" {1 Q, K- u  e1 J/ A2 O   其他与开漏输出模式相似:
, P- D9 \) b- }4 R    控制电路输出为1:N-MOS截止,IO口电平由外部上拉/下拉决定
. j" n1 ~/ b# t% G/ f& S( s    控制电路输出为0:N-MOS开启,IO口输出低电平


5 D  r5 u' n" u# G' G2 [! V9 b

7、复用功能的开漏输出;

我们使用了某个硬件接口,但是这个接口需要开漏输出,则GPIO处于这个状态。例如我们使用片内外设功能(I2C的SCL,SDA),即我们口头说的硬件I2C,需要输出是个开漏,且使用了MCU的GPIO复用功能。

/ L4 s* i- W% r: p3 m& f
19.jpg

7 B' E. r) A) l% y9 s   与开漏输出相比较:
  f5 E2 f- o/ W6 i5 e# R: H    输出控制寄存器部分相同7 y  Z6 J, h# i; D8 l- h
    输出驱动器部分加入了P-MOS管部分# W/ P1 Q* I$ v0 L% i" d) h3 }6 W
   当输出控制电路输出1时:
" Y" n, O3 B4 O: ~# |    P-MOS管导通N-MOS管截止,被上拉到高电平,IO口输出为高电平1
7 h2 V# R$ e, U& i7 k8 e6 Y. Y   当输出控制电路输出0时:
: W  A/ d9 x- ]- G* ?- N, W    P-MOS管截止N-MOS管导通,被下拉到低电平,IO口输出为低电平0; i' }8 E" Q+ ]  g/ d$ T
   同时IO口输出的电平可以通过输入电路读取


- W* \0 d2 ]3 W/ Q. E# Z

8、 复用推挽输出;

我们使用了某个硬件接口,但是这个接口需要推挽输出,则GPIO处于这个状态。例如我们使用片内外设功能(SPI接口的管脚)。

  
20.jpg

6 O0 y; d7 F( i1 G, D- n
  与推挽输出模式唯一的区别在于输出控制电路之前电平的来源! }# t% u$ D$ R  o& I
   开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的9 \  r  f! _& ~
   开漏推挽输出模式的输出电平是由复用功能外设输出决定的
5 `7 {  J$ [3 g" N8 N) Y- ~+ Z
注:推挽输出和开漏输出的区别
6 f6 L( |* |/ r8 q   推挽输出:! [# }$ g4 \* ~3 k2 n
    可以输出强高/强低电平,可以连接数字器件, E4 L# N) c( X0 u' T9 O5 ^( A
   开漏输出:
; l* Q% ?4 ~5 O. C) s" r    只能输出强低电平(高电平需要依靠外部上拉电子拉高),适合做电流型驱动,吸收电流能力较强(20ma之内)
; v7 Z* h8 T( r  l4 G6 ?- J
总结一下,在STM32中选用IO模式:
(1)模拟输入_AIN ——应用ADC模拟输入
(2)浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1
4 i$ O% d7 T  o; R4 C(3)带上拉输入_IPU——IO内部上拉电阻输入
( F7 k5 F+ S( R$ I5 ]( h, Z(4)带下拉输入_IPD—— IO内部下拉电阻输入
4 J, V2 C! t$ t; N- A(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能9 K9 D) r/ ^' V7 F$ x- Z
(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的' N. u: c; D4 o3 y, |
(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)
# T1 Z) R0 `) k, C9 w- H# I; m(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)

3 m, ~5 p% F; t/ L1 ]) c6 b
【2、STM32-IO口相关寄存器】
0 w! ~, @, B5 K" P" ^! W+ e 每组GPIO包含系列7个寄存器(7组GPIO共包含7*7=49个寄存器)5 f& B4 N' d0 |3 O" [
 两个32位配置寄存器7 u% N9 I: i) s- P1 Z
  GPIOx_CRL 低16位8 s9 W  D6 N. x9 g
  GPIOx_CRH 高16位
: @* y* ^3 k% v4 q! V  p/ X 两个32位数据寄存器
1 G& d/ N' z) \; E8 Z( V6 S" \  e) N, a% F  GPIOx_IDR 输入数据寄存器
: |$ u% Y+ M! t& K) R  GPIOx_ODR 输出数据寄存器9 r) U7 o7 C- G
 一个32位置位/复位寄存器
5 G1 z; e& x# S: Y6 z' t  GPIOx_BSRR
+ _! P0 [; S4 L3 g  `! ] 一个16位复位寄存器
1 v5 m; Q8 h, w* E- X9 A# f5 |  GPIOx_BRR
1 y% E1 K" _) G1 N 一个32位锁定寄存器+ l8 J! x4 K0 ~6 F2 B
  GPIOx_LCKR
- N& `3 Q: v/ C$ g; f3 Q; L/ I
1 b. Z2 P, P) t/ K& ]

8 W6 w. t& N( n/ v* K6 s5 v0 R六,STM32-IO口相关寄存器讲解
( M2 m5 G) I/ M4 @: U  w- u 1,端口配置寄存器:, a% o5 y+ I# v% y% x! C
  STM32每组GPIO位16个IO口,每4位控制一个IO口,所以32位控制8个IO口
3 C4 s. F6 R0 l2 M3 D  分为低16位:GPIOx_CRL和高16位:GPIOx_CRH共32位控制一组GPIO的16个IO口
  
21.jpg
( g( C4 @, k- L/ l$ ^! D0 W
  如图:以端口配置寄存器低16位为例,每四位控制一个IO口(高16位同理); L$ u. K" Z( A8 a7 s8 a
  MODEx的2位 : 配置IO口输出/输出模式(1种输出+3种不同速度的输出模式)( D& m' d  f; B* ~( ^, ]
  CNFx的2位 : 配置IO口输入/输出状态下(由MODEx控制)的输入/输出模式 
以GPIOA_CRL为例,配置IO口PA0 -> MODE0=00(输入模式) CNF0=10(上拉/下拉输入模式)
% ]6 H2 d1 T1 u2 g8 T此种配置下到底是上拉还是下拉输入模式还需由ODR寄存器决定
 
  22.jpg  
6 c) H3 I+ I6 Q1 Z) p
  关于上拉/下拉的控制我们将在下面-数据寄存器-中介绍ODR输出寄存器时详细说明$ i! b, P2 @& t& e* U6 b

) U5 d7 W) c1 Z1 X2 ?

: w* l# L( z7 X: i- G9 h2,数据寄存器(以输入数据寄存器GPIOx_IDR为例)1 Z  X8 D* J% ~7 @% S* i
  每一组IO口都具有一个GPIOx_IDR的32位寄存器(实际只使用低16位,高16位保留),即16位控制16个IO口,每一位控制一个
  
23.jpg

$ b; l4 x$ U1 u- p
  如图:IDR寄存器共32位,0~15位代表一组IO口16个IO当前值% `( y+ ^( ^/ {* w& u3 v, x+ n
    这里我们已经了解了输入/输出数据寄存器,现在说下上面提到的问题:
) x3 z/ I- A/ s  当IO口配置为输入模式且配置为上拉/下拉输入模式(即MODEx=00 CNFx=10时),ODR决定到底是上拉还是下拉2 p( O1 r! S! `$ S& {
   1)当输出模式时,ODR为输出数据寄存器* t) ^# g9 L& u' m6 M5 B  U: i$ U3 K
   2)当输入模式时,ODR用作区分当前位输入模式到底是上拉输入(ODRx=0)还是下拉输入(ODRx=1)
8 f7 j1 |) A$ ]2 P$ T+ Q  ]/ X0 O& `- [. u' w
- h& S" Q4 s- ^: z; Q% S
3,端口位设置/清除寄存器(GPIOx_BSRR)
  
24.jpg
: E6 L: P. O* \
  BSRR寄存器作用:9 Q. y! T* o( p0 A9 W; m
   BSRR寄存器为32位寄存器,低16位BSx为设置为(1设置0不变),高16位BRx为重置位(1:清除0:不变)   
# B% q7 I  l" [* ?- ?% ~% {( a9 J' Q, Z. d7 H! z
. j% p& z+ i3 I/ s! f0 B# ]
当然,最终的目的还是通过BSRR间接设置ODR寄存器,改变IO口电平  8 B$ M3 |( W4 n) j: ]8 ?" V
6 z2 I6 K- [; B" H
, O4 f$ K, G2 ]
4,端口位清除寄存器(GPIOx_BRR)
  
25.jpg

" x2 |; y% L2 m  {, Q
  GPIOx_BRR寄存器作用同GPIOx_BSRR寄存器高16位
3 ~! X( r# B. w& Q; e4 m  一般我们使用BSRR低16位和BRR的低16位(STM32F4系列取消了BSRR的高16位)
$ P/ O" k2 y/ [: O9 z- a/ [1 o! _% s: Y0 u" b! [6 u9 {9 p
5,锁存寄存器:使用较少暂不分析2 {7 R6 _2 _* w0 w1 _

5 u9 I2 u3 O+ O% k- x" ?

1 ~* t1 @# f% l" {7 a七,端口的复用和重映射 
- @2 \) g4 C6 q+ e8 ^1,端口的复用:4 C" i& Y5 S0 {0 V
  大部分IO口可复用为外部功能引脚,参考芯片数据手册(IO口复用和重映射)
  
26.jpg
! Y) w7 N* e, L# D$ r- A; v
  例如:STM32F103ZET6的PA9和PA10引脚可复用为串口发送和接收功能引脚,也可复用为定时器1的通道2和通道3  端口复用的作用:最大限度的利用端口资源  0 S2 a9 Q7 ~0 L3 C* P* e
' I" m! ?2 |; m0 }0 p( ?* v

9 j; M: E" h/ f2,端口的重映射:
  
27.jpg

* y; ~/ Q. Q' G$ r
  串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7
4 k, U/ a+ N5 [; K* y7 u  端口重映射的作用:方便布线  
  t+ Q5 k8 \6 |' @
8 a1 E, E7 a! k5 r0 K5 L4 |" N' N9 ~

1 u% E" Z6 [. f' R) |3,STM32所有的IO口都可作为中断输入(51单片机只有2个端口可以作为外部中断输入)# T( ?# s, a* Q# m/ L; l% }
【3、GPIO软件运行的过程】
       首先,我们打开iBox开发板的例程LED_DEMO,点击软件上方的“Start/stop Debug Session”按钮,如下图:

4 W+ ?2 l) x$ O+ X/ P. m
28.png
7 R0 I+ r0 m# `, |' b
程序首先从主函数开始。我们点击左上角的step或快捷键F11,
; c( v" v' l3 M
29.png

5 ]3 Y' @% R, R, m# \4 F# n

/ g; x& w$ q' t6 Z2 S3 w
就会发现左边黄色的箭头移动到主函数位置:
, @; H% q" A/ M$ ^! F
30.jpg
. \7 y" x* L. j' p0 V+ w9 V
7 ~- p% F: D  H/ ?  Y/ n* Z) T
继续F11,我们发现黄色箭头移至下图所示位置:

5 e3 \9 c3 r7 P* `2 ?
31.png

7 i9 g; e7 I, n
上面的函数是使能GPIOE端口的时钟的。GPIOE是属于APB2这条高速总线上的,所以用函数RCC_APB2PeriphClockCmd(xxx, xxx) 来启动对应的I/O端口。GPIO只有在时钟上得以启动,我们才能使用它们。

$ l9 V2 F: B8 @8 C1 F3 Q3 a
我们再次点击,发现进入了RCC_APB2PeriphClockCmd ( xxx , xxx)函数里面。

& S/ Q% B! w; ?
32.jpg

2 _5 P% W$ L# B7 s9 o
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));8 w2 D. _9 }' C1 g, `, d( S, o+ {
assert_param(IS_FUNCTIONAL_STATE(NewState));这两行是用来检测形参是否是指定范围内的值。继续F11会发现黄色光标跳到了"RCC->APB2ENR |= RCC_APB2Periph;"。这里就是把RCC_APB2Periph_GPIOE的值赋给RCC_APB2ENR。在此暂时不对此函数做详细介绍。
& P2 {' S' N4 ]2 G9 t$ N9 W
接下来的四次单步运行,我们会发现程序跳回了主函数。黄色箭头会依次在下图中四行代码中移动。

2 V5 c6 f7 y8 T( V" X
33.png

/ i: c: U" d1 o: J
我们逐行分析:
34.png
* q+ B* ?9 g5 |, G3 s
第一行的作用是选择IO端口的工作方式。由于驱动LED需要较大电流,所以此处采用推挽输出的方式。如果想更改为其他方式,只需将上图等号后面的” GPIO_Mode_Out_PP”改为其他工作方式即可。
GPIO_Mode_Out_PP 推挽输出
GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_AF_PP 复用推挽输出
GPIO_Mode_AF_OD 复用开漏输出
GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入

: C: [( K& N2 k4 a1 f7 Z2 T  \
35.png
, j0 H! _2 j- y$ P0 S; V+ b) f
第二行的作用是选择IO端口号。此处我们选择了GPIOE的9、10、12口。GPIO_Pin_x其x的值可以是0~15,如果我们同时选择多个IO口,可以用上图方式用“|”隔开,也可以用GPIO_Pin_All来选择GPIOE组下的所有端口。

0 A- F9 ~+ f# F' Z# g3 a+ j1 @
36.png
% [' m$ P* j6 z2 E: ^/ w
第三行的作用是设置IO端口的速度。端口的速度只有在IO端口被设置为输出的时候才需要设置。如果为输入模式则不需要设置。端口速度可以2/10/50MHz。上面我们设置IO端口工作方式为推挽输出,所以此处需要设置IO端口的速度。

* @* v6 V" J! E5 K5 T/ h1 c3 U
37.png

( I7 j4 {9 ?; F+ X9 {
第四行的作用是运行GPIO的初始化库函数。把上面三条的设置内容写入到IO端口对应的寄存器当中。其中第一个参数是我们要写入的哪一组端口。第二个参数就是对这一组端口的设置内容,也就是我们刚才设置的结构体内容。最终完成三个端口的初始化。
, u& a+ T6 ^- K2 S2 n
再次单步执行,我们发现此程序跳到了GPIO_Init()函数。

9 t3 T& t/ V, z* q! z5 ^, M& i! t
38.png

( u; i; B& U  e$ L) B
此函数后续我们会详细介绍,此处暂不做介绍。
, h( z( e9 I% D- Q0 J6 b, A# n2 }
直接step out(Ctrl+F11)跳到下一步。我们发现程序回到了主函数如下图所示位置。
; ]- z8 k5 O4 C* [
39.png
" `* B  V) y7 K
此处用到了函数GPIO_SetBits。此函数中只有两个参数:第一个参数是选择GPIO的组(此处我们选择了GPIOE);第二个参数是选择GPIO的Pin(此处我们选择了9、10、12)。此函数是专门用来将IO口置位(输出高电平)。下图代码将GPIOE组下的9、10、12号端口设为高电平。函数执行至此,我们可以观察到iBox上的三个指示灯全部亮了起来。
与GPIO_SetBits对应的函数为GPIO_ResetBits。此函数的功能为清楚指定数据端口位,也就是用来将IO口清零的函数。使用方法与GPIO_SetBits相同。操作IO口的方法还有很多种,后面我们会逐步介绍。

" l; K: r: k; \; |9 v/ E
继续单步运行程序,发现黄色箭头跳转到下图所示位置:
( K- g" z6 |/ X) F
40.png
% A4 u/ u8 D: d6 M4 j6 m  B" q5 L
GPIOx->BSRR是端口位设置/清除寄存器。其作用为将端口位设置或清除。

2 ^" d' M: s4 e9 v: B# R
继续单步运行,程序再次跳回主函数如下图所示的位置:

0 Z  w# [1 A& c) G( ?( y5 P
41.png
3 h8 d! X$ {" j! ]0 b/ C
其中“for(; ;)”可以理解为“while(1)”。相比之下for式死循环更加高效一些。
即不设初值,不判断条件,循环变量不增值,无终止的循环,程序会一直执行大括号里面的内容。
    上图大括号里面的第一行用到了GPIO_SetBits,后面的两个参数分别为GPIO_LED_PORT和GPIO_LED_ALL我们右键选择“Go To Definition Of 'GPIO_LED_PORT',就会跳转到下图所示的位置:
- T; P, F) C6 `
42.png
: Y* b8 x0 e  ~; |& c$ \
不难看出,这里定义了GPIOE为GPIO_LED_PORT;定义了GPIO_Pin_9、GPIO_Pin_10、GPIO_Pin_11、GPIO_Pin_12为GPIO_LED_ALL。
这样一来,我们就很容易理解这四条语句的含义了:首先用GPIO_SetBits将GPIOE组下的9、10、11、12号端口置高,延时一段时间后,用GPIO_ResetBits将GPIOE组下的9、10、11、12号端口清零,再延时一段时间,由于是在for(;;)中执行,程序会一直在这里循环,这样就实现了LED闪灯的效果。

+ \, ^% s- ]+ T
转载自:硬十

% L' o8 ?" v" A5 v" Q0 \0 \7 ]4 `: N1 p5 ]$ y) P; z
赞 收藏 评论0 发布时间:2023-2-23 22:10

举报

0个回答

所属标签

相似技术帖

官网相关资源

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