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

【经验分享】STM32 单片机开发模式的发展

[复制链接]
STMCU小助手 发布时间:2022-4-27 21:04
6.1 STM32结构0 T0 o: Z+ ^8 q: X! w. o
从“2.2 ARM与STM32的关系”可知,ARM公司负责设计内核,半导体芯片厂商拿到内核授权后,根据产品需求,添加各类组件,生产芯片售卖。如图 6.1.1 所示,为STM32的组成示意图,其中Cortex-M3内核、调试系统都是ARM公司设计,内部总线、外设、存储、时钟复位等都由ST公司开发。! ^( h8 r2 U$ ?$ \9 _

" c6 O5 L6 \" G6 v- T1 H在编程之前,对STM32的总线结构、存储结构、外设寄存器等有个大致了解,有助于理解编程中的一些操作。此外,中断与异常(Nested Vectored Interrupt Controller,NVIC)、时钟复位(Reset and Clock,RCC)也很重要,且与编程紧密相关,在后面相关实验章节里再专门讲解。
; t5 D) |: K" V; k
/ l. a) H" j$ O, C: m5 w对于开发者,掌握一款MCU的开发需要重点关注四大模块:时钟复位、中断异常、存储映射和外设寄存器组。       / P; v/ J; l- e. Z0 J5 h

6 b* d/ h9 [' ~2 ]' t; C& K, Q$ M M{KR~]FJ0IVE(_XRX7879]P.png
7 e) L# d  U, `: b8 M5 ?& [2 e' B8 B3 q- w5 x; z" m+ O
图 6.1.1 STM32组成示意图/ c! e( I8 L9 P# W6 p6 A! c/ ]2 R

- F! t, i( d+ K5 |8 G2 n7 m
) d- Q: Z$ {5 j. r" N3 t6.1.1 STM32总线结构
* a" U$ g$ j  \6 F7 [/ B9 b* ~总线(Bus)是各种信号线的集合,是嵌入式系统中各部件之间传输数据信息、地址信息和控制信息的公共通道。
/ ^  ^$ i( a( h: W3 f
! T- Y5 W0 c, V2 R与总线相关的主要参数有总线宽度、总线频率和总线带宽。总线宽度是指总线能同时传输的数据位数,如8位、32位、64位;总线频率是指总线的工作速度,频率越高,速度越快;总线带宽用来描述总线传输数据的快慢,总线带宽=总线宽度x总线频率/8,单位为MB/s。# [+ v" Q- z+ K
+ [0 |8 V; G" O$ T4 a, {
STM32的总线结构如图 6.1.2 所示,可以分为6部分。' I' G3 t/ s6 U; G3 @

( G* {) S  M, U" {6 i" G1 A (YU46GW8)E6QJRVH]6H3~VP.png
7 b2 b& h) u7 M* l' q! x" N# f/ k# g/ A# W5 w/ J
图 6.1.2 STM32总线结构框图2 Q7 z* T) O7 {; ~2 n% {0 `5 J& `' |

$ H- j8 L) Z! t; }) b( r/ V
, o1 Z/ n* N+ |0 o4 I①ICode总线(Instruction bus):用于访问存储空间里指令的总线;
0 [) m- }* e8 W! O* d  @6 f7 R
3 i7 F( T/ t. S) A! @3 i6 C②DCode总线(Data bus):用于访问存储空间里数据的总线;: H6 E  p9 w: `2 B
, I7 ]: P- P5 K2 }- Y( P
③System总线:用于访问指令、数据以及调试模块接口;0 R, k% {" K' o0 o* I3 W* R* M
" ?6 j7 @% \( A( g/ n) X
④DMA总线:用于内存与外设之间的数据传输;( S& ^. ]: ^2 m6 j: D
. h8 ?+ {; I) a: H/ B
⑤Bus matrix(总线矩阵):用于总线之间的访问优先级管理控制;
6 U* R9 ?- Z5 G+ G
( D( h' X6 _9 V  w⑥APB总线:用于外设接口的数据传输;ARM公司推出AMBA片上总线结构,该总线主要包含先进高速总线(Advanced High-speed  Bus,AHB)和先进外设总线(Advanced Peripheral Bus,APB),分别连接高速设备和低速设备。基于这个总线结构,ICode、Dcode、System Bus都是AHB总线。这里AHB系统总线经过两个AHB-APB桥转换成了两个APB总线。APB1上挂接有DAC、UART等外设,其最高频率可达36MHz;APB2上挂接有ADC、GPIO等外设,其最高频率可达72MHz。4 Y* ?- _2 v2 F9 p1 }8 C' i

' S8 U7 g; p4 z& p在MCU每次复位后,所有的外设时钟都会默认处于关闭状态。因此,在使用外设前需要操作复位和时钟寄存器(Reset and Clock Control,RCC)开启所需外设的时钟。4 @- ?) y/ I: \- K* Q4 h* n& ]- M5 V

' f$ R+ X+ |, V1 Y" o# X
; K# h+ p6 F" K. A' j6 `0 v6.1.2 STM32存储结构5 W6 I( L, D) U4 X" |0 W+ @
CPU通过总线访问各个外设,现在通往外设的“路”已经铺好,还需要规定各个外设的“门牌号”,以便精准控制每个外设。ARM Cortex-M3系列的处理器,采用存储器与I/O设备(外设)统一编址的方式,将部分存储器地址范围用于外设,这种通过存储器地址访问外设的方式,称为存储器地址映射。& N* j# [0 S  N; t% X9 q# C
; X0 v$ P& s' j
对于32位的处理器,可寻址的范围为232字节,即,也就是0x00000000至0xFFFFFFFF。ARM将这4G空间从低地址到高地址依次划分为代码区(Code)、片上SRAM区(SRAM)、片上外设(Peripheral)、片外RAM(External RAM)、片外外设(External Device)和系统级(System level),如图 6.1.3 所示。
% n; _3 E; x+ b) ^+ R/ {3 j0 r% X %{QW`K0UE[[)~ZD%QL%N(6H.png 9 _# `2 {& n+ m. P8 v2 Z( E
* b$ z9 G$ W4 W- A3 t3 t: r
图 6.1.3 ARM Cortex-M3存储器映射结构图5 A9 V/ K" k  g: u8 h" b2 [9 t

5 U6 g! P3 C, v2 A+ F9 ^2 G" O ARM公司只是大概的规定了存储器空间的映射,允许各芯片厂商在指定范围内自行定义和使用这些存储空间,未分配的空间为保留的地址空间。
, H6 V) y6 o: O- k" `( ~  o) D% Y  f% U6 i4 Y9 s, w6 N
STM32在ARM规定的基础上,将4G空间分为了Block0、Block1、Block2、……、Block7,共8块,每块大小为512MB,如下表 6.1.1 所示,详细结构如图 6.1.4 所示。
# U3 K, n/ \; S. @' I0 r3 O4 p& ~4 Q( l
1Q)WBW9[B9[GXG30T1}S1@U.png
* R) p' r* s2 V) P7 z
0 D5 n0 O( ?. M: q; {表 6.1.1 STM32存储器映射结构
% o; a2 a- p' d3 ^1 l
) V. j5 s. o$ X6 @$ I EB46}~}`(}%FZ7QD9(Y1.png
2 [  }3 g4 B, J' s7 V, G8 e- w4 f: U" D" |9 ^0 }! W
图 6.1.4 STM32存储器映射结构
: q9 N5 H$ |) z. C% y0 J0 X
. k) A& }2 i/ J' U  ~. \( u①0x0000 0000 ~ 0x1FFF FFFF(512MB):作为代码区,用于存放下载的代码。系统上电后,将从该部分读取代码;
* A( X8 y: h, c4 ?3 `
& ?; O  n, ~# O- C$ \# a$ c②0x2000 0000 ~ 0x3FFF FFFF(512MB):作为SRAM区,用于存放运行代码。系统上电后,将从Flash读取代码,放到SRAM里,CPU再从SRAM读取代码运行;* E$ G& f7 R. [

2 X7 t7 ?+ x+ L2 |0 L7 {③0x4000 0000 ~ 0x5FFF FFFF(512MB):作为片上外设区,用于存放厂商外设寄存器。要操作外设,即修改这里对应的外设寄存器;注意这里的RCC和Port B外设的地址范围,后面很快就会用到;
7 J2 a* d0 F2 V' @! |, _: Z3 Q9 O+ ~: {2 v; B7 R2 L1 |& i# y3 R
④0x6000 0000 ~ 0x9FFF FFFF(1GB):作为片外RAM,用于扩展RAM。当SRAM或者Flash不够用时,MCU通过FSMC外接其它IC芯片,则在这个地址范围读写IC芯片数据;
  E* |8 z" z" k- |, V# I* j  ^! Z$ m; R; r* A) T, {
⑤0xA000 0000 ~ 0xDFFF FFFF(1GB):作为片外外设区,用于读写扩展IC芯片的寄存器。ST只用了这里的一半空间,另外一空间未使用;# J, R2 c" A3 Y% v  s4 {! s

& s! n" |- T: e3 q1 D⑥0xE000 0000 ~ 0xFFFF FFFF(512MB):作为内核外设区,用于存放Cortex-M3内核的内部外设。Cortex-M3内核的内部外设有NVIC、Systick等;( y# e1 F* Y  E

7 ~* a$ }3 Y& O在Block0、Block1圈出的三个范围,分别对应前面介绍的三个启动模式。当从主存储器启动,则是从Flash启动;当从系统存储器启动,则是从System memory启动;当从SARM启动,则是从SRAM启动。
' v9 w! V, g1 R- B+ L* M$ g: o- G5 x/ [- a( e
对于开发人员,通常就是对Block2的片上外设寄存器进行读写操作,以控制外设资源,实现所需效果。
5 U5 F1 h% N# r6 O% b5 c. e$ d0 v" d& l' g$ ]2 T. I/ ]7 F/ o

1 \1 }8 z. q. t) P3 g: J6.1.3 STM32寄存器$ |) d2 \" U  u+ C$ Z
寄存器是用来存储二进制数据的时序逻辑电路,由众多晶体管组成。
0 |+ ^$ F) o& H% d5 D$ i8 g  N2 {4 H4 L5 r$ F3 }7 M9 K, U
前面提到的寄存器,都是外设寄存器。这些外设寄存器由芯片厂商设计,与存储器统一编址,常用C语言的指针来表示外设寄存器地址,实现对外设寄存器的访问和操作。
. x. ^8 L& r; L! D* x; R
6 v; h) I, q# I; o7 A: S在嵌入式系统中,除了外设寄存器,还有一类叫CPU内部寄存器。这些内部寄存器由ARM设计,在CPU内部,常用汇编语言直接操作,用于暂存参与运算的数据和内核的一些控制。
, e4 y/ k2 c( Y9 T. G7 Y+ B8 [3 E# Z# v) _- W* D
开发人员,通常只操作外设寄存器实现需求功能,后面实验会详细讲解外设寄存器。而内部寄存器在实际开发中接触会比较少,后面汇编点灯实验会涉及部分相关知识,本小结简单介绍下内部寄存器。- X) v6 N! r% B  ^& u5 e7 e
4 m1 i. y1 y6 [) ^! R2 s  ~+ ^' ?
ARM Cortex-M3微处理器的内部寄存器,又分为普通寄存器和特殊功能寄存器。普通寄存器如图 6.1.5 所示。, I, @. o4 }5 P/ u7 w" c
; h. {9 b) y* i/ i
{CAXMA33BW2{K{SGE2U2N88.png * h$ @' b% y1 q* L

3 ^+ O& U3 G1 p3 E& }. g, @图 6.1.5 Cortex-M3通用寄存器
* H  _. w1 f5 Y: X0 ^: v
! `2 p6 |# N8 ^' \) \
1 @: b; G, Z; z2 B' _! }R0-R12(General-Purpose Registers):用于数据操作的32位通用寄存器;一些16位的Thumb指令,只能访问低寄存器(R0~R7);" g) _4 T# A+ l9 T' h+ k

% X: G% M. d0 F# Y' C  s# `R13(Stack Pointers,SP)Cortex-M3包含两个堆栈指针寄存器;同一时刻只能看到其中一个;
' H. x3 F5 Y" p4 c- @1 X2 I( D" R9 P! q# Z4 G
主堆栈指针寄存器(Main Stack Pointer,MSP):操作系统(OS)内核和异常处理程序使用的默认堆栈指针;: j# @( u4 ~  q
' L/ S, L! K9 G7 ]
进程堆栈指针寄存器(Process Stack Pointer,PSP):用于用户代码;. V- v+ x% G9 F5 o" m
  O7 |  q" q2 Q% j, j$ l
R14(Link Register,LR):链接寄存器;调用子例程时,返回地址将存储在链接寄存器中;) l* k3 I- g4 n. v  x( ^) V1 L
3 n) N- ?2 h1 V3 p8 U; K; s& B
R15(Program Counter,PC):程序计数器;总是指向下一条指令所在单元的地址,可以写入该寄存器以控制程序流;& r3 h" K% a1 p$ r  M- k* E& a2 M
# }# T; N" x& P  t2 I  q2 y8 z3 |
Cortex-M3处理器还具有许多特殊寄存器,如图 6.1.6 所示。
2 [9 J  K; K) ]9 j  @' ]4 ~/ [
7 s& S2 ~* `6 x+ V ACZ1(GR63E{0W_75{OMN0BL.png 8 _! P" u6 o& }! r; j9 s6 y
6 q  ?; ^$ y8 C
图 6.1.6 Cortex-M3特殊寄存器5 @8 V$ U" v( y# O/ S
7 u* x9 u' O5 J4 k1 y1 h- D9 B+ g

: e7 _- c4 x3 @& o2 X3 BxPSR(Program Status registers):程序状态寄存器;用于存放程序运作中的各种状态信息以及中断等状态;由应用状态寄存器(APSR)、中断状态寄存器(IPSR)和执行状态寄存器(EPSR)组成;" i, {* m* y6 _, L- k1 U
& H( X/ L3 O) Y! A2 \& i
PRIMASK、FAULTMASK和BASEPRI:中断屏蔽寄存器;用于控制异常和中断的屏蔽;
1 a" u1 R% `; M
. w  T) x8 ]% z/ h, j# }CONTROL:控制寄存器;用于定义特权状态和当前使用哪一个堆栈指针;
- E/ m- m2 x3 n, _2 O9 G
3 O) @8 @" l' A6 L3 S! t# }3 N
& a& c+ b, V- }* c# M【总结】
( s0 i! J* ?3 z# ^; S9 L2 {: Q
7 y% \2 A# ?  A: w+ _STM32由ARM公司设计的Cortex-M3内核与ST公司开发的外设资源组成。# _: q) N. N4 N3 s  U" u/ d3 Z
  F: w% b2 o: R, A/ {/ ^
Cortex-M3内核有内部寄存器,主要用于运算和内核的控制,这块对于初学者较难,暂时了解即可。
) C+ Q+ i5 c1 I, [$ c% N' [; n$ a6 _5 ?. y
Cortex-M3内核通过总线和外设连接,重点了解大部分外设都挂载APB即可。1 ^, f, b) a; ?8 ~

" G, g+ A( p3 V/ }+ ]. ZSTM32采用存储器与外设统一编址的方式,控制外设,则对应操作指定地址的外设寄存器即可,这是后续实验的重点。
. E$ h. f2 c( Z. u
% ~- z; V  z3 Z* t. o4 G
  q- U7 U6 N% z. E# m
1 G% h. v" o  R; _0 M6.2 使用汇编开发4 T' O2 c, c+ {) M4 X8 ~
汇编语言(Assembly Language)是一种用于电子计算机、微处理器、微控制器或其它可编程器件的低级语言。' u) F8 k8 \5 V) z! ^

% K0 }: \$ o, R' G% x9 b$ z( R在单片机出现之初,由于性能限制,都是使用汇编进行开发。随着技术的发展,制程工艺的提升,单片机的处理速度越来越快,越来越多的单片机使用C语言开发。如今,一些低端MCU还在使用汇编开发;一些高要求程序优化的场合也会使用汇编;MCU/MPU的启动初始化部分也是汇编。本小结就带领读者感受下如何使用汇编操作开发板LED灯。
: p) @! w3 C2 d9 W2 q& F, W- x' S; W5 o! K( t8 h
在编写代码前,本应该分析《开发板原理图》和《参考手册》,从而得知需要操作哪些外设寄存器,但这不是本小结主要内容,具体的分析方法放在后面对应的实验章节,读者暂时无需深入理解以下内容的由来:
+ \5 z( |( M6 w2 `& H: Z" Z& W$ S
+ U  ^6 c. f$ |4 a0 a3 M' _1. 从《开发板原理图》可知,控制GPIOB 0引脚,即可控制开发板三色灯的红色灯;) |( d% Z& |6 a$ P8 V1 B2 Y
3 l7 C' R6 E6 [
2. 从前面STM32总线结构分析可知,GPIOA挂载APB2上,由RCC控制;
: L" V4 J9 J, f3 l2 b" G5 }$ @/ m# Y' ~1 y: J' K
3. 从前面STM32存储结构分析可知,RCC起始地址为0x4002 1000,再仔细查看《参考手册》RCC寄存器部分,可知寄存器RCC_APB2ENR(偏移地址:0x18)的第2位(IOPAEN),设置为1则GPIO A组的使能;8 e- E. M2 ^: D; r* B, d, N, j# _# f

6 T# F/ y! S' `2 [5 i" z! W4. 从前面STM32存储结构分析可知,GPIO A起始地址为0x4001 0800,再仔细查看《参考手册》GPIO寄存器部分,可知寄存器GPIOx_CRL(偏移地址:0x00)的第4:5位(MODEy),设置为1则GPIO A1为输出,寄存器GPIOx_ODR(偏移地址:0x0C)的第1位(ODRy),设置为1则GPIO A1为输出高,设置为0则GPIO A1为输出低;
8 H8 u" h9 V/ k, y: X+ x* M! a, C1 ?$ _
9 p+ R. E8 n- t# x6 F
有了以上基础,就可以编程控制LED灯了,本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\1_使用汇编开发\”。4 S" i- `+ N2 u' u. z4 b3 a

0 o' S4 |' t/ V& R/ g1 G5 H如代码段 6.2.1 所示。汇编代码对初学者不友好,读者对代码内容不理解也正常,也无需深入理解汇编指令,这里主要目的是展示如何用汇编操作寄存器。
6 P5 g/ B0 M$ Y7 E' S
1 C3 [  E3 |5 i$ u代码段 6.2.1 汇编操作寄存器(startup_stm32f10x_md.s)
$ s8 C3 g* \; ~' j" k* Q/ ~
4 B8 J, X! C3 h5 R0 t" Q" S* s+ R
  1. ; Reset handler* a& h, _: m$ Y9 c, f% R5 f

  2. ) X6 T* E% k+ f" l5 r6 T6 X
  3. Reset_Handler   PROC% |  K- o5 _4 d. {$ j8 N8 w' F

  4. ( C0 Z/ f0 ^' e4 M, l2 Z
  5. / I* T' b, Z& x; q1 d/ [& T
  6. * I6 x- _2 N8 {* L; E
  7.     ; 使能GPIOA
    + V: H! V8 O( p
  8. - b$ M6 i. N1 ]3 ?6 z7 U
  9.     LDR R0, =(0x40021000 + 0x18)  ; RCC_APB2ENR
    / _, D/ ?9 u5 Y* Z. J0 k! V

  10.   o6 M0 N2 H. I/ ^+ D8 F: |
  11.     LDR R1, [R0]
    ) W0 f1 {! F( O! M% V, O. H

  12. 9 R6 Z" V8 N5 H- j# B) w
  13.     ORR R1, R1, #(1<<2)1 \  h: G9 Z' Z5 B8 x3 v. \& z1 n0 B

  14. * O* y1 C1 v9 W: M4 g
  15.     STR R1, [R0]- E$ _8 b1 l7 A3 a
  16. . w  v/ s' T" @8 ?# k8 l
  17. . i' Y8 o: H; [( H2 I  C& [/ H

  18. . X5 F/ X; B0 h
  19.     ; 设置GPIOA1为输出引脚
    % \4 q# i6 o, g3 l( q3 a% p
  20. 6 s- v5 C  m9 Q  f: k6 @
  21.     LDR R0, =(0x40010C00 + 0)  ; GPIOA_ CRL
    $ Q( R3 U, v0 [! R

  22. / ?; v2 x7 V% a( |+ K% K. `' o
  23.     LDR R1, [R0]
    9 V; b& e2 N- Q- F3 T, b$ \8 i' N
  24. . r& F" f3 H/ q$ c0 W+ {+ t. w
  25.     ORR R1, R1, #(1<<4)" _) g9 K6 Y' p6 V2 w( o

  26. & W& r* \  Z2 p- {$ D
  27.     STR R1, [R0]
    ' ^( S0 J) ^# L- U  [1 l

  28. 7 f* ~, }* L4 ?

  29. * ~. H+ ?: F; l4 l

  30. 4 |, m* D0 ~0 Q2 Z: O( y2 f
  31. MainLoop
    - W4 i6 r4 C; l3 P+ Y2 N4 c2 W

  32. - M: @5 p/ `  n# C' d; K
  33.     ; 设置GPIOA1输出高电平# M% ^! Q7 u- m1 w+ c& K+ x& @
  34. 1 z6 h. y' T- s; N8 m" P$ W
  35.     LDR R0, =(0x40010800 + 0xC)  ; GPIOA_ODR
      y$ Y8 j* R4 \
  36. 0 r% q; T4 w% x2 h* v, B" `& O7 \
  37. $ K+ k# {2 Y5 ^0 M& S6 S' N% U; Q9 ?

  38. $ i- m1 B% @! ~5 Q+ l7 f
  39.     LDR R1, [R0]
    & L+ B' K' `- w' u+ y
  40. 3 U1 m, D* b( B
  41.     ORR R1, R1, #(1<<1)
    5 j0 ?% ?1 t+ u8 f  y( G9 S( b2 W
  42. 6 M! P" }' g$ Z0 \- F2 V! Q( h
  43.     STR R1, [R0]: u% }1 l6 ?  ?" r4 |
  44. $ J3 x8 i2 i9 O
  45.     BL Delay4 t; H9 R& h' Q

  46. 3 h+ r- ?/ _. O6 _, O( R6 E  `
  47. / [0 m8 O4 |; U# G

  48. ! f/ a! K. \5 l
  49.     ; 设置GPIIOA1输出低电平: Q1 f# M* ]' ]3 Y

  50.   Z* s6 C" A# p4 }4 ?
  51.     LDR R0, =(0x40010800 + 0xC)  ; GPIOA_ODR1 H9 r/ H4 F' W

  52. : |! O4 i- a! K: u7 d7 Z" P
  53.     LDR R1, [R0]1 m' P- {& Q/ n; {

  54. 0 K  |( L1 L3 y3 K
  55.     BIC R1, R1, #(1<<1)
    7 @4 |# f* a/ r' l  j* Y' q

  56. ' w0 \5 Z4 `6 k5 w* U' P
  57.     STR R1, [R0]
    2 o4 z! L3 D: M3 ~8 u; W
  58. 6 Z. j- A+ n+ E) u# n8 X" T
  59.     BL Delay' @, m) r" [7 w1 H% _
  60. & \( E) _3 ^% w
  61.     B MainLoop
      q0 _2 B% Q/ N  \8 Z# E4 H

  62. 3 n' |8 A; s  X: D. q
  63. 3 P% v6 F/ n/ T7 j2 n

  64. / B7 A; G# ]" P, T# Q' {
  65. Delay8 X0 d& q* p- Q) k, c0 o
  66. ! q+ y% L$ ~+ B- N  h
  67.     LDR R0, =300000
    ' z; F/ y% \; o! v. Q: z0 K% ?

  68.   l' e6 p7 {7 y! P- i
  69. DelayLoop
    4 H6 i% d# W. `! w* Z: ^
  70. 9 [' N/ x) j' D8 o) ^5 L
  71.     SUBS R0, #1% q& Q  ?! \/ m* f) X6 W

  72. 1 ]" w! Z/ d9 X# {+ I9 L
  73.     BNE DelayLoop. E( B; ]! q. `9 b* H$ U, z
  74. 5 X  G- Y  `! q6 w8 Y; u
  75.     BX LR; B9 r( ~6 O/ K0 t& a! ]: G
  76. 7 s* I& B7 `0 N1 N) C4 \7 _) I
  77.     NOP
    % V6 Y9 G7 y! v0 h, M$ x

  78. ' u* v$ D" }- q* O1 E
  79.     ENDP
    , [/ X' F1 j9 H

  80. - J, s( N/ r' @) G
  81.     END
复制代码
; u3 z9 s% L' O* ^
% e+ g: M& `4 S) R5 M* @
4~8行:设置外部寄存器RCC_APB2ENR的第2位(IOPAEN)为1,使能GPIO A组的时钟;# ]' k, n' {& T5 W

. O5 O: k4 n6 ~% i, W5行:将0x40021018(RCC_APB2ENR的基地址+偏移地址)放入内部寄存器R0中;
2 S: v: I) M5 s
# `! B) D$ c2 v6行:将0x40021018地址的值(此时RCCC_APB2ENR寄存器的值),放入内部寄存器R1;, T; i: j. I! w& ~/ G

. q: Y/ [1 g6 t7行:将R1的Bit2设置为1,并将设置后的结果放入R1;  O7 [$ K& {0 B' F. E7 n
0 O7 q/ [9 y2 l% f" _' q+ \0 b. {
8行:将R1的内容,放入R0所指向的地址,也就是将修改后的数据放入RCCC_APB2ENR寄存器;
2 ?6 _# E8 q8 `2 T: a8 ]# ?$ G" p3 g- X2 @# u% m1 b7 d
10~14行:设置外部寄存器GPIOA_ CRL的第4:5位(MODE)为1,让GPIO A1为输出模式;6 G2 o* T6 `, N# d
" G  Z/ J* r% J& h4 u, q1 j
16~30行:设置外部寄存器GPIOA_ODR的第0位(ODR)先后为1和0,让GPIO A1为输出高、低电平;) _4 F  \3 I7 J* }0 s+ T8 Z
- Q& p! B# K: Z/ Z0 y. \8 r$ P
32~40行:循环减R0实现延时效果;
6 h) B6 M. ?0 |+ h! N& p; C5 @% N4 `! ]  H" t$ J1 N
以上汇编代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机开发模式的发展\1_使用汇编开发\ Project\Led_Reg.uvprojx”工程,编译、下载,体验效果。
* n9 x6 `2 y7 k2 G9 l
& r) j0 h. b7 Q2 q3 _可以感受到,使用汇编编写程序,生涩难懂,因此越来越少的单片机使用汇编来编写代码,下面再来体验下C语言实现一样的效果。
8 f& u! t2 A+ T: j) X
9 m6 k" Y/ D$ ]6 k7 O; w" z& l4 f* f8 [  n" {2 d
6.3 C语言操作寄存器开发' v( R6 o7 W+ a1 M( o6 f7 {8 M
在使用C语言操作寄存器前,仍需要先分析《开发板原理图》和《参考手册》,从而得知需要操作哪些外设寄存器,假设读者已经了解需要操作哪些外设寄存器。+ C/ t( i% P4 [2 L
& w1 d+ t" v# |* i
本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言操作寄存器开发\”。5 J; o+ |5 L/ `/ m
9 }1 \% S2 V& V0 v
使用C语言之前,需要先设置栈,如代码段 6.3.1 所示,在上电复位后立即设置栈,随后跳入main函数执行。3 \" U3 |: E6 F0 K  l
. w3 O5 c! W! g( C
代码段 6.3.1 设置栈(startup_stm32f10x_md.s); r* y7 @" x1 p4 H

) w; `( c- S7 I
  1. ; Reset handler
    9 G1 R8 J- Q- c
  2. 9 K9 X' i% ?9 i) x" ~
  3. Reset_Handler   PROC  K( u" I1 x8 Q; a
  4. ( @( m( `. g& N( o1 w; [& Z) c
  5.                 IMPORT  main
    8 b6 f9 ~7 d; n/ h

  6. - F& Q0 h$ _, z4 V2 z
  7.                 LDR     SP, =0x20000000+0x1004 t8 v: P) D( ?$ [  o) r5 s
  8. 8 H) C6 C4 t  }7 M& Y
  9.                 BL      main
    ! g5 O4 H3 y5 Y' \! ^

  10. 1 L0 Z, n" e) {3 z; b
  11.                 ENDP
    6 t* @. f8 C2 b, H% t) g; `  X
  12. ! |( }" S) Z1 n# o8 }
  13.                 END
复制代码
  e# v$ e- R% E# ^
/ r) c7 b$ m% [7 @2 g& i* F
代码段 6.3.2 C语言操作寄存器(main.c)# l& P4 }, C6 W: [( E- T( |
  E7 j: m  b6 ?5 j! S0 E2 o9 A
  1. #define RCC_APB2ENR  (0x40021000 + 0x18)
    * ?" u  H- {# t+ X0 S/ O, G# R
  2. : d: k4 c5 i. O6 x* z3 T1 Q
  3. #define GPIOA_BASE   (0x40010800)1 I6 R0 C. V: B
  4. / N9 E. J4 g7 p; E- j
  5. #define GPIOA_CRL    (GPIOA_BASE + 0)
    3 {: r. o% p: v6 c( E2 E* z
  6. ' e% J. S" J- }- l/ q) J6 d; o  q: X
  7. #define GPIOA_ODR    (GPIOA_BASE + 0X0c)
    ; M+ C1 Y1 [" `3 B: T+ e: g' f  `2 l

  8. 2 q" s* ]3 u- I0 `8 O; O6 a

  9. 0 c9 ]+ P1 i; u; s* l

  10. 8 x% H# C* V5 v5 c" E
  11. void delay(volatile long i)
    " @' |0 R. }; ?% e* P
  12. $ y! m9 G6 w# `# }
  13. {/ u3 r( m% c0 F7 l3 `; |

  14. & c1 _2 @6 i% R& R9 k; j2 m
  15.     while (i--);& K+ N9 P' p+ \0 s( q

  16. 8 O- |2 r$ H. C1 {: m( z- [
  17. }; v% E0 E. T2 `, m' |: o2 V! {

  18.   P) J" q6 C0 O9 x% ^) N- a
  19. ! Z- P- c7 E" z6 T- h! W) s
  20. 5 Z0 k& a: Y0 c" U( ^
  21. int main(void)* r' c  k2 @% R
  22. ; Q& P' U( Y/ ]% v7 W9 o5 h9 I( v
  23. {
    & L- a! N% d  [7 c: O

  24. 2 T7 S4 d& x. K! Y6 Z
  25.     volatile unsigned int *pRccApb2Enr;
      t7 a9 [+ K  E& N

  26. ( ?5 i2 F2 k+ k" `- `* F! b# t+ ^5 {
  27.     volatile unsigned int *pGpioaCrl;
    ! m, \! S8 C" r- ?, u/ e; i( P# ]$ {
  28. 6 G" W( W" M' Y6 K
  29.     volatile unsigned int *pGpioaOdr;% J# [1 ~  I3 {4 T9 s
  30. 8 g% Y+ Y4 V2 y5 x0 Y

  31. / o+ j  }, l+ ?% m, k% {

  32. 4 K) {) o! s. _
  33.     pRccApb2Enr = (volatile unsigned int *)RCC_APB2ENR;! d4 q7 p3 P3 G' d$ r' E

  34. ; d2 I5 o4 D3 v/ A
  35.     pGpioaCrl   = (volatile unsigned int *)GPIOA_CRL;
    $ Q8 U( D* c. J) g8 }5 C( [
  36. ) v: V! y  O( a- x, {( L3 {; K0 T
  37.     pGpioaOdr   = (volatile unsigned int *)GPIOA_ODR;
    2 m8 S4 O& X/ }" k
  38. 0 _; D  r2 H; R/ \. {
  39. 0 M% t, T! r: j5 x
  40. # R4 R- t2 L9 R8 L
  41.     /* 1. 使能GPIOB的时钟
    . h/ C) V+ D3 m8 I6 q
  42. 3 F3 d+ i, x) Z! d' S
  43.       * GPIO是挂载在APB2上的,所以我们要使能APB2的外设时钟使能位
    # T9 A& ~5 f2 M3 e3 e7 y4 x, I

  44. . Y! Q( ^  w- ?
  45.       * 从RCC_APB2ENR寄存器描述中我们可以看到APB2ENR[8:2]是GPIO[G:A]时钟的控制位,控制GPIO时钟是失能或者使能
    + m" w8 t. Z1 @
  46. ! f" i  P9 t# D% z, j" x
  47.       * 开发板上的LED的GPIO组是使用的GPIOA,即要使能APB2ENR[2]位,为其赋值“1”即可打开GPIOA的时钟 */
    ; ^& ]% ~8 S8 P- L* T
  48. 4 B0 t4 O9 \0 s! K: P
  49.     *pRccApb2Enr |= (1<<2);
    , R- \% k. }; l5 u+ e  w
  50. ! j: h9 g, q) m1 J+ `1 {% a
  51. * N/ n! t" l. `6 `  R% ]
  52. ; t! T; d6 Z! t- U
  53.     /* 2. 设置GPIO为输出功能:1 D/ e; }- c- c, X: h! I1 H6 c$ {
  54. % b5 i2 J7 u) V: p
  55.      * MODE_0=1,PB0输出模式,输出速率最大10MHz( g8 a! X& m) H" R: {

  56.   G; @7 T! P, ^* M* g8 }" i$ q
  57.      */
    3 r0 w* R1 e8 p3 q
  58. ; o& t5 ]& p6 D5 E% |, j
  59.   *pGpioaCrl |= (1<<4);
    # W4 D1 K; i4 e/ I5 _# `5 U0 s
  60. 0 A0 |; T# g% Q. A# J2 r" X2 y; W

  61. * u! z# i. K5 K9 Q* `8 P- t* B  I, B

  62. 9 @5 d' d; u5 i; J) W6 }4 Z
  63.     while (1)) b+ E1 @( P& z' g3 S: ?! D$ z' n7 ~

  64.   e' S3 z4 i1 ?# {
  65.     {
      V5 P  {- m8 A/ o) c
  66. 4 U' E/ _- q7 f$ a
  67.     *pGpioaOdr |= (1<<0);
    5 D) f$ ?3 Z3 G. ]: N9 m
  68. ' H$ W* `% }2 `: H
  69.         delay(1000000);1 Z3 m* \+ Y7 q; m/ U) `3 I3 b
  70. 5 ^! h  u9 |6 r! |7 b3 a+ Q$ q
  71.     *pGpioaOdr &= ~(1<<0);
    3 `- f( P' E0 {, T% D

  72. $ C! X# f: X3 C* [, d" `. R9 i2 C; B
  73.         delay(1000000);
    # _. w8 B2 U8 H1 Y. c. T6 A  Q
  74. ' ?3 t9 A3 q# Z+ o- W
  75.     }3 M" R( e- ]( q$ L8 f7 Q' C) U

  76. 0 @, w* T7 o9 j- W* C4 @" j- Z; f# d
  77. }
复制代码

5 j1 n6 M6 L8 _) z3 C* [2 y$ J* J9 i* w6 N
主函数如代码段 6.3.2 所示。
% l! }  J0 Z2 }7 A1 k4 q. O2 _* R5 ~/ ?7 ]$ n6 C& I/ ?
1~4行:用宏定义了需要用到的各外设寄存器地址:RCC_APB2ENR、GPIOA_ CRL、GPIOA_ODR;
6 k, O  s, V6 M& [9 \
! F: p3 o* L- h0 I. r4 B0 J6~9行:循环递减传入的变量i,实现延时效果;" L. S" w3 ~2 \$ N" a: ]

7 ?3 i' s" h$ H8 [0 ]) p/ Q) f13~15行:定义指针变量;
: Y' X0 F* }! a( ]  {6 C# P; B3 ?  v) J* K% E& `. i
17~19行:设置指针指向对应寄存器;
% W! `. x2 i( r" l: g  I7 _4 x" V2 T! i* I7 Q/ _
25~30行:设置RCC_APB2ENR、GPIOA_ CRL对应位;
. l: j, i, t1 I4 L1 f. K+ B5 z1 k" l, p" R
32~38行:循环修改GPIOA_ODR实现LED灯交替闪烁;
5 G! [% ~4 R" `( N* K( H
( ~7 u4 N3 Z) K2 L4 j  b以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 2_C语言操作寄存器开发\ Project\Led_Reg.uvprojx”工程,编译、下载,体验效果。
) r5 `' T! n* r; U0 i* T4 |/ y" {4 V3 B0 C/ U3 G. s
使用C语言后,只需定义指针变量指向对应寄存器,修改该指针变量的值,即可修改对应寄存器,操作上方便了很多。: p0 \1 w0 [+ l& M. b/ x! Z; K
! G) B3 M7 n) K4 z. Y3 r, g

4 h3 H& R, p9 n+ P
3 f& t$ P9 ^6 F8 _6.4 C语言使用标准库开发
! \" }+ J8 }2 J( O) G4 |$ f* i前面的两种开发方式,适用于任何一款芯片,但需要仔细阅读《参考手册》找到对应寄存器,然后用指针变量指向寄存器。- U2 j& d+ |+ D* D: z
3 G8 Y$ X6 g/ [( ?4 C- {+ c' }
ST公司为了让用户更快上手开发,产生用户粘性,将外设寄存器提前定义好,准备一些常用接口函数供用户使用,这就是标准库的雏形。- g1 q( _$ @: _

8 G; h; d& Z: E! X! b4 d下面来感受下使用标准库的简便性。使用标准库通常不需要知道外设寄存器地址,只需要分析《开发板原理图》得知会用到哪一个引脚。  u  e$ T& t1 c4 ]4 k# F

$ X3 p: H4 s* c本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言使用标准库开发\”。, N- c  n' e5 W
7 d& }8 w& T: J
首先使用C语言需要设置栈,这些初始化操作标准库已经帮忙完成了,我们只需要编写主函数即可,如代码段 6.4.1 所示。* A. P$ r, ?1 Q5 r2 j4 e; _& C

7 `, K. u, M# s; \7 o, V9 Z1 E代码段 6.4.1 C语言使用标准库开发(main.c)9 Y( ~1 M. F- x. z$ }; X# |. w( y5 j

! o5 T  A- a6 i' a# ~
  1. #include "main.h"
    1 d2 F" a' I  l
  2. 5 S, p8 Y3 w# a# E/ A% `- M
  3. #include "stm32f10x.h"
    * e* J/ C* h! D* y5 B
  4. 7 x" ]4 s- t" W' r: L* |
  5. #include "driver_led.h"$ a' o7 r" J. n0 P

  6. & V0 a% g3 b* n3 {% q

  7. ; |' p- m: g0 f; }8 _
  8. $ r3 J8 u0 d9 i( z5 F# f; ]2 h
  9. void delay(volatile long i)
    % L6 O, }- T, Q

  10. 7 S5 ]! l/ g& V/ p0 F$ o$ M
  11. {% l" L, I& |3 n" [2 t  K

  12. 0 R5 S1 [. s/ N. U- [
  13.     while (i--);
    ( ~4 w  ]3 a" I3 i- e  Z* G

  14. - u9 A6 Y6 Y' \$ t+ s9 a  {, a
  15. }
    " O0 v, I( |( A" ^0 c; v/ m
  16. , K9 b- ?: S/ k; s
  17. ( J& h* z5 T# z- q# R

  18. $ {9 `/ i0 f% k- b$ D
  19. int main(void)
    4 G. w" l9 _$ M+ m. n% Z8 [+ ]
  20. 4 F2 q( x$ u8 g, f
  21. {2 b0 q. @& V( e8 C# p2 l/ x
  22. : ~9 n# t2 i( V0 L/ j9 K9 R
  23. - B. x$ R+ D0 k9 G4 M6 Y( z
  24. ) {' ^5 w8 `* |: J; ?# e5 E, }. u  W
  25.     // 初始化LED
    7 Y# D; g8 i9 g/ A

  26. ; V" j, D. j2 [: L) M6 t" Y) \
  27.     LedGpioInit();+ ]) o; d. u. u; \

  28. 9 ?% d  I4 d: N

  29. ) a" K/ |7 ~( z/ u! i" v" V1 V

  30. ; i2 y$ w+ k/ ^- Y# O3 n4 d
  31.     // 点亮LED灯3 P6 S+ q% s/ j! M# A, k$ h, k1 Z$ [+ Z

  32. ( s3 l- F9 `) Y: a( }: a; o2 u/ s
  33.         while(1)
    ! V3 {& o9 l+ X7 ~; Z! G
  34. 6 l% n6 {& Z. k6 W: X. T/ v
  35.         {# n+ f4 T- `( a
  36. ! q7 ?. T' r& u) C: ~. f$ ]. u
  37.             GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);( }8 H, [# M4 k% Z2 s
  38. ( t$ E7 p7 v6 Q9 G' s" L
  39.             delay(1000000);
    5 g2 ~6 a# g1 G4 [

  40. - }! u) m6 C& `' t# ?
  41.             GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);: t! t- N! F. X! J7 P7 ^

  42. 7 U0 _: g+ V9 ^7 x% T
  43.             delay(1000000);
    ) H# J# L' m4 @' [1 I& X( N, `' |

  44. 4 O6 L! ]0 Q8 w$ X
  45.         }
    8 t% i+ k4 @: p! Q6 }
  46. 9 T2 C: Q- _8 ^1 t
  47. }
    8 r5 A7 y8 L! [* d8 o
复制代码

1 ]6 r9 e% I3 M9 K1 F6 q
; O+ T$ ~7 T% k- _5 N' Q, g5~8行:循环递减传入的变量i,实现延时效果;4 s- Q, j3 j. Z" V

2 g9 p; p- T$ a$ {8 ?$ k14行:调用编写的“LedGpioInit()”函数,初始化需要的GPIO,该函数定义在“driver_led.c”里,如代码段 6.4.2 所示;
* I& f- S* Y! {
3 `1 v! q' I6 ?" Q1 y. _7 ?5 n17~23行:循环修改GPIOA_ODR实现LED灯交替闪烁;这里不再涉及寄存器操作,只需要调用标准库提供的“GPIO_WriteBit()”函数即可,该函数需要三个参数,分别是:GPIO端口、该端口引脚号、电平高低。这里操作GPIOA 1引脚,因此传入“GPIOA”、“GPIO_Pin_1”、“Bit_SET”或“Bit_RESET”,使用这些宏定义,用户基本就无须查阅《参考手册》,上手相对容易很多;
. i$ A4 @* D' J) _+ V# _1 H2 X. g) w/ v' ]. o
代码段 6.4.2 GPIO初始化(driver_led.c)
2 w+ o' a9 `% q. U- ]. i/ b, G# |
; g* `4 }0 `3 ?8 `# q2 m, I
  1. #include "driver_led.h", @2 \2 K: G: H- \; y  I* i

  2. % K5 P3 l& A! T3 v3 o6 L6 T

  3. 4 B  m/ j; H8 X) R. c9 i( L

  4.   ^5 }+ O) i9 ]) r) X
  5. /*% C% i0 f' r4 Z! [1 _+ M

  6. / Q3 [6 D, k5 w) c
  7. *  函数名:void LedGpioInit(void)
    ! [: r7 x0 F/ F9 W
  8. 9 c( H) |9 F2 x# R
  9. *  输入参数:无
    . F: K5 A( [1 a9 f6 I9 O& c* J! z

  10. 0 E8 E" j' h. W5 O% s% M& T
  11. *  输出参数:无6 L' P, F: `1 v
  12. - T/ E+ a% r$ G9 \
  13. *  返回值:无
    3 ?' T$ T/ T( e9 G8 z" x

  14. & ]- \; M# }( f2 b+ G2 F5 x- a
  15. *  函数作用:初始化LED的引脚,配置为推挽输出+ ^8 G/ s. w( a
  16. ( W1 {! c3 |9 k( J
  17. */( s% l/ g  _: M+ h' ^( m2 ^  P

  18. 4 F, C5 k, g9 n" @* Z
  19. void LedGpioInit(void)  E( x0 F2 _3 O! l

  20. ! X+ [+ L$ ^+ W- {0 D5 U$ X' @
  21. {
    ( O& _4 Y0 i. d
  22. ! n) C% j& ~- @& o
  23.     // 定义GPIO的结构体变量9 h" U) d$ z# M, O0 E( p

  24. 6 O3 u" l- u4 W( C5 s6 s
  25.     GPIO_InitTypeDef GPIO_InitStructure;" e8 E. H! E( N: v! b. _& u. ^# g
  26. " ~, R% I$ L2 R& z6 W
  27.     // 使能LED的GPIO对应的时钟
    ( w1 c' z2 j( r8 ^" L% m
  28. ! ^- G. H2 N: K1 B9 V/ ~6 @9 Y/ P, G
  29.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);% c  A' H/ K4 u0 t) K

  30. . x& ^  g3 a# x9 ]; Y4 @* l

  31. . a2 O% n' V/ H2 o* _! e! L" b
  32. 0 R. |5 v. p8 d# z. @: G8 |
  33.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;         // 选择LED的引脚8 l6 g! g( k, S$ w9 G
  34. - D+ A+ {5 ~; U9 L$ D. O2 b
  35.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 设置为推挽输出模式. _% l+ ~3 [. H
  36. 9 s: z8 F& r0 U' e- y
  37.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 引脚反转速度设置为快8 ?- P, u, m$ k7 I6 J& I( U5 t4 Q
  38. ; Q9 ?9 ]$ f* \1 G

  39. 6 M, e3 b$ D4 Y0 D

  40. . r8 F( ]/ Q: X0 D. m! F# Y! F! C8 x/ X
  41.     // 初始化引脚配置% f/ b& s% q" Z% z
  42. + z" \2 N) u: [! E" x6 F
  43.     GPIO_Init(GPIOA, &GPIO_InitStructure);
    / Y. I2 ?# h( h! Z0 x5 b! r

  44. " R) e$ [* m$ `8 r7 K" e9 |
  45. }
复制代码

0 a8 y7 K; K7 ]15行:使用标准库提供的“RCC_APB2PeriphClockCmd()”,传入需要设置的外设名字、是否使能;6 L# f( G3 P: _5 F: I
, y: a9 U  I: N3 ?, B6 t
13~22行:将GPIO属性封装为结构体,通过设置结构体成员的属性,完成对GPIO的设置;
, b4 }, U4 V' Z: m
. s1 S) R+ Q7 o1 R" i: \0 ]$ r/ c
# F% v2 ]6 g1 J# d/ V" o. G0 k" l* |4 u2 b; @
以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 3_C语言使用标准库开发\ Project\ Led_Standard.uvprojx”工程,编译、下载,体验效果。" a  L$ X! X+ w" c

9 C! ~& p1 J2 \2 _使用标准库后,基本不涉及外设寄存器的地址,同时提供很多函数实现相关功能的操作,用户只要熟悉之后,很快就能举一反三,修改相关传入参数,实现自定义需求。
+ u8 L; u3 Z2 G+ w- p' @; Z  M' m- S1 m# L  ?

  r; L7 _9 f- C  ^/ @% M3 ?( T/ ~& y1 |7 M0 I) \% S9 ^1 V
6.5 C语言使用HAL库开发; }& I/ ?+ d2 v  M/ P* u
ST公司先后推出了两套主要库:标准外设库(Standard Peripherals Library)和HAL库(Hardware Abstraction Layer)。标准外设库是STM32最早发布的固件库,对STM32芯片进行了完整的封装,包含所有外设的性能特征,每个外设驱动都由一组函数组成,这组函数覆盖了该外设所有功能。因为发布比较早,教程比较多,目前仍有不少开发者使用。标准外设库是针对某一系列STM32芯片而设计的,没有可移植性,目前该库已将停止了更新、研发,最近几年发布的新STM32芯片已经不再支持。$ B0 u8 G, l3 X* f9 y

/ q' F* I6 E, g7 r& y; w9 g5 }HAL库是ST公司这几年极力推荐的官方库,目前支持STM32全系产品,可以说HAL库就是用来取代之前的标准外设库的。相比标准外设库,HAL库具有更高的抽象整合水平,API集中关注外设的公共函数功能,使得可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。此外还有LL库(Low Layer),相比其它固件库更接近硬件层,一般和HAL库结合使用,可以看作是HAL库的补充。+ p# c6 r" Y+ B) N
) X% a4 q; v# ~% Q! U2 b
本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言使用HAL库开发\”。
5 s5 E9 j& I& U: z3 [  X& E7 M; m& S" d
HAL库和标准库的使用区别不大,也无需知道外设寄存器地址,主函数如代码段 6.5.1 所示。4 @) B! @/ \3 w% |* t! ?
# C+ P$ o9 t' c  d0 M' @
代码段 6.5.1 C语言使用HAL库开发(main.c)
; J. @) |' I9 f% c) s% j. u8 [1 x. C# w2 j. j$ H
  1. #include "main.h"* H- B! j! h. Q8 K' U
  2. ; p( |' R! M$ ^! T0 T
  3. #include "driver_led.h"" O$ i7 O1 Z1 S/ n, W: J
  4. & Q& P% |3 I" o% C3 `. O
  5. 8 _1 n" \% r/ m, @5 u
  6. % i, c$ n+ w+ s* ]
  7. int main(void)
    ( ^& d) e$ q+ r) C" R1 S

  8. 2 g8 G. \7 |# K- Y
  9. {+ b) b! j9 O/ F* v( W
  10. ; C4 Q- \1 r: F# p8 {( W7 |3 ~1 Q7 F+ j
  11.     // 初始化HAL库函数必须要调用此函数: A1 x9 z5 m; f5 L, J* G, x$ C
  12. " m  A) m7 y! P: O, \8 z
  13.     HAL_Init();" G% Y6 n$ P8 x! ?* Y9 d

  14. / E/ z$ k: p$ p( c
  15.     // 系统时钟即AHB/APB时钟配置* E% u4 q+ q5 t+ B) F
  16. " t2 y- R, a  B4 s7 a: K
  17.     SystemClock_Config();2 B' X) D) c+ m5 W/ W) T

  18. * I/ w/ S5 u( D1 Q

  19. 1 R& ^; m0 V9 N

  20. % K4 ]  }' v+ {' A6 @& ?# V
  21.     // 初始化LED
    3 K- x/ }7 }# B' C% m& ]
  22. ! H2 c( k0 S2 p3 \4 i9 T0 }& n* l
  23.     LedGpioInit();
    0 p' O; [3 c4 B" d. i
  24. 6 a1 D. \' m; |; h. u
  25. 3 r9 }8 f9 H* X1 p0 h' @; n4 Q
  26. % V7 H6 i& G* U9 r; z& ?5 s
  27.     // 点亮LED灯
    0 U4 ?  F8 ]$ n; b% h' K
  28. ( @! i" q  ?( }8 C; P( D, @8 ]
  29.     while(1)
    4 ~- x- u3 O$ x0 Z$ e  `

  30. " C7 I8 O4 {& f$ m9 l% \; y! N2 y
  31.     {& r7 N% H. U8 J0 O3 J& ]2 `
  32. , ^; ~+ c7 p8 R# m6 k! }8 j9 P
  33.       HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
    * `3 n! t4 g8 A+ s

  34. 3 j/ l; ~9 A3 {# k. }4 `# i+ y
  35.       HAL_Delay(1000);
    + y" C+ ?' r+ q( \8 S8 J' p4 f

  36. ( [; w% P, \  F  B9 {/ I
  37.       HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
    ; K3 a5 N! [  `# Y0 k; C
  38. ' n; B- A7 W1 ]% \) d$ P  J
  39.       HAL_Delay(1000);
    + d3 b$ k+ |  s
  40.   {/ K1 e1 v2 ]* w! w5 M
  41.     }3 T8 e% I7 }# T1 B9 k# ]* m

  42. - ?! _% T9 w7 p" p; u' S& G
  43. }
复制代码

; _. J- b) U# t# Y/ L3 A2 k6~9行:使用HAL的一些初始化操作;
3 X0 N! Q/ h1 O0 U. U1 [
; z  L- X' ?( j' C+ V' v4 e7 y* i" C11~12行:调用编写的“LedGpioInit()”函数,初始化需要的GPIO,该函数定义在“driver_led.c”里,如代码段 6.5.2 所示;
$ Z# @1 y7 Z9 h9 I' O% B
. `/ p6 ^: ^2 O4 p/ [& J) t- P7 a, Y15~21行:通过调用HAL提供的“HAL_GPIO_WritePin”函数控制引脚输出的电平高低;HAL库还提供延时函数“HAL_Delay()”,用户不用再自己编写延时函数;
( K: U! M/ P: H
+ y6 ?# c) T, d) H6 f* {代码段 6.5.2 GPIO初始化(driver_led.c)2 ^2 K4 T0 k6 _9 D+ H; Z3 Z
% k$ g; v& i/ Q( g/ @0 m
  1. #include "driver_led.h"0 P% }3 y1 F2 |' s# q

  2. " ~/ y* s4 _# q+ A8 |0 x

  3. - r/ ?$ }/ e: {( i6 y7 }. N

  4. 5 M5 c9 V! M* U, W: p6 C
  5. /*
    8 a1 W% I1 H9 T( H0 _3 C
  6. # Z( Z( \2 h) T0 L/ G& _( c) T0 I
  7. *  函数名:void LedGpioInit(void)+ G, P, B: z7 e' _3 Q4 M' Q7 [5 ^# Z

  8. ' J5 j- ^4 J! L/ j- e. l6 f
  9. *  输入参数:无
    6 v! b; z7 J% b5 `' U" I

  10. ! E) {7 n5 E/ a0 e; Y
  11. *  输出参数:无
    ' K- g8 V! x- [; h8 R3 Y

  12. - {: I) |: o) E. C. u+ t
  13. *  返回值:无
      G' O- b* K3 x
  14.   z0 j3 C6 o6 c# k5 P
  15. *  函数作用:初始化LED的引脚,配置为上拉推挽输出& u5 o6 d6 Q$ M+ Y
  16. $ h/ U& {1 n: h7 e  D
  17. */( T8 h2 q1 K- Q

  18. / \" w4 {5 l# B; q$ d: j" a+ b8 T6 Z0 d
  19. void LedGpioInit(void)- S. Q* M; R# l+ P- t4 H9 h
  20. 0 x  i6 w4 w( ~: y
  21. {. K( x/ p* Q& _+ r; M# V

  22. ! B$ R. K( w0 ^( J
  23.     // 定义GPIO的结构体变量  f2 R2 U  B5 Y9 k- z9 y

  24. ' @  L. ?: X- Q) r4 A. I
  25.     GPIO_InitTypeDef GPIO_InitStruct = {0};: J4 R" g* O& d4 c# A

  26. ! q: Q6 ^% a; j) R; e- f# h! y8 [) G
  27.     // 使能LED的GPIO对应的时钟- R: x4 m. W4 ^8 |. G. ^2 d, e

  28. & k8 g, k( n: L2 l  x$ _
  29.     __HAL_RCC_GPIOA_CLK_ENABLE();2 `* c8 K$ J5 F0 _

  30. : Z. z( @4 J9 y7 U/ X4 M' _

  31. ( V& @6 N% X+ K/ G8 f
  32. 6 z+ k3 t# F" A/ E& G  Q
  33.     GPIO_InitStruct.Pin = GPIO_PIN_1;         // 选择LED的引脚
    0 [' q3 {0 y+ ]* z, ^

  34. 9 Y* B; N* e1 O7 p1 r( j
  35.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式
    1 V- }8 F6 h3 e0 \% e
  36. , S" I8 U# X2 ?* I" b& P; W
  37.     GPIO_InitStruct.Pull = GPIO_PULLUP;         // 默认上拉5 y9 U0 z9 ?: Q3 V# b  d4 S
  38. 0 E; B7 l; ?  ~
  39.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 引脚反转速度设置为快
    , l$ ?% |. q* k5 ~9 K2 l) O

  40. ( K. Z6 f( J3 M4 ?

  41. . A$ D& Z: V1 h" _

  42. 8 }8 ?" m7 Q# Z
  43.     // 初始化引脚配置
    % b( ?! E5 B" j1 T, ^2 X( N
  44. ! k; L8 x9 v- }7 G
  45.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    - |9 B) p3 u: U' {
  46. : i4 x' u, S  @; f9 U4 G) B

  47. % {  i3 d  b. q* ~

  48. 2 b) ]* D2 {9 [2 k. {* P% g
  49. }' Z1 X& e- X( O# C7 N: }8 u" _* \
复制代码

' c; ]% |' g4 X, o. |& H! E9 F9 Q3 d* y
15行:使用HAL库提供的“__HAL_RCC_GPIOB_CLK_ENABLE()”使能GPIO A端口时钟;
9 }  A5 u( f, ?1 a+ I
/ ?2 Y. O' `% z13~23行:将GPIO属性封装为结构体,通过设置结构体成员的属性,完成对GPIO的设置;" d' A$ c: z: O! w$ {/ s( u" e

; h( Y- x5 k0 C0 o$ e3 M
2 _5 `/ U; t2 D! Z5 p1 v  I
) y2 h" ]) N: V以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 4_C语言使用HAL库开发\ Project\ Led_Hal.uvprojx”工程,编译、下载,体验效果。4 q! ^, v1 }3 \" L0 q( g
  |" m; Q9 X& B$ J
使用HAL库的体验和标准库差不多,但HAL兼顾了其它ST芯片,用户可以无缝过渡到ST其它芯片的使用。各开发模式之间的差异如表 6.5.1 所示。
5 J2 u; U. P8 g% Q; D! i+ o5 [3 u; ]% l
F8QUSP`{56@22SA6JY7HRK4.png % Q" C" {4 D; j) A; [% S( [+ U

1 {9 _+ c) p, k9 W( y# F! J表 6.5.1 各开发模式差异
. g+ n- O7 C7 [  e4 o5 h8 e& s+ ^: i* P7 y" |, ?6 J9 w: Y
4 {% I1 k8 l3 a+ R$ m
【总结】0 k1 ?, Z' ?- O; S

# B- _7 \% l4 h本章开始讲解了一些STM32结构的基础知识,这些知识对STM32的整体理解会有很大帮助,读者应多理解。# p- J/ q0 k7 E
1 t: P) ]/ ?7 H* }
随后介绍的四种开发模式,读者重点理解C语言操作寄存器开发和使用HAL库开发。后续的实验会以HAL库为主,但读者也应理解HAL库的本质也是操作寄存器,因此理解如何操作寄存器,对以后学习、调试,都会有帮助。     
1 S3 N6 E4 U; k( x
8 w* U! g. r& a) O' U, w! b+ t% Y8 m2 }
赞 收藏 评论0 发布时间:2022-4-27 21:04

举报

0个回答

所属标签

相似技术帖

官网相关资源

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