STM32单片机GPIO口点亮流水灯
3 D# o: z/ C: d* y# |任何一个单片机,最简单的外设莫过于 IO 口的高低电平控制了,本文将通过一个经典的流水灯程序,了解 STM32F1 的 IO 口作为输出LED:DS0 和 DS1 交替闪烁,实现类似流水灯的效果。
) ?: G a/ s+ K/ r+ L" p
l) N5 w% m H4 h _* D8 O+ ^STM32IO口简介( {. {6 v3 i* V* W( R9 Y4 P* L
本文将要实现的是控制ALIENTEK 精英 STM32开发板上的两个LED实现一个类似流水灯的效果,该实验的关键在于如何控制 STM32 的 IO 口输出。了解了 STM32 的 IO 口如何输出的,就可以实现流水灯了。
3 `( i( S% d' WSTM32上有众多IO口,每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。每个 IO 口可以自由编程,但 IO 口寄存器必须要按 32 位字被访问。STM32 的很多 IO 口都是 5V 兼容的,这些 IO 口在与 5V 电平的外设连接的时候很有优势,具体哪些 IO 口是 5V 兼容的,可以从该芯片的数据手册管脚描述章节查到(I/O Level 标 FT 的就是 5V 电平兼容的)。
7 `4 N, k n( q) T' A# n3 T5 Y
9 x- n) M/ S( Z; W7 G/ |
8 Q( r+ ^& h; ^' Z
$ c5 f4 f. N% M5 {9 C% ]STM32 的 IO 口相比 51 而言要复杂得多,所以使用起来也困难很多。首先 STM32 的 IO 口
8 L7 `; v ^4 P2 q' J8 C6 D( K可以由软件配置成如下 8 种模式:
2 ]- D- t* s5 a1 h: P1、输入浮空
% c% Q; {- T# h1 @3 R6 H2 n" ^9 |: L+ T2、输入上拉
* v& o( v) ~2 ?1 c; v5 N3、输入下拉0 d; c4 ?' I7 y
4、模拟输入
! w" D9 o; ^9 ^# y/ R5、开漏输出
( g4 }! a$ h' V6、推挽输出 ~/ v0 ]1 }+ V/ U0 v1 q
7、推挽式复用功能
- Y, E2 h/ N5 f" V `3 ]8、开漏复用功能
- i# ?4 G) T& `% o4 n5 A$ q1 e
+ Q4 W( F8 l8 N2 R
5 F7 e. o: D- }6 d" ~
' r- I7 s5 C7 g1 _2 z1 O
STM32 的 CRL 控制着每组 IO 端口(A~G)的低 8 位的模式。每个 IO 端口的位占用 CRL 的 4 个位,高两位为 CNF,低两位为 MODE。这里我们可以记住几个常用的配置,比如 0X0 表示模拟输入模式(ADC 用)、0X3 表示推挽输出模式(做输出口用,50M 速率)、0X8 表示上/下拉输入模式(做输入口用)、0XB 表示复用输出(使用 IO 口的第二% d/ T4 y" m/ n! B# k2 x1 m
功能,50M 速率)。CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8位输出口。! P7 x8 e7 P/ W. R! n) }
这里我们点亮流水灯是使用库函数进行配置GPIO口的相关参数。
4 d& F% d, L" _- Z& ]# }
" T- A2 F% K. V7 X* s- d; _# ~5 S2 R l
基于固件库函数配置GPIO口
6 }3 W. \/ y. t& k! u8 @GPIO 相关的函数和定义分布在固件库文件 stm32f10x_gpio.c 和头文件 stm32f10x_gpio.h 文件中。9 N$ U. V/ k/ l* R/ y* o
在固件库开发中,操作寄存器 CRH 和 CRL 来配置 IO 口的模式和速度是通过 GPIO 初始化函数完成:
1 ?7 q8 e4 k: X7 c- void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);- Y8 w- @6 J% S. b' p; s% @
复制代码 ! n7 a4 x. S `0 {5 f6 ?( }
这个函数有两个参数:0 n/ z$ p$ I# }- i, X
第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。( V/ V( V) x& u
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。结构体的具体定义如下。
' H3 X3 k/ L1 t' A& O# V- typedef struct
( G8 R5 X _; h2 z+ L' ~- C% h5 \ - { 0 ]3 @* m/ X0 x g: _! S$ S$ E
- int16_t GPIO_Pin; ) D3 s8 `$ d; Y+ X; J' N) A5 J
- GPIOSpeed_TypeDef GPIO_Speed;
' |3 Y9 V7 g$ A' t% o O( L - GPIOMode_TypeDef GPIO_Mode; ; {* @) X! K; E# W1 ~3 ]: }+ ~. m
- }GPIO_InitTypeDef;
' d2 {2 q, e1 o% z. _3 I
复制代码 4 ~' N& a$ v/ q! w9 S5 y
那么如何具体配置一个IO口呢?) V8 c2 g5 g3 U( e7 d; S
初始化结构体初始化 GPIO 的常用格式是:4 S; q3 G, F! q+ M; h( E4 `
- GPIO_InitTypeDef GPIO_InitStructure;
! u) ^, `; o2 g! C - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置6 p, o0 H. ? q4 ]5 M
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出1 P- F& T/ z* F( H% p, V
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz
& n" `* f" {1 \ - GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数配置 GPIO8 Z0 M" o& Y* R# h. K
复制代码
9 P/ j$ W- f4 i5 B7 z ^& z上面代码的意思是设置 GPIOB 的第 5 个端口为推挽输出模式,同时速度为 50M。从上面初始化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来设置是要初始化哪个或者哪些 IO 口;第二个成员变量 GPIO_Mode 是用来设置对应 IO 端口的输出输入模式,第三个参数是 IO 口速度设置,有三个可选值。GPIO_Mode和GPIO_Speed均是由枚举类型定义。具体定义分别为
% t2 ~5 W1 h# R, O! l; ]" e( h p `7 h, G) n, J4 h) q3 ~% X1 i
GPIO_Mode:$ R1 u* W6 ^: ~# G
- typedef enum
; m! M; i6 Z5 R8 W+ J, X8 h - {
! P2 r& V9 ^1 t! ~/ ^8 x - GPIO_Mode_AIN = 0x0, //模拟输入; f1 I' ]1 d# o9 I
- GPIO_Mode_IN_FLOATING = 0x04, //浮空输入' a* X. [6 D1 }' o! f
- GPIO_Mode_IPD = 0x28, //下拉输入3 d/ |2 H7 }1 Q0 g
- GPIO_Mode_IPU = 0x48, //上拉输入
7 j- {1 O% n6 G+ E1 W - GPIO_Mode_Out_OD = 0x14, //开漏输出4 c9 J" V7 x9 a( k9 v1 ~
- GPIO_Mode_Out_PP = 0x10, //通用推挽输出* r/ S& i) r& f0 F+ K
- GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
/ A$ L! ?4 R+ G: Z7 g9 T. } - GPIO_Mode_AF_PP = 0x18 //复用推挽2 ]' ] v3 [% n+ g4 m
- }GPIOMode_TypeDef;5 k$ c% I) v" r' S
复制代码 9 }5 j7 }" C4 |+ `% |- ^8 {# ?6 K
GPIO_Speed:; P' v* Q( k% ^( G
- typedef enum+ _+ O* s+ V3 y, F
- { : b' E: Y/ m# ?7 ^# Q
- GPIO_Speed_10MHz = 1,
! e7 e& V7 [. k% h - GPIO_Speed_2MHz, ' I& }9 D$ \' [( ^! `/ k! P
- GPIO_Speed_50MHz$ q3 Z# }2 K' Y2 {! P% U& t
- }GPIOSpeed_TypeDef; T0 e) w2 R6 L
复制代码 / a$ y' w/ r3 c3 P1 q. Q! e3 B7 P
这样我们就可以初始化好一个GPIO口。
}* v3 K1 f& e+ P
+ z+ Y8 g" w1 K2 `: }% x8 E7 p! b/ H1 a% }
实现流水灯的基本代码1 Q3 p. G) \9 D. q5 W* k
想要实现流水灯,要先了解控制GPIO口输出高低电平的函数。
; d) w/ T2 {* Y x8 |& G7 }; v* [9 b! E% [
8 `& c7 I- o# e* C# q输出高电平- GPIO_SetBits(GPIOB, GPIO_Pin_5);//PB.5输出高电平
1 `. C" x0 b: j0 ^9 A, [
复制代码 / x$ K/ I. x G* E6 G3 @) r6 I
输出低电平
' e/ |( p; |: D- @+ m7 a% W4 x1 v- GPIO_ResetBits(GPIOB, GPIO_Pin_5);//PB.5输出低电平, }& u6 @4 j9 M, j' ]1 b
复制代码 7 b" W' M* |: w' y
使用IO口的基本操作为
# F) Q* _4 {6 q3 H4 t5 u& C8 {1) 使能 IO 口时钟。调用函数为 RCC_APB2PeriphClockCmd()。 2) 初始化 IO 参数。调用函数 GPIO_Init();
: M+ [- q3 W! ~2 \2 _( W, o7 R1 n2 |3) 操作 IO。* N0 Q4 W4 T5 y* a1 O2 g
; B$ ?$ c2 A$ y9 a, N% d" a# G/ X m3 _7 D$ V4 ]" m
实操一波: Q$ M! h" _+ x' Y2 e
先建立led.c和led.h文件
a8 L, |& n7 j2 M! @# i* D- D4 M- #include "led.h"6 {6 W0 E) M) v* h5 r+ o- M4 g5 T
- //初始化 PB5 和 PE5 为输出口.并使能这两个口的时钟
: Q1 h. R0 @, b: Z! s* N - //LED IO 初始化
% x2 _1 Y5 z: G7 z& \+ _8 D1 J - void LED_Init(void) B8 ]# {% o8 Z% G" \
- {, V/ N" |! H; }7 v% C3 Z
- GPIO_InitTypeDef GPIO_InitStructure;
, a- ]) m7 _6 j/ ]* m - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
4 S$ ?4 d. z( E6 W. Z - RCC_APB2Periph_GPIOE, ENABLE); //使能 PB,PE 端口时钟
3 l% u3 S5 ^) z - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 推挽输出
& t2 S) |2 {4 r# y% s0 Z8 G$ L - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出3 v0 X6 R$ o6 c9 t$ k5 `5 F
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;- A/ v# I3 G I1 o
- GPIO_Init(GPIOB, &GPIO_InitStructure);
8 X, x7 f F- a2 Y- ~ - GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高' D2 {5 j( v* R2 G3 C0 a# k# D8 B
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 推挽输出8 ]* t; D) Z% W
- GPIO_Init(GPIOE, &GPIO_InitStructure);; t3 {& I8 d' W
- GPIO_SetBits(GPIOE,GPIO_Pin_5); /PE.5 输出高
5 E. e7 R9 r) F4 R1 m; f - }4 C8 Q: B6 ]5 ?2 p! z
复制代码 + T$ }9 u. ]) N# [$ w9 M4 H
在led.h文件里,可以使用如下宏定义
2 L! U) V. \# Q4 E- #ifndef __LED_H
$ u0 E3 y4 d" q. l4 ?0 z& v - #define __LED_H
* ?: y3 j- f5 K$ s - #include "sys.h"- d; S1 ?% h& g7 q8 ?
- //LED 端口定义
) |" ~3 Y4 h, k: i6 ^ - #define LED0 PBout(5)// DS08 V" f+ g9 [* m4 }$ \1 E
- #define LED1 PEout(5)// DS10 j* p& H4 ?: s- A
- void LED_Init(void);//初始化
0 t R! z! \- E/ ?0 i, C - #endif
+ {) L; y. b: a
复制代码
- k3 @6 m: G4 h1 D5 t+ Y* e* R W这样就可以实现位带操作来实现操作某个 IO 口的 1 个位
q+ _4 L$ b4 Q/ U' I( U! `/ `1 c6 d' f0 c8 F) \
我们在main.c里写下主函数
3 u1 ]" u: C8 ?4 E, l7 V- #include "led.h"! Q# {& o; k7 S- {& m+ I$ S$ m
- #include "delay.h"
# M$ B C. O* m6 N - #include "sys.h"% y. [( ~- H& W
- //ALIENTEK 精英 STM32 开发板实验 1
; `/ }0 l5 W( B) M - //跑马灯实验
3 s! T1 [2 U. U7 @ - int main(void)
- Y, t' D6 }8 o3 V9 b" N+ ] - {$ d" g$ H( q# L
- delay_init(); //延时函数初始化
9 a( z- Z( r6 f7 |1 d* ~4 m6 r - LED_Init(); //初始化与 LED 连接的硬件接口7 V) o6 D T' S, i- x. c, U
- while(1)5 J9 U1 t/ j6 u, L4 L7 q
- { 4 j% b) ~5 p3 w+ O0 K2 ?" D
- LED0=0; 7 K' c t! b: _1 C) H# I
- LED1=1;7 k+ y( p- ~- y, u) a# y
- delay_ms(300); //延时 300ms. h9 C# _* ~' D* J
- LED0=1;
+ H, M* k9 c1 s0 }& H+ \6 G - LED1=0;6 X ?! M7 S" R' y5 I/ G) \ B
- delay_ms(300); //延时 300ms) u a$ F8 R5 D. |- {
- } : v3 Q8 B2 O& x* t; G' L& V
- }
. l1 }9 G: D( O2 H/ L/ P9 `
复制代码
' R7 y; j6 |; o4 D4 l! i J如果没有使用宏定义,也可以使用输出电平函数进行设计8 K5 d; R. N: m2 [( z" p
- GPIO_SetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 1,等同 LED0=1;! e& t* q3 a. Q e" G( O
- GPIO_ResetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 0,等同 LED0=0;+ I" H9 {/ q( v1 h: K. X, \( p1 A
复制代码 + n5 {: s# D/ F
注:9 z- n$ A: `" B8 K; o+ Y
由于stm32精英板的led灯只有两个,如果点亮外设的led灯,最好注意一下IO口的选择,许多IO口有多种功能,在选择IO口上可以考虑那些,未接任何外设,完全独立的IO口。* y4 {' v& D& [7 A7 b$ C0 s" k
————————————————9 O* ?" O* H* J1 ~4 r3 l
版权声明:Pessimist~
& C. z9 G: W) k1 O. @如有侵权请联系删除
L N% S* f; p9 `
' t9 \ j3 F. w- y8 B# u* K, x" \$ m& c7 C+ c( h6 ~" X
$ W, R% ^( t4 g) R! D. G6 z
|