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

STM32经验分享 第6章 单片机开发模式的发展

[复制链接]
STMCU小助手 发布时间:2022-8-30 13:39
6.1 STM32结构3 n* l! t% @  L+ ?0 y( h* n
从“2.2 ARM与STM32的关系”可知,ARM公司负责设计内核,半导体芯片厂商拿到内核授权后,根据产品需求,添加各类组件,生产芯片售卖。如图 6.1.1 所示,为STM32的组成示意图,其中Cortex-M3内核、调试系统都是ARM公司设计,内部总线、外设、存储、时钟复位等都由ST公司开发。
2 n6 s4 y3 C/ A
3 H3 d- H4 Q, O7 ~! w7 g, n在编程之前,对STM32的总线结构、存储结构、外设寄存器等有个大致了解,有助于理解编程中的一些操作。此外,中断与异常(Nested Vectored Interrupt Controller,NVIC)、时钟复位(Reset and Clock,RCC)也很重要,且与编程紧密相关,在后面相关实验章节里再专门讲解。
# b9 \" g7 W( ~( d6 [5 K% Z, T0 c3 G% o. j' e0 h& B. j  X
对于开发者,掌握一款MCU的开发需要重点关注四大模块:时钟复位、中断异常、存储映射和外设寄存器组。
' @$ u# v+ j3 K! c. w4 e$ v
7(5W4V33QN%_HDN(3KY@%Y0.png

/ ~: `$ z8 h, P. s
) @+ G9 A6 K: R' z, [8 Y7 x3 b
图 6.1.1 STM32组成示意图

6 H( l+ Q% \  c& s: }9 K' w+ i! V: |$ M$ y1 O9 \. b

, D9 C$ [5 i& N: A4 F6.1.1 STM32总线结构: S& |9 b; m! W8 K8 L
总线(Bus)是各种信号线的集合,是嵌入式系统中各部件之间传输数据信息、地址信息和控制信息的公共通道。7 ^) T' H$ L2 U8 X  X
( Z5 t/ g# I; g, [/ i* Z
与总线相关的主要参数有总线宽度、总线频率和总线带宽。总线宽度是指总线能同时传输的数据位数,如8位、32位、64位;总线频率是指总线的工作速度,频率越高,速度越快;总线带宽用来描述总线传输数据的快慢,总线带宽=总线宽度x总线频率/8,单位为MB/s。# N: F0 X; P/ r0 B/ k( q8 j, H' |

) [% c* _9 e6 G* USTM32的总线结构如图 6.1.2 所示,可以分为6部分。
9 K4 V5 ]% U' a/ {9 y) R- D, e+ \$ @5 _9 u, f7 A. [1 N) f6 T
POOG$LJZ}GGY@D21_@24LHP.png
( w7 g# C: f' Z7 [3 d/ J3 G- ^/ B8 L  f
3 P3 A  t6 v5 n6 y" s& L5 G
图 6.1.2 STM32总线结构框图
# Z3 ^. M* S5 q; k) b0 ?* h
+ k) c8 G; H1 F9 d
2 }- {7 n1 [# D; h+ ?+ V
①ICode总线(Instruction bus):用于访问存储空间里指令的总线;; ^1 r9 |2 Y6 e8 X- Y/ K

4 @& U/ ~( n7 ~! f: B) |3 |0 Y②DCode总线(Data bus):用于访问存储空间里数据的总线;
/ A" {' S6 |8 |+ e" i$ e& J; d, m* R; ~& D8 V( g3 G
③System总线:用于访问指令、数据以及调试模块接口;- K6 B) X% h# Q
; d) q3 \7 \' w2 V- x- @
④DMA总线:用于内存与外设之间的数据传输;! a# |! I  W. E

' ]4 g! V2 e8 C6 V⑤Bus matrix(总线矩阵):用于总线之间的访问优先级管理控制;
+ f0 q8 u4 |' b: B( y2 U; |1 Z2 }) f! W* B) |$ d
⑥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。$ {7 v9 ?: e# K/ q  X4 X4 e& D9 u

9 {2 N( }% z/ a/ `4 v" R在MCU每次复位后,所有的外设时钟都会默认处于关闭状态。因此,在使用外设前需要操作复位和时钟寄存器(Reset and Clock Control,RCC)开启所需外设的时钟。+ [! }4 r+ j4 w3 W6 `
/ A' `2 O/ r3 b, `/ I$ k5 ~1 D

1 x  h( l6 l1 t' U% B6.1.2 STM32存储结构5 G# I/ c# ?* b6 L$ i/ e6 E( b* O4 Y
CPU通过总线访问各个外设,现在通往外设的“路”已经铺好,还需要规定各个外设的“门牌号”,以便精准控制每个外设。ARM Cortex-M3系列的处理器,采用存储器与I/O设备(外设)统一编址的方式,将部分存储器地址范围用于外设,这种通过存储器地址访问外设的方式,称为存储器地址映射。, `9 r+ o: `% H! R) b( s

+ @) D; J$ f9 V, }+ e5 V; h对于32位的处理器,可寻址的范围为232字节,即,也就是0x00000000至0xFFFFFFFF。ARM将这4G空间从低地址到高地址依次划分为代码区(Code)、片上SRAM区(SRAM)、片上外设(Peripheral)、片外RAM(External RAM)、片外外设(External Device)和系统级(System level),如图 6.1.3 所示。1 M, R9 m- J0 ^2 a0 a4 e

: w( s' x3 n+ d1 H9 o; o
`~(Z_AEJE)U)H`}RX92D{9G.png
( p' B/ x" Q7 [8 J0 y

8 \0 F* E; X: ]
图 6.1.3 ARM Cortex-M3存储器映射结构图

! h# w5 Z; r% r) U ARM公司只是大概的规定了存储器空间的映射,允许各芯片厂商在指定范围内自行定义和使用这些存储空间,未分配的空间为保留的地址空间。3 h. g; ?9 R# m, ^! \
) o% _# G9 T: f# X1 f% v) X$ Z. H
STM32在ARM规定的基础上,将4G空间分为了Block0、Block1、Block2、……、Block7,共8块,每块大小为512MB,如下表 6.1.1 所示,详细结构如图 6.1.4 所示。- y4 @. e, p- I4 I1 G3 s

* P4 C; ?$ D# M9 H( {
K[A[LRLZ`${)YZBM)K6V03H.png
0 z' D" V/ d! }3 e" i# \6 K; P
7 M2 v' u. W/ t3 ~8 i
表 6.1.1 STM32存储器映射结构
5 l0 C" y- W; ?9 {7 b1 X+ d2 s

) C% f* D* p$ m% `
2 y1 f) A0 A3 l1 z7 K; G
JOI(~`EIH8F1V)_B]{LIL1K.png
; T4 \( g4 B  j

7 h1 J, }( a- L* ]0 I4 [1 c6 i
图 6.1.4 STM32存储器映射结构
0 \( [2 T4 U$ ]3 I: i# v( Z
①0x0000 0000 ~ 0x1FFF FFFF(512MB):作为代码区,用于存放下载的代码。系统上电后,将从该部分读取代码;8 Q, e; z, Z( E( c& `! s& D
. {5 D$ E; m) C, ^7 U) i
②0x2000 0000 ~ 0x3FFF FFFF(512MB):作为SRAM区,用于存放运行代码。系统上电后,将从Flash读取代码,放到SRAM里,CPU再从SRAM读取代码运行;  y; d! o- t4 L6 q8 u5 y+ A

5 i8 J7 D% W  F7 |& ^4 @3 ]) H③0x4000 0000 ~ 0x5FFF FFFF(512MB):作为片上外设区,用于存放厂商外设寄存器。要操作外设,即修改这里对应的外设寄存器;注意这里的RCC和Port B外设的地址范围,后面很快就会用到;# a8 c# q! U; I. r

' [) b. h; t; `. t% }& T④0x6000 0000 ~ 0x9FFF FFFF(1GB):作为片外RAM,用于扩展RAM。当SRAM或者Flash不够用时,MCU通过FSMC外接其它IC芯片,则在这个地址范围读写IC芯片数据;# \) m; y. k, p( X1 E
0 C7 U% Z+ \6 g; v1 X
⑤0xA000 0000 ~ 0xDFFF FFFF(1GB):作为片外外设区,用于读写扩展IC芯片的寄存器。ST只用了这里的一半空间,另外一空间未使用;2 t7 U" C! p; S+ ?& G0 {8 \* x) [* z
. Y4 \# ?6 q+ M0 v/ j0 a3 b
⑥0xE000 0000 ~ 0xFFFF FFFF(512MB):作为内核外设区,用于存放Cortex-M3内核的内部外设。Cortex-M3内核的内部外设有NVIC、Systick等;
2 P8 I% y4 T6 }! a  V" d$ }$ F1 R
/ N$ v6 r" d" X) H3 g: \, f在Block0、Block1圈出的三个范围,分别对应前面介绍的三个启动模式。当从主存储器启动,则是从Flash启动;当从系统存储器启动,则是从System memory启动;当从SARM启动,则是从SRAM启动。
! ^- A* l, ~. N- d
! @1 P, Q$ a% ]2 H$ W! D对于开发人员,通常就是对Block2的片上外设寄存器进行读写操作,以控制外设资源,实现所需效果。5 n/ `( |* J3 C" N9 |3 ]

  u+ i6 N! S3 p% `/ a7 \, W$ q: y7 T' d" Q) V
6.1.3 STM32寄存器

0 m$ o6 k& Q) c/ ]0 b+ D/ B- R寄存器是用来存储二进制数据的时序逻辑电路,由众多晶体管组成。, E6 c, x7 j" {
0 R% H& C# f/ `$ r! X. J
前面提到的寄存器,都是外设寄存器。这些外设寄存器由芯片厂商设计,与存储器统一编址,常用C语言的指针来表示外设寄存器地址,实现对外设寄存器的访问和操作。( J" m( J; V6 f) [& ^) J: h
% _% k* X$ P# u& d. \
在嵌入式系统中,除了外设寄存器,还有一类叫CPU内部寄存器。这些内部寄存器由ARM设计,在CPU内部,常用汇编语言直接操作,用于暂存参与运算的数据和内核的一些控制。- `, K; b1 l0 i- B% G4 y
7 W; k, q: F! q9 J
开发人员,通常只操作外设寄存器实现需求功能,后面实验会详细讲解外设寄存器。而内部寄存器在实际开发中接触会比较少,后面汇编点灯实验会涉及部分相关知识,本小结简单介绍下内部寄存器。  K4 a  B" i  q3 R2 n( Y, ~; z
/ j8 R, k4 R! d$ k
ARM Cortex-M3微处理器的内部寄存器,又分为普通寄存器和特殊功能寄存器。普通寄存器如图 6.1.5 所示。
$ R; K0 N$ w1 e7 Q  I+ U. a
* p1 b+ ^& d4 T$ F! p; h* B9 L
@{[MGP)8435JFZY(NLTJ@(7.png
, l# @5 j' c# q# f, `1 @

" ^/ l" z3 Q1 x# P" v5 L4 }. ^" s
图 6.1.5 Cortex-M3通用寄存器

' z4 {" A/ u& O( m% w
9 s% ?7 S3 v% J; X+ e2 k5 z
- s8 w, ~# r/ ?, g3 z  E. T% XR0-R12(General-Purpose Registers):用于数据操作的32位通用寄存器;一些16位的Thumb指令,只能访问低寄存器(R0~R7);; [2 L7 k1 P( H6 b5 |1 }

- P% E: p  F& MR13(Stack Pointers,SP)Cortex-M3包含两个堆栈指针寄存器;同一时刻只能看到其中一个;2 ]; y. j) E8 ]; b, ^0 g4 @

4 ]) Q; N( z( l4 b& Y主堆栈指针寄存器(Main Stack Pointer,MSP):操作系统(OS)内核和异常处理程序使用的默认堆栈指针;: V9 f! y* r5 K, q# Y

* Y$ O# W' K! o6 b进程堆栈指针寄存器(Process Stack Pointer,PSP):用于用户代码;# b; W0 e) A: U3 E) S

+ D! q% M5 y; @% I! E8 P% tR14(Link Register,LR):链接寄存器;调用子例程时,返回地址将存储在链接寄存器中;
) Z2 q% Q2 t0 ?+ i
) q$ n! [+ B! P. d3 r/ |( y+ MR15(Program Counter,PC):程序计数器;总是指向下一条指令所在单元的地址,可以写入该寄存器以控制程序流;
6 L3 a7 U) U8 c4 I1 v' ^* m1 n2 ?( u* K& J* E( k

. s; z' D2 b7 q4 zCortex-M3处理器还具有许多特殊寄存器,如图 6.1.6 所示。
6 C5 H; T: G6 [9 B) r% f  D
" a1 n+ y4 K9 |7 ^* G+ _$ M
P19NVD8WP`A5VJF61{RYIGA.png
1 c  ?* i3 n& P# g2 y/ g
6 }" V6 b7 U4 i/ T+ n5 Z  N
图 6.1.6 Cortex-M3特殊寄存器
2 ]8 H. a/ H8 W% P3 w
6 ^4 d' _% O7 ^: v% t9 M
5 v1 i8 g% A: f( g  S
xPSR(Program Status registers):程序状态寄存器;用于存放程序运作中的各种状态信息以及中断等状态;由应用状态寄存器(APSR)、中断状态寄存器(IPSR)和执行状态寄存器(EPSR)组成;
3 L" H/ l: g5 Z: M. V- b# F# q4 S! t; N( P
PRIMASK、FAULTMASK和BASEPRI:中断屏蔽寄存器;用于控制异常和中断的屏蔽;0 c" G* y! L- h5 e: {2 V
  [5 _7 u8 t$ J1 L/ m+ L2 Z
CONTROL:控制寄存器;用于定义特权状态和当前使用哪一个堆栈指针;, F- H8 P* ]1 E7 f
* c! H. L2 F% {; k" G

$ C$ @. A0 A% p& f+ h) |【总结】
2 d+ _: I; ]& ]. t; s$ T
, p+ |& D+ `% `6 I: Q3 rSTM32由ARM公司设计的Cortex-M3内核与ST公司开发的外设资源组成。
; _' s* u" i2 u5 w4 C5 L( R3 A9 v1 o( {5 a! F8 N* W7 b
Cortex-M3内核有内部寄存器,主要用于运算和内核的控制,这块对于初学者较难,暂时了解即可。, U( @9 t* D! h- I. F
6 {' z' o( d: D: I3 _, y
Cortex-M3内核通过总线和外设连接,重点了解大部分外设都挂载APB即可。1 ^' @$ Z8 ^4 C0 }8 K  C- C

3 [3 j) x1 X" B9 K9 OSTM32采用存储器与外设统一编址的方式,控制外设,则对应操作指定地址的外设寄存器即可,这是后续实验的重点。8 ]5 E* F7 v4 M! \; T  J& A% [) `
' g7 ^( f. f7 s5 Q; g
5 P+ e! b. A& O; f! R4 p4 O8 P3 X
$ z8 J8 p0 M" q3 |7 Z, J+ E
6.2 使用汇编开发% f& _& x3 a2 g, w# Q, T4 d
汇编语言(Assembly Language)是一种用于电子计算机、微处理器、微控制器或其它可编程器件的低级语言。4 {9 s2 L  s0 {! \. n: c1 ]
6 g7 H/ a! p9 u/ U6 l& U, q
在单片机出现之初,由于性能限制,都是使用汇编进行开发。随着技术的发展,制程工艺的提升,单片机的处理速度越来越快,越来越多的单片机使用C语言开发。如今,一些低端MCU还在使用汇编开发;一些高要求程序优化的场合也会使用汇编;MCU/MPU的启动初始化部分也是汇编。本小结就带领读者感受下如何使用汇编操作开发板LED灯。
/ U# V4 |) f' Z/ l% X5 k$ b2 x6 G4 j# X5 R( Q
在编写代码前,本应该分析《开发板原理图》和《参考手册》,从而得知需要操作哪些外设寄存器,但这不是本小结主要内容,具体的分析方法放在后面对应的实验章节,读者暂时无需深入理解以下内容的由来:
. k* N/ K: k  u5 y, |8 q
( R. K0 y& K* \# x) ]1. 从《开发板原理图》可知,控制GPIOB 0引脚,即可控制开发板三色灯的红色灯;
7 j$ H! e" X9 a' a1 [5 D7 e9 A- e1 Z2 z' H1 z0 f
2. 从前面STM32总线结构分析可知,GPIOA挂载APB2上,由RCC控制;
2 X  U3 o5 D3 E, u+ c2 {7 t2 Y/ s4 I5 k1 s8 B2 w+ m$ Y, M
3. 从前面STM32存储结构分析可知,RCC起始地址为0x4002 1000,再仔细查看《参考手册》RCC寄存器部分,可知寄存器RCC_APB2ENR(偏移地址:0x18)的第2位(IOPAEN),设置为1则GPIO A组的使能;# R2 I3 w5 w- X6 L) S' f

6 n! e# N8 z* B- ?/ R3 J0 a  m4. 从前面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为输出低;
: W7 {+ I. W$ N" \1 U: [* {/ i
4 ^" V. w; A+ V9 H; N/ H7 e0 ^# L4 `6 P1 C; A9 f- G

5 W9 _) Q4 {& n/ r1 f/ d7 n8 y有了以上基础,就可以编程控制LED灯了,本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\1_使用汇编开发\”。6 D  w- g" }: |

+ N/ J1 g8 m& l1 k3 K如代码段 6.2.1 所示。汇编代码对初学者不友好,读者对代码内容不理解也正常,也无需深入理解汇编指令,这里主要目的是展示如何用汇编操作寄存器。2 D: M$ H" u$ k* Q7 `9 N( |

5 [, m2 s9 T+ J/ U& u# y4 T2 M代码段 6.2.1 汇编操作寄存器(startup_stm32f10x_md.s)% ?' K5 K- E' o2 Y

% y5 i3 r! H, ]& T2 ]
  1. ; Reset handler
    % |( Z4 e* o+ O# `" m

  2. 1 u" k+ t% ^$ F# s' x, |' [
  3. Reset_Handler   PROC
    8 A& l& g! B+ l0 l1 f

  4. ' A3 g  v! E, {

  5. . v. k0 W0 G  c( f" U, g. F& \8 \
  6. 3 M  P% T1 p) N) N4 Q% k
  7.     ; 使能GPIOA2 U- X. E% |4 T* \4 x

  8.   f7 e/ p* M5 x3 T! [& m: _0 ~
  9.     LDR R0, =(0x40021000 + 0x18)  ; RCC_APB2ENR: S1 \; C+ ~, P  g- R
  10. 8 K: w* ]' |7 g5 w5 Y) {" r
  11.     LDR R1, [R0]
    & r) K- [. c( ?# |* j3 }

  12. " w/ H  G9 P7 Z+ p
  13.     ORR R1, R1, #(1<<2)% }8 H+ L* S' i
  14. " I0 q6 {. v' Z$ X4 \4 J# J
  15.     STR R1, [R0]# g  d4 d: _" r. p: y! j
  16. ( Y3 N/ w2 n1 T( S+ W8 r
  17. " ?4 k1 i$ T5 M  A/ ]. k4 _

  18.   j0 q: G& l+ H1 a
  19.     ; 设置GPIOA1为输出引脚+ I) c5 M+ S4 h$ K: o( T/ i
  20. 3 v; G0 k9 B% Y3 |3 ^* u+ A
  21.     LDR R0, =(0x40010C00 + 0)  ; GPIOA_ CRL1 e" E- W. t, ?3 V( B) V# S

  22. * b+ H6 T5 M  R0 u+ l- M9 P0 G
  23.     LDR R1, [R0]
    * @. l! Z# g2 A$ R! V0 k. f. o

  24. 5 r. r5 N/ s9 C) S
  25.     ORR R1, R1, #(1<<4)( g! M3 o0 @  k1 V- O/ B

  26. . L- ]- t1 |4 q* J8 {) g
  27.     STR R1, [R0]
    " T" n* q" N; q% I
  28. 7 C. K6 W  `# e  F% y; v8 G% |

  29.   p) n! R9 P+ g$ U
  30. , z2 v" R9 h9 `
  31. MainLoop/ i3 ]. g& x4 R/ t

  32. ' X0 _. ?0 t+ U. \/ x" [: z+ @
  33.     ; 设置GPIOA1输出高电平
    ; `; R6 Q2 U6 ]6 q

  34. : m6 j: M8 P- @
  35.     LDR R0, =(0x40010800 + 0xC)  ; GPIOA_ODR
    ; z+ [0 p5 H, @/ K, T/ I* Z

  36. ' P  }  n/ X2 x  I; r( y! {* ^
  37. 2 D4 ]6 c5 a; T4 T

  38. - n6 m) n- [/ k- U  R  e
  39.     LDR R1, [R0]
    - m* X5 M; k& m& p* L- N
  40. $ v+ W. D( e! W# c( C  `  s" s
  41.     ORR R1, R1, #(1<<1)0 J3 N  u9 C2 O- U- S+ s, U) Q

  42. # M5 e1 r& z/ I. P' W3 v  w
  43.     STR R1, [R0]- r* N3 Q  V6 k$ i% e
  44.   F2 P) @3 Y/ w5 Z$ [: Q' s4 t
  45.     BL Delay
    / N9 C* Y8 A- X  X7 ~+ _* `

  46. ! L1 B6 E3 z$ j$ p5 t: {

  47. : P2 y( A4 L& V
  48. 3 q2 I: u0 T# t/ v2 _
  49.     ; 设置GPIIOA1输出低电平
    & y$ L- V  \$ z' E# A

  50. 1 n0 s; t' z0 k7 N
  51.     LDR R0, =(0x40010800 + 0xC)  ; GPIOA_ODR
    5 c: f8 R( d* @0 t- h
  52. + ^# m7 f& e0 y2 Z+ {, s' {
  53.     LDR R1, [R0]
    + }' q, X) R7 g2 x

  54. + r2 a. r! q: w
  55.     BIC R1, R1, #(1<<1)- q( o1 R% ]0 X7 m  B* e) D8 ^
  56. 0 ]9 w; D: D+ h  a7 c/ w
  57.     STR R1, [R0]
    8 s' y2 r5 m6 p0 s

  58. * H: |( u$ b4 W3 Y1 f  a
  59.     BL Delay  y/ L2 g* E. @, h' E& U) \3 O

  60. " m& g) ?5 R7 c% e: B  B
  61.     B MainLoop/ C. K0 P9 N# c5 P: W$ c5 w
  62. 2 b0 H8 l# a$ \; [+ z$ K
  63. ! e/ G! z$ `- T7 k+ U0 j
  64. * U/ _0 Q) X  y+ `1 z
  65. Delay
    : Q2 T" y1 T. m
  66. # g) ^- V$ r. D+ g2 C/ p" K
  67.     LDR R0, =300000# e( x+ Y5 Z; ~) f) @' L
  68. 6 w) H5 k, B2 K; G1 o3 I0 g
  69. DelayLoop, ^+ d' h' \) b) p3 s5 I3 r6 S
  70. + m" ^1 Z: m- v
  71.     SUBS R0, #1
    ; c+ X+ `3 y. f/ \. k) H) l
  72. - Y! G$ m2 T: W/ e
  73.     BNE DelayLoop+ e' b2 t- h* I4 y+ t( a- M& e

  74. : S% z: |5 w/ g; m1 {, q
  75.     BX LR+ I. F% z  G  N+ M$ k' X3 X

  76.   N0 _" o+ G5 z6 N/ N/ R2 F
  77.     NOP' [% y' _) }" i( ]: }* [

  78. ) k) W, t$ k% G" l+ u+ U
  79.     ENDP
    ' f; s# {1 s1 d7 S* h2 ]% Y8 N
  80. 5 r( ]1 o! c& [, c& [- Y' ~- _& F
  81.     END
复制代码
# H2 E9 h) w9 r1 M) Y3 b
4~8行:设置外部寄存器RCC_APB2ENR的第2位(IOPAEN)为1,使能GPIO A组的时钟;
- |5 p5 U# c; K. B% Z2 X' E) h& @' r
& @7 E! q! f6 s3 y5行:将0x40021018(RCC_APB2ENR的基地址+偏移地址)放入内部寄存器R0中;
6 D3 v2 A6 H3 o- F8 k3 G2 p2 H) Y: F1 Y6 A$ O! V: t
6行:将0x40021018地址的值(此时RCCC_APB2ENR寄存器的值),放入内部寄存器R1;, o3 {0 A- I7 _# b/ A" u8 a

$ ]3 W$ {; m0 ^4 O9 {! W7行:将R1的Bit2设置为1,并将设置后的结果放入R1;
8 m' t1 y; ?0 v5 p6 |0 B$ |$ v5 A. a7 I- P. i$ [0 @
8行:将R1的内容,放入R0所指向的地址,也就是将修改后的数据放入RCCC_APB2ENR寄存器;
! R( C; S! H* l& V* e
& Y4 U7 b/ @' J8 m; p. D10~14行:设置外部寄存器GPIOA_ CRL的第4:5位(MODE)为1,让GPIO A1为输出模式;
! y6 N5 |7 U. N6 K* [7 D9 A0 P! ~3 }8 K5 m2 T9 y$ \$ ^5 [+ z2 d
16~30行:设置外部寄存器GPIOA_ODR的第0位(ODR)先后为1和0,让GPIO A1为输出高、低电平;3 o' e0 q/ [; d% K3 z- ]5 e
8 O* b6 v2 r3 l9 H$ Y
32~40行:循环减R0实现延时效果;
5 j5 y# j# D9 k- J/ {6 {. ?) o7 {0 s3 @7 \; d- e: b# c4 A
以上汇编代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机开发模式的发展\1_使用汇编开发\ Project\Led_Reg.uvprojx”工程,编译、下载,体验效果。" y, x6 z6 e1 e! q6 j* J  i

2 ?$ a# j& F; Q, O9 y. E" r可以感受到,使用汇编编写程序,生涩难懂,因此越来越少的单片机使用汇编来编写代码,下面再来体验下C语言实现一样的效果。
5 I. W  n! ?  L3 u) C2 v; D/ C3 ~: b4 ]

$ M6 O3 S9 G1 V: c! ^+ \" n6.3 C语言操作寄存器开发& X) A) t& q; x3 }( ^, u
在使用C语言操作寄存器前,仍需要先分析《开发板原理图》和《参考手册》,从而得知需要操作哪些外设寄存器,假设读者已经了解需要操作哪些外设寄存器。' w- [+ h" D  c' ^' S7 R

( E0 @. H( Y" f& u本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言操作寄存器开发\”。
4 v  g1 x7 e1 [4 E8 h- j# T- I. d0 K! a/ `# }4 B: P% Q6 N! H6 n
使用C语言之前,需要先设置栈,如代码段 6.3.1 所示,在上电复位后立即设置栈,随后跳入main函数执行。$ f8 p9 h1 a) O/ N" g
% [) b* H* G4 j
代码段 6.3.1 设置栈(startup_stm32f10x_md.s)4 Q4 l2 p% k; }* a1 c8 x

- K2 ^  e$ x5 q$ c
  1. ; Reset handler8 F: R; r3 d1 S

  2. 8 \" V: k6 s/ H3 D1 M, i. R2 O
  3. Reset_Handler   PROC
    4 n" ]4 S* {" x/ \: H

  4. . C7 e/ f3 H1 {. z+ Y* U% Z
  5.                 IMPORT  main) ^' l; i1 r, n- ?( w$ ?

  6. 7 T, N5 C8 l+ K7 }( T/ e: j
  7.                 LDR     SP, =0x20000000+0x1005 e& t) r6 w' v& f/ f5 t( E

  8.   i2 N* w. K# U& E+ C: V( Y+ b
  9.                 BL      main8 `/ ~, H( _+ o
  10. # `8 x0 ^1 c- E# z5 z
  11.                 ENDP& A9 e# E+ j1 I% {4 y( S2 l. V
  12. ! Y9 o" i2 |) H$ m; b
  13.                 END
    / I, e( l6 O" q! U* j$ L" E
复制代码
! b) n' A' K( J0 }( \
代码段 6.3.2 C语言操作寄存器(main.c); ^/ V, |1 E* p8 T( H+ B

4 n$ P" `$ A* l$ z5 `+ J
  1. #define RCC_APB2ENR  (0x40021000 + 0x18)
    $ H9 W1 k9 U+ X; t
  2. & q4 e7 c& t  H$ W
  3. #define GPIOA_BASE   (0x40010800)
    4 f; M% z4 y4 F

  4. ; g# X' [8 P& b! V( v# T
  5. #define GPIOA_CRL    (GPIOA_BASE + 0)3 R  F, J3 u5 s- s, c; C
  6. , \9 q# a8 n+ s, z3 w$ B  a
  7. #define GPIOA_ODR    (GPIOA_BASE + 0X0c): _' R3 j5 E4 \9 S6 p6 T) a

  8. ; o, X0 \, t! j: ?
  9. % H: u3 t+ G* p

  10. , O& m$ n1 }, W
  11. void delay(volatile long i)# _5 m, V6 K1 ?

  12. 6 h' ~8 F0 D  U2 W7 h- M4 L- ^
  13. {+ w7 n7 i5 W& j% j& s

  14. , z" M" a0 q# u4 n. O0 w+ f+ a
  15.     while (i--);
    0 o/ I8 N% |. x; j

  16. 7 \1 ^6 j# j% k. U$ {
  17. }. w1 l  m: C( Q+ p) ?' N6 N

  18. 4 F( p) J& x* a7 `
  19. ( q# M8 ]) i% J8 [

  20. . E$ K* u- {8 G/ I( m
  21. int main(void)" P$ x9 x, g7 `$ w1 J. {' x  O

  22. 4 n  I$ v1 m: u4 t0 Z
  23. {
    , F9 x7 i$ I) F+ c- i" o

  24. / e3 F) D# q4 k% \3 i' M6 o
  25.     volatile unsigned int *pRccApb2Enr;
    + k) N2 T. g  t1 _- g' x! P

  26. 0 P: h0 D/ B/ H6 A
  27.     volatile unsigned int *pGpioaCrl;2 _0 E* J5 F' \- S! @0 b

  28. $ z5 d! A, W' L8 e% k
  29.     volatile unsigned int *pGpioaOdr;
    , k+ s5 c. u4 E
  30. . I: G4 V( {3 \

  31. 6 l7 v3 t1 \0 K: }; r6 M+ S
  32. ) B9 k7 w- u7 u+ F
  33.     pRccApb2Enr = (volatile unsigned int *)RCC_APB2ENR;2 C) q/ u; U, b/ P( l8 A8 L* _7 [* U
  34. % I' O8 [: e+ n7 \
  35.     pGpioaCrl   = (volatile unsigned int *)GPIOA_CRL;  e- p3 s: b- f; U) m0 ?1 P3 ?5 D$ j" P6 R

  36. 5 |2 n. _' X  [* R2 k& \$ t. o( U
  37.     pGpioaOdr   = (volatile unsigned int *)GPIOA_ODR;
    8 z" M: Q9 G8 {4 j/ W4 ~2 M" i

  38. $ P" E: S* `- h

  39. 0 v* u0 J! Y+ X3 A$ K

  40. & b  A. g$ E! M4 I5 u  B
  41.     /* 1. 使能GPIOB的时钟
    ; V! |# G+ x# Q

  42. ' \( W; F- C" r' k% u; Q# D
  43.       * GPIO是挂载在APB2上的,所以我们要使能APB2的外设时钟使能位7 R: w9 V% N. E

  44. $ w/ J! |( K# U' ^( i% c8 I
  45.       * 从RCC_APB2ENR寄存器描述中我们可以看到APB2ENR[8:2]是GPIO[G:A]时钟的控制位,控制GPIO时钟是失能或者使能) c8 K/ ~$ c) _4 L& y

  46. / k& q4 }; T3 M* l) @
  47.       * 开发板上的LED的GPIO组是使用的GPIOA,即要使能APB2ENR[2]位,为其赋值“1”即可打开GPIOA的时钟 */
    3 W) \) Z+ ~7 J( S% y

  48. 7 b3 X$ g1 Y! E- B$ @) q+ z- S
  49.     *pRccApb2Enr |= (1<<2);
    + V, q1 T7 T$ q
  50. ( f4 p" w  @$ P, {- E

  51. : \+ I) o/ s- P/ e  W

  52. ) H5 R5 k* g/ Q
  53.     /* 2. 设置GPIO为输出功能:: U+ G; o& L- h/ o

  54. 7 p2 z$ s0 I% K8 I; k
  55.      * MODE_0=1,PB0输出模式,输出速率最大10MHz
      F+ E0 Z0 o# |/ F9 \

  56. 7 k# K6 a# M  Z0 `/ ^2 e7 U0 q' v/ a
  57.      */3 F! W3 l0 T& [0 ~* h
  58. ( U! h/ R0 |3 i) Z1 F* c
  59.   *pGpioaCrl |= (1<<4);& E/ V7 i% Z4 R
  60. " ]& V; T% n% h- C: q
  61. 1 [8 d; Q1 I, i  {% X

  62. " f# |- h& N3 `
  63.     while (1); t3 H& Y0 c8 m8 `7 A
  64. . J  n8 c# \3 S& X! W  H
  65.     {% w' O, \3 I% H$ f( U/ q
  66. ) F. ^( d  E: W; P& W2 ]
  67.     *pGpioaOdr |= (1<<0);
    ( @" y5 ?- d0 X3 u3 n: ?- ^1 P
  68. # k- u2 ]. V  H/ C2 C/ F
  69.         delay(1000000);2 \" @& F6 _& i9 _; S& P

  70. 5 u( s3 N7 t1 ]$ g; j( _
  71.     *pGpioaOdr &= ~(1<<0);* k/ A3 G+ _) ~3 {% j* {; q7 E
  72. 8 c8 p$ {% L9 |6 j" i( T
  73.         delay(1000000);, V& d) \9 B4 e  |- F* T
  74. " A5 X" B  t: ?+ V; b. ^
  75.     }
    / _; C3 A2 e/ a& I* p4 c3 n3 [

  76. 7 i! U5 r8 ]6 L# V8 A: a: ]( H/ @
  77. }
复制代码

: e, a  @+ S& {4 B5 I4 w( D+ |
  `( o9 a3 _1 s' W' X5 j主函数如代码段 6.3.2 所示。
: t$ h$ w2 o$ T0 k# X
+ [1 I4 _8 K; `6 @( M2 K2 J1~4行:用宏定义了需要用到的各外设寄存器地址:RCC_APB2ENR、GPIOA_ CRL、GPIOA_ODR;
: q+ h' u* I9 H/ v) Q: ?8 o$ B0 k
2 x: i/ v, j& y% |1 |( S: f6~9行:循环递减传入的变量i,实现延时效果;: L7 \8 `9 v  F* f

' J* S! w: ?$ K  E0 D/ _, c13~15行:定义指针变量;7 E; e5 k) d1 f8 Q6 L$ P# X* X

# `: Q& V/ [, p/ X4 V% n17~19行:设置指针指向对应寄存器;
6 ]7 k6 B( d! ~0 C8 i& `( A+ p; J/ z: g' e# A; u
25~30行:设置RCC_APB2ENR、GPIOA_ CRL对应位;1 }& m# m2 L% K- n3 Q/ Z

# p% ^% x! E: E* m. _32~38行:循环修改GPIOA_ODR实现LED灯交替闪烁;
  D, W' m6 Z+ C2 S0 _# t7 v
' V! G7 t$ F* M! P+ |以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 2_C语言操作寄存器开发\ Project\Led_Reg.uvprojx”工程,编译、下载,体验效果。
. z" U4 p' L. p$ f( I
; e' y4 X- \; }- u1 E( z4 Y; z使用C语言后,只需定义指针变量指向对应寄存器,修改该指针变量的值,即可修改对应寄存器,操作上方便了很多。( O9 c1 S/ g( }9 ~2 }5 C
/ u6 g' F$ y$ ]! a
+ W8 m( U9 G3 x+ N& S6 `; P' r
6.4 C语言使用标准库开发2 l- P; ~8 H3 E" v6 v! o% J' B$ Z
前面的两种开发方式,适用于任何一款芯片,但需要仔细阅读《参考手册》找到对应寄存器,然后用指针变量指向寄存器。2 G7 ~9 n6 \3 N

  r$ f2 Z- G7 N) C" @9 g( a4 XST公司为了让用户更快上手开发,产生用户粘性,将外设寄存器提前定义好,准备一些常用接口函数供用户使用,这就是标准库的雏形。
3 ^' q/ X1 c1 w( t7 s/ h1 n5 K: l4 @" N+ X0 M8 u2 v
下面来感受下使用标准库的简便性。使用标准库通常不需要知道外设寄存器地址,只需要分析《开发板原理图》得知会用到哪一个引脚。
9 u# X# j: Z& h3 o( u' b& T  c* ~
本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言使用标准库开发\”。" b. p8 z! G5 ?+ D& ]- O8 q2 n7 R

5 [, b; q  P8 l: N  R首先使用C语言需要设置栈,这些初始化操作标准库已经帮忙完成了,我们只需要编写主函数即可,如代码段 6.4.1 所示。+ n2 ?* |3 Y7 P- D* o# {; n. L, t

/ n! M) B& }# f代码段 6.4.1 C语言使用标准库开发(main.c)- e* J/ L/ W& e7 ]9 j  x
* Y& S) b4 a6 k  j+ p
  1. #include "main.h"
    9 v4 J% P0 F7 }2 X6 k3 F

  2. 0 q/ ?2 p$ k4 G  ^/ C) N5 v
  3. #include "stm32f10x.h"
    * o  j+ d) y  F
  4. 1 R2 J: S$ w& Y, e
  5. #include "driver_led.h"
      T: P* q% F5 }* t8 |
  6. / U) \1 T5 ?% x+ F/ n) p9 F

  7. . m6 j8 ]+ }: [0 K9 X$ t9 E
  8. 5 V; U+ @7 q3 K- k# i' s5 k
  9. void delay(volatile long i)
    0 P% z  w$ \0 l+ f6 I  K, P

  10. , P0 T& `) [. f: _9 X
  11. {
    & Y8 v- Z. y- [& L  D4 ]3 ?

  12. ' Y. d4 p8 R* ^- C; N0 J
  13.     while (i--);+ l  ~# G6 ], N! u$ w1 ~( l' T

  14. . k- L& u# M; R5 }5 G3 k0 L% U9 Y
  15. }
    0 ?/ p1 U& Y4 H* Y0 u3 _1 r$ o

  16. 2 {5 D2 O  {3 Z7 s& r7 `# t

  17. , L1 @# \& }- H5 p4 |5 q9 |
  18. , |9 s! g2 @. M. _. d8 I
  19. int main(void)
    & o7 s8 k: L* ?6 W
  20. ) W* E$ n( q2 ?& c, ?3 w
  21. {3 o% H9 I! Q7 d# A6 q  `' ]

  22. . v) |/ ]* \5 \' G/ e$ j2 e

  23. 3 y8 \9 W& C( G8 o! ]( ^5 C' o. L
  24. ! k! }) G) J3 B6 ?
  25.     // 初始化LED" U8 U( `+ t. ^$ B

  26. + F8 y, v! x5 _1 M5 P9 ^. S* t' q. ^
  27.     LedGpioInit();; H( R1 B/ r3 T5 I, s$ d

  28. . a' m% g0 {: `  R8 a

  29. 6 M8 [7 T' [0 J
  30. 5 v" y$ i  v) @- x# v0 R2 m
  31.     // 点亮LED灯, N* f, U) t9 m# u/ e5 n$ P
  32. 3 q# u/ @( n2 p, P9 f
  33.         while(1)
    * f! z) A! v& A' o% f
  34. # K2 r% y: j+ x5 C+ D7 L' z9 E
  35.         {
    6 v7 W; p; N* Z3 h# F

  36. . |. Y8 b6 [8 k8 _* A
  37.             GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
    4 V3 S8 D1 Y+ R" a
  38. + t- E2 V9 {7 N6 z6 l
  39.             delay(1000000);0 U+ S" ~2 {9 w$ n$ R* p5 X8 g
  40. # x2 y$ ~0 v' ^
  41.             GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);5 ]4 C6 H4 R" m  z

  42. . m+ z3 y, N5 {; p0 J, Q
  43.             delay(1000000);) {6 l3 u, P2 U

  44. ' h" s0 a1 S1 \
  45.         }
    % q7 S" F! q! P- C
  46. : n* I( Y* T" `/ S
  47. }
复制代码

, x8 \8 q4 V1 K5 ^- l6 k2 @7 {6 P. h3 T1 m- [
5~8行:循环递减传入的变量i,实现延时效果;
; R% G8 B1 ?+ a5 ?0 L$ W2 \5 l: h1 Y+ ?7 x5 \
14行:调用编写的“LedGpioInit()”函数,初始化需要的GPIO,该函数定义在“driver_led.c”里,如代码段 6.4.2 所示;
% e* p0 l8 ]; O3 H$ o6 F; V! j% `7 X9 E8 F5 V
17~23行:循环修改GPIOA_ODR实现LED灯交替闪烁;这里不再涉及寄存器操作,只需要调用标准库提供的“GPIO_WriteBit()”函数即可,该函数需要三个参数,分别是:GPIO端口、该端口引脚号、电平高低。这里操作GPIOA 1引脚,因此传入“GPIOA”、“GPIO_Pin_1”、“Bit_SET”或“Bit_RESET”,使用这些宏定义,用户基本就无须查阅《参考手册》,上手相对容易很多;* C: M  I: _2 V) l
- S+ u" @0 ]3 h
代码段 6.4.2 GPIO初始化(driver_led.c): a; ~  z4 x/ t  v* q  U3 v
8 G5 P  ^: r+ O3 t
  1. #include "driver_led.h"
    7 [2 O0 ^9 ^! D; k  d% b

  2. 0 J' S7 ?5 d' ?2 `

  3. 2 X- r* D: E* F

  4. % V% b% p0 n, r7 ]! ^  r
  5. /*
    ; ?3 ]; H5 \' a' F! z# H% Y

  6. , `" C4 v4 y% d5 j! c; B
  7. *  函数名:void LedGpioInit(void): `3 a1 f1 p; o1 _/ y/ c
  8. 5 n, I4 m3 m+ V
  9. *  输入参数:无
    - v, S! t8 Q: Y$ Y3 u2 `
  10. / Y5 }8 N% E& W
  11. *  输出参数:无, @% L  _) N. h/ n" q
  12. 7 O9 {7 g; C% @
  13. *  返回值:无' f) C; T3 i3 K1 \, r5 C! g
  14. ; g1 S6 F: p2 H9 b0 b* c
  15. *  函数作用:初始化LED的引脚,配置为推挽输出( T' M1 N# `* I* ?- Z( w9 v( {
  16. 9 n, y* V4 U# L1 o1 C  I
  17. */- c& a# E# F; n& k2 _9 A( f

  18. & w, x0 t  [4 h. f
  19. void LedGpioInit(void)
    . q3 c4 z7 P9 |& {( T3 X
  20. + s% X! s. K: ^% V/ ~) r
  21. {+ d. g5 E& v1 b4 _8 T
  22.   Q8 b9 c, e% s6 M: ?, N6 g0 w
  23.     // 定义GPIO的结构体变量3 b$ P9 ?' Z! ]: [' s% z1 W

  24. 2 ~5 k0 a: O% ^! f$ ^5 \$ B3 a
  25.     GPIO_InitTypeDef GPIO_InitStructure;
    . s) x* [* O% V% p( d
  26. * h8 H# m, ?: L8 h8 x% [: x# Y
  27.     // 使能LED的GPIO对应的时钟
    8 @9 u0 b: e% v2 n

  28. - n$ w/ x0 D2 k* t
  29.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);; C+ J) T0 b( Y# V3 ]
  30. 8 o$ k; ?6 r# i  @' C5 L$ Z; w: b

  31. , a  t( X, H7 J2 B0 o* f" C
  32. 1 d- S# P  |6 Q- t" L
  33.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;         // 选择LED的引脚6 g% R; |. }* P0 r# s
  34. - H5 c1 ~! h: Z, G% ^1 K+ P
  35.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 设置为推挽输出模式4 j9 k8 ~/ S2 N  S3 y

  36. & P) y) t" c  d& S4 ?
  37.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 引脚反转速度设置为快
    2 L  L% j; A( y7 r) g: Y! h
  38. / Z% M6 B6 o2 Z+ G9 T7 W  [

  39. 8 y) W; E6 t& x* T% o7 @+ H5 Y
  40. ) ~% ^$ A, v5 c8 R
  41.     // 初始化引脚配置) V- f7 u- I* w0 g7 |

  42. ( E# k" S# H9 N) H9 c2 K  g
  43.     GPIO_Init(GPIOA, &GPIO_InitStructure);
    3 C9 P9 _' I* w' m/ \

  44. ) W1 Q: b6 Q/ N, B% d# Q7 c# Z
  45. }
复制代码
* h% K: f/ Y5 V1 ~* |- w
15行:使用标准库提供的“RCC_APB2PeriphClockCmd()”,传入需要设置的外设名字、是否使能;) I# m( d$ r. k; S
8 t6 Y7 r2 L- B9 v" [, j
13~22行:将GPIO属性封装为结构体,通过设置结构体成员的属性,完成对GPIO的设置;9 D! D  s' `  @- b+ Z; i
" J! A% V( J2 ?% V  S

7 t0 B9 b/ O# z" K. G! I7 s) J) G* M% U# W6 S% h
以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 3_C语言使用标准库开发\ Project\ Led_Standard.uvprojx”工程,编译、下载,体验效果。
2 h* o# `8 A3 u/ ~
6 O' S# j: s8 \使用标准库后,基本不涉及外设寄存器的地址,同时提供很多函数实现相关功能的操作,用户只要熟悉之后,很快就能举一反三,修改相关传入参数,实现自定义需求。. z  t' g, x: N% e! s
% `; l4 `: m0 \! ~: w5 }' h
. o# L! L! z$ F  ~) `
6.5 C语言使用HAL库开发1 Y3 c" B% r- O/ |; k6 W
ST公司先后推出了两套主要库:标准外设库(Standard Peripherals Library)和HAL库(Hardware Abstraction Layer)。标准外设库是STM32最早发布的固件库,对STM32芯片进行了完整的封装,包含所有外设的性能特征,每个外设驱动都由一组函数组成,这组函数覆盖了该外设所有功能。因为发布比较早,教程比较多,目前仍有不少开发者使用。标准外设库是针对某一系列STM32芯片而设计的,没有可移植性,目前该库已将停止了更新、研发,最近几年发布的新STM32芯片已经不再支持。- C$ h  T& i: P. e4 i0 {5 T0 v

# F" i5 G5 l1 o& K1 ]- KHAL库是ST公司这几年极力推荐的官方库,目前支持STM32全系产品,可以说HAL库就是用来取代之前的标准外设库的。相比标准外设库,HAL库具有更高的抽象整合水平,API集中关注外设的公共函数功能,使得可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。此外还有LL库(Low Layer),相比其它固件库更接近硬件层,一般和HAL库结合使用,可以看作是HAL库的补充。
! I; |# i1 [) }  C5 a0 @. e8 E: s& R2 ^  D
本章涉及的代码位于100ASK_STM32F103_MINI开发板资料的“5_程序源码\0_单片机开发模式的发展\2_C语言使用HAL库开发\”。9 k2 i+ a. A, g4 Z2 A! Q
& [' G( N$ N( W) U9 d6 K, p
HAL库和标准库的使用区别不大,也无需知道外设寄存器地址,主函数如代码段 6.5.1 所示。
4 P8 F4 x. O& m8 @# ~* Q. z
+ m3 e" T& L, ]! {5 i代码段 6.5.1 C语言使用HAL库开发(main.c)
; c, g! h) T6 ]6 |5 m/ F" M8 W* s! ]" ?/ t5 {6 v2 ~
  1. #include "main.h"
    " E$ x) D" D) ~. V

  2. & y. Y) r/ L* a0 q7 g+ R
  3. #include "driver_led.h"1 U! Y/ X& d0 W

  4.   J) b2 _5 b; I( Y: \9 Z

  5. 3 E) u( f9 u8 z, |/ X' v

  6. ' R6 v8 Y/ ?/ m# e, D
  7. int main(void)8 r: ^) e" L. j& q5 k, g
  8. 8 S- c. w3 ^- G2 A2 [
  9. {
    4 [5 Z% K3 W) w* ]; }
  10. ! V. V7 r5 v# ]0 |
  11.     // 初始化HAL库函数必须要调用此函数2 n4 s& @+ I# i) `- [- i
  12. 8 H, |/ n- m( u$ m8 x: x
  13.     HAL_Init();- f% I  R4 ~3 A

  14. , @, Z- Q5 V. B8 p
  15.     // 系统时钟即AHB/APB时钟配置4 x  x# A" }! g& z
  16. 0 p6 b+ c1 I9 R# F8 g
  17.     SystemClock_Config();
    4 a. ?( U* x$ Q7 K- R
  18. 5 L8 Z' W* W# ]" ~3 G
  19. 9 G  }* n" _9 s8 a- Y1 H

  20. ' O# B. a( N- m% V) T! \
  21.     // 初始化LED
    / p: i, T4 H. v8 m
  22. ( s+ w' q- V7 o4 q; o6 v( {$ R2 _
  23.     LedGpioInit();$ x! m2 d$ \) I4 M

  24. 9 I+ r' a" `, Q2 B

  25. ; j' h4 ]: D6 A* s) r2 y% D$ B6 r" ]

  26. $ w" j  l6 z* R/ a7 ~7 n
  27.     // 点亮LED灯# |5 B7 \7 q$ Z) A+ o

  28. 8 F& {7 K7 q5 b7 U% [4 i' n
  29.     while(1)
    3 @" e  b% ]3 B* }2 g9 q8 t
  30.   i; t! u$ C: p
  31.     {! Q/ `- d5 J1 A( {* {
  32. 2 L6 |( P! x) s) s. }) c% L
  33.       HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);) K7 L5 L9 F& Q( V
  34. + ?8 e% ?& _+ c
  35.       HAL_Delay(1000);
    + c: M; Q* U* U1 n3 J- c
  36.   ]3 ?9 K, \% F# b# R& [) h5 W
  37.       HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);5 o$ P4 @' K+ e9 J
  38. + a- C( C2 g' ^3 v8 q# e; b
  39.       HAL_Delay(1000);
    ; X, J( e+ t0 G- X9 x: x7 A

  40. ) R! W. `0 r5 n4 j4 }
  41.     }- J7 o3 @; J+ @. L  m; p4 p
  42. 7 X% q9 g- b' O$ w6 H, M+ C# d
  43. }
复制代码
7 p0 y; ^2 h( e' N
6~9行:使用HAL的一些初始化操作;
7 b9 G0 O1 E6 k6 C* G
$ y  i8 h& W1 M11~12行:调用编写的“LedGpioInit()”函数,初始化需要的GPIO,该函数定义在“driver_led.c”里,如代码段 6.5.2 所示;5 m, q0 M- c3 J# R. f" w

# j/ n& w( e( K) r: z0 q& z! A15~21行:通过调用HAL提供的“HAL_GPIO_WritePin”函数控制引脚输出的电平高低;HAL库还提供延时函数“HAL_Delay()”,用户不用再自己编写延时函数;
. U. M* ~9 h# a. c* L9 X0 w0 r" [( J& l1 F- G& i
代码段 6.5.2 GPIO初始化(driver_led.c)
$ [9 j$ v9 ]: {- [
* c0 S! R. a( V# c6 {# _
  1. #include "driver_led.h"; K  {; ?9 i/ L6 B6 Y8 R9 c) G

  2. 2 Q0 X; V, [8 v0 {5 O2 H; J

  3. 5 ?% K( n( s" e: F3 Z* a. l
  4. 3 c* {/ _9 m; `3 C. e
  5. /*0 A3 C: \/ `! Y0 Y5 D

  6. 2 N* K8 s" a0 a
  7. *  函数名:void LedGpioInit(void)2 M5 G9 c) L. x' m# E6 }

  8. 9 ~" T" q  U( D4 w. @4 J; Z9 z$ U
  9. *  输入参数:无
    3 G/ D# W0 l. E- i2 j
  10. $ }' H2 W2 b/ U  V0 U& y0 H: F
  11. *  输出参数:无& Q4 q5 K. O  F2 k; n  o4 A% u
  12. , X1 |# g" y' q4 U3 }0 k
  13. *  返回值:无& {( W, n7 m# \- K1 t. Y* T: L  o
  14. - S- v( O6 b; g! f5 e0 g8 H
  15. *  函数作用:初始化LED的引脚,配置为上拉推挽输出
    4 O9 H# y7 M- g9 `
  16. - a, O, i3 O2 f$ }4 w8 B- h8 o
  17. */
    4 `" a! i# O4 ], Q  g2 o" m& L

  18. 4 B2 a& j5 ]4 p9 [  [
  19. void LedGpioInit(void)4 Q% @2 o9 P+ C; Q. h7 g/ ]" N& V4 E
  20. ( ~) f9 I2 \" V" v: q( J" e+ ], |
  21. {& b* R3 O( f' ~

  22. 2 H# l7 P4 I! l' b4 J+ ^/ g6 z+ t9 v
  23.     // 定义GPIO的结构体变量. W0 }; d+ N8 V/ P. b' k
  24. 6 C* \1 t# U4 m9 K, C( n3 \; s
  25.     GPIO_InitTypeDef GPIO_InitStruct = {0};- n0 Y  e5 w% y+ Y, X3 M

  26. 9 y8 R2 {4 y: X. Y# X- w$ x: x; w
  27.     // 使能LED的GPIO对应的时钟! h2 A; |" y, [' j% E* L( q+ h

  28. # L; I% Y9 `4 ?: Q
  29.     __HAL_RCC_GPIOA_CLK_ENABLE();
    ( Z7 N* ^! C7 G- W5 b# S7 `

  30. / X9 R- c) j# k; c" b$ ^
  31. 1 H+ H2 Z* n: O% E; h0 p$ E- t
  32. 3 ^$ _; F# z$ ~: s% l$ F, ^# `
  33.     GPIO_InitStruct.Pin = GPIO_PIN_1;         // 选择LED的引脚
    , M3 R& v- ], ^+ }6 n
  34. 8 [3 f) C. n8 {% k
  35.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式; n- x& c" F. I; m. f
  36. 9 o( B' z+ U) r/ e. R
  37.     GPIO_InitStruct.Pull = GPIO_PULLUP;         // 默认上拉4 E( U) N/ p% Y( |
  38. 2 ?) S3 ^! {( }3 R+ N; }5 g# d
  39.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 引脚反转速度设置为快
      a+ r# n* Y  u
  40.   G4 ]  U. R* g; B, ~5 l( _

  41. ) X" X. ]3 q. p, S

  42. 2 Y& w3 G# {0 |, r; x
  43.     // 初始化引脚配置0 D$ o. D% q% O' E4 [4 l! S2 p2 g

  44. " a6 ^+ n2 [( @2 y( H, F
  45.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);4 {% R& j, [+ s9 d: G% {! g
  46. - \- ~1 r6 ?/ ]6 z1 n
  47. 1 z( v" I% L9 }6 B1 E' G+ e

  48. ( @2 k) V. E! Z- }, @9 o' ?
  49. }
复制代码

* {3 X& {! c' e- V* ~  Z4 L15行:使用HAL库提供的“__HAL_RCC_GPIOB_CLK_ENABLE()”使能GPIO A端口时钟;
, g, P/ V8 _# A- W* o
, [* m# Y4 w$ P; c' m- V+ N+ F13~23行:将GPIO属性封装为结构体,通过设置结构体成员的属性,完成对GPIO的设置;8 ^0 P6 k! u/ ^) j! X: n

2 Q4 i# G" T7 f6 I& p5 w7 H3 z1 e7 `# E; U
以上代码就实现了开发板用户LED灯(蓝色)交替闪烁效果,读者可以打开配套资料的“5_程序源码\0_单片机编程模式的发展\ 4_C语言使用HAL库开发\ Project\ Led_Hal.uvprojx”工程,编译、下载,体验效果。
  }1 Q% `9 G' J' h6 m, @  k  q; H8 F$ m& o& V' a
使用HAL库的体验和标准库差不多,但HAL兼顾了其它ST芯片,用户可以无缝过渡到ST其它芯片的使用。各开发模式之间的差异如表 6.5.1 所示。
0 U- b$ w6 o) e; q
! Y* s- c7 e% V* {
2ICK}7KSL2]XHSVZ{G5YNYM.png
( w3 p% }  R6 [; U
. k% \6 i% q# ]6 p  X& s/ P# O
表 6.5.1 各开发模式差异

8 v1 x5 ?0 n& l2 f: h" L8 y. d
+ O9 L4 ?( E& ^, P1 M: Z; s) v2 J
5 H, `, S* K+ I" y3 ?【总结】7 _6 t( b: D# Y7 f1 R7 ~9 {

4 y4 V" f( \4 f  c本章开始讲解了一些STM32结构的基础知识,这些知识对STM32的整体理解会有很大帮助,读者应多理解。! v( @' q& W! r) S, R' O- `) j4 g
. x$ h& y, }  g" Z8 B
随后介绍的四种开发模式,读者重点理解C语言操作寄存器开发和使用HAL库开发。后续的实验会以HAL库为主,但读者也应理解HAL库的本质也是操作寄存器,因此理解如何操作寄存器,对以后学习、调试,都会有帮助。     . k& p, [3 p' y  V2 n! n; ]- x
9 }" O( |% _7 @% k
作者:攻城狮子黄 6 U& U7 P7 ]6 n7 w5 i' r
7 q" o& q9 r3 j) p3 z' V! S
4 v$ M- E, s  \4 y: `1 f  O
赞 收藏 评论0 发布时间:2022-8-30 13:39

举报

0个回答

所属标签

相似技术帖

官网相关资源

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