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
/ ~: `$ 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
( 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( 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( {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; 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, 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+ _$ M1 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 ]- ; Reset handler
% |( Z4 e* o+ O# `" m
1 u" k+ t% ^$ F# s' x, |' [- Reset_Handler PROC
8 A& l& g! B+ l0 l1 f
' A3 g v! E, {
. v. k0 W0 G c( f" U, g. F& \8 \- 3 M P% T1 p) N) N4 Q% k
- ; 使能GPIOA2 U- X. E% |4 T* \4 x
f7 e/ p* M5 x3 T! [& m: _0 ~- LDR R0, =(0x40021000 + 0x18) ; RCC_APB2ENR: S1 \; C+ ~, P g- R
- 8 K: w* ]' |7 g5 w5 Y) {" r
- LDR R1, [R0]
& r) K- [. c( ?# |* j3 }
" w/ H G9 P7 Z+ p- ORR R1, R1, #(1<<2)% }8 H+ L* S' i
- " I0 q6 {. v' Z$ X4 \4 J# J
- STR R1, [R0]# g d4 d: _" r. p: y! j
- ( Y3 N/ w2 n1 T( S+ W8 r
- " ?4 k1 i$ T5 M A/ ]. k4 _
j0 q: G& l+ H1 a- ; 设置GPIOA1为输出引脚+ I) c5 M+ S4 h$ K: o( T/ i
- 3 v; G0 k9 B% Y3 |3 ^* u+ A
- LDR R0, =(0x40010C00 + 0) ; GPIOA_ CRL1 e" E- W. t, ?3 V( B) V# S
* b+ H6 T5 M R0 u+ l- M9 P0 G- LDR R1, [R0]
* @. l! Z# g2 A$ R! V0 k. f. o
5 r. r5 N/ s9 C) S- ORR R1, R1, #(1<<4)( g! M3 o0 @ k1 V- O/ B
. L- ]- t1 |4 q* J8 {) g- STR R1, [R0]
" T" n* q" N; q% I - 7 C. K6 W `# e F% y; v8 G% |
p) n! R9 P+ g$ U- , z2 v" R9 h9 `
- MainLoop/ i3 ]. g& x4 R/ t
' X0 _. ?0 t+ U. \/ x" [: z+ @- ; 设置GPIOA1输出高电平
; `; R6 Q2 U6 ]6 q
: m6 j: M8 P- @- LDR R0, =(0x40010800 + 0xC) ; GPIOA_ODR
; z+ [0 p5 H, @/ K, T/ I* Z
' P } n/ X2 x I; r( y! {* ^- 2 D4 ]6 c5 a; T4 T
- n6 m) n- [/ k- U R e- LDR R1, [R0]
- m* X5 M; k& m& p* L- N - $ v+ W. D( e! W# c( C ` s" s
- ORR R1, R1, #(1<<1)0 J3 N u9 C2 O- U- S+ s, U) Q
# M5 e1 r& z/ I. P' W3 v w- STR R1, [R0]- r* N3 Q V6 k$ i% e
- F2 P) @3 Y/ w5 Z$ [: Q' s4 t
- BL Delay
/ N9 C* Y8 A- X X7 ~+ _* `
! L1 B6 E3 z$ j$ p5 t: {
: P2 y( A4 L& V- 3 q2 I: u0 T# t/ v2 _
- ; 设置GPIIOA1输出低电平
& y$ L- V \$ z' E# A
1 n0 s; t' z0 k7 N- LDR R0, =(0x40010800 + 0xC) ; GPIOA_ODR
5 c: f8 R( d* @0 t- h - + ^# m7 f& e0 y2 Z+ {, s' {
- LDR R1, [R0]
+ }' q, X) R7 g2 x
+ r2 a. r! q: w- BIC R1, R1, #(1<<1)- q( o1 R% ]0 X7 m B* e) D8 ^
- 0 ]9 w; D: D+ h a7 c/ w
- STR R1, [R0]
8 s' y2 r5 m6 p0 s
* H: |( u$ b4 W3 Y1 f a- BL Delay y/ L2 g* E. @, h' E& U) \3 O
" m& g) ?5 R7 c% e: B B- B MainLoop/ C. K0 P9 N# c5 P: W$ c5 w
- 2 b0 H8 l# a$ \; [+ z$ K
- ! e/ G! z$ `- T7 k+ U0 j
- * U/ _0 Q) X y+ `1 z
- Delay
: Q2 T" y1 T. m - # g) ^- V$ r. D+ g2 C/ p" K
- LDR R0, =300000# e( x+ Y5 Z; ~) f) @' L
- 6 w) H5 k, B2 K; G1 o3 I0 g
- DelayLoop, ^+ d' h' \) b) p3 s5 I3 r6 S
- + m" ^1 Z: m- v
- SUBS R0, #1
; c+ X+ `3 y. f/ \. k) H) l - - Y! G$ m2 T: W/ e
- BNE DelayLoop+ e' b2 t- h* I4 y+ t( a- M& e
: S% z: |5 w/ g; m1 {, q- BX LR+ I. F% z G N+ M$ k' X3 X
N0 _" o+ G5 z6 N/ N/ R2 F- NOP' [% y' _) }" i( ]: }* [
) k) W, t$ k% G" l+ u+ U- ENDP
' f; s# {1 s1 d7 S* h2 ]% Y8 N - 5 r( ]1 o! c& [, c& [- Y' ~- _& F
- 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- ; Reset handler8 F: R; r3 d1 S
8 \" V: k6 s/ H3 D1 M, i. R2 O- Reset_Handler PROC
4 n" ]4 S* {" x/ \: H
. C7 e/ f3 H1 {. z+ Y* U% Z- IMPORT main) ^' l; i1 r, n- ?( w$ ?
7 T, N5 C8 l+ K7 }( T/ e: j- LDR SP, =0x20000000+0x1005 e& t) r6 w' v& f/ f5 t( E
i2 N* w. K# U& E+ C: V( Y+ b- BL main8 `/ ~, H( _+ o
- # `8 x0 ^1 c- E# z5 z
- ENDP& A9 e# E+ j1 I% {4 y( S2 l. V
- ! Y9 o" i2 |) H$ m; b
- 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- #define RCC_APB2ENR (0x40021000 + 0x18)
$ H9 W1 k9 U+ X; t - & q4 e7 c& t H$ W
- #define GPIOA_BASE (0x40010800)
4 f; M% z4 y4 F
; g# X' [8 P& b! V( v# T- #define GPIOA_CRL (GPIOA_BASE + 0)3 R F, J3 u5 s- s, c; C
- , \9 q# a8 n+ s, z3 w$ B a
- #define GPIOA_ODR (GPIOA_BASE + 0X0c): _' R3 j5 E4 \9 S6 p6 T) a
; o, X0 \, t! j: ?- % H: u3 t+ G* p
, O& m$ n1 }, W- void delay(volatile long i)# _5 m, V6 K1 ?
6 h' ~8 F0 D U2 W7 h- M4 L- ^- {+ w7 n7 i5 W& j% j& s
, z" M" a0 q# u4 n. O0 w+ f+ a- while (i--);
0 o/ I8 N% |. x; j
7 \1 ^6 j# j% k. U$ {- }. w1 l m: C( Q+ p) ?' N6 N
4 F( p) J& x* a7 `- ( q# M8 ]) i% J8 [
. E$ K* u- {8 G/ I( m- int main(void)" P$ x9 x, g7 `$ w1 J. {' x O
4 n I$ v1 m: u4 t0 Z- {
, F9 x7 i$ I) F+ c- i" o
/ e3 F) D# q4 k% \3 i' M6 o- volatile unsigned int *pRccApb2Enr;
+ k) N2 T. g t1 _- g' x! P
0 P: h0 D/ B/ H6 A- volatile unsigned int *pGpioaCrl;2 _0 E* J5 F' \- S! @0 b
$ z5 d! A, W' L8 e% k- volatile unsigned int *pGpioaOdr;
, k+ s5 c. u4 E - . I: G4 V( {3 \
6 l7 v3 t1 \0 K: }; r6 M+ S- ) B9 k7 w- u7 u+ F
- pRccApb2Enr = (volatile unsigned int *)RCC_APB2ENR;2 C) q/ u; U, b/ P( l8 A8 L* _7 [* U
- % I' O8 [: e+ n7 \
- pGpioaCrl = (volatile unsigned int *)GPIOA_CRL; e- p3 s: b- f; U) m0 ?1 P3 ?5 D$ j" P6 R
5 |2 n. _' X [* R2 k& \$ t. o( U- pGpioaOdr = (volatile unsigned int *)GPIOA_ODR;
8 z" M: Q9 G8 {4 j/ W4 ~2 M" i
$ P" E: S* `- h
0 v* u0 J! Y+ X3 A$ K
& b A. g$ E! M4 I5 u B- /* 1. 使能GPIOB的时钟
; V! |# G+ x# Q
' \( W; F- C" r' k% u; Q# D- * GPIO是挂载在APB2上的,所以我们要使能APB2的外设时钟使能位7 R: w9 V% N. E
$ w/ J! |( K# U' ^( i% c8 I- * 从RCC_APB2ENR寄存器描述中我们可以看到APB2ENR[8:2]是GPIO[G:A]时钟的控制位,控制GPIO时钟是失能或者使能) c8 K/ ~$ c) _4 L& y
/ k& q4 }; T3 M* l) @- * 开发板上的LED的GPIO组是使用的GPIOA,即要使能APB2ENR[2]位,为其赋值“1”即可打开GPIOA的时钟 */
3 W) \) Z+ ~7 J( S% y
7 b3 X$ g1 Y! E- B$ @) q+ z- S- *pRccApb2Enr |= (1<<2);
+ V, q1 T7 T$ q - ( f4 p" w @$ P, {- E
: \+ I) o/ s- P/ e W
) H5 R5 k* g/ Q- /* 2. 设置GPIO为输出功能:: U+ G; o& L- h/ o
7 p2 z$ s0 I% K8 I; k- * MODE_0=1,PB0输出模式,输出速率最大10MHz
F+ E0 Z0 o# |/ F9 \
7 k# K6 a# M Z0 `/ ^2 e7 U0 q' v/ a- */3 F! W3 l0 T& [0 ~* h
- ( U! h/ R0 |3 i) Z1 F* c
- *pGpioaCrl |= (1<<4);& E/ V7 i% Z4 R
- " ]& V; T% n% h- C: q
- 1 [8 d; Q1 I, i {% X
" f# |- h& N3 `- while (1); t3 H& Y0 c8 m8 `7 A
- . J n8 c# \3 S& X! W H
- {% w' O, \3 I% H$ f( U/ q
- ) F. ^( d E: W; P& W2 ]
- *pGpioaOdr |= (1<<0);
( @" y5 ?- d0 X3 u3 n: ?- ^1 P - # k- u2 ]. V H/ C2 C/ F
- delay(1000000);2 \" @& F6 _& i9 _; S& P
5 u( s3 N7 t1 ]$ g; j( _- *pGpioaOdr &= ~(1<<0);* k/ A3 G+ _) ~3 {% j* {; q7 E
- 8 c8 p$ {% L9 |6 j" i( T
- delay(1000000);, V& d) \9 B4 e |- F* T
- " A5 X" B t: ?+ V; b. ^
- }
/ _; C3 A2 e/ a& I* p4 c3 n3 [
7 i! U5 r8 ]6 L# V8 A: a: ]( H/ @- }
复制代码
: 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
- #include "main.h"
9 v4 J% P0 F7 }2 X6 k3 F
0 q/ ?2 p$ k4 G ^/ C) N5 v- #include "stm32f10x.h"
* o j+ d) y F - 1 R2 J: S$ w& Y, e
- #include "driver_led.h"
T: P* q% F5 }* t8 | - / U) \1 T5 ?% x+ F/ n) p9 F
. m6 j8 ]+ }: [0 K9 X$ t9 E- 5 V; U+ @7 q3 K- k# i' s5 k
- void delay(volatile long i)
0 P% z w$ \0 l+ f6 I K, P
, P0 T& `) [. f: _9 X- {
& Y8 v- Z. y- [& L D4 ]3 ?
' Y. d4 p8 R* ^- C; N0 J- while (i--);+ l ~# G6 ], N! u$ w1 ~( l' T
. k- L& u# M; R5 }5 G3 k0 L% U9 Y- }
0 ?/ p1 U& Y4 H* Y0 u3 _1 r$ o
2 {5 D2 O {3 Z7 s& r7 `# t
, L1 @# \& }- H5 p4 |5 q9 |- , |9 s! g2 @. M. _. d8 I
- int main(void)
& o7 s8 k: L* ?6 W - ) W* E$ n( q2 ?& c, ?3 w
- {3 o% H9 I! Q7 d# A6 q `' ]
. v) |/ ]* \5 \' G/ e$ j2 e
3 y8 \9 W& C( G8 o! ]( ^5 C' o. L- ! k! }) G) J3 B6 ?
- // 初始化LED" U8 U( `+ t. ^$ B
+ F8 y, v! x5 _1 M5 P9 ^. S* t' q. ^- LedGpioInit();; H( R1 B/ r3 T5 I, s$ d
. a' m% g0 {: ` R8 a
6 M8 [7 T' [0 J- 5 v" y$ i v) @- x# v0 R2 m
- // 点亮LED灯, N* f, U) t9 m# u/ e5 n$ P
- 3 q# u/ @( n2 p, P9 f
- while(1)
* f! z) A! v& A' o% f - # K2 r% y: j+ x5 C+ D7 L' z9 E
- {
6 v7 W; p; N* Z3 h# F
. |. Y8 b6 [8 k8 _* A- GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);
4 V3 S8 D1 Y+ R" a - + t- E2 V9 {7 N6 z6 l
- delay(1000000);0 U+ S" ~2 {9 w$ n$ R* p5 X8 g
- # x2 y$ ~0 v' ^
- GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);5 ]4 C6 H4 R" m z
. m+ z3 y, N5 {; p0 J, Q- delay(1000000);) {6 l3 u, P2 U
' h" s0 a1 S1 \- }
% q7 S" F! q! P- C - : n* I( Y* T" `/ S
- }
复制代码
, 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
- #include "driver_led.h"
7 [2 O0 ^9 ^! D; k d% b
0 J' S7 ?5 d' ?2 `
2 X- r* D: E* F
% V% b% p0 n, r7 ]! ^ r- /*
; ?3 ]; H5 \' a' F! z# H% Y
, `" C4 v4 y% d5 j! c; B- * 函数名:void LedGpioInit(void): `3 a1 f1 p; o1 _/ y/ c
- 5 n, I4 m3 m+ V
- * 输入参数:无
- v, S! t8 Q: Y$ Y3 u2 ` - / Y5 }8 N% E& W
- * 输出参数:无, @% L _) N. h/ n" q
- 7 O9 {7 g; C% @
- * 返回值:无' f) C; T3 i3 K1 \, r5 C! g
- ; g1 S6 F: p2 H9 b0 b* c
- * 函数作用:初始化LED的引脚,配置为推挽输出( T' M1 N# `* I* ?- Z( w9 v( {
- 9 n, y* V4 U# L1 o1 C I
- */- c& a# E# F; n& k2 _9 A( f
& w, x0 t [4 h. f- void LedGpioInit(void)
. q3 c4 z7 P9 |& {( T3 X - + s% X! s. K: ^% V/ ~) r
- {+ d. g5 E& v1 b4 _8 T
- Q8 b9 c, e% s6 M: ?, N6 g0 w
- // 定义GPIO的结构体变量3 b$ P9 ?' Z! ]: [' s% z1 W
2 ~5 k0 a: O% ^! f$ ^5 \$ B3 a- GPIO_InitTypeDef GPIO_InitStructure;
. s) x* [* O% V% p( d - * h8 H# m, ?: L8 h8 x% [: x# Y
- // 使能LED的GPIO对应的时钟
8 @9 u0 b: e% v2 n
- n$ w/ x0 D2 k* t- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);; C+ J) T0 b( Y# V3 ]
- 8 o$ k; ?6 r# i @' C5 L$ Z; w: b
, a t( X, H7 J2 B0 o* f" C- 1 d- S# P |6 Q- t" L
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // 选择LED的引脚6 g% R; |. }* P0 r# s
- - H5 c1 ~! h: Z, G% ^1 K+ P
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出模式4 j9 k8 ~/ S2 N S3 y
& P) y) t" c d& S4 ?- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 引脚反转速度设置为快
2 L L% j; A( y7 r) g: Y! h - / Z% M6 B6 o2 Z+ G9 T7 W [
8 y) W; E6 t& x* T% o7 @+ H5 Y- ) ~% ^$ A, v5 c8 R
- // 初始化引脚配置) V- f7 u- I* w0 g7 |
( E# k" S# H9 N) H9 c2 K g- GPIO_Init(GPIOA, &GPIO_InitStructure);
3 C9 P9 _' I* w' m/ \
) W1 Q: b6 Q/ N, B% d# Q7 c# Z- }
复制代码 * 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 ~
- #include "main.h"
" E$ x) D" D) ~. V
& y. Y) r/ L* a0 q7 g+ R- #include "driver_led.h"1 U! Y/ X& d0 W
J) b2 _5 b; I( Y: \9 Z
3 E) u( f9 u8 z, |/ X' v
' R6 v8 Y/ ?/ m# e, D- int main(void)8 r: ^) e" L. j& q5 k, g
- 8 S- c. w3 ^- G2 A2 [
- {
4 [5 Z% K3 W) w* ]; } - ! V. V7 r5 v# ]0 |
- // 初始化HAL库函数必须要调用此函数2 n4 s& @+ I# i) `- [- i
- 8 H, |/ n- m( u$ m8 x: x
- HAL_Init();- f% I R4 ~3 A
, @, Z- Q5 V. B8 p- // 系统时钟即AHB/APB时钟配置4 x x# A" }! g& z
- 0 p6 b+ c1 I9 R# F8 g
- SystemClock_Config();
4 a. ?( U* x$ Q7 K- R - 5 L8 Z' W* W# ]" ~3 G
- 9 G }* n" _9 s8 a- Y1 H
' O# B. a( N- m% V) T! \- // 初始化LED
/ p: i, T4 H. v8 m - ( s+ w' q- V7 o4 q; o6 v( {$ R2 _
- LedGpioInit();$ x! m2 d$ \) I4 M
9 I+ r' a" `, Q2 B
; j' h4 ]: D6 A* s) r2 y% D$ B6 r" ]
$ w" j l6 z* R/ a7 ~7 n- // 点亮LED灯# |5 B7 \7 q$ Z) A+ o
8 F& {7 K7 q5 b7 U% [4 i' n- while(1)
3 @" e b% ]3 B* }2 g9 q8 t - i; t! u$ C: p
- {! Q/ `- d5 J1 A( {* {
- 2 L6 |( P! x) s) s. }) c% L
- HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);) K7 L5 L9 F& Q( V
- + ?8 e% ?& _+ c
- HAL_Delay(1000);
+ c: M; Q* U* U1 n3 J- c - ]3 ?9 K, \% F# b# R& [) h5 W
- HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);5 o$ P4 @' K+ e9 J
- + a- C( C2 g' ^3 v8 q# e; b
- HAL_Delay(1000);
; X, J( e+ t0 G- X9 x: x7 A
) R! W. `0 r5 n4 j4 }- }- J7 o3 @; J+ @. L m; p4 p
- 7 X% q9 g- b' O$ w6 H, M+ C# d
- }
复制代码 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 {# _- #include "driver_led.h"; K {; ?9 i/ L6 B6 Y8 R9 c) G
2 Q0 X; V, [8 v0 {5 O2 H; J
5 ?% K( n( s" e: F3 Z* a. l- 3 c* {/ _9 m; `3 C. e
- /*0 A3 C: \/ `! Y0 Y5 D
2 N* K8 s" a0 a- * 函数名:void LedGpioInit(void)2 M5 G9 c) L. x' m# E6 }
9 ~" T" q U( D4 w. @4 J; Z9 z$ U- * 输入参数:无
3 G/ D# W0 l. E- i2 j - $ }' H2 W2 b/ U V0 U& y0 H: F
- * 输出参数:无& Q4 q5 K. O F2 k; n o4 A% u
- , X1 |# g" y' q4 U3 }0 k
- * 返回值:无& {( W, n7 m# \- K1 t. Y* T: L o
- - S- v( O6 b; g! f5 e0 g8 H
- * 函数作用:初始化LED的引脚,配置为上拉推挽输出
4 O9 H# y7 M- g9 ` - - a, O, i3 O2 f$ }4 w8 B- h8 o
- */
4 `" a! i# O4 ], Q g2 o" m& L
4 B2 a& j5 ]4 p9 [ [- void LedGpioInit(void)4 Q% @2 o9 P+ C; Q. h7 g/ ]" N& V4 E
- ( ~) f9 I2 \" V" v: q( J" e+ ], |
- {& b* R3 O( f' ~
2 H# l7 P4 I! l' b4 J+ ^/ g6 z+ t9 v- // 定义GPIO的结构体变量. W0 }; d+ N8 V/ P. b' k
- 6 C* \1 t# U4 m9 K, C( n3 \; s
- GPIO_InitTypeDef GPIO_InitStruct = {0};- n0 Y e5 w% y+ Y, X3 M
9 y8 R2 {4 y: X. Y# X- w$ x: x; w- // 使能LED的GPIO对应的时钟! h2 A; |" y, [' j% E* L( q+ h
# L; I% Y9 `4 ?: Q- __HAL_RCC_GPIOA_CLK_ENABLE();
( Z7 N* ^! C7 G- W5 b# S7 `
/ X9 R- c) j# k; c" b$ ^- 1 H+ H2 Z* n: O% E; h0 p$ E- t
- 3 ^$ _; F# z$ ~: s% l$ F, ^# `
- GPIO_InitStruct.Pin = GPIO_PIN_1; // 选择LED的引脚
, M3 R& v- ], ^+ }6 n - 8 [3 f) C. n8 {% k
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式; n- x& c" F. I; m. f
- 9 o( B' z+ U) r/ e. R
- GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉4 E( U) N/ p% Y( |
- 2 ?) S3 ^! {( }3 R+ N; }5 g# d
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 引脚反转速度设置为快
a+ r# n* Y u - G4 ] U. R* g; B, ~5 l( _
) X" X. ]3 q. p, S
2 Y& w3 G# {0 |, r; x- // 初始化引脚配置0 D$ o. D% q% O' E4 [4 l! S2 p2 g
" a6 ^+ n2 [( @2 y( H, F- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);4 {% R& j, [+ s9 d: G% {! g
- - \- ~1 r6 ?/ ]6 z1 n
- 1 z( v" I% L9 }6 B1 E' G+ e
( @2 k) V. E! Z- }, @9 o' ?- }
复制代码
* {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* {( 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
|