在进行一款单片机学习时,最基本也是最简单的外设就是控制I/O口的高低电平。LED、蜂鸣器以及数码管这些都是可以作为外围电路连接在单片机的I/O口上,进而可以实现通过单片机对其进行控制。在本章节中,会以这三种外围电路的控制来学习stm32单片机中的外设资源—GPIO(General-purpose input/output)。
# U# x( K' _' Q9 ?$ J# q' B( C9 g. W2 N/ g. y) Y. o5 u
1、点亮LED灯
5 Z8 L' ^; W+ w, `+ k6 b {, h所使用的基于stm32f103zet6芯片的开发板中,关于LED外围电路的设计如下图中所示。从图中可以看出,只有当二极管(LED)的阴极电压为0V(低电平)时其会导通。因此通过单片机将相应接口的GPIO设置为低电平后,便能够控制LED灯亮,设置为高电平时,LED灯灭。
7 V8 M; l& v4 S2 K5 d/ l) N+ C( g4 s& n7 `& I
' T: v/ T! T5 j* q2 i. T* e- i+ W; w$ N+ c% @
1.1 寄存器方式& e$ l- m3 G; @
通过前一章节对寄存器开发方式的简单介绍后,在本小节中使用寄存器开发方式实现对LED灯的亮灭控制。下图是一个关于GPIO的结构图,通过对其了解可以知道GPIO可以被配置成输入、输出、复用功能以及模拟输入输出四种模式。其中输入模式又被细分为模拟(ADC采集)、上拉、下拉以及浮空(输入电平不确定,完全由外部的输入决定,按键电路)模式;输出模式被分为推挽(输出高低电平)和开漏模式(输出高阻态或低电平);复用功能也被分为推挽和开漏两种模式,但是输出信号源于其它外设,输入正常可用,但一般直接用外设的寄存器来获取该数据信号;模拟输入输出模式,上下拉无影响。$ ^4 U. ^/ O3 g( N
+ ^. p( R6 O A( n+ V
% j& d* q F; M清楚了GPIO的模式配置后,下面通过寄存器来操作端口PC0输出一个低电平,进而使连接在其上的一颗LED灯亮起。最开始的模板准备工作在前一章节中已经详细描述,在此就不做过多赘述。
) F& M/ `( h1 |3 Z2 |打开之前已经建立好了的寄存器开发模板,在main.c文件中编写的代码如下。$ V6 _. z2 n( ]- `+ a6 H
0 ^, g; {! x2 A. ?- #include "stm32f10x.h" //头文件中定义各种寄存器* y# j/ F& W4 w3 Z! u5 j# A! Z
- void SystemInit() //初始化; A+ b+ {8 Y. K5 a
- {
* I& s; D# Q3 { -
: y3 C& G7 @* c" }! e/ r' d( d - }/ y/ G4 f& Z2 {7 b- U
- $ h0 m: m6 {- _
- int main() //主函数
" f, A# h# g6 x, J. m; N - {+ u9 F" h# d1 O, o4 ?
- RCC_APB2ENR |= 1<<4; //开启GPIOC挂载的总线APB2的时钟
" m D7 g/ @, e' F9 u1 v" V - GPIOC_CRL &= ~(0x0F<<(4*0)); //
5 \! A0 K4 l( ^7 }8 s8 C. s7 T5 P - GPIOC_CRL |= (3<<4*0); //配置GPIOC为通用推挽输出模式' P# o! X/ E1 y8 w+ g( ?
- GPIOC_BSRR = (1<<(16+0)); //配置PC0端口为低电平,LED亮
( R3 |' f b# c8 X; v - while(1); , S5 S# [( q! b' g& M
- }
复制代码 3 _ H8 @. |& ^3 u4 P1 d
1.2 库函数方式8 Q7 m0 z U8 t; A( O. E2 g
本小节应用库函数开发的方式来点亮一颗LED。重复上一章节中库函数工程模板的建立,对stm32的GPIO进行操作就需要将标准库中的stm32f10x_gpio.c和stm32f10x_cc.c文件添加到项目中,前者是关于GPIO操作的函数声明及模式选项配置的宏定义,后者是关于时钟的一些函数和宏定义。在这里采用模块化编程,也就是将led看做一个功能模块,在项目中新建led.c及led.h文件,最后在main.c文件中包含使用。具体的代码如下所示:$ _8 a y" P$ ~- B6 t
% P$ l/ p8 Z2 U- P
led.c! }3 Z6 Y; M' A5 R4 i8 U- K" E Z
4 X( y' F' B% w5 b0 h
- #include "led.h", [5 F, K- M: o% a; Q6 ~
6 z/ ^' R* n* c1 ]9 g3 u- void LED_Init(void)
% y. d. ]0 k# b, m - {8 o: y" i0 ]0 v
- GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO操作结构体变量2 J: ?5 f# `) m7 b8 k5 d
- + I$ }- i" L( N
- RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE); //开启时钟. w1 y7 w- `! J( c/ _' `2 M
-
: E/ o, \1 i: ~, Y; z - GPIO_InitStructure.GPIO_Pin = LED_PIN; //设置led所在IO口
3 r5 e, q2 _- b -
5 s% `6 ^# c# H; ^ - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出模式 |, r9 g' c- h4 @5 N
- ' T+ K& t+ y/ }( s( Q) L4 Q
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置传输速率) U- w7 L/ z/ @+ `' s2 z+ `
-
1 z& _5 o4 y+ r# `4 I - GPIO_Init(LED_PORT, &GPIO_InitStructure); //初始化GPIO口
) c, | }5 j+ O) y - 0 ^" H- Q: T- l" T4 B3 E- n1 E
- GPIO_SetBits(LED_PORT, LED_PIN); //将IO口设置为高电平,LED灭
3 m1 T; K; B! N8 n8 a' i8 D - # X# C/ t: l; B% _
- }
复制代码
2 N& w! e, c6 O0 l3 t5 uled.h
+ p6 P. F# y, ?! W5 ~3 |. T5 |* ~! R& F
- #ifndef _led_H
& ~; U9 O2 K8 A" Q! I - #define _led_H
+ T; V+ a" u! D8 p( w - * [3 E- D5 z1 d% x* i
- #include "stm32f10x.h"
2 R( \- D, W5 m: B - & L6 Q! i7 K$ y# {9 K
- #define LED_PORT_RCC RCC_APB2Periph_GPIOC
' e+ u; o$ P5 R, P) u - #define LED_PIN (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)1 c- p% f0 j" u6 [; E$ ?3 b
- #define LED_PORT GPIOC
* m$ _. f* {# [ - 9 n4 c: A! J% r. v E
- void LED_Init(void);( Y: o* a0 T9 v# ]. s
" T) T; p: `$ h+ m' Y- #endif
复制代码 2 I1 p/ u+ G5 T- @
main.c# D8 X3 W' c8 v
& q0 G& b9 u: @* t( J
- #include "stm32f10x.h"& Q- I5 X# I& Z4 A! J9 K
- #include "led.h"9 U- {6 e' M2 q4 Z# w6 s
- int main()( d; h% R; v- ]8 w& K
- {
$ v- m; E8 p0 J - LED_Init();
# D* y$ V) e; R$ N5 ?& D - while(1)
9 b2 Y0 O/ t6 A( [: O - {! E2 {+ g) ~$ C3 x6 ]
- GPIO_ResetBits(LED_PORT,GPIO_Pin_0); //翻转IO口电平
$ A: Q2 q% v* m- Q, B - }- {" W) f v2 N% X
- }
复制代码
/ q' n4 }& T6 `! M4 n1.3 HAL库方式
9 _( A. ?7 c$ j- j+ A# B3 d打开在上一章节中已经建立好的STM32CubeMX项目,在芯片的图形化界面中找到PC0端口单击后将其设置为GPIO_Output模式;
0 f. ]. ~9 x+ r' l$ ?. M9 c# e e% N) n; S9 D5 }
$ U: _: [7 V/ e# l+ F
4 s' U' {% e) N: X- ]* T7 r5 ] X2 ^
在左侧的System Core菜单中选择RCC(时钟配置),将HSE和LSE设置为Crystal/ceramic Resonator模式,即外部晶振;* [& R7 F: W/ J9 S
/ d- ]% f, F- }% R$ Q
/ U# T; A, ?! e9 }" i! b% L x; O% w+ G! r
将GPIO选项中的GPIO Mode and Configuration中的GPIO mode设置为Output Push Pull(推挽输出),User Label设置为LED(端口标签),其它的选项默认即可;/ f' ?" a4 b8 g: M
" |- i3 f" L9 E5 E
, f' }! y$ V3 c- S5 p
4 m9 h$ H5 f, D! x1 I
随后切换主菜单到Clock Configuration时钟配置,在HCLK下方的框中输入72,然后按下回车则系统的时钟就被配置到了72MHz,最后点击GENRATE CODE,即可生成代码;# ~ \! R' p& C8 I+ [4 X2 E; \: K# C
) r4 x) j1 J$ b5 f- Z) z
7 {" v* v4 j) ?# L- w. ]3 [8 q7 F
3 g/ {5 f0 F1 W8 x4 T紧接着在keil5中打开自动生成的项目,如下图中所示。可以看出整个项目的构成与标准库开发的方式类似,只不过这里的代码都是自动生成的。其中gpio.c文件中的代码为GPIO初始化与上一节中的led.c中的函数功能是相同的,只不过这里是使用了HAL库函数。在需要注意的是如果需要在这些CubeMX软件自动生成的文件中编写自定义的代码,是需要将其写在下述这段注释之间的,XXX表示的是某一个模块,比如Init(初始化)、SystInit(系统时钟初始化)等等;- /* USER CODE BEGIN XXX*/
7 ^) w& k: Q% W2 d+ v0 L8 m - code+ I1 F: l# o, G8 O F1 L. |
- /* USER CODE END XXX */
复制代码
3 A L4 @1 _% T( C2 f% i6 K( d
5 B; W% E7 O6 U5 F% d/ s+ |+ B0 X8 q% |* K+ x
最后点亮一颗LED,只需要在main()函数中写下如下代码即可。
9 p7 k* O: Q1 n- {* w/ n- b2 S c
- /* USER CODE BEGIN 1 */$ K0 I- m% c1 D9 u) J, `- r" v
- ) C B9 Z" x9 m$ D& \2 |' d
- /* USER CODE END 1 */2 b8 f4 G% w7 @4 Z9 j
- ' q8 h# A. n! O) w; h
- /* MCU Configuration--------------------------------------------------------*/4 u" x3 t0 w) Y, R
- 6 q; C ~/ g3 p6 I) l( @! J3 k" [
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */" V" s9 G4 k! S
- HAL_Init();5 w* g2 G; o9 h. a0 f5 M0 U
- 7 Y' Y6 ~3 N+ J2 T
- /* USER CODE BEGIN Init */
5 @3 F8 b4 P' A# c9 A
7 |1 _: I- }+ W: T; o4 W" H- /* USER CODE END Init */
/ ^1 m; T2 _5 S( p. o. H* A
5 f9 D4 B+ q6 c4 d0 @3 h+ j) i- /* Configure the system clock *// Z, v: \1 W7 T d2 @( t' S6 W6 x
- SystemClock_Config();
5 H* w6 ]% p6 m* ]6 h
& Z) U0 y- z) c0 t: l) q6 _5 t- /* USER CODE BEGIN SysInit */: E0 D! T/ {& V& i. e
- / d& Z6 `- D) a" O- n/ |
- /* USER CODE END SysInit */
4 [, G: m6 x9 s) s/ }, R U/ A) G - 1 x( l) K$ k; m% W+ }4 H4 j% M
- /* Initialize all configured peripherals */
) m* n5 y( p& P( y - MX_GPIO_Init();
4 k- _ u+ ?0 ^+ r F$ X- v - /* USER CODE BEGIN 2 */) M L8 ^8 G( O
3 R/ S" A4 F9 L3 ]- /* USER CODE END 2 */$ e W# o/ Q3 t' [! a
- 9 H4 ]! s7 |% `
- /* Infinite loop */
( G4 O# b: m- [5 w$ T$ E - /* USER CODE BEGIN WHILE */
$ R9 \* _4 g! I x - while (1)
5 H2 q% J' L; O2 {4 @+ h/ d1 U$ l - {
; j% J8 o# S$ P( }3 o - /* USER CODE END WHILE */, I2 `% s! i0 ~: V' A& c6 e
5 J8 S& \! b1 d! x1 o! k& {- /* USER CODE BEGIN 3 */+ @& m. U& c) o- \9 ^- Z! d
- HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);: u. n; h$ D7 Q1 m, ?
- }: W: d. M. m5 L: R+ Z
- /* USER CODE END 3 */ t) e. P* J" v( C% m
- }0 E6 ? ?) J" s1 s+ T
复制代码 ) c2 b6 g' h" n2 D+ W6 P
1.4 三种方式的总结与比较; a% y* A+ W, P. H- f; x
经过了对LED的简单操作,可以发现在这三种方式中,寄存器开发方式最为复杂。其在编写程序前需要查找好所使用寄存器的地址等相关信息,出现了bug时很难精准的定位、开发周期长的缺点,因此寄存器开发常常不会被采用。标准库方式在其基础上简化了很多步骤,只需要通过调用相关外设的接口函数便可实现对应的功能,因此这一种方式也被广泛地应用到stm32单片机的开发中。HAL库开发方式由于CubeMX软件的存在,可以以图形化的方式对各种外设进行配置,大大方便了配置的步骤流程相比较于标准库来说还要更加便捷。
- z7 r- {0 b, h
* S5 J, F0 s+ |- \! p1 r+ N0 [2、无源蜂鸣器的使用
) p% z3 U; G4 b! M2 R% q蜂鸣器是一种可以发声的器件,无源蜂鸣器需要对其提供1.5~5KHz频率的脉冲信号,其才可以发出声音。蜂鸣器电路的设计如下图中所示,通过一个三极管将单片机IO口的电流放大后再驱动蜂鸣器,这一点是为了避免出现一个IO输出电流过大导致其它IO口的电流过小的情况。从电路图中可以看出,只需要控制PB5口输出一定频率的脉冲波蜂鸣器即可发声,详细的代码如下所示。
$ K7 e& F1 u! @" t' {
1 i: _" x1 T/ ], @- T: g
, J2 A A# Y, L3 w2 M
$ L1 e; J1 X }( B4 ebeep.c
0 o4 {2 v/ E) `, b1 E f: p+ Z! J; \2 x" L( _
- #include "beep.h"
" {' i4 ^3 M; U9 W3 M& Y7 v" P - - D/ J Z5 @7 x( Q/ `: G
- void Beep_Init()
9 q! l& a4 O1 H- L1 u8 w - {
2 F! a4 ?2 d; e) h- [ - GPIO_InitTypeDef GPIO_InitStructure;
! X, D! A! V. H: g7 u6 m0 G* T! } - # W# G+ Q; w9 q
- RCC_APB2PeriphClockCmd(BEEP_PORT_RCC,ENABLE);
+ W3 B9 ^% e5 L: f* y2 I -
7 V$ t, Q1 K9 M - GPIO_InitStructure.GPIO_Pin=BEEP_PIN;6 Q) f! i1 E& _, ?' X6 |+ G
- 8 @: L+ s+ V2 A5 c( b: \9 Q
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;: o+ V$ s, w+ N
-
6 l0 z8 x* H p - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;5 o, h! N* N9 d; }: |' H
-
u1 I( R# U. {( `+ t - GPIO_Init(BEEP_PORT,&GPIO_InitStructure);
# W6 U/ L7 E2 s% k - }
复制代码
`, t' M, s4 y/ y$ G" i5 pbeep.h
* \5 O- n; P* T; v1 W. L w( H9 i; s$ k" G- J1 {* }2 r
- #ifndef _beep_H
& U7 a2 u7 J9 F8 b+ Z. z7 o# x - #define _beep_H* E* O& |8 Z: M1 O
3 {9 b j' h. |" ^, v9 ^- #include "system.h") `% E; V- f9 L
- ! V( U0 W9 O$ v2 o: u6 ^7 D# c
- #define BEEP_PORT_RCC RCC_APB2Periph_GPIOB+ R, g- M% w M
- & P8 n: l3 H$ |' K/ w, n: V
- #define BEEP_PIN GPIO_Pin_5
# [/ F# O" ^+ X$ c - ; v. Y% [$ d/ k( N
- #define BEEP_PORT GPIOB
8 ]5 ]( a; V8 j, \' \ - void Beep_Init(void);
9 U$ y8 L' [; `4 r2 H - , K5 j; y& c/ z" r
- #define BEEP PBout(5)
" J! I2 ^1 a" m9 n4 k7 P' n
5 ~! `4 o2 I% r9 h- #endif
' p! e( v# B! o# e9 i
复制代码
1 S: |- b: P. ~! A" j+ T5 L0 u9 L3 |main.c
+ d9 W3 q- I' _1 S2 u4 F- j# w/ g
- #include "system.h"/ ~, P: }; g) B
- #include "led.h"
6 E) P1 T; _& A, S - #include "SysTick.h"
( l/ Q. V4 Y ~7 n" |( Q6 R - #include "beep.h"+ I) R3 o8 P/ [) `) m
2 J/ N1 u. L, r0 t' K7 S1 W- int main()6 w: U; }7 W M$ q
- {' `, N9 X$ @) z6 o7 q! D* j
- u16 i=0;
3 Y" p* v! V6 n3 X; n6 |0 d/ E - SysTick_Init(72);
2 _" P v: t- o% e/ `( k; u. M - LED_Init();0 H+ B9 R: K! b9 `
- Beep_Init();
$ D$ i6 n$ O& c' Y" d; q! m - 3 _! N& I4 x) q8 y
- while(1)# T! @! l9 b9 W& e
- {
; \9 k9 r6 u* F1 @7 H. L - i++;
' q& o+ h0 v( U4 ~! G$ @ - if(i%10==0)
4 ^2 a) T& g- G - {
7 g2 p* `4 Y+ R- R* \ - BEEP=!BEEP;" v; u3 T1 V7 q- L% ]& b* {
- }. ]# l& ]! C) P6 d- z
- delay_us(10);; K1 O8 l7 J7 @2 N! n
- }& O9 r" S7 \6 Y/ E$ U& s9 U) H1 o. _- P
- }
复制代码
: m4 N" @) f- ?) r; {4 Q0 n2 a3、无源蜂鸣器的使用! h) q% [# f5 \4 r1 U0 k
数码管是一种发光器件,基本单元是发光二极管,本小节中用于数码管显示的单片机外围电路如下图中所示。数码管是一个8个LED组成的器件,因此要点亮数码管和LED方式相同,给相应的IO口一个低电平即可。此外,如果需要让数码管显示指定的数字,那么就需要同时点亮对应段的LED。比如需要显示数字“0”,则需要控制a,b,c,d,e,f段对应的PC0~5端口输出低电平即可。
+ {' r9 e: _- W0 \3 M0 p, ~2 i. I) p9 Y
$ L0 A. ?# ?$ P- ~: ~; u" u
4 Q' N3 ^/ j! v+ X/ v( F/ n" bsmg.c" h* e1 u. u8 f
) A' @# J+ Y7 O- r4 r3 h
- #include "smg.h"0 Y( X; i- {$ Z" R6 v' r
- 6 F3 e' N$ t5 r# [1 V
- void SMG_Init()
) @* g* ~* k \0 W; }! U - {! O/ M1 O' O8 O4 S; S3 q9 o8 d
- GPIO_InitTypeDef GPIO_InitStructure;& W; N4 s2 ?( ]
- ! x) d- G' J5 W# \% n; V' }# ^
- RCC_APB2PeriphClockCmd(SMG_PORT_RCC,ENABLE);, W: W2 a1 p" J6 _3 J5 Z* b2 e
-
, s/ G ?$ W4 w6 e. F' j& s - GPIO_InitStructure.GPIO_Pin=SMG_PIN;+ y4 D7 O/ ]0 Q4 Q
- % S: _+ v: s# p# `: r
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
4 }" D% l# `, U -
4 l4 @8 A, A" D1 j3 |4 | - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
9 k: k' O4 P! u; x -
1 a& a H `% U! Z - GPIO_Init(SMG_PORT,&GPIO_InitStructure);
7 u. V* l8 r* H -
& {" R$ n/ t% I, a- f. i( b - }
8 V0 h/ ~; U$ W7 \1 ?' X1 l7 p
复制代码 9 S' N7 x. N; k% C9 p/ [
smg.h1 W/ C/ r- P2 P
' j) L7 Y/ p8 {! `% j D
- #ifndef _smg_H
# [* T, y3 g& X, \; W7 J - #define _smg_H9 d5 F; A* i! x4 W+ x7 H) _
- 8 x$ Z3 H1 q. T/ |0 {
- #include "system.h"
, a; F6 P1 r8 Z4 t2 i
4 }5 }3 m. R% i- F. r G$ F" ~- #define SMG_PORT_RCC RCC_APB2Periph_GPIOC
1 \' n6 j, O! {6 G, R3 N - 0 ~ s& g7 a* U& ]. \( Q
- #define SMG_PIN (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
/ _! K" Y& {: | d# x/ [5 | - + v* s3 f; l4 ~) Z$ \, Q
- #define SMG_PORT GPIOC2 \; h2 H$ C8 ?% q* b; E8 P
- void SMG_Init(void);* U7 G% E4 I. T$ q
- 0 q2 l) w0 J8 l5 ]4 L$ {
- #endif
复制代码
. A. [7 T/ D' N' h) a C4 S& Imain.c
, C. @; e9 J9 U' U/ B4 L R6 D1 r& _" u5 X" T* v
- #include "system.h"
6 n" d: ^+ i- \4 {9 O | - #include "led.h"
; P+ R7 J" ^& { C) `% K2 @8 L s - #include "SysTick.h"; \0 \0 c; @/ _8 \7 V6 \0 F% z+ \
- #include "smg.h"
8 s0 C0 A$ D7 w. K" t; C3 ?+ _ - . t, s( a6 |" S' d8 A' E
- u8 smgduan[16]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07,+ d4 v' f' P) r) F
- 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71}; //数码管显示0~F的编码# A$ `2 G! Y) w. j) V
( P2 P) r) d' E! }& N* o- int main() S2 K5 j, D/ S
- {
3 P2 k% _* H* t" a6 P V - u8 i;0 i: g1 R5 Z# h
- SysTick_Init(72);8 K% |! \$ S% F
- LED_Init();
- v5 B6 j( Y6 N: f3 c# k3 \ - SMG_Init();
) [ Q* G1 k- T+ H/ U& }$ Q9 K - # Q: A- V+ x! c: d
- while(1)
6 n, {8 p7 @" P$ o1 q - {# c$ |* L1 E" s
- for(i=0;i<16;i++)
1 ^' W9 ^! p5 j+ j5 m - {
F( e% y4 W; z - GPIO_Write(SMG_PORT,~smgduan<i>);! x7 }7 s8 h# e+ Z: V* A, z9 A! l
- delay_ms(1000);
2 q) n; U7 W/ a. Y+ X; a% [$ | - }
% d) R, r! |& b+ K$ R - }
3 T; {* F- g8 I! x - }
# `* ` V# M) f5 l, n - # `- T( g& l; k7 N1 v9 k: I# S! {
- - ]0 i# E7 a0 R! \( A& g
- </i>
复制代码
/ f2 d2 W- P. P# y% f% K7 X' r( }. J0 t: t1 `
|