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
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
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
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
* 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
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
* 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
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- ; Reset handler* a& h, _: m$ Y9 c, f% R5 f
) X6 T* E% k+ f" l5 r6 T6 X- Reset_Handler PROC% | K- o5 _4 d. {$ j8 N8 w' F
( C0 Z/ f0 ^' e4 M, l2 Z- / I* T' b, Z& x; q1 d/ [& T
- * I6 x- _2 N8 {* L; E
- ; 使能GPIOA
+ V: H! V8 O( p - - b$ M6 i. N1 ]3 ?6 z7 U
- LDR R0, =(0x40021000 + 0x18) ; RCC_APB2ENR
/ _, D/ ?9 u5 Y* Z. J0 k! V
o6 M0 N2 H. I/ ^+ D8 F: |- LDR R1, [R0]
) W0 f1 {! F( O! M% V, O. H
9 R6 Z" V8 N5 H- j# B) w- ORR R1, R1, #(1<<2)1 \ h: G9 Z' Z5 B8 x3 v. \& z1 n0 B
* O* y1 C1 v9 W: M4 g- STR R1, [R0]- E$ _8 b1 l7 A3 a
- . w v/ s' T" @8 ?# k8 l
- . i' Y8 o: H; [( H2 I C& [/ H
. X5 F/ X; B0 h- ; 设置GPIOA1为输出引脚
% \4 q# i6 o, g3 l( q3 a% p - 6 s- v5 C m9 Q f: k6 @
- LDR R0, =(0x40010C00 + 0) ; GPIOA_ CRL
$ Q( R3 U, v0 [! R
/ ?; v2 x7 V% a( |+ K% K. `' o- LDR R1, [R0]
9 V; b& e2 N- Q- F3 T, b$ \8 i' N - . r& F" f3 H/ q$ c0 W+ {+ t. w
- ORR R1, R1, #(1<<4)" _) g9 K6 Y' p6 V2 w( o
& W& r* \ Z2 p- {$ D- STR R1, [R0]
' ^( S0 J) ^# L- U [1 l
7 f* ~, }* L4 ?
* ~. H+ ?: F; l4 l
4 |, m* D0 ~0 Q2 Z: O( y2 f- MainLoop
- W4 i6 r4 C; l3 P+ Y2 N4 c2 W
- M: @5 p/ ` n# C' d; K- ; 设置GPIOA1输出高电平# M% ^! Q7 u- m1 w+ c& K+ x& @
- 1 z6 h. y' T- s; N8 m" P$ W
- LDR R0, =(0x40010800 + 0xC) ; GPIOA_ODR
y$ Y8 j* R4 \ - 0 r% q; T4 w% x2 h* v, B" `& O7 \
- $ K+ k# {2 Y5 ^0 M& S6 S' N% U; Q9 ?
$ i- m1 B% @! ~5 Q+ l7 f- LDR R1, [R0]
& L+ B' K' `- w' u+ y - 3 U1 m, D* b( B
- ORR R1, R1, #(1<<1)
5 j0 ?% ?1 t+ u8 f y( G9 S( b2 W - 6 M! P" }' g$ Z0 \- F2 V! Q( h
- STR R1, [R0]: u% }1 l6 ? ?" r4 |
- $ J3 x8 i2 i9 O
- BL Delay4 t; H9 R& h' Q
3 h+ r- ?/ _. O6 _, O( R6 E `- / [0 m8 O4 |; U# G
! f/ a! K. \5 l- ; 设置GPIIOA1输出低电平: Q1 f# M* ]' ]3 Y
Z* s6 C" A# p4 }4 ?- LDR R0, =(0x40010800 + 0xC) ; GPIOA_ODR1 H9 r/ H4 F' W
: |! O4 i- a! K: u7 d7 Z" P- LDR R1, [R0]1 m' P- {& Q/ n; {
0 K |( L1 L3 y3 K- BIC R1, R1, #(1<<1)
7 @4 |# f* a/ r' l j* Y' q
' w0 \5 Z4 `6 k5 w* U' P- STR R1, [R0]
2 o4 z! L3 D: M3 ~8 u; W - 6 Z. j- A+ n+ E) u# n8 X" T
- BL Delay' @, m) r" [7 w1 H% _
- & \( E) _3 ^% w
- B MainLoop
q0 _2 B% Q/ N \8 Z# E4 H
3 n' |8 A; s X: D. q- 3 P% v6 F/ n/ T7 j2 n
/ B7 A; G# ]" P, T# Q' {- Delay8 X0 d& q* p- Q) k, c0 o
- ! q+ y% L$ ~+ B- N h
- LDR R0, =300000
' z; F/ y% \; o! v. Q: z0 K% ?
l' e6 p7 {7 y! P- i- DelayLoop
4 H6 i% d# W. `! w* Z: ^ - 9 [' N/ x) j' D8 o) ^5 L
- SUBS R0, #1% q& Q ?! \/ m* f) X6 W
1 ]" w! Z/ d9 X# {+ I9 L- BNE DelayLoop. E( B; ]! q. `9 b* H$ U, z
- 5 X G- Y `! q6 w8 Y; u
- BX LR; B9 r( ~6 O/ K0 t& a! ]: G
- 7 s* I& B7 `0 N1 N) C4 \7 _) I
- NOP
% V6 Y9 G7 y! v0 h, M$ x
' u* v$ D" }- q* O1 E- ENDP
, [/ X' F1 j9 H
- J, s( N/ r' @) G- 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- ; Reset handler
9 G1 R8 J- Q- c - 9 K9 X' i% ?9 i) x" ~
- Reset_Handler PROC K( u" I1 x8 Q; a
- ( @( m( `. g& N( o1 w; [& Z) c
- IMPORT main
8 b6 f9 ~7 d; n/ h
- F& Q0 h$ _, z4 V2 z- LDR SP, =0x20000000+0x1004 t8 v: P) D( ?$ [ o) r5 s
- 8 H) C6 C4 t }7 M& Y
- BL main
! g5 O4 H3 y5 Y' \! ^
1 L0 Z, n" e) {3 z; b- ENDP
6 t* @. f8 C2 b, H% t) g; ` X - ! |( }" S) Z1 n# o8 }
- 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
- #define RCC_APB2ENR (0x40021000 + 0x18)
* ?" u H- {# t+ X0 S/ O, G# R - : d: k4 c5 i. O6 x* z3 T1 Q
- #define GPIOA_BASE (0x40010800)1 I6 R0 C. V: B
- / N9 E. J4 g7 p; E- j
- #define GPIOA_CRL (GPIOA_BASE + 0)
3 {: r. o% p: v6 c( E2 E* z - ' e% J. S" J- }- l/ q) J6 d; o q: X
- #define GPIOA_ODR (GPIOA_BASE + 0X0c)
; M+ C1 Y1 [" `3 B: T+ e: g' f `2 l
2 q" s* ]3 u- I0 `8 O; O6 a
0 c9 ]+ P1 i; u; s* l
8 x% H# C* V5 v5 c" E- void delay(volatile long i)
" @' |0 R. }; ?% e* P - $ y! m9 G6 w# `# }
- {/ u3 r( m% c0 F7 l3 `; |
& c1 _2 @6 i% R& R9 k; j2 m- while (i--);& K+ N9 P' p+ \0 s( q
8 O- |2 r$ H. C1 {: m( z- [- }; v% E0 E. T2 `, m' |: o2 V! {
P) J" q6 C0 O9 x% ^) N- a- ! Z- P- c7 E" z6 T- h! W) s
- 5 Z0 k& a: Y0 c" U( ^
- int main(void)* r' c k2 @% R
- ; Q& P' U( Y/ ]% v7 W9 o5 h9 I( v
- {
& L- a! N% d [7 c: O
2 T7 S4 d& x. K! Y6 Z- volatile unsigned int *pRccApb2Enr;
t7 a9 [+ K E& N
( ?5 i2 F2 k+ k" `- `* F! b# t+ ^5 {- volatile unsigned int *pGpioaCrl;
! m, \! S8 C" r- ?, u/ e; i( P# ]$ { - 6 G" W( W" M' Y6 K
- volatile unsigned int *pGpioaOdr;% J# [1 ~ I3 {4 T9 s
- 8 g% Y+ Y4 V2 y5 x0 Y
/ o+ j }, l+ ?% m, k% {
4 K) {) o! s. _- pRccApb2Enr = (volatile unsigned int *)RCC_APB2ENR;! d4 q7 p3 P3 G' d$ r' E
; d2 I5 o4 D3 v/ A- pGpioaCrl = (volatile unsigned int *)GPIOA_CRL;
$ Q8 U( D* c. J) g8 }5 C( [ - ) v: V! y O( a- x, {( L3 {; K0 T
- pGpioaOdr = (volatile unsigned int *)GPIOA_ODR;
2 m8 S4 O& X/ }" k - 0 _; D r2 H; R/ \. {
- 0 M% t, T! r: j5 x
- # R4 R- t2 L9 R8 L
- /* 1. 使能GPIOB的时钟
. h/ C) V+ D3 m8 I6 q - 3 F3 d+ i, x) Z! d' S
- * GPIO是挂载在APB2上的,所以我们要使能APB2的外设时钟使能位
# T9 A& ~5 f2 M3 e3 e7 y4 x, I
. Y! Q( ^ w- ?- * 从RCC_APB2ENR寄存器描述中我们可以看到APB2ENR[8:2]是GPIO[G:A]时钟的控制位,控制GPIO时钟是失能或者使能
+ m" w8 t. Z1 @ - ! f" i P9 t# D% z, j" x
- * 开发板上的LED的GPIO组是使用的GPIOA,即要使能APB2ENR[2]位,为其赋值“1”即可打开GPIOA的时钟 */
; ^& ]% ~8 S8 P- L* T - 4 B0 t4 O9 \0 s! K: P
- *pRccApb2Enr |= (1<<2);
, R- \% k. }; l5 u+ e w - ! j: h9 g, q) m1 J+ `1 {% a
- * N/ n! t" l. `6 ` R% ]
- ; t! T; d6 Z! t- U
- /* 2. 设置GPIO为输出功能:1 D/ e; }- c- c, X: h! I1 H6 c$ {
- % b5 i2 J7 u) V: p
- * MODE_0=1,PB0输出模式,输出速率最大10MHz( g8 a! X& m) H" R: {
G; @7 T! P, ^* M* g8 }" i$ q- */
3 r0 w* R1 e8 p3 q - ; o& t5 ]& p6 D5 E% |, j
- *pGpioaCrl |= (1<<4);
# W4 D1 K; i4 e/ I5 _# `5 U0 s - 0 A0 |; T# g% Q. A# J2 r" X2 y; W
* u! z# i. K5 K9 Q* `8 P- t* B I, B
9 @5 d' d; u5 i; J) W6 }4 Z- while (1)) b+ E1 @( P& z' g3 S: ?! D$ z' n7 ~
e' S3 z4 i1 ?# {- {
V5 P {- m8 A/ o) c - 4 U' E/ _- q7 f$ a
- *pGpioaOdr |= (1<<0);
5 D) f$ ?3 Z3 G. ]: N9 m - ' H$ W* `% }2 `: H
- delay(1000000);1 Z3 m* \+ Y7 q; m/ U) `3 I3 b
- 5 ^! h u9 |6 r! |7 b3 a+ Q$ q
- *pGpioaOdr &= ~(1<<0);
3 `- f( P' E0 {, T% D
$ C! X# f: X3 C* [, d" `. R9 i2 C; B- delay(1000000);
# _. w8 B2 U8 H1 Y. c. T6 A Q - ' ?3 t9 A3 q# Z+ o- W
- }3 M" R( e- ]( q$ L8 f7 Q' C) U
0 @, w* T7 o9 j- W* C4 @" j- Z; f# d- }
复制代码
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# ~- #include "main.h"
1 d2 F" a' I l - 5 S, p8 Y3 w# a# E/ A% `- M
- #include "stm32f10x.h"
* e* J/ C* h! D* y5 B - 7 x" ]4 s- t" W' r: L* |
- #include "driver_led.h"$ a' o7 r" J. n0 P
& V0 a% g3 b* n3 {% q
; |' p- m: g0 f; }8 _- $ r3 J8 u0 d9 i( z5 F# f; ]2 h
- void delay(volatile long i)
% L6 O, }- T, Q
7 S5 ]! l/ g& V/ p0 F$ o$ M- {% l" L, I& |3 n" [2 t K
0 R5 S1 [. s/ N. U- [- while (i--);
( ~4 w ]3 a" I3 i- e Z* G
- u9 A6 Y6 Y' \$ t+ s9 a {, a- }
" O0 v, I( |( A" ^0 c; v/ m - , K9 b- ?: S/ k; s
- ( J& h* z5 T# z- q# R
$ {9 `/ i0 f% k- b$ D- int main(void)
4 G. w" l9 _$ M+ m. n% Z8 [+ ] - 4 F2 q( x$ u8 g, f
- {2 b0 q. @& V( e8 C# p2 l/ x
- : ~9 n# t2 i( V0 L/ j9 K9 R
- - B. x$ R+ D0 k9 G4 M6 Y( z
- ) {' ^5 w8 `* |: J; ?# e5 E, }. u W
- // 初始化LED
7 Y# D; g8 i9 g/ A
; V" j, D. j2 [: L) M6 t" Y) \- LedGpioInit();+ ]) o; d. u. u; \
9 ?% d I4 d: N
) a" K/ |7 ~( z/ u! i" v" V1 V
; i2 y$ w+ k/ ^- Y# O3 n4 d- // 点亮LED灯3 P6 S+ q% s/ j! M# A, k$ h, k1 Z$ [+ Z
( s3 l- F9 `) Y: a( }: a; o2 u/ s- while(1)
! V3 {& o9 l+ X7 ~; Z! G - 6 l% n6 {& Z. k6 W: X. T/ v
- {# n+ f4 T- `( a
- ! q7 ?. T' r& u) C: ~. f$ ]. u
- GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_SET);( }8 H, [# M4 k% Z2 s
- ( t$ E7 p7 v6 Q9 G' s" L
- delay(1000000);
5 g2 ~6 a# g1 G4 [
- }! u) m6 C& `' t# ?- GPIO_WriteBit(GPIOA, GPIO_Pin_1, Bit_RESET);: t! t- N! F. X! J7 P7 ^
7 U0 _: g+ V9 ^7 x% T- delay(1000000);
) H# J# L' m4 @' [1 I& X( N, `' |
4 O6 L! ]0 Q8 w$ X- }
8 t% i+ k4 @: p! Q6 } - 9 T2 C: Q- _8 ^1 t
- }
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- #include "driver_led.h", @2 \2 K: G: H- \; y I* i
% K5 P3 l& A! T3 v3 o6 L6 T
4 B m/ j; H8 X) R. c9 i( L
^5 }+ O) i9 ]) r) X- /*% C% i0 f' r4 Z! [1 _+ M
/ Q3 [6 D, k5 w) c- * 函数名:void LedGpioInit(void)
! [: r7 x0 F/ F9 W - 9 c( H) |9 F2 x# R
- * 输入参数:无
. F: K5 A( [1 a9 f6 I9 O& c* J! z
0 E8 E" j' h. W5 O% s% M& T- * 输出参数:无6 L' P, F: `1 v
- - T/ E+ a% r$ G9 \
- * 返回值:无
3 ?' T$ T/ T( e9 G8 z" x
& ]- \; M# }( f2 b+ G2 F5 x- a- * 函数作用:初始化LED的引脚,配置为推挽输出+ ^8 G/ s. w( a
- ( W1 {! c3 |9 k( J
- */( s% l/ g _: M+ h' ^( m2 ^ P
4 F, C5 k, g9 n" @* Z- void LedGpioInit(void) E( x0 F2 _3 O! l
! X+ [+ L$ ^+ W- {0 D5 U$ X' @- {
( O& _4 Y0 i. d - ! n) C% j& ~- @& o
- // 定义GPIO的结构体变量9 h" U) d$ z# M, O0 E( p
6 O3 u" l- u4 W( C5 s6 s- GPIO_InitTypeDef GPIO_InitStructure;" e8 E. H! E( N: v! b. _& u. ^# g
- " ~, R% I$ L2 R& z6 W
- // 使能LED的GPIO对应的时钟
( w1 c' z2 j( r8 ^" L% m - ! ^- G. H2 N: K1 B9 V/ ~6 @9 Y/ P, G
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);% c A' H/ K4 u0 t) K
. x& ^ g3 a# x9 ]; Y4 @* l
. a2 O% n' V/ H2 o* _! e! L" b- 0 R. |5 v. p8 d# z. @: G8 |
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // 选择LED的引脚8 l6 g! g( k, S$ w9 G
- - D+ A+ {5 ~; U9 L$ D. O2 b
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出模式. _% l+ ~3 [. H
- 9 s: z8 F& r0 U' e- y
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 引脚反转速度设置为快8 ?- P, u, m$ k7 I6 J& I( U5 t4 Q
- ; Q9 ?9 ]$ f* \1 G
6 M, e3 b$ D4 Y0 D
. r8 F( ]/ Q: X0 D. m! F# Y! F! C8 x/ X- // 初始化引脚配置% f/ b& s% q" Z% z
- + z" \2 N) u: [! E" x6 F
- GPIO_Init(GPIOA, &GPIO_InitStructure);
/ Y. I2 ?# h( h! Z0 x5 b! r
" R) e$ [* m$ `8 r7 K" e9 |- }
复制代码
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
- #include "main.h"* H- B! j! h. Q8 K' U
- ; p( |' R! M$ ^! T0 T
- #include "driver_led.h"" O$ i7 O1 Z1 S/ n, W: J
- & Q& P% |3 I" o% C3 `. O
- 8 _1 n" \% r/ m, @5 u
- % i, c$ n+ w+ s* ]
- int main(void)
( ^& d) e$ q+ r) C" R1 S
2 g8 G. \7 |# K- Y- {+ b) b! j9 O/ F* v( W
- ; C4 Q- \1 r: F# p8 {( W7 |3 ~1 Q7 F+ j
- // 初始化HAL库函数必须要调用此函数: A1 x9 z5 m; f5 L, J* G, x$ C
- " m A) m7 y! P: O, \8 z
- HAL_Init();" G% Y6 n$ P8 x! ?* Y9 d
/ E/ z$ k: p$ p( c- // 系统时钟即AHB/APB时钟配置* E% u4 q+ q5 t+ B) F
- " t2 y- R, a B4 s7 a: K
- SystemClock_Config();2 B' X) D) c+ m5 W/ W) T
* I/ w/ S5 u( D1 Q
1 R& ^; m0 V9 N
% K4 ] }' v+ {' A6 @& ?# V- // 初始化LED
3 K- x/ }7 }# B' C% m& ] - ! H2 c( k0 S2 p3 \4 i9 T0 }& n* l
- LedGpioInit();
0 p' O; [3 c4 B" d. i - 6 a1 D. \' m; |; h. u
- 3 r9 }8 f9 H* X1 p0 h' @; n4 Q
- % V7 H6 i& G* U9 r; z& ?5 s
- // 点亮LED灯
0 U4 ? F8 ]$ n; b% h' K - ( @! i" q ?( }8 C; P( D, @8 ]
- while(1)
4 ~- x- u3 O$ x0 Z$ e `
" C7 I8 O4 {& f$ m9 l% \; y! N2 y- {& r7 N% H. U8 J0 O3 J& ]2 `
- , ^; ~+ c7 p8 R# m6 k! }8 j9 P
- HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
* `3 n! t4 g8 A+ s
3 j/ l; ~9 A3 {# k. }4 `# i+ y- HAL_Delay(1000);
+ y" C+ ?' r+ q( \8 S8 J' p4 f
( [; w% P, \ F B9 {/ I- HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
; K3 a5 N! [ `# Y0 k; C - ' n; B- A7 W1 ]% \) d$ P J
- HAL_Delay(1000);
+ d3 b$ k+ | s - {/ K1 e1 v2 ]* w! w5 M
- }3 T8 e% I7 }# T1 B9 k# ]* m
- ?! _% T9 w7 p" p; u' S& G- }
复制代码
; _. 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
- #include "driver_led.h"0 P% }3 y1 F2 |' s# q
" ~/ y* s4 _# q+ A8 |0 x
- r/ ?$ }/ e: {( i6 y7 }. N
5 M5 c9 V! M* U, W: p6 C- /*
8 a1 W% I1 H9 T( H0 _3 C - # Z( Z( \2 h) T0 L/ G& _( c) T0 I
- * 函数名:void LedGpioInit(void)+ G, P, B: z7 e' _3 Q4 M' Q7 [5 ^# Z
' J5 j- ^4 J! L/ j- e. l6 f- * 输入参数:无
6 v! b; z7 J% b5 `' U" I
! E) {7 n5 E/ a0 e; Y- * 输出参数:无
' K- g8 V! x- [; h8 R3 Y
- {: I) |: o) E. C. u+ t- * 返回值:无
G' O- b* K3 x - z0 j3 C6 o6 c# k5 P
- * 函数作用:初始化LED的引脚,配置为上拉推挽输出& u5 o6 d6 Q$ M+ Y
- $ h/ U& {1 n: h7 e D
- */( T8 h2 q1 K- Q
/ \" w4 {5 l# B; q$ d: j" a+ b8 T6 Z0 d- void LedGpioInit(void)- S. Q* M; R# l+ P- t4 H9 h
- 0 x i6 w4 w( ~: y
- {. K( x/ p* Q& _+ r; M# V
! B$ R. K( w0 ^( J- // 定义GPIO的结构体变量 f2 R2 U B5 Y9 k- z9 y
' @ L. ?: X- Q) r4 A. I- GPIO_InitTypeDef GPIO_InitStruct = {0};: J4 R" g* O& d4 c# A
! q: Q6 ^% a; j) R; e- f# h! y8 [) G- // 使能LED的GPIO对应的时钟- R: x4 m. W4 ^8 |. G. ^2 d, e
& k8 g, k( n: L2 l x$ _- __HAL_RCC_GPIOA_CLK_ENABLE();2 `* c8 K$ J5 F0 _
: Z. z( @4 J9 y7 U/ X4 M' _
( V& @6 N% X+ K/ G8 f- 6 z+ k3 t# F" A/ E& G Q
- GPIO_InitStruct.Pin = GPIO_PIN_1; // 选择LED的引脚
0 [' q3 {0 y+ ]* z, ^
9 Y* B; N* e1 O7 p1 r( j- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式
1 V- }8 F6 h3 e0 \% e - , S" I8 U# X2 ?* I" b& P; W
- GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉5 y9 U0 z9 ?: Q3 V# b d4 S
- 0 E; B7 l; ? ~
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 引脚反转速度设置为快
, l$ ?% |. q* k5 ~9 K2 l) O
( K. Z6 f( J3 M4 ?
. A$ D& Z: V1 h" _
8 }8 ?" m7 Q# Z- // 初始化引脚配置
% b( ?! E5 B" j1 T, ^2 X( N - ! k; L8 x9 v- }7 G
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- |9 B) p3 u: U' { - : i4 x' u, S @; f9 U4 G) B
% { i3 d b. q* ~
2 b) ]* D2 {9 [2 k. {* P% g- }' 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
% 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 }
|