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

STM32从原理图PCB到移植RTOS GPIO

[复制链接]
STMCU小助手 发布时间:2023-2-23 22:10
我当时不理解,觉得领导挑人随心情,不体恤我们这些招聘的锻炼干部的辛苦。过第二天到了济南,老大跟我说:“这些人招聘进来,都是要给你做下属的,如果很难带出来,也是给你自己找麻烦。”
不过现在想来,觉得不无道理。
前期经典文章软硬兼修
3 X5 h1 S7 R. z
% p+ V3 }# U9 f8 {/ c
我们通过这篇文章,从芯片内部原理、电路设计、软件开发、软件运行,全流程看一下GPIO初始化、工作的全过程。帮助很多朋友只做MCU硬件的,又很想提升的朋友,进行学习和理解。
1 N8 i! }. Z3 N( Y$ F8 P" _/ q
【1、熟悉芯片的内部结构和工作原理】
任何开发,我们都应该去看厂家的Datasheet和设计参考先,其他的教材、文档、书籍、网站、博客,都是基于原厂的资料进行编辑的。
所以我们应该下载并仔细阅读下面两个文档:
* F4 P" L" B) D6 a# v# e$ J
1.jpg

' B+ s. F, o! n! {) F' C6 B  |3 W
2.jpg
+ E! g; ]4 g3 S2 U" ~0 o

" F( r: E, b& A$ M
STM32实物图:
   3.jpg
! d7 j& L! E7 M5 @
STM32的144个管脚,除了为芯片供电的电源、GND、时钟、复位管脚之外,几乎所有管脚都可以用作GPIO

7 N2 M) f3 B& |( b5 Q
 2. STM32,LQFP144PIN的引脚分布图:" u6 Y4 M% Y# B6 n6 E( C
4.jpg
( }) P. O( \, R7 X+ @* g
STM32F103ZET6:共144个引脚,7组IO口,每组16个IO口
0 [+ p! p7 ^" O/ P$ J' v
7*16=112个IO口(这7组IO口分别为GPIOA,GPIOB…GPIOG)
; o* s) o2 f! I( @$ Z7 g: N; i3 [/ X2 @例如GIOA包含PA0,PA1,PA2…PA15,每组16个IO口。
从硬件上面,按照:A、B、C、D、E、F、G分为7组。; V) K4 K8 z1 M3 r* S7 |/ d
每组有16个管脚。

( [6 ?7 n( [* D% }
IO口的基本结构和工作方式" Q$ @% Z3 \8 n( t
5.jpg
1 S3 J; E4 K' w# _4 k! [
8 F# m1 g0 E' B  j8 b- y. {; a
stm32的GPIO的配置模式有好几种,包括:
4种输入模式0 p/ {& e7 ]) Y6 ]7 t9 Q5 \
   输入浮空
6 q- c; Y7 g% G) s9 J' i- Z! \! y   输入上拉3 j* ]: o  M6 c+ V
   输入下拉
# T/ ?. k3 V8 }! n   模拟输入 
, n2 f% U; W- Y! n2 R* i3 O4种输出模式
* C& k; r& O* ~' ~& W( P4 o   开漏输出: L: c8 s& }# D6 i1 G
   开漏复用功能. C+ f0 _" c* w" i
   推挽输出$ s) {  q- Z# [8 N8 Y" ~
   推挽复用功能
) z! k- J$ |" V
可配置3种最大翻转速度2 ~5 N& O' M7 r2 g( T/ s0 `
   2MHz- K' ]* J1 i! g) A1 W
   10MHz
6 [- ~7 y, D/ ^   50MHz8 E; L: C: F; k: m2 _3 A% ~
$ F) }; ~: P0 r

1、模拟输入;

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

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

5 b% Z, r$ ?! H0 e6 d7 L- Z

6.jpg


7 K  y1 U* ^0 O1 n  w

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


  N) v6 _4 F3 W5 a$ P4 U

7.jpg


$ F. ]8 h% z( ^5 H+ u0 G1 W

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

8.jpg

# s/ ^' J8 W' U& H0 D" W

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

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

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

, t: j, \( C: K- E) p) C0 o

9.jpg

  
上拉和下拉部分均为关闭状态(AD转换-模拟量转换为数字量)
1 S5 E' P  ?6 i施密特触发器为截止状态  p* d) n! F( a' {- M2 I
通过模拟输入通道输入到CPU6 B6 A0 f& q$ R. j* w* k% B( R. c! y
IO口外部电压为模拟量(电压形式非电平形式),作为模拟输入范围一般为0~3.3V


5 b6 F7 }/ G# Y* M( q

2、 浮空输入;

10.jpg
# u& }2 ?4 b# Z( N! n

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


+ C( C& z6 }( ~' x  ]% i" |$ u
11.jpg

0 T) z% W2 ~. b1)外部通过IO口输入电平,外部电平通过上下拉部分(浮空模式下都关闭,既无上拉也无下拉电阻)
+ ^5 u& }9 U; R1 c. @2)传输到施密特触发器(此时施密特触发器为打开状态)" p. L; C& }0 g
3)继续传输到输入数据寄存器IDR2 D$ i! f9 T7 ]: p- U. J0 C6 L/ r
4)CPU通过读输入数据寄存器IDR实现读取外部输入电平值。在输入浮空模式下可以读取外部输入电平


  M8 |* W6 B% Y7 @

3、上拉输入;

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

" S1 D& W4 J: u/ V7 s
12.jpg

, F: c0 ~$ S  _3 a4 ]
13.jpg

$ c/ c( Q% v( }: x4 z和输入浮空模式相比较,不同之处在于内部有一个上拉电阻连接到VDD(输入上拉模式下,上拉电阻开关接通,阻值约30-50K)
. D: Q3 s2 a' X' V5 v外部输入通过上拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取。

5 U+ ]( [5 o! f$ b8 t  ]

4、下拉输入;

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


( {- s$ F) H; B# s* b

14.jpg
: A0 {$ g7 X+ H* m, [& w7 z* a4 o
15.jpg
; g2 S/ Z" w, v$ a# ~
  
        和输入浮空模式相比较,不同之处在于内部有一个下拉电阻连接到VSS(输入下拉模式下,下拉电阻开关接通,阻值约30-50K)
+ ]3 ~# v. C7 @        外部输入通过下拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取

3 @6 k3 W1 ]5 {" R9 ?" s6 B

5、开漏输出;

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

* n: D# p: Z5 B5 ]. W* O; X
16.jpg
/ x' }  s; p' _- p  |) ?. P
17.jpg
  
1,CPU写入位设置/清楚寄存器BSRR,映射到输出数据寄存器ODR9 a& ]+ k( S8 f" k- d
2,联通到输出控制电路(也就是ODR的电平)
+ b. L8 H0 @! u8 C3,ODR电平通过输出控制电路进入N-MOS管  
& X( _8 ^2 m. u4 _-ODR输出1: ; G8 R( I4 j8 e
   N-MOS截止,IO端口电平不会由ODR输出决定,而由外部上拉/下拉决定。7 z4 V1 b1 U( B+ }& e
  在输出状态下,输出的电平可以被读取,数据存入输入数据寄存器,由CPU读取,实现CPU读取输出电平。
- c# i$ B, m7 H" c) {& Q; @  所以,当N-MOS截止时,如果读取到输出电平为1,不一定是我们输出的1,有可能是外部上拉产生的1。
# K+ @. J, y3 y
2 G1 g7 ?* }  o4 H( Q% e9 d  n
  -ODR输出0:4 z9 p8 }# g, ^& `2 R
   N-MOS开启,IO端口电平被N-MOS管拉倒VSS,使IO输出低电平。" a6 Y3 u; z& N+ K% D% E
   此时输出的低电平同样可以被CPU读取到。
5 K4 M8 A1 Q/ c5 z

6、 推挽输出;

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


- e, e2 w0 r7 g1 f& s$ H6 T5 X/ B1 Y

18.jpg


+ K) a8 a" b7 h+ e

   与开漏输出模式唯一的区别在于输出控制电路之前电平的来源
' ]+ [/ v0 p$ O3 S5 z! O: S    开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的
& m( r; L2 f1 p, K5 W+ ^, [0 A    开漏推挽输出模式的输出电平是由复用功能外设输出决定的, }5 J9 s4 L7 y: x% }
   其他与开漏输出模式相似:
- Y4 Z6 R4 H) N8 U    控制电路输出为1:N-MOS截止,IO口电平由外部上拉/下拉决定+ J6 }) S+ v3 D. H+ ~
    控制电路输出为0:N-MOS开启,IO口输出低电平

0 o: u6 u7 d6 W) u5 d7 d9 v/ E

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

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

9 F( y, i  |9 S( [$ V& ^
19.jpg

: u$ |9 w. j, h1 Q8 a& s   与开漏输出相比较:. b# D8 ^& ~4 R' h# a( g" @
    输出控制寄存器部分相同6 J" Z' S1 a' w5 r) I' t
    输出驱动器部分加入了P-MOS管部分3 i8 J! M  v. }" y
   当输出控制电路输出1时:
5 w: I& q% D  b    P-MOS管导通N-MOS管截止,被上拉到高电平,IO口输出为高电平1: D, Q, t. g8 M0 c
   当输出控制电路输出0时:
) n. }7 b% \7 k5 F8 @' O' R5 U* L    P-MOS管截止N-MOS管导通,被下拉到低电平,IO口输出为低电平0
6 Q* {+ X2 `- j! Q+ q& v   同时IO口输出的电平可以通过输入电路读取

* s4 }7 H: F, Z9 H3 o  L* s) w7 s. R

8、 复用推挽输出;

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

  
20.jpg
3 P1 @9 ?, H; B
  与推挽输出模式唯一的区别在于输出控制电路之前电平的来源
( g: h3 ?/ Z5 P& I" v8 e   开漏输出模式的输出电平是由CPU写入输出数据寄存器控制的
# p, h; v/ o. n! |. k5 v   开漏推挽输出模式的输出电平是由复用功能外设输出决定的

; R& I' V4 p' e9 C/ {3 V- s
注:推挽输出和开漏输出的区别: e- W0 P' Q0 \. i1 X, U5 G3 E
   推挽输出:
" `+ F5 N# P9 R6 I0 _    可以输出强高/强低电平,可以连接数字器件  h( J9 t, l; u6 V2 K
   开漏输出:
9 |) I$ I  U3 r% Q$ G4 L7 |# |    只能输出强低电平(高电平需要依靠外部上拉电子拉高),适合做电流型驱动,吸收电流能力较强(20ma之内)

! b! p4 k* k& w
总结一下,在STM32中选用IO模式:
(1)模拟输入_AIN ——应用ADC模拟输入
(2)浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1! `; {) g, k4 K% `
(3)带上拉输入_IPU——IO内部上拉电阻输入0 V( K1 l. p. z3 Z! t
(4)带下拉输入_IPD—— IO内部下拉电阻输入8 `& s5 [$ d4 u2 Z8 S  f9 P, l
(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能
4 z* ^9 D& W( y" b; O(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的
% Z. f5 {9 ^  N- s9 e% T(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)
1 u5 R/ m8 q( \% X(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)
: @* P! ]; N& F; \2 y
【2、STM32-IO口相关寄存器】; Z( W/ X4 R9 }  A8 {- w
 每组GPIO包含系列7个寄存器(7组GPIO共包含7*7=49个寄存器)
' H. T9 ?' ^! f6 c1 E, n- J7 ? 两个32位配置寄存器
8 ~2 a9 w$ d0 l( [/ z. E  GPIOx_CRL 低16位, I  B- x& Z0 l; n! `- p$ f  _, v
  GPIOx_CRH 高16位5 u! X7 g" L5 J+ b+ F1 V
 两个32位数据寄存器% X6 ^9 O7 s$ G# i- S2 o6 _! U
  GPIOx_IDR 输入数据寄存器
+ b! R( G2 [4 _% N/ A! _' y  GPIOx_ODR 输出数据寄存器
4 {( [& ?1 C3 V" Z8 Z: H& Q6 ~ 一个32位置位/复位寄存器7 v; F. M8 B, R/ O0 o$ Y
  GPIOx_BSRR
  Y  {4 Z6 N+ \1 r 一个16位复位寄存器: I( r9 p- `5 E. e
  GPIOx_BRR
/ a" @; C5 T$ O3 b+ u' C3 f 一个32位锁定寄存器
, N3 ]8 Y+ G( n% j- `) q  GPIOx_LCKR
' Q" ?. l+ i- r5 K/ X- d* o3 E: J, F- P8 T; O. \, `$ b
) X! B, k/ N8 r8 o3 p
六,STM32-IO口相关寄存器讲解
$ x$ j  c" T# Z: `# }. o3 M 1,端口配置寄存器:
' G$ u" g, y1 S+ s% O# {' \  STM32每组GPIO位16个IO口,每4位控制一个IO口,所以32位控制8个IO口
9 I! t6 @! }! }& I$ g, ?. N  分为低16位:GPIOx_CRL和高16位:GPIOx_CRH共32位控制一组GPIO的16个IO口
  
21.jpg

* M3 I5 z/ N7 h- m  g
  如图:以端口配置寄存器低16位为例,每四位控制一个IO口(高16位同理)7 p3 y! ^+ f0 s+ g1 T
  MODEx的2位 : 配置IO口输出/输出模式(1种输出+3种不同速度的输出模式)! u! u3 F6 _; |2 T" x6 N9 e6 X, X+ L
  CNFx的2位 : 配置IO口输入/输出状态下(由MODEx控制)的输入/输出模式 
以GPIOA_CRL为例,配置IO口PA0 -> MODE0=00(输入模式) CNF0=10(上拉/下拉输入模式)
; S0 U5 g/ f* ?9 o  h, h' J# u+ s- Z此种配置下到底是上拉还是下拉输入模式还需由ODR寄存器决定
 
  22.jpg  
: C- v: h$ Z1 S- H& E  a
  关于上拉/下拉的控制我们将在下面-数据寄存器-中介绍ODR输出寄存器时详细说明
* Z( d5 M- l+ d+ R/ r$ G5 O" w7 P3 }, s$ [3 t- W3 l* `/ m& F

  d# P5 ?6 Y- k: Y2,数据寄存器(以输入数据寄存器GPIOx_IDR为例)9 z6 b( X3 j; p7 j# l  l% G( R
  每一组IO口都具有一个GPIOx_IDR的32位寄存器(实际只使用低16位,高16位保留),即16位控制16个IO口,每一位控制一个
  
23.jpg

5 c" F3 R7 m! H& d' G1 V. a
  如图:IDR寄存器共32位,0~15位代表一组IO口16个IO当前值
7 o$ o$ i$ q3 z    这里我们已经了解了输入/输出数据寄存器,现在说下上面提到的问题:* {0 S0 j# J  i1 D* U1 q. F( b
  当IO口配置为输入模式且配置为上拉/下拉输入模式(即MODEx=00 CNFx=10时),ODR决定到底是上拉还是下拉
4 N7 S' T' i3 |4 E5 b5 k   1)当输出模式时,ODR为输出数据寄存器
/ z, n1 ]9 m- z   2)当输入模式时,ODR用作区分当前位输入模式到底是上拉输入(ODRx=0)还是下拉输入(ODRx=1)9 G; K' P- f+ p8 H1 S: E1 N/ S

  n6 B1 Q* Z; U" g1 l  |

1 X+ a% \7 u/ e4 i# S5 C3,端口位设置/清除寄存器(GPIOx_BSRR)
  
24.jpg

" Y$ a% D, [4 ^/ e# j- f1 ]
  BSRR寄存器作用:
; x2 [( A7 c) v5 r% e4 M- R# @5 ^   BSRR寄存器为32位寄存器,低16位BSx为设置为(1设置0不变),高16位BRx为重置位(1:清除0:不变)   ( M4 ]$ a8 A( N1 U2 i
* }- Q* A3 }& _" [# i; t! A+ E

9 _/ d" U4 I. J7 e; D# E当然,最终的目的还是通过BSRR间接设置ODR寄存器,改变IO口电平  
! ~! ^1 x8 [! L. c7 u; T' g. ]- @! M8 J  Z+ }0 V0 n7 }# }

) N; D& l" E  O+ k. w2 h4,端口位清除寄存器(GPIOx_BRR)
  
25.jpg

. F8 s9 k  N* c" S' F7 z( p
  GPIOx_BRR寄存器作用同GPIOx_BSRR寄存器高16位
/ ]& y1 {. }' X  ~0 o3 s/ \  一般我们使用BSRR低16位和BRR的低16位(STM32F4系列取消了BSRR的高16位)
+ s) H- u6 \5 F0 e: G: m
' z& C( f  _# [' h, K& M* X5,锁存寄存器:使用较少暂不分析$ |( P* w1 k0 E/ J5 }* E. Z
* p  @5 P. R/ P4 k
; u! U- P& \0 z: m
七,端口的复用和重映射 0 k) z( \6 I, `0 [8 x
1,端口的复用:+ M( k1 a9 Q; R& S: ]
  大部分IO口可复用为外部功能引脚,参考芯片数据手册(IO口复用和重映射)
  
26.jpg
! O  Y* `& u9 _; ^2 _/ G4 ?
  例如:STM32F103ZET6的PA9和PA10引脚可复用为串口发送和接收功能引脚,也可复用为定时器1的通道2和通道3  端口复用的作用:最大限度的利用端口资源  
/ W9 `' _7 r/ [, }3 }8 j) x  H2 n0 C0 b" j6 H
! j) C( J% g) D
2,端口的重映射:
  
27.jpg

) d4 v$ T8 e8 f/ ?3 Z) i5 {4 S9 w
  串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7
# F% z- [% g4 U  端口重映射的作用:方便布线  5 k3 [6 G; t. j$ ^2 {7 C5 |

$ L/ ~" b  [+ }) w

# c$ o: x2 H+ W( v9 q3,STM32所有的IO口都可作为中断输入(51单片机只有2个端口可以作为外部中断输入). Y' q" e0 q; I* D- v
【3、GPIO软件运行的过程】
       首先,我们打开iBox开发板的例程LED_DEMO,点击软件上方的“Start/stop Debug Session”按钮,如下图:
( A* J3 a/ l& ?9 z2 n) |/ {" {
28.png

( U: s4 c8 {9 F8 Z/ \
程序首先从主函数开始。我们点击左上角的step或快捷键F11,

; v/ j' w% m( T& N8 s0 e- |$ F
29.png

5 G! J* k/ ^1 D' t) Q% c# U; N

, `( [* p. }; `' K
就会发现左边黄色的箭头移动到主函数位置:

: J" w9 i. c( R
30.jpg
( l4 ]! R! u, J9 _$ F
/ S/ k, ^' t- ^5 x9 q
继续F11,我们发现黄色箭头移至下图所示位置:

4 _1 A1 o" t" M$ W! Y
31.png

: t8 f/ a! b; j) y% x4 T0 n! g
上面的函数是使能GPIOE端口的时钟的。GPIOE是属于APB2这条高速总线上的,所以用函数RCC_APB2PeriphClockCmd(xxx, xxx) 来启动对应的I/O端口。GPIO只有在时钟上得以启动,我们才能使用它们。

. U5 o1 ]2 G6 C
我们再次点击,发现进入了RCC_APB2PeriphClockCmd ( xxx , xxx)函数里面。

. S: R* d5 ?$ V6 ]: u4 V) ^
32.jpg

% X) a) O$ H- C0 w: o7 r4 O6 y% ~& p
assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));; P* X% U; M# R4 L& j! U- H
assert_param(IS_FUNCTIONAL_STATE(NewState));这两行是用来检测形参是否是指定范围内的值。继续F11会发现黄色光标跳到了"RCC->APB2ENR |= RCC_APB2Periph;"。这里就是把RCC_APB2Periph_GPIOE的值赋给RCC_APB2ENR。在此暂时不对此函数做详细介绍。
9 o# J! c9 Q# t' u9 ?: T( h
接下来的四次单步运行,我们会发现程序跳回了主函数。黄色箭头会依次在下图中四行代码中移动。

# ]. {; M- j: ?+ X# B7 b! Q
33.png

* Z" w* E0 x. N
我们逐行分析:
34.png

  K2 ]; [: w* H0 q3 e' H2 @, g
第一行的作用是选择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 上拉输入

9 E) ?  H! @: l1 _
35.png

$ {9 x) P* U+ w- W- F6 A: A1 u
第二行的作用是选择IO端口号。此处我们选择了GPIOE的9、10、12口。GPIO_Pin_x其x的值可以是0~15,如果我们同时选择多个IO口,可以用上图方式用“|”隔开,也可以用GPIO_Pin_All来选择GPIOE组下的所有端口。

8 I% @4 O( d* K- ~
36.png
+ o' [" W! C' g; t7 I, Z) ~
第三行的作用是设置IO端口的速度。端口的速度只有在IO端口被设置为输出的时候才需要设置。如果为输入模式则不需要设置。端口速度可以2/10/50MHz。上面我们设置IO端口工作方式为推挽输出,所以此处需要设置IO端口的速度。
. j4 ^  T: @2 P5 I8 {9 W
37.png
' K* U7 W% @7 p& Z4 {9 G/ y! P4 f
第四行的作用是运行GPIO的初始化库函数。把上面三条的设置内容写入到IO端口对应的寄存器当中。其中第一个参数是我们要写入的哪一组端口。第二个参数就是对这一组端口的设置内容,也就是我们刚才设置的结构体内容。最终完成三个端口的初始化。

, Q9 l5 u$ z1 @! u! J0 q  t6 ^! ?
再次单步执行,我们发现此程序跳到了GPIO_Init()函数。
' R3 s4 x8 H# h1 j- n( J
38.png

5 i' F& t* U2 G/ J6 v4 f, t
此函数后续我们会详细介绍,此处暂不做介绍。

0 V! k& V5 Y" Z6 s# r; S
直接step out(Ctrl+F11)跳到下一步。我们发现程序回到了主函数如下图所示位置。

* ^- G/ f4 m8 ~/ @3 [$ |5 U- h# j
39.png
7 ^3 R! Z7 |$ E0 u9 e
此处用到了函数GPIO_SetBits。此函数中只有两个参数:第一个参数是选择GPIO的组(此处我们选择了GPIOE);第二个参数是选择GPIO的Pin(此处我们选择了9、10、12)。此函数是专门用来将IO口置位(输出高电平)。下图代码将GPIOE组下的9、10、12号端口设为高电平。函数执行至此,我们可以观察到iBox上的三个指示灯全部亮了起来。
与GPIO_SetBits对应的函数为GPIO_ResetBits。此函数的功能为清楚指定数据端口位,也就是用来将IO口清零的函数。使用方法与GPIO_SetBits相同。操作IO口的方法还有很多种,后面我们会逐步介绍。
3 g  j7 T% ?5 v
继续单步运行程序,发现黄色箭头跳转到下图所示位置:
% V3 }0 p  |+ _% v
40.png
2 |! _9 n4 D& N0 @
GPIOx->BSRR是端口位设置/清除寄存器。其作用为将端口位设置或清除。

5 R# [0 p7 _2 _) x+ c2 c: |
继续单步运行,程序再次跳回主函数如下图所示的位置:

/ q$ f7 I; _4 q+ H$ p
41.png
8 Y6 F. N* F7 R# T. j/ {% Y" I
其中“for(; ;)”可以理解为“while(1)”。相比之下for式死循环更加高效一些。
即不设初值,不判断条件,循环变量不增值,无终止的循环,程序会一直执行大括号里面的内容。
    上图大括号里面的第一行用到了GPIO_SetBits,后面的两个参数分别为GPIO_LED_PORT和GPIO_LED_ALL我们右键选择“Go To Definition Of 'GPIO_LED_PORT',就会跳转到下图所示的位置:
7 \, `% q. d6 X0 r( l# j; c
42.png

6 H6 O- `0 E! u, I. I
不难看出,这里定义了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闪灯的效果。
5 x7 {) R: {! \1 p, a
转载自:硬十

( G! r% F. T  Y& ?- M- {* V
. F3 L/ |# P6 i, E8 s3 v7 j
收藏 评论0 发布时间:2023-2-23 22:10

举报

0个回答

所属标签

相似分享

官网相关资源

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