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

入手STM32单片机的知识点总结

[复制链接]
STMCU小助手 发布时间:2022-10-11 18:00
  本文将以STM32F10x为例,对标准库开发进行概览。主要分为三块内容:5 [- ]9 v. R9 j! N, s. f+ q' M
1.STM32系统结构, k( Q; B( H- U5 W. g5 P- O
2.寄存器0 x5 K! t; ], ~
3.通过点灯案例,详解如何基于标准库构建STM32工程; Y$ c7 m+ C( n0 ?
STM32系统结构  J; s) q6 n" b" J0 D3 e/ C

& Z  I8 O% e  ?2 Z; H" ~1 w 微信图片_20221011180000.png
0 r8 m: `  H" t+ g9 B" H+ j  k0 G/ o7 \# I7 M' B/ m' n( X/ M, ^
    上图,STM32f10xxx系统结构。! P. c/ p, b, K, ?3 D: p

4 P1 v& r( r) ?  A内核IP
: h; t- p1 j3 D    从结构框图上看,Cortex-M3内部有若干个总线接口,以使CM3能同时取址和访内(访问内存),它们是:指令存储区总线(两条)、系统总线、私有外设总线。有两条代码存储区总线负责对代码存储区(即 FLASH 外设)的访问,分别是 I-Code 总线和 D-Code 总线。  r  E; U  P# `. d% Q
3 B% |# l# T7 _9 Z
    I-Code用于取指,D-Code用于查表等操作,它们按最佳执行速度进行优化。
2 k7 Z# i7 K+ v5 Q% u( B5 j! j% Q: E1 A% E$ c: U
    系统总线(System)用于访问内存和外设,覆盖的区域包括SRAM,片上外设,片外RAM,片外扩展设备,以及系统级存储区的部分空间。7 p: d( z7 M+ t+ Y: g# z9 I, u

' x& T1 O4 _$ ^) Q, r1 S) c+ q    私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。! J1 i& d2 P) `' v6 i0 I5 K: P% ~% D/ k

/ |. \4 D9 ?' ]! g! A    还有一个DMA总线,从字面上看,DMA是data memory access的意思,是一种连接内核和外设的桥梁,它可以访问外设、内存,传输不受CPU的控制,并且是双向通信。简而言之,这个家伙就是一个速度很快的且不受老大控制的数据搬运工。相关文章:详解STM32中的DMA原理。
$ w& u3 y# c1 [4 B" j: f2 I; o2 ]# F2 h* R# \2 K7 ]* G
处理器外设(内核之外的外设)& E4 |! E' l  r- T& \' C8 `' R
    从结构框图上看,STM32的外设有串口、定时器、IO口、FSMC、SDIO、SPI、I2C等,这些外设按照速度的不同,分别挂载到AHB、APB2、APB1这三条总线上。
% m+ Q1 {) ^; H- a; o

5 y5 N0 n0 T, Y/ t/ y, [0 o! X9 j2 L寄存器
1 {% h& u; o  d' x1 L: b    什么是寄存器?寄存器是内置于各个IP外设中,是一种用于配置外设功能的存储器,并且有想对应的地址。一切库的封装始于映射。
" m  c) q- d0 L$ E: f$ s- L) x" E" L2 D/ F( d6 q4 J

+ `$ C( Z/ h" t. k% Q/ O5 e9 Z
微信图片_20221011175956.png

# F- U5 F7 u, q6 ]
微信图片_20221011175940.png
& Z: a, r" k3 I/ w! {

: E' K- c( `! s: U" x* X% w( E# h6 {8 M    是不是看的眼都花了,如果进行寄存器开发,就需要怼地址以及对寄存器进行字节赋值,不仅效率低而且容易出错。* |4 Q% \/ |1 \

# v, m2 V7 v/ ~    库的存在就是为了解决这类问题,将代码语义化。语义化思想不仅仅是嵌入式有的,前端代码也在追求语义特性。
- {/ F3 K1 w8 D! c
9 b3 ?4 j( H. e) {$ P5 H从点灯开始学习STM32
' ?' {* |  M8 `+ V2 ^. I- r- W
微信图片_20221011175937.png

9 t, S  Q  Q8 X0 d! c, \: y( e" Z) u) g
: W0 G; N+ i" {  f' g# r
内核库文件分析2 c) ]* I! x- U: Q# e5 {
cor_cm3.h
( W- J, N! x1 r+ ?% q    这个头文件实现了:
9 J( E) C/ [7 N6 K0 Z$ K) s1.内核结构体寄存器定义。
$ K4 F8 f3 ~% P/ n9 }2.内核寄存器内存映射。
: N# v- Q% G+ R5 z' j3 S$ E+ p5 J3.内存寄存器位定义。跟处理器相关的头文件stm32f10x.h实现的功能一样,一个是针对内核的寄存器,一个是针对内核之外,即处理器的寄存器。
' B4 X* k0 m6 ]; i+ {) Q
4 Q; N4 g8 Q4 w- ^2 ymisc.h
) Z' x# r7 F5 P* P  n  [    内核应用函数库头文件,对应stm32f10x_xxx.h。
9 D3 R) l5 u. ]" e5 e5 [* u, X) ^! d6 R% I: [" Z
misc.c+ x" o% Z; |, E
    内核应用函数库文件,对应stm32f10x_xxx.c。在CM3这个内核里面还有一些功能组件,如NVIC、SCB、ITM、MPU、CoreDebug,CM3带有非常丰富的功能组件,但是芯片厂商在设计MCU的时候有一些并不是非要不可的,是可裁剪的,比如MPU、ITM等在STM32里面就没有。- c+ y: x+ v, g" l

7 }  h" u/ {' X: M" x    其中NVIC在每一个CM3内核的单片机中都会有,但都会被裁剪,只能是CM3 NVIC的一个子集。在NVIC里面还有一个SysTick,是一个系统定时器,可以提供时基,一般为操作系统定时器所用。misc.h和mics.c这两个文件提供了操作这些组件的函数,并可以在CM3内核单片机直接移植。
, K! e  I1 V) l( d1 \$ n! Z) w! H, A+ e- F2 B
$ ?4 |0 [- ]. U3 A
处理器外设库文件分析
. z$ L3 j( w! W/ l  I/ Wstartup_stm32f10x_hd.s
/ }3 ^: T  M. X( r5 P' U$ D    这个是由汇编编写的启动文件,是STM32上电启动的第一个程序,启动文件主要实现了7 y- y7 Q0 n/ ?8 }
1.初始化堆栈指针 SP;' B) h, y9 A3 F4 N# Z
2.设置 PC 指针=Reset_Handler ;
- {- j1 ~; A. x, E4 [: W7 u! u  p3.设置向量表的地址,并 初始化向量表,向量表里面放的是 STM32 所有中断函数的入口地址2 K5 m, G" s* u. U7 M6 U1 b
4.调用库函数 SystemInit,把系统时钟配置成 72M,SystemInit 在库文件 stytem_stm32f10x.c 中定义;6 m0 q3 h' I( Z2 x2 Y+ V
5.跳转到标号_main,最终去到 C 的世界。
$ \: V, b1 n; v3 V; Q, R9 ?; _( Q1 F" J
system_stm32f10x.c
" h7 O9 H# [& z' ?1 T    这个文件的作用是里面实现了各种常用的系统时钟设置函数,有72M,56M,48, 36,24,8M,我们使用的是是把系统时钟设置成72M。2 N" U3 E1 ?8 T- b
2 K3 s( T7 U* J; a& r3 `0 j
Stm32f10x.h
& n" G! k: o6 T/ C    这个头文件非常重要,这个头文件实现了:) e) m* d' S# r+ V4 }; D" l( E9 W
1.处理器外设寄存器的结构体定义。
6 O0 N2 X3 B' k/ y- W$ O2 o  f3 ^& s2.处理器外设的内存映射。" O! o9 t9 Q) E
3.处理器外设寄存器的位定义。
4 J4 \& B) ?6 _4 F5 ~# ?5 ^. \( N2 k8 y* L6 ~3 L8 @
    关于 1 和 2 我们在用寄存器点亮 LED 的时候有讲解。
; w6 ]) \- |9 Z' }" i4 m& }

* I7 }4 w2 j7 ]* [$ C$ K" H3 Z1 N! D    其中 3:处理器外设寄存器的位定义,这个非常重要,具体是什么意思?9 U1 R9 b, ], B* q

/ v5 z2 R- D) p    我们知道一个寄存器有很多个位,每个位写 1 或者写 0 的功能都是不一样的,处理器外设寄存器的位定义就是把外设的每个寄存器的每一个位写 1 的 16 进制数定义成一个宏,宏名即用该位的名称表示,如果我们操作寄存器要开启某一个功能的话,就不用自己亲自去算这个值是多少,可以直接到这个头文件里面找。相关文章:C语言操作寄存器的常见手法。7 n, S/ f& c: `

& E9 V' i/ G3 C" D. v" n    我们以片上外设 ADC 为例,假设我们要启动 ADC 开始转换,根据手册我们知道是要控制 ADC_CR2 寄存器的位 0:ADON,即往位 0 写 1,即:
' w- U$ g) G7 o' n% C3 b2 L" H+ C
# |5 T) Q  }* ^$ ?! V
  1. ADC->CR2=0x00000001;
复制代码

( q  A& w6 k( x( x# t9 M    这是一般的操作方法。现在这个头文件里面有关于 ADON 位的位定义:
  p; L( y3 t- {/ n. F" p& ^
& q. P2 V/ |- q5 @7 O- a. p
  1. #define ADC_CR2_ADON ((uint32_t)0x00000001)
复制代码
- T* e5 A% R5 ?* |$ d
有了这个位定义,我们刚刚的代码就变成了:
# z! H4 X% [5 b6 b! J  a' J
) r* Q% M1 d2 e; b9 {% M6 I1 T
  1. ADC->CR2=ADC_CR2_ADON
复制代码
& O2 j; _* T6 g1 L4 ]
stm32f10x_xxx.h
, x- v5 q( n( f# s9 l    外设 xxx 应用函数库头文件,这里面主要定义了实现外设某一功能的结构体,比如通用定时器有很多功能,有定时功能,有输出比较功能,有输入捕捉功能,而通用定时器有非常多的寄存器要实现某一个功能。
+ X' C( G" P5 d9 F/ N6 G% v6 W* o) C/ J3 |
    比如定时功能,我们根本不知道具体要操作哪些寄存器,这个头文件就为我们打包好了要实现某一个功能的寄存器,是以机构体的形式定义的,比如通用定时器要实现一个定时的功能,我们只需要初始化 TIM_TimeBaseInitTypeDef 这个结构体里面的成员即可,里面的成员就是定时所需要操作的寄存器。
& B* `8 s- M& Y+ J1 B
/ Y6 I/ t; \" U$ ~3 k2 r8 K
    有了这个头文件,我们就知道要实现某个功能需要操作哪些寄存器,然后再回手册中精度这些寄存器的说明即可。
5 `; y, K* v& ~- {
* O7 K! u+ Z, n; h; q% istm32f10x_xxx.c
: c7 y& Z' o$ e; k0 f    stm32f10x_xxx.c:外设 xxx 应用函数库,这里面写好了操作 xxx 外设的所有常用的函数,我们使用库编程的时候,使用的最多的就是这里的函数。
9 |* b3 B2 C; t7 d* p4 k6 i' a& ~) l/ J6 C0 d5 `
SystemInit
& p2 j! T$ ?7 R( L# j$ T    工程中新建main.c 。+ U. ]6 y* ^# E1 P3 N2 ~$ ^

1 J" M- l: u% r8 [8 I1 ]    在此文件中编写main函数后直接编译会报错:
  |8 g% W4 W- S6 w# O- s2 F8 F) @# ?# ~6 x. E
Undefined symbol SystemInit (referred from startup_stm32f10x_hd.o).
9 _. p5 v6 P, B( U$ U* e3 A) s    错误提示说SystemInit没有定义。从分析启动文件startup_stm32f10x_hd.s时我们知道。
+ O! s/ P1 J. o; g% b- j

. P5 @% ]# o8 l) @
  1. <font face="Tahoma"><font size="3"><font color="#000000">;Reset handler</font></font></font>7 H' p. ], V$ A
  2. <font face="Tahoma"><font size="3"><font color="#000000">Reset_Handler PROC</font></font></font>0 U5 B8 k8 ]3 I9 e
  3. <font face="Tahoma"><font size="3"><font color="#000000">EXPORT Reset_Handler [WEAK]</font></font></font>" T) T3 c9 }: |' S9 V
  4. <font face="Tahoma"><font size="3"><font color="#000000">IMPORT __main</font></font></font>
    # Y% Y$ r( i1 s) i5 e4 C) N
  5. <font face="Tahoma"><font size="3"><font color="#000000">;IMPORT SystemInit</font></font></font>
      G+ d- p, O- x; @3 [9 w
  6. <font face="Tahoma"><font size="3"><font color="#000000">;LDR R0, =SystemInit</font></font></font>
    6 y- Y9 R, N5 t" B
  7. <font face="Tahoma"><font size="3"><font color="#000000">BLX R0</font></font></font>
    9 ~$ ~3 o0 x: o+ m9 k
  8. <font face="Tahoma"><font size="3"><font color="#000000">LDR R0, =__main</font></font></font>* V. A5 L2 b( j' p( F# n
  9. <font face="Tahoma"><font size="3"><font color="#000000">BX R0</font></font></font>
    , Y3 ~+ J; }0 W
  10. <font face="Tahoma"><font size="3"><font color="#000000">ENDP</font></font></font>
复制代码
3 ], s7 D, K' j8 o5 `0 a1 @
汇编中;分号是注释的意思
1 a+ x/ y$ K5 [+ l    第五行第六行代码Reset_Handler调用了SystemInit该函数用来初始化系统时钟,而该函数是在库文件system_stm32f10x.c中实现的。我们重新写一个这样的函数也可以,把功能完整实现一遍,但是为了简单起见,我们在main文件里面定义一个SystemInit空函数,为的是骗过编译器,把这个错误去掉。& n: C1 w: m9 J& N9 a5 j

8 q8 ~) H& z& r& u! o4 B    关于配置系统时钟之后会出文章RCC时钟树详细介绍,主要配置时钟控制寄存器(RCC_CR)和时钟配置寄存器(RCC_CFGR)这两个寄存器,但最好是直接使用CubeMX直接生成,因为它的配置过程有些冗长。
' Y# K. |! o0 G: |0 b) [0 Y  a
$ z: \3 r+ q$ W" i* Q1 P$ |3 r    如果我们用的是库,那么有个库函数SystemInit,会帮我们把系统时钟设置成72M。. i5 h+ q3 \7 [' N# U0 f6 P

1 `& B' p; j0 U: `  I$ A- y* g, g4 c0 q% R    现在我们没有使用库,那现在时钟是多少?答案是8M,当外部HSE没有开启或者出现故障的时候,系统时钟由内部低速时钟LSI提供,现在我们是没有开启HSE,所以系统默认的时钟是LSI=8M。: j+ B- A9 ?' N, X, v
- G4 y% H: }" R! U) D- W. |
库封装层级
4 _$ k0 t1 R2 X; p  L& o% o0 w8 d9 |4 p  ]
微信图片_20221011175930.png
! o' [' M9 R# {

* m1 r2 y: ]( {* U    如图,达到第四层级便是我们所熟知的固件库或HAL库的效果。当然库的编写还需要考虑许多问题,不止于这些内容。我们需要的是了解库封装的大概过程。
  ~5 l  b: h8 G8 S9 N% R

% k, j! J  `& w3 H    将库封装等级分为四级来介绍是为了有层次感,就像打怪升级一样,进行认知理解的升级。
0 Y6 }0 G0 m7 G- H! j! A5 g

0 B$ K/ F6 X8 A. a6 M% R    我们都知道,操作GPIO输出分三大步:3 V6 `5 |$ J2 {
3 }. [  N  p3 U  {) Z* J
时钟控制:
  n5 R* G9 a" O* _: W    STM32 外设很多,为了降低功耗,每个外设都对应着一个时钟,在系统复位的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。
/ ]& z0 S: J5 G$ _5 g2 ^6 G) }% e5 V. ?. C) G- r
    STM32 的所有外设的时钟由一个专门的外设来管理,叫RCC(reset and clockcontrol),RCC 在STM32 参考手册的第六章。- M  H0 d, Y7 {* ]
2 G! y4 A9 T! b/ R- C) R/ G) ~9 A
    STM32 的外设因为速率的不同,分别挂载到三条总系上:AHB、APB2、APB1,AHB为高速总线,APB2 次之,APB1 再次之。所以的IO 口都挂载到APB2 总线上,属于高速外设。$ Y1 K/ n; i; l

$ N% x; W: `) A+ T, N5 u

) }: P5 F& }- t- }" Y- d9 F模式配置:
$ v' N) G" r1 F+ o& V/ p    这个由端口配置寄存器来控制。端口配置寄存器分为高低两个,每4bit 控制一个IO 口,所以端口配置低寄存器:CRL 控制这IO 口的低8 位,端口配置高寄存器:CRH控制这IO 口的高8bit。- B. ?- j- t+ l) i: q

& x& n% f0 h8 U- \* s; z. h1 w* C: n    在4 位一组的控制位中,CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0]用来控制输出模式的速率,又称驱动电路的响应速度,注意此处速率与程序无关,GPIO引脚速度、翻转速度、输出速度区别输入有4种模式,输出有4种模式,我们在控制LED 的时候选择通用推挽输出。. S, e# n0 \# y

$ g+ I. F8 G; M- r3 E    输出速率有三种模式:2M、10M、50M,这里我们选择2M。: P# t+ N( [+ g" F+ H

- }5 c2 k5 G) T$ S/ B" u! `电平控制:; D+ A0 J& s5 M2 f& i% g
    STM32的IO口比较复杂,如果要输出1和0,则要通过控制:端口输出数据寄存器ODR来实现,ODR 是:Output data register的简写,在STM32里面,其寄存器的命名名称都是英文的简写,很容易记住。
( f1 i% E6 T4 j
* d) B/ @8 \# U3 `% }    从手册上我们知道ODR是一个32位的寄存器,低16位有效,高16位保留。低16位对应着IO0~IO16,只要往相应的位置写入0或者1就可以输出低或者高电平。1 ?& e* N0 X2 F( d- |, T0 ~- @
  r( h7 [) B) K3 L! a
    第一层级:基地址宏定义 ( ?* |$ W1 Y; ^* c0 [" |

* V2 B% P9 O0 ^  ]& ` 微信图片_20221011175925.png ( r. V5 U5 ?1 ^
2 y& D+ _: I! x1 ]6 B. D+ G9 d
    时钟控制:  _4 ^8 a  m' M7 _* W
微信图片_20221011175922.png

+ |# h3 X( P7 {# |
% \' {% w7 v, I8 O    在STM32中,每个外设都有一个起始地址,叫做外设基地址,外设的寄存器就以这个基地址为标准按照顺序排列,且每个寄存器32位,(后面作为结构体里面的成员正好内存对齐)。
3 ]) D& q7 z8 k- Z" Q3 k& [% `) g9 b$ D  j: |8 Y
查表看到时钟由APB2外设时钟使能寄存器(RCC_APB2ENR)来控制,其中PB端口的时钟由该寄存器的位3写1使能。我们可以通过基地址+偏移量0x18,算出RCC_APB2ENR的地址为:0x40021018。那么使能PB口的时钟代码则如下所示:
$ |2 V3 M/ X- P
( q, e. n# R0 ?) t0 p3 J
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define RCC_APB2ENR *(volatile unsigned long *)0x40021018</font></font></font>
    8 h( [6 j& z1 q$ x& x
  2. <font face="Tahoma"><font size="3"><font color="#000000">
    - h& e- c: }, ^+ R7 s; \
  3. </font></font></font>
    ! V* K6 Z) w8 B& j4 P4 b$ _' [
  4. <font face="Tahoma"><font size="3"><font color="#000000"> // 开启端口B 时钟</font></font></font>! p; L/ c+ e& ~. P2 u- m
  5. <font face="Tahoma"><font size="3"><font color="#000000"> RCC_APB2ENR |= 1<<3;</font></font></font>
复制代码
1 [, Q% c3 k* P1 C; ~
    模式配置:  M9 E. s% i: X$ q
微信图片_20221011175918.png
& Y- c2 J( c- W: k! U

( v- W1 F' Y, p! z8 b: W, q    同RCC_APB2ENR一样,GPIOB的起始地址是:0X4001 0C00,我们也可以算出GPIO_CRL的地址为:0x40010C00。那么设置PB0为通用推挽输出,输出速率为2M的代码则如下所示:: r& ]! s3 h/ I- w. e- I
2 b; M% @( j9 g! ]9 D- {
微信图片_20221011175914.png ' z) r0 g. J# D
' V% [; y1 }9 D8 V
    同上,从手册中我们看到ODR寄存器的地址偏移是:0CH,可以算出GPIOB_ODR寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C。现在我们就可以定义GPIOB_ODR这个寄存器了,代码如下:
, M6 s2 W. L, r" d$ ~+ ?/ \* c1 O
3 D- R" D6 N$ d
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C</font></font></font>
    , _+ M( i/ h% Y7 P
  2. <font face="Tahoma"><font size="3"><font color="#000000">
    4 V# P6 W2 q) d7 W" d' }
  3. </font></font></font>! O1 |5 O% [1 s( I* z' y) ]1 ~- {
  4. <font face="Tahoma"><font size="3"><font color="#000000">//PB0 输出低电平</font></font></font>
    6 A( D; H, q  V2 _$ m0 ^( X# q
  5. <font face="Tahoma"><font size="3"><font color="#000000">GPIOB_ODR = 0<<0;</font></font></font>
复制代码

% j- x6 f: G; I1 r/ p1 V, A- u* I    第一层级:基地址宏定义完成用STM32控制一个LED的完整代码:( \& Y+ e3 ]/ t* \3 y' ?0 n5 K
8 ?. W% T6 W4 G, F2 S/ H$ ?
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define RCC_APB2ENR *(volatile unsigned long *)0x40021018</font></font></font>
    + K3 i) T# U0 K, {
  2. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_CRL *(volatile unsigned long *)0x40010C00</font></font></font>" `- C4 r+ R/ s8 H3 d
  3. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C</font></font></font>) _) ~0 n' P0 S6 r3 [% g
  4. <font face="Tahoma"><font size="3"><font color="#000000">& `% S! L1 b( g: N3 ~% k
  5. </font></font></font>
    4 J, z1 H* y+ V/ s
  6. <font face="Tahoma"><font size="3"><font color="#000000">int main(void)</font></font></font>
    9 s/ w8 B9 `: x4 `! t( B( {
  7. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>
    5 R) U3 s' B6 x% c" ]$ k
  8. <font face="Tahoma"><font size="3"><font color="#000000"> // 开启端口B 的时钟</font></font></font>. z8 H' _9 Q# S% c
  9. <font face="Tahoma"><font size="3"><font color="#000000"> RCC_APB2ENR |= 1<<3;</font></font></font>
    5 O* z0 w- G7 c
  10. <font face="Tahoma"><font size="3"><font color="#000000">
    $ K% |1 X1 L$ y( J5 M
  11. </font></font></font>
    $ b2 W4 A' D, l' c  a
  12. <font face="Tahoma"><font size="3"><font color="#000000"> // 配置PB0 为通用推挽输出模式,速率为2M</font></font></font>
    ! m# Y  `4 B, {+ O% F) ?6 S
  13. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOB_CRL = (2<<0) | (0<<2);</font></font></font>/ F& ?( `6 y2 v' d) T
  14. <font face="Tahoma"><font size="3"><font color="#000000">: w8 k/ l% z* `, s- I% L  l7 H
  15. </font></font></font>
    - o' j& }1 J! m8 V# x1 p
  16. <font face="Tahoma"><font size="3"><font color="#000000"> // PB0 输出低电平,点亮LED</font></font></font>
    " ?! P$ ~: u. W/ s
  17. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOB_ODR = 0<<0;</font></font></font>
    5 }8 g# A8 Z$ E# G* l5 u& e
  18. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
    4 j' O) [, F1 v# H7 W+ s
  19. <font face="Tahoma"><font size="3"><font color="#000000">" e$ k& B3 J( l) Y0 T
  20. </font></font></font>7 Z- ?, N: k: q9 x; ]& S& H
  21. <font face="Tahoma"><font size="3"><font color="#000000">void SystemInit(void)</font></font></font>! Z3 i( ~/ i8 |; M) Z3 N- K: x& ^
  22. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>
    % [! ?. M6 M% R  R* s* P% F2 P4 N
  23. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码

$ J+ V* `; t, I% w: w    第二层级:基地址宏定义+结构体封装
4 @7 J1 p% W; F+ r3 {/ o3 U8 U
5 Q" L& h' n+ P. B( C' y8 u外设寄存器结构体封装
: p1 V7 j9 ]) l6 J    上面我们在操作寄存器的时候,操作的是寄存器的绝对地址,如果每个寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占32个或者16个字节,这种方式跟结构体里面的成员类似。7 ?5 t* \% W3 A/ [
7 `6 C+ V5 z5 w2 y+ Q6 v8 h
    所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可。
3 G6 n+ G; I5 V6 k
: @9 ~8 X2 j; G7 Z2 S6 Q. E. {    下面我们先定义一个GPIO寄存器结构体,结构体里面的成员是GPIO的寄存器,成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样。
9 I0 G4 m$ O( i1 h, ]" T" `
9 Q' g# X+ S1 ?7 e
  1. <font face="Tahoma"><font size="3"><font color="#000000">typedef struct </font></font></font>7 b9 w+ Q1 X7 p1 x. D
  2. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>  f. F9 d7 I9 V; ^8 n. ]3 k
  3. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t CRL;</font></font></font>" j' U. o, @2 m) C2 ]9 J, C
  4. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t CRH;</font></font></font>* T8 j+ D; Y! e& \, S* C
  5. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t IDR;</font></font></font>  J; @8 ]3 G/ E! H7 @9 j
  6. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t ODR;</font></font></font>+ ~. X! R0 w. m; ]! s8 W
  7. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t BSRR;</font></font></font>
    $ u/ F) o6 \: q6 @$ l. m2 J
  8. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t BRR;</font></font></font>' I8 c3 O6 v! r$ ~
  9. <font face="Tahoma"><font size="3"><font color="#000000"> volatile uint32_t LCKR;</font></font></font>5 ~% q7 x, t, M* j* G9 @
  10. <font face="Tahoma"><font size="3"><font color="#000000">} GPIO_TypeDef;</font></font></font>
复制代码
, n+ _3 W) D. V' A9 P! R1 p
    在《STM32 中文参考手册》8.2 寄存器描述章节,我们可以找到结构体里面的7个寄存器描述。在点亮LED的时候我们只用了CRL和ODR这两个寄存器,至于其他寄存器的功能大家可以自行看手册了解。: ]* U! N  [7 z* f- _! |% x
/ U" F% p+ w2 J1 ]4 v
    在GPIO结构体里面我们用了两个数据类型,一个是uint32_t,表示无符号的32位整型,因为GPIO的寄存器都是32位的。这个类型声明在标准头文件stdint.h 里面使用typedef对unsigned int重命名,我们在程序上只要包含这个头文件即可。
% M9 l% G$ \* d( c3 W! G  [7 R5 p7 V6 n8 u- i0 s9 [
    另外一个是volatile作用就是告诉编译器这里的变量会变化不因优化而省略此指令,必须每次都直接读写其值,这样就能确保每次读或者写寄存器都真正执行到位。, G; ]1 T# [* Y- s' U
3 e; B& S8 l1 D& X
外设封装
' I" [" C3 ^+ ?1 S
微信图片_20221011175907.png
% ^* u9 I2 f$ ]

. E* Q2 M$ G% I2 U    STM32F1系列的GPIO端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每个端口都含有GPIO_TypeDef结构体里面的寄存器,我们可以根据手册各个端口的基地址把GPIO的各个端口定义成一个GPIO_TypeDef类型指针,然后我们就可以根据端口名(实际上现在是结构体指针了)来操作各个端口的寄存器,代码实现如下:2 [0 ?1 r6 P/ D6 S8 t

. \: p9 X0 B2 W- O/ w
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOA ((GPIO_TypeDef *) 0X4001 0800)</font></font></font>! }& ~, V" F! o; ?) C. i& Q; w
  2. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB ((GPIO_TypeDef *) 0X4001 0C00)</font></font></font>! H3 |+ q3 W6 J; d
  3. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOC ((GPIO_TypeDef *) 0X4001 1000)</font></font></font>
    % i# w, m: V" o% l3 C4 D3 k! E
  4. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOD ((GPIO_TypeDef *) 0X4001 1400)</font></font></font>
    8 m9 n% U4 A2 T1 d& G) w" F8 d
  5. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOE ((GPIO_TypeDef *) 0X4001 1800)</font></font></font>
    & x: b6 n- D: P  I
  6. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOF ((GPIO_TypeDef *) 0X4001 1C00)</font></font></font>
    $ y! D: d+ r0 s& B5 T; s+ |9 J
  7. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOG ((GPIO_TypeDef *) 0X4001 2000)</font></font></font>
复制代码
7 A% y! N) E% r
外设内存映射) y7 E% S( ~5 O$ Y; ^- F( q' @
    讲到基地址的时候我们再引人一个知识点:Cortex-M3存储器系统,这个知识点在《Cortex-M3权威指南》第5章里面讲到。CM3的地址空间是4GB,如下图所示:
9 N% l: c6 B( ]" w. R
, ]! x, J: ~/ l/ W7 s) u7 i" e$ r
& h- {/ b& i$ P1 `3 k+ `
微信图片_20221011175901.png

! m# j  K: {& v4 f3 U9 V
( |7 Y* P, ~* t7 h- N! w' q

6 \  w; t+ W- C    我们这里要讲的是片上外设,就是我们所说的寄存器的根据地,其大小总共有512MB,512MB是其极限空间,并不是每个单片机都用得完,实际上各个MCU厂商都只是用了一部分而已。STM32F1系列用到了:0x4000 0000 ~0x5003 FFFF。现在我们说的STM32的寄存器就是位于这个区域。
3 T5 i" n. {9 \* b; r( y: V! C

4 b# x7 B7 Q% A& O9 ?' ~1 Y) }, XAPB1、APB2、AHB 总线基地址
3 F$ v! ]6 Q3 u8 p* _9 c# L1 p! \    现在我们说的STM32的寄存器就是位于这个区域,这里面ST设计了三条总线:AHB、APB2和APB1,其中AHB和APB2是高速总线,APB1是低速总线。不同的外设根据速度不同分别挂载到这三条总线上。
/ \0 v; P" X- b. i. z& w
( o+ L0 m/ a# p
    从下往上依次是:APB1、APB2、AHB,每个总线对应的地址分别是:APB1:0x40000000,APB2:0x4001 0000,AHB:0x4001 8000。1 g/ j2 m+ r# e* d! K7 d  r
/ ~6 X- B1 z4 y$ M3 ]) C
    这三条总线的基地址我们是从《STM32 中文参考手册》2.3小节—存储器映像得到的:APB1的基地址是TIM2定时器的起始地址,APB2的基地址是AFIO的起始地址,AHB的基地址是SDIO的起始地址。其中APB1地址又叫做外设基地址,是所有外设的基地址,叫做PERIPH_BASE。0 K8 a( A6 s+ w1 L
: R4 ^* A% i" n* @+ D
    现在我们把这三条总线地址用宏定义出来,以后我们在定义其他外设基地址的时候,只需要在这三条总线的基址上加上偏移地址即可,代码如下:- p, m2 o' M- R% k
) L7 u2 B+ u" F; j
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define PERIPH_BASE ((uint32_t)0x40000000)</font></font></font>3 o- v' }. w5 ]. v& L& P  N* D. R
  2. <font face="Tahoma"><font size="3"><font color="#000000">#define APB1PERIPH_BASE PERIPH_BASE</font></font></font>! l# R! t4 l0 A3 H% ~& X& L/ {
  3. <font face="Tahoma"><font size="3"><font color="#000000">#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)</font></font></font>
    % K: K, ?9 e, q9 B& q$ u
  4. <font face="Tahoma"><font size="3"><font color="#000000">#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)</font></font></font>
复制代码

( v8 |* H- u5 a$ ^" a) KGPIO 端口基地址( p% l. S: M5 C( Z: H1 K9 n. E
    因为GPIO挂载到APB2总线上,那么现在我们就可以根据APB2的基址算出各个GPIO端口的基地址,用宏定义实现代码如下:
5 w! A  D" a2 }" K0 [" ~  @- I3 s& U' O/ c; k: t3 ~  X# @
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)</font></font></font>
    " N! y4 j& j- o0 B9 f
  2. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)</font></font></font># b. i& V' i" Q1 @( j+ h
  3. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)</font></font></font>& i* w$ Q8 ^; {& a8 {2 S( I1 ]
  4. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)</font></font></font>
    * O0 ?# g, K9 Z2 T  C
  5. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)</font></font></font>
    : g* g4 A8 X0 P
  6. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)</font></font></font>. @3 A3 Z8 c4 j3 g$ o
  7. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)</font></font></font>
复制代码

7 j% m2 N, {' c' w5 W+ |    第二层级:基地址宏定义+结构体封装完成用STM32控制一个LED的完整代码:- T( {% d; |5 i8 L6 b# n

2 W. L# u( @8 G* l. \
  1. <font face="Tahoma"><font size="3"><font color="#000000">#include <stdint.h></font></font></font>
    . v7 V6 D% ^6 {. h
  2. <font face="Tahoma"><font size="3"><font color="#000000">#define __IO volatile</font></font></font><font face="Tahoma"><font size="3"><font color="#000000">
    ! Y" c) W3 |7 N1 S* ^
  3. </font></font></font>
    9 t$ ~0 [) `; s/ q0 r9 v
  4. <font face="Tahoma"><font size="3"><font color="#000000">typedef struct </font></font></font>
    1 F; y8 P5 `, n0 v. O) T: j3 v
  5. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>- r. O, T+ d8 _# O, ~, v
  6. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t CRL;</font></font></font>! P& Y  P; B  ?- y4 ~0 ^
  7. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t CRH;</font></font></font>
    . p0 c1 D8 M, ~/ M0 B4 g
  8. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t IDR;</font></font></font>
    - n  z/ y" W+ D3 ?/ N
  9. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t ODR;</font></font></font>5 ~1 f% K7 Y- u: ^! Q) s
  10. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t BSRR;</font></font></font>
    & Y$ N3 ^* w% c3 i; r2 q: k
  11. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t BRR;</font></font></font>
    & y+ H; W' G' R+ r5 B* E+ w
  12. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t LCKR;</font></font></font>( X( B1 `$ e0 ]$ n  D9 D
  13. <font face="Tahoma"><font size="3"><font color="#000000">} GPIO_TypeDef;</font></font></font>
    - \# ]' G0 g4 q+ |
  14. 8 L# s) S3 r3 c9 l3 Q8 r# u
  15. <font face="Tahoma"><font size="3"><font color="#000000">typedef struct </font></font></font>; W; V+ d8 l1 p2 i! ^5 z$ n# j
  16. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>8 {0 }2 p; n+ c& B% w) T( o3 M
  17. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t CR;</font></font></font>
    & c+ T3 ~" S' _$ Y# w' K
  18. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t CFGR;</font></font></font>6 F4 L' y% K8 w& M
  19. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t CIR;</font></font></font>3 m7 C. j% A7 G
  20. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t APB2RSTR;</font></font></font>
    6 w) {% V1 Y# Z# y7 \
  21. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t APB1RSTR;</font></font></font>
    , e' N1 N6 n! M6 f+ S2 `
  22. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t AHBENR;</font></font></font>! {$ V. `9 A7 r9 x1 h7 O
  23. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t APB2ENR;</font></font></font>1 g0 E& `/ u8 X  u* \
  24. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t APB1ENR;</font></font></font>
    $ e% e. M. s) D5 n
  25. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t BDCR;</font></font></font>5 N) C$ q5 S# `; {+ m
  26. <font face="Tahoma"><font size="3"><font color="#000000"> __IO uint32_t CSR;</font></font></font>
    7 z' C" W6 o7 j+ Q* w
  27. <font face="Tahoma"><font size="3"><font color="#000000">} RCC_TypeDef;</font></font></font>  s1 e: _0 p! j# v" n

  28. 7 b: A3 ?* ?' t9 s
  29. <font face="Tahoma"><font size="3"><font color="#000000">#define PERIPH_BASE ((uint32_t)0x40000000)</font></font></font>
    , H9 e/ z9 C  n  s/ l/ `& ?: @

  30. / b. c  B3 e! `/ v- Q- w
  31. <font face="Tahoma"><font size="3"><font color="#000000">#define APB1PERIPH_BASE PERIPH_BASE</font></font></font>
    % q+ z: a' h- M2 z: ]
  32. <font face="Tahoma"><font size="3"><font color="#000000">#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)</font></font></font>; N% \2 \+ r) B* b- W$ z
  33. <font face="Tahoma"><font size="3"><font color="#000000">#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)</font></font></font>' O1 x2 \% V3 x+ k3 w( E0 t& q

  34. + W0 T9 E- p  L) M2 F
  35. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)</font></font></font>* j% B" u, ~6 F6 n( g$ y
  36. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)</font></font></font>
    4 ]) s9 ~, B5 O. V7 H
  37. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)</font></font></font>/ N5 [4 j3 i0 t# w
  38. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)</font></font></font>$ p) n! V$ \# V; `% k3 L$ p
  39. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)</font></font></font>
    / A+ ]; W" G1 O5 n( K, @
  40. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)</font></font></font>
    . S# s+ v  |3 R0 u. z
  41. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)</font></font></font>! l& U% U; a- P- r8 a$ ^( v
  42. <font face="Tahoma"><font size="3"><font color="#000000">#define RCC_BASE (AHBPERIPH_BASE + 0x1000)</font></font></font><font face="Tahoma"><font size="3"><font color="#000000">' Q# T; B: k( r8 R3 d" B& W
  43. </font></font></font>
    9 ]; r6 S$ C( p! m; W8 N( [+ q
  44. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)</font></font></font>* Z+ z! x2 N( C0 K" L0 W
  45. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)</font></font></font>
    0 F& Z- X0 f. Y- |
  46. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)</font></font></font>
    3 K1 i4 {, g# O- G
  47. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)</font></font></font>; m0 L9 k: _) K: f+ {  }
  48. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)</font></font></font>- w. {7 b; ^4 S$ q% H
  49. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)</font></font></font>+ _3 f6 q' q8 r  Y
  50. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)</font></font></font>8 Z% e9 M  a) {- V: n
  51. <font face="Tahoma"><font size="3"><font color="#000000">#define RCC ((RCC_TypeDef *) RCC_BASE)</font></font></font>
    , Z. W, ]5 J7 D6 I" }' h- ]7 I+ d
  52. <font face="Tahoma"><font size="3"><font color="#000000">& x; K0 }, B/ L. s# n- g
  53. </font></font></font>
    ; C+ \' \# o6 ^5 Y6 X5 A5 i
  54. <font face="Tahoma"><font size="3"><font color="#000000">#define RCC_APB2ENR *(volatile unsigned long *)0x40021018</font></font></font>$ r, T- j% S, F( X
  55. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_CRL *(volatile unsigned long *)0x40010C00</font></font></font>
    " p  s% P) `; u- a, X. R6 v; `
  56. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C</font></font></font>
    6 B* E, ?" `/ ~9 I& E
  57. ; o% W1 H; g8 \# a4 `& _* R
  58. <font face="Tahoma"><font size="3"><font color="#000000">int main(void)</font></font></font>
      r+ O/ {* T( a
  59. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>2 ]$ A% ~; n$ h5 s+ Q2 I4 t0 Q
  60. <font face="Tahoma"><font size="3"><font color="#000000"> // 开启端口B 的时钟</font></font></font>5 z- I' @+ B. l
  61. <font face="Tahoma"><font size="3"><font color="#000000"> RCC->APB2ENR |= 1<<3;</font></font></font>
    . Q9 f3 M) _( C" t( t% ~
  62. 2 j  Y; W3 G+ `) ^( V
  63. <font face="Tahoma"><font size="3"><font color="#000000"> // 配置PB0 为通用推挽输出模式,速率为2M</font></font></font>! C$ W9 [# Z: H# d5 Z4 b
  64. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOB->CRL = (2<<0) | (0<<2);</font></font></font>- }" B8 r5 {( {3 w, g% z

  65. 9 m' @3 c' o9 T+ V; [; ?
  66. <font face="Tahoma"><font size="3"><font color="#000000"> // PB0 输出低电平,点亮LED</font></font></font>
    - l0 y  K. _  {( ~5 f  {& Y
  67. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOB->ODR = 0<<0;</font></font></font>4 D) l8 D/ }2 l4 H" R
  68. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
    4 G+ J' l0 s7 _; W3 M: D! O! S! M

  69. / n1 j) Z) N: o; P
  70. <font face="Tahoma"><font size="3"><font color="#000000">void SystemInit(void)</font></font></font># L" [/ L+ K3 ]& t* E5 C) {
  71. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>' t9 C$ I  c+ j" E- u" {' i
  72. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码

! o( |# G" u) v4 E1 ]& [* {  V    第二层级变化:7 E" w2 \- U3 j1 Z

7 z7 v8 S: I) N    ①、定义一个外设(GPIO)寄存器结构体,结构体的成员包含该外设的所有寄存器,成员的排列顺序跟寄存器偏移地址一样,成员的数据类型跟寄存器的一样。
" ~+ V) _0 F! O3 _7 E5 G) o/ B" u" u/ E( \- f7 y
    ②外设内存映射,即把地址跟外设建立起一一对应的关系。
% @# z/ v1 X; _% L8 ?9 S" h  w- \# T; ~* D. g
    ③外设声明,即把外设的名字定义成一个外设寄存器结构体类型的指针。
; H, {; k8 n( P& [( V" s* x) I/ Q3 A$ }9 \, {. w& Z
    ④通过结构体操作寄存器,实现点亮LED。
% N2 ^2 l) \: l: P# @) U- y3 n! \" F
  g  U3 W& E& Q% o    第三层级:基地址宏定义+结构体封装+“位封装”(每一位的对应字节封装): u  S& W2 x9 K+ Y; S

/ b' _' Z$ A9 w( n上面我们在控制GPIO输出内容的时候控制的是ODR(Output data register)寄存器,ODR是一个16位的寄存器,必须以字的形式控制其实我们还可以控制BSRR和BRR这两个寄存器来控制IO的电平,下面我们简单介绍下BRR寄存器的功能,BSRR自行看手册研究。% Q7 b% F5 a+ Z( l6 K! k, j

# q5 M+ u) C& w, M+ t  T9 }1 a4 n

! ~$ i$ h, d% }! o& G6 k
微信图片_20221011175855.png

0 o" ]6 {* @# ^* l$ B
9 r- X# T" P. h0 Y. {

) r4 Z: U4 f- z    位清除寄存器BRR只能实现位清0操作,是一个32位寄存器,低16位有效,写0没影响,写1清0。现在我们要使PB0输出低电平,点亮LED,则只要往BRR的BR0位写1即可,其他位为0,代码如下:
8 j9 d0 J! \( V
5 K$ r9 m0 z8 [* k& ^/ C: ]
  1. GPIOB->BRR = 0X0001;
复制代码
0 H) u9 c8 Q! m: M- D- N2 F
    这时PB0就输出了低电平,LED就被点亮了。
( p. x6 B* m7 ~/ s3 @! u, t; @# J# c& O' U8 x
    如果要PB2输出低电平,则是:
) d3 a2 f3 m8 y! l, T

+ P+ R3 x6 o& w! |! q8 I
  1. GPIOB->BRR = 0X0004;
复制代码
. C: F7 b6 Y; q# e3 p
    如果要PB3/4/5/6。。。。。。这些IO输出低电平呢?" h- C5 i* M& c% g& D; C" Y

2 ^  s$ R- b2 }    道理是一样的,只要往BRR的相应位置赋不同的值即可。因为BRR是一个16位的寄存器,位数比较多,赋值的时候容易出错,而且从赋值的16进制数字我们很难清楚的知道控制的是哪个IO。
' `4 k5 m5 L" q& |& ]! R9 c% v8 S1 p8 R8 B- M! r
    这时,我们是否可以把BRR的每个位置1都用宏定义来实现,如GPIO_Pin_0就表示0X0001,GPIO_Pin_2就表示0X0004。只要我们定义一次,以后都可以使用,而且还见名知意。“位封装”(每一位的对应字节封装) 代码如下:
) O( n% u# r$ N, d* y9 `9 P
7 S$ p$ p# n9 F/ y
  1. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */</font></font></font>; C! z- N5 v$ ]+ a
  2. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */</font></font></font>( `% }4 a/ r0 B
  3. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */</font></font></font>
    # x& {6 t) A- O% n; G* H4 y
  4. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */</font></font></font>1 Z0 k* x: M8 I9 S& i
  5. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */</font></font></font>; s* @3 _% w0 g- y
  6. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */</font></font></font>
    0 r6 W! V# d" ~# C% V  X. d
  7. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */</font></font></font>0 u3 X. d0 i( o+ z& c
  8. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */</font></font></font>
    ) h9 z1 x6 ]2 ]4 S4 s
  9. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */</font></font></font>5 S+ J4 y& M' O
  10. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */</font></font></font>
    ' x2 g) g! y& A0 g
  11. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */</font></font></font>; o* o9 {; O4 ]4 A% p$ A
  12. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */</font></font></font>. W, C1 `  b& W5 J1 @, `
  13. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */</font></font></font>
    . [# {, [/ @% G4 C$ o; B/ t' }  ~/ U
  14. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */</font></font></font>2 W) }* ]* E7 R: N% n
  15. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */</font></font></font>' }. t; s# P4 C: ]: S$ R
  16. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */</font></font></font>
    # a3 ^% |4 @- g7 l- n% i
  17. <font face="Tahoma"><font size="3"><font color="#000000">#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */</font></font></font>
复制代码
. M5 X; s* ?/ R: C
    这时PB0就输出了低电平的代码就变成了:
6 d) G8 Q5 @" p$ X( Q4 K, q4 I" C% f5 a$ B
  1. GPIOB->BRR = GPIO_Pin_0;
复制代码

/ |2 h7 v9 ~9 X8 T, ]+ w* e    如果同时让PB0/PB15输出低电平,用或运算,代码:( G" n/ X1 q5 h3 g9 I

3 ^6 o2 s; {, Y" s8 z0 Y2 D! U( {
  1. GPIOB->BRR = GPIO_Pin_0|GPIO_Pin_15;
复制代码

: G& d6 {" o5 D    为了不使main函数看起来冗余,上述库封装 的代码不应该放在main里面,因为其是跟GPIO相关的,我们可以把这些宏放在一个单独的头文件里面。; D' a8 j$ u& {: h

3 U9 ], d, m* F" z6 T0 W+ Q; \1 G    在工程目录下新建stm32f10x_gpio.h,把封装代码放里面,然后把这个文件添加到工程里面。这时我们只需要在main.c里面包含这个头文件即可。
7 v- \7 d2 z- o; r. b6 z

4 f2 O0 `: K8 S( L: I0 p    第四层级:基地址宏定义+结构体封装+“位封装”+函数封装5 r: R$ J: L" ]. {- L
# B  ~- X: R; b: Y3 X8 |7 `9 b; ~3 d
    我们点亮LED的时候,控制的是PB0这个IO,如果LED接到的是其他IO,我们就需要把GPIOB修改成其他的端口,其实这样修改起来也很快很方便。
" [8 S' v9 d6 e6 K6 h' S& ?
6 {* o# y' V* T  Q4 A4 V6 e, C4 j    但是为了提高程序的可读性和可移植性,我们是否可以编写一个专门的函数用来复位GPIO的某个位,这个函数有两个形参,一个是GPIOX(X=A...G),另外一个是GPIO_Pin(0...15),函数的主体则是根据形参GPIOX 和GPIO_Pin来控制BRR寄存器,代码如下:
5 N/ w' `7 }- [2 h$ J
& [7 l3 G/ ^' ]- |
  1. <font face="Tahoma"><font size="3"><font color="#000000">void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)</font></font></font>$ d! y6 f6 o! g. n* `; |" o5 y6 I, ^
  2. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>
    % y3 W$ K& m8 f
  3. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOx->BRR = GPIO_Pin;</font></font></font>+ S) {" G+ s) j. y  h* F4 Y
  4. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码

* _+ U8 t1 j0 G1 h$ ~& M    这时,PB0输出低电平,点亮LED的代码就变成了:
' x* T  u6 v) a7 \
. |" _4 U$ U. i0 v
  1. GPIO_ResetBits(GPIOB,GPIO_Pin_0);
复制代码
% G2 b0 A* ]7 U$ d" i
    同理, 我们可以控制BSRR这个寄存器来实现关闭LED,代码如下:
2 S: @" h. K, }' S, h( b5 p
7 Y2 B, R) A: }3 ^
  1. <font face="Tahoma"><font size="3"><font color="#000000">// GPIO 端口置位函数</font></font></font>
    ! X, \. s& _) g$ J% z' I6 s
  2. <font face="Tahoma"><font size="3"><font color="#000000">void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)</font></font></font>; x  R8 P& ]. R7 ~% M! w
  3. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>6 K6 ?# r: d2 F0 ^, ^
  4. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOx->BSRR = GPIO_Pin;</font></font></font>
    2 _7 m; [, m1 ^. R9 A
  5. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码

* w& R+ r& j  N9 H! Z; Q4 M    这时,PB0输出高电平,关闭LED的代码就变成了:
) t' m4 f2 h7 a/ Q+ a
9 S! R- m  `  `8 S
  1. GPIO_SetBits(GPIOB,GPIO_Pin_0);
复制代码
0 G* Y  ^  e! E  M8 G4 m/ B
    同样,因为这个函数是控制GPIO的函数,我们可以新建一个专门的文件来放跟gpio有关的函数。相关文章:STM32中GPIO工作原理详解。9 F# N$ p" Y$ _+ n- u: l% d& L2 G) C

: w/ X5 G1 G6 k7 h    在工程目录下新建stm32f10x_gpio.c,把GPIO相关的函数放里面。这时我们是否发现刚刚新建了一个头文件stm32f10x_gpio.h,这两个文件存放的都是跟外设GPIO相关的。+ f# l) S, l0 r
, D- |6 w9 b0 O6 r" E: ]
    C文件里面的函数会用到h头文件里面的定义,这两个文件是相辅相成的,故我们在stm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h这个头文件。别忘了把stm32f10x.h这个头文件也包含进去,因为有关寄存器的所有定义都在这个头文件里面。8 E4 f8 v  o! W

# u; E+ e- ?7 U& l    如果我们写其他外设的函数,我们也应该跟GPIO一样,新建两个文件专门来存函数,比如RCC这个外设我们可以新建stm32f10x_rcc.c和stm32f10x_rcc.h。其他外依葫芦画瓢即可。
1 J0 O% ]9 \* U2 t3 Y
7 Q; z0 g% B- S$ d实例编写
  l  I# h# A- U) s  b# Z    以上,是对库封住过程的概述,下面我们正在地使用库函数编写LED程序。
) Y7 M; o. K' w1 ~4 }' O! s: F' `9 `: Y4 h8 m. I8 U" v
①管理库的头文件
6 j, O5 k. B# ~    当我们开始调用库函数写代码的时候,有些库我们不需要,在编译的时候可以不编译,可以通过一个总的头文件stm32f10x_conf.h来控制,该头文件主要代码如下:; |1 O% u& p9 J4 H4 X: u

5 a- E4 H1 ^  |) w: t" @: m& V3 a
OOC5C(XIT@EJRN4D1}}EJEK.png
  V$ j. A+ b! D; Q
3 \2 p" b: I+ m7 o5 \
    这里面包含了全部外设的头文件,点亮一个LED我们只需要RCC和GPIO 这两个外设的库函数即可,其中RCC控制的是时钟,GPIO控制的具体的IO口。所以其他外设库函数的头文件我们注释掉,当我们需要的时候就把相应头文件的注释去掉即可。
* N5 s7 Y" A- ~# `& Q2 j8 u9 T% b) u; O" W( E4 I& b$ S
    stm32f10x_conf.h这个头文件在stm32f10x.h这个头文件的最后面被包含,在第8296行:
& S, ]' B$ F6 X  U& z5 \0 ^
) z2 U+ }# Q" k! Q( z
  1. <font face="Tahoma"><font size="3"><font color="#000000">#ifdef USE_STDPERIPH_DRIVER</font></font></font>
    / P# @& y; V/ a! Y% {8 z& Q' V
  2. <font face="Tahoma"><font size="3"><font color="#000000">#include "stm32f10x_conf.h"</font></font></font>3 T' t1 @2 q, [& A5 O: J# |4 j; P
  3. <font face="Tahoma"><font size="3"><font color="#000000">#endif</font></font></font>
复制代码

( R% G% r, J5 L5 A( R, E    代码的意思是,如果定义了USE_STDPERIPH_DRIVER这个宏的话,就包含stm32f10x_conf.h这个头文件。
$ D/ ?2 |9 U6 b# Q4 V* z" _8 `- `; Z; n  v" A
    我们在新建工程的时候,在魔术棒选项卡C/C++中,我们定义了USE_STDPERIPH_DRIVER 这个宏,所以stm32f10x_conf.h 这个头文件就被stm32f10x.h包含了,我们在写程序的时候只需要调用一个头文件:stm32f10x.h即可。
' i' l; P& @1 Z+ Y6 `! e  r8 R' l! d1 h; E, d' `
②编写LED初始化函数) W1 i. g2 g% B! g
    经过寄存器点亮LED的操作,我们知道操作一个GPIO输出的编程要点大概如下:; p, K9 x1 ]2 S0 K- K

& z. D3 z5 u6 l0 @1、开启GPIO的端口时钟
* J3 V3 l: ^5 m% [+ W/ Q( X; }* S7 s2 I) ?3 {3 d8 s# A2 _+ G
2、选择要具体控制的IO口,即pin
8 u  H4 A9 [* U9 s/ F
" b- H( {$ U1 t( ^* o% D3、选择IO口输出的速率,即speed
5 ?% q* A5 j# i/ L% I* Q! {6 M8 D) K$ q2 l
4、选择IO口输出的模式,即mode
0 |1 e) A8 J( o- X8 o/ W! f8 \
" T' d; F, k$ L1 l8 t# ?5、输出高/低电平
# R! j* a4 [+ \: n" V
1 X+ }  [, W5 j
    STM32的时钟功能非常丰富,配置灵活,为了降低功耗,每个外设的时钟都可以独自的关闭和开启。STM32中跟时钟有关的功能都由RCC这个外设控制,RCC中有三个寄存器控制着所以外设时钟的开启和关闭:RCC_APHENR、RCC_APB2ENR和RCC_APB1ENR,AHB、APB2和APB1代表着三条总线,所有的外设都是挂载到这三条总线上,GPIO属于高速的外设,挂载到APB2总线上,所以其时钟有RCC_APB2ENR控制。
* F9 n, g3 u- Y& D! M2 p+ i7 G! f1 `- Q
GPIO 时钟控制+ R. ~) U, y' L7 G* c/ ?7 x4 V
    固件库函数:RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函数的原型为:
4 W; r% H1 v" |7 U: b

3 D1 l1 k2 S) z1 j7 I' [
  1. <font face="Tahoma"><font size="3"><font color="#000000">void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph,</font></font></font>
    & M( L  q9 y/ g, N1 h
  2. <font face="Tahoma"><font size="3"><font color="#000000">                              FunctionalState NewState)</font></font></font>
    ) k- _+ t6 ?" j2 ?- n3 E
  3. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>4 z' x  v8 J+ E0 I, |3 f' f
  4. <font face="Tahoma"><font size="3"><font color="#000000">/* Check the parameters */</font></font></font>8 h+ |- q/ f" B3 c, o7 _& O
  5. <font face="Tahoma"><font size="3"><font color="#000000"> assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph));</font></font></font>1 S) s' k- u  _- W" A( e1 b
  6. <font face="Tahoma"><font size="3"><font color="#000000"> assert_param(IS_FUNCTIONAL_STATE(NewState));</font></font></font>  g2 a0 C' i5 Q  k7 [
  7. <font face="Tahoma"><font size="3"><font color="#000000">if (NewState != DISABLE) </font></font></font>
    6 B2 y/ z$ z: R8 s0 u8 |/ `
  8. <font face="Tahoma"><font size="3"><font color="#000000"> {</font></font></font>
    ) X  a; T: C& U  C
  9. <font face="Tahoma"><font size="3"><font color="#000000">  RCC->APB2ENR |= RCC_APB2Periph;</font></font></font>" y$ Q0 [9 [- r. _* Z
  10. <font face="Tahoma"><font size="3"><font color="#000000"> } </font></font></font>
    ) |8 _4 n* d) ]: K; h; ^4 y# Q  \: m5 `- ]
  11. <font face="Tahoma"><font size="3"><font color="#000000">else</font></font></font>
    , X* q7 ^5 }- r; J8 T% i2 D- D
  12. <font face="Tahoma"><font size="3"><font color="#000000"> {</font></font></font>* L, H8 A# H  O" e3 ^: y
  13. <font face="Tahoma"><font size="3"><font color="#000000">  RCC->APB2ENR &= ~RCC_APB2Periph;</font></font></font>" w9 ?' f) f' w) g' F% V* y
  14. <font face="Tahoma"><font size="3"><font color="#000000"> }</font></font></font>
    * a) H! [/ T4 o3 L
  15. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码
( V) G8 O8 T2 Y& ~! v4 p
    当程序编译一次之后,把光标定位到函数/变量/宏定义处,按键盘的F12或鼠标右键的Go to definition of,就可以找到原型。固件库的底层操作的就是RCC外设的APB2ENR这个寄存器,宏RCC_APB2Periph_GPIOB的原型是:0x00000008,即(1<<3),还原成存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件库操作,寄存器操作的代码可读性就很差,只有才查阅寄存器配置才知道具体代码的功能,而固件库操作恰好相反,见名知意。
( }8 ~% N) f! Q! X$ {. q" q
8 ]3 i4 [+ j- D% a/ J/ tGPIO 端口配置9 c$ Q2 ?' q# d; h
    GPIO的pin,速度,模式,都由GPIO的端口配置寄存器来控制,其中IO0~IO7由端口配置低寄存器CRL控制,IO8~IO15由端口配置高寄存器CRH配置。固件库把端口配置的pin,速度和模式封装成一个结构体:- t& B7 X2 W& m, \
  1. <font face="Tahoma"><font size="3"><font color="#000000">typedef struct</font></font></font>
    ) u9 s& h" Q% R# F
  2. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>4 e; P/ j# B# w. ^' O! }
  3. <font face="Tahoma"><font size="3"><font color="#000000">uint16_t GPIO_Pin;</font></font></font>& I% {0 u& i3 P9 }
  4. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOSpeed_TypeDef GPIO_Speed;</font></font></font>4 m3 F' g7 B) I6 P) }
  5. <font face="Tahoma"><font size="3"><font color="#000000"> GPIOMode_TypeDef GPIO_Mode;</font></font></font>
    " S" H* F$ A9 o% Y  ?: R
  6. <font face="Tahoma"><font size="3"><font color="#000000">} GPIO_InitTypeDef;</font></font></font>
复制代码
. B5 ~, C# Y: _# W. [( f1 s
    pin可以是GPIO_Pin_0~GPIO_Pin_15或者是GPIO_Pin_All,这些都是库预先定义好的宏。speed也被封装成一个结构体:
& A- f+ c- n! C9 K" Z' t$ W+ m
  1. <font face="Tahoma"><font size="3"><font color="#000000">typedef enum</font></font></font>3 \. T0 A  w2 K) S) m5 L0 q- @5 r
  2. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>
    " B4 E! @: U5 d+ U
  3. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Speed_10MHz = 1,</font></font></font>
    4 w. x6 f0 E/ X5 `8 K% T
  4. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Speed_2MHz,</font></font></font>$ k' ]5 _5 E& t2 q
  5. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Speed_50MHz</font></font></font>9 V" e- k8 r+ I6 v9 x: B- u
  6. <font face="Tahoma"><font size="3"><font color="#000000">} GPIOSpeed_TypeDef;</font></font></font>
复制代码

9 ~, u! s% ?; g    速度可以是10M,2M或者50M,这个由端口配置寄存器的MODE位控制,速度是针对IO口输出的时候而言,在输入的时候可以不用设置。mode也被封装成一个结构体:
  L$ |, @/ }+ W: J, I
8 I$ b: Q( L" F3 x, Y
  1. <font face="Tahoma"><font size="3"><font color="#000000">typedef enum</font></font></font>' x: Y& v  r6 E7 e
  2. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>0 l4 Y) }6 x/ r$ Q
  3. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_AIN = 0x0, // 模拟输入</font></font></font>6 P3 I! L6 f6 X8 x  r/ Z
  4. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入(复位后的状态)</font></font></font>
    : W; c5 Q. P! u
  5. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_IPD = 0x28, // 下拉输入</font></font></font>
    5 k& L. \) ^% @! z
  6. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_IPU = 0x48, // 上拉输入</font></font></font>/ Z8 X2 F9 P7 E1 ?% g1 F+ f
  7. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_Out_OD = 0x14, // 通用开漏输出</font></font></font>
      _: |4 F) k, h* v  J* g% n2 ?
  8. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_Out_PP = 0x10, // 通用推挽输出</font></font></font>
    6 o# q- V. f3 W. S& I; g% W
  9. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出</font></font></font>. L4 S! O2 _  G( {( x
  10. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Mode_AF_PP = 0x18 // 复用推挽输出</font></font></font>
    7 k4 B1 W+ {5 c/ F7 q' A1 L9 T
  11. <font face="Tahoma"><font size="3"><font color="#000000">} GPIOMode_TypeDef;</font></font></font>
复制代码
! |+ q6 z4 B1 `6 Q4 h; d
    IO口的模式有8种,输入输出各4种,由端口配置寄存器的CNF配置。相关文章:STM32中GPIO工作原理详解。平时用的最多的就是通用推挽输出,可以输出高低电平,驱动能力大,一般用于接数字器件。
: y3 C- b2 z7 o6 U# D2 w  F+ w) {
    最终用固件库实现就变成这样:: V; I4 V$ J7 E' F  A# O5 H

; Z1 }$ Y( J% {  k* s. f! @# j5 e
  1. <font face="Tahoma"><font size="3"><font color="#000000">// 定义一个GPIO_InitTypeDef 类型的结构体</font></font></font>
    * u/ }4 Q( R, Z/ y2 z
  2. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitTypeDef GPIO_InitStructure;</font></font></font>; B: D! N3 |3 N: c9 e

  3. 0 l; e: ~: m! V! P) c/ K
  4. <font face="Tahoma"><font size="3"><font color="#000000">// 选择要控制的IO 口</font></font></font>
    " ]( k6 l' B' Q" y
  5. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;</font></font></font>: D3 a3 Q0 K/ j

  6. / _* [/ ?4 e- h# K/ Y9 |
  7. <font face="Tahoma"><font size="3"><font color="#000000">// 设置引脚为推挽输出</font></font></font>( X% \0 I( |! w/ `3 g. N4 V
  8. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;</font></font></font>
    8 E0 Z8 s# F: P1 o7 ]/ [& ?; z. V

  9. 4 D, I- z" Q- Y$ b& P
  10. <font face="Tahoma"><font size="3"><font color="#000000">// 设置引脚速率为50MHz</font></font></font>5 J3 r" p2 L: N1 ]
  11. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;</font></font></font>
    : y7 H2 D6 f. v3 I4 [
  12. . e4 `) a, @$ n, W
  13. <font face="Tahoma"><font size="3"><font color="#000000">/*调用库函数,初始化GPIOB0*/</font></font></font>
    # z+ p1 h% A5 F" F) g% Q) [
  14. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_Init(GPIOB, &GPIO_InitStructure);</font></font></font>
复制代码

+ c, i4 ~) D' f) T( U3 c9 d8 H    倘若同一端口下不同引脚有不同的模式配置,每次对每个引脚配置完成后都要调用GPIO初始化函数,代码如下:
- y3 W8 b6 z' t2 s  }; K8 d
+ h; e$ k1 O0 }& u5 H5 E; ^5 M
  1. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;</font></font></font>
    $ v% @5 `9 W9 n. C5 M
  2. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ;                      </font></font></font>
    % K4 |) n9 ?1 Z/ h( C: c; y  Q
  3. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          //上拉输入</font></font></font>
    7 d% ?  g5 _7 D9 v9 b# k* z
  4. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_Init(GPIOB, &GPIO_InitStructure);</font></font></font>% T6 W; r8 R* \+ L7 h: w& k
  5. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;                     </font></font></font>
    5 I3 z& s1 p1 v5 V
  6. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       //推挽输出</font></font></font>- C7 R7 y3 L0 B1 p/ k" s/ x# p
  7. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;</font></font></font>9 L+ q0 N0 D+ W8 o. K" d* k
  8. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_Init(GPIOB, &GPIO_InitStructure);</font></font></font>
复制代码
- i$ ^7 w8 M( ]2 X1 K; K
GPIO 输出控制9 I( H2 y* T/ w
    GPIO输出控制,可以通过端口数据输出寄存器ODR、端口位设置/清除寄存器BSRR和端口位清除寄存器BRR这三个来控制。端口输出寄存器ODR是一个32位的寄存器,低16位有效,对应着IO0~IO15,只能以字的形式操作,一般使用寄存器操作,相关文章请移步此处:C语言操作寄存器的常见手法。- e- L" B, f! M
5 R* H0 A! S0 A) x' z
  1. <font face="Tahoma"><font size="3"><font color="#000000">// PB0 输出高电平,点亮LED</font></font></font>: w0 X' p' ]9 R3 L/ l8 S% n* Q
  2. <font face="Tahoma"><font size="3"><font color="#000000">GPIOB->ODR = 1<<0;</font></font></font>
复制代码
% o$ Z# z* h! S7 G! ]' w
    端口位清除寄存器BRR是一个32位的寄存器,低十六位有效,对应着IO0~IO15,只能以字的形式操作,可以单独对某一个位操作,写1清0。
9 Z; ?% Q- U7 [6 C' r* }2 K/ G) O) ^2 W: \: k
  1. <font face="Tahoma"><font size="3"><font color="#000000">// PB0 输出低电平,点亮LED</font></font></font>
    6 t% }3 @) \9 i
  2. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_ResetBits(GPIOB, GPIO_Pin_0);</font></font></font>
复制代码
, L+ m& K: J8 U, Z: S
    BSRR是一个32位的寄存器,低16位用于置位,写1有效,高16位用于复位,写1有效,相当于BRR寄存器。高16位我们一般不用,而是操作BRR这个寄存器,所以BSRR这个寄存器一般用来置位操作。
1 @# \: K8 Y1 Q; S# `
; S! `6 q( L, |- L& F9 _
  1. <font face="Tahoma"><font size="3"><font color="#000000">// PB0 输出高电平,熄灭LED</font></font></font>; o. [! W3 ~8 L
  2. <font face="Tahoma"><font size="3"><font color="#000000">GPIO_SetBits(GPIOB, GPIO_Pin_0);</font></font></font>
复制代码
: F0 ]: h. g' U8 l
    综上:固件库LED GPIO初始化函数。
. n  b& P% }: s. C
7 [+ C) Y2 @2 B9 u- O+ J
  1. <font face="Tahoma"><font size="3"><font color="#000000">void LED_GPIO_Config(void)</font></font></font>; q+ r/ ]7 E3 f- ^5 |! m7 h! l/ }
  2. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>
    ' ?( t- Y( e- q+ F2 F
  3. <font face="Tahoma"><font size="3"><font color="#000000">// 定义一个GPIO_InitTypeDef 类型的结构体</font></font></font>1 n& ?, A0 Y& S( X, K" y
  4. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_InitTypeDef GPIO_InitStructure;</font></font></font>
    4 s5 E& {8 Y8 {! |* O
  5. 0 d" v) Q1 J8 ]) a
  6. <font face="Tahoma"><font size="3"><font color="#000000">// 开启GPIOB 的时钟</font></font></font>7 t9 J) W% y6 t
  7. <font face="Tahoma"><font size="3"><font color="#000000"> RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);</font></font></font>
    $ X1 r- l" R/ U9 q1 F* Y

  8. + l3 H% [8 V1 v% t- l
  9. <font face="Tahoma"><font size="3"><font color="#000000">// 选择要控制的IO 口</font></font></font>' z( ]8 X8 e  |' E; f
  10. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;</font></font></font>
    4 J4 ?  U/ p" a' |* D8 `6 _& l
  11.   z  P1 H1 ?7 t
  12. <font face="Tahoma"><font size="3"><font color="#000000">// 设置引脚为推挽输出</font></font></font>
    0 @7 q* D: C! |% f4 e) b
  13. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;</font></font></font>: f! J$ r. N0 p1 B+ T$ L
  14. . \: N) k# Z# W6 f) \
  15. <font face="Tahoma"><font size="3"><font color="#000000">// 设置引脚速率为50MHz</font></font></font>
    . F9 q! x) V* {& }- }  v* Z4 f
  16. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;</font></font></font>- T  S$ W: Z4 q8 y: {  R8 z5 Y

  17. % ]/ M" I) t2 q" a6 r' N
  18. <font face="Tahoma"><font size="3"><font color="#000000">/*调用库函数,初始化GPIOB0*/</font></font></font>
    # r0 C" w, x6 G6 L
  19. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_Init(GPIOB, &GPIO_InitStructure);</font></font></font>
    6 k+ o7 O* t4 `- j! W

  20. ) n" K" A5 T) {; O1 X8 x: v
  21. <font face="Tahoma"><font size="3"><font color="#000000">// 关闭LED</font></font></font>
    , [& @8 }. v+ T3 B0 u! m- z
  22. <font face="Tahoma"><font size="3"><font color="#000000"> GPIO_SetBits(GPIOB, GPIO_Pin_0);</font></font></font>
    5 u8 {9 n: _) e  z$ f, R
  23. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码

1 c4 Q# Z' D( T4 J, ]+ S主函数
- A3 l; W* G. @; a4 `- y' F; |
  1. <font face="Tahoma"><font size="3"><font color="#000000">#include "stm32f10x.h"</font></font></font>
    + C8 w2 K6 m- J% @
  2. <font face="Tahoma"><font size="3"><font color="#000000">
    6 @, a% B% g% {
  3. </font></font></font>
      T% M* Z9 l4 }5 [, G% T; ^
  4. <font face="Tahoma"><font size="3"><font color="#000000">void SOFT_Delay(__IO uint32_t nCount);</font></font></font>
    : S2 o0 d3 \. \1 ?
  5. <font face="Tahoma"><font size="3"><font color="#000000">void LED_GPIO_Config(void);</font></font></font>
    # g. X: `0 }/ O3 [6 s* @

  6. ( X/ O+ \" a2 m2 c7 y
  7. <font face="Tahoma"><font size="3"><font color="#000000">int main(void)</font></font></font>
    * B0 o+ h, s# Q4 K1 T1 z
  8. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>
    ' N; g" m) Q3 j, S# W7 v$ N& a! N( [2 a
  9. <font face="Tahoma"><font size="3"><font color="#000000">// 程序来到main 函数之前,启动文件:statup_stm32f10x_hd.s 已经调用</font></font></font>
    5 b' ~: {0 L/ T! _& J4 ~$ ?; z7 L$ L2 \
  10. <font face="Tahoma"><font size="3"><font color="#000000">// SystemInit()函数把系统时钟初始化成72MHZ</font></font></font>: O( D5 \( j8 I2 g
  11. <font face="Tahoma"><font size="3"><font color="#000000">// SystemInit()在system_stm32f10x.c 中定义</font></font></font>
    / [( k# G# B; M3 |& J8 b/ h! q; a
  12. <font face="Tahoma"><font size="3"><font color="#000000">// 如果用户想修改系统时钟,可自行编写程序修改</font></font></font>7 J; K6 q  q% e. d6 `. j
  13. % E; G$ V/ w. b+ B) ~5 v4 v
  14. <font face="Tahoma"><font size="3"><font color="#000000"> LED_GPIO_Config();</font></font></font>* s: T5 {4 H6 |! a) A! o

  15. 7 _. ]0 P0 |7 M8 M, H% q7 C5 v
  16. <font face="Tahoma"><font size="3"><font color="#000000">while ( 1 ) </font></font></font>& A4 N% c( @3 Q# ]$ Y
  17. <font face="Tahoma"><font size="3"><font color="#000000"> {</font></font></font>
    5 _3 T- C8 A1 R) ?: f1 B. }3 I7 ?4 x/ ~
  18. <font face="Tahoma"><font size="3"><font color="#000000">// 点亮LED</font></font></font>, T4 a) n& X+ K# G
  19. <font face="Tahoma"><font size="3"><font color="#000000">  GPIO_ResetBits(GPIOB, GPIO_Pin_0);</font></font></font>
    . e5 ~, v7 ?6 d
  20. <font face="Tahoma"><font size="3"><font color="#000000">  Time_Delay(0x0FFFFF);</font></font></font>
    ; P( l9 d( r% t( X8 p0 h2 w
  21. + @2 }* q" e. L- `
  22. <font face="Tahoma"><font size="3"><font color="#000000">// 熄灭LED</font></font></font>
    ! T7 ]9 V' f( }2 A! {
  23. <font face="Tahoma"><font size="3"><font color="#000000">  GPIO_SetBits(GPIOB, GPIO_Pin_0);</font></font></font>
    6 t$ V7 B) s, a$ I9 u
  24. <font face="Tahoma"><font size="3"><font color="#000000">  Time_Delay(0x0FFFFF);</font></font></font>
    7 s! X7 N* h/ c% D4 m! p7 b1 p
  25. <font face="Tahoma"><font size="3"><font color="#000000"> }</font></font></font>" b0 W9 Z# Q& j; D9 H9 T
  26. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>1 o5 K% Z1 t1 I+ g+ h1 }/ E
  27. <font face="Tahoma"><font size="3"><font color="#000000">// 简陋的软件延时函数</font></font></font>
    " O. ~2 x9 J$ U
  28. <font face="Tahoma"><font size="3"><font color="#000000">void Time_Delay(volatile uint32_t Count)</font></font></font>
    + P$ H1 N% a# b* Q
  29. <font face="Tahoma"><font size="3"><font color="#000000">{</font></font></font>  l( _8 _( j6 s  O% ]
  30. <font face="Tahoma"><font size="3"><font color="#000000">for (; Count != 0; Count--);</font></font></font>
    + {  u! @9 {7 `7 Q4 G7 M; ]& V
  31. <font face="Tahoma"><font size="3"><font color="#000000">}</font></font></font>
复制代码

+ N' a' w( Q1 }" [& X( }% F; v/ t    注意void Time_Delay(volatile uint32_t Count)只是一个简陋的软件延时函数,如果小伙伴们有兴趣可以看一看MultiTimer,它是一个软件定时器扩展模块,可无限扩展所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。
, y; U3 L& \3 D2 E* u: G
. n* h9 W# q8 |* z

/ E( M$ n1 b! b转载自:EE时间+ y, |# f- \, R- w: E! T

8 |0 _0 H3 U4 t0 c& f# a$ l/ S2 a; ]5 X/ n* a/ ~% S+ ]
赞 收藏 评论0 发布时间:2022-10-11 18:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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