STM32单片机GPIO口点亮流水灯: S0 e! W) q! I! @+ }! \( _
任何一个单片机,最简单的外设莫过于 IO 口的高低电平控制了,本文将通过一个经典的流水灯程序,了解 STM32F1 的 IO 口作为输出LED:DS0 和 DS1 交替闪烁,实现类似流水灯的效果。& g; v3 G2 o6 [9 B% C# c
% S. U$ F2 O+ zSTM32IO口简介, |5 C* c, i! y( y
本文将要实现的是控制ALIENTEK 精英 STM32开发板上的两个LED实现一个类似流水灯的效果,该实验的关键在于如何控制 STM32 的 IO 口输出。了解了 STM32 的 IO 口如何输出的,就可以实现流水灯了。
+ L2 U& F7 l7 d! H9 o# _" J1 CSTM32上有众多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 电平兼容的)。* ?, P- o; [) z% T* f p6 f9 p9 Z
7 o: |. k/ K: r1 P1 C* A" U
2 U) {0 I& j1 B
) Z" f3 S; m" M T4 F/ g1 V# ZSTM32 的 IO 口相比 51 而言要复杂得多,所以使用起来也困难很多。首先 STM32 的 IO 口: c% R8 ^& A& ~
可以由软件配置成如下 8 种模式:
! V9 u; J p) `' | B1、输入浮空
9 M6 \: ~3 |' R. I" A7 C' o2、输入上拉 f* B) D. q# G4 N
3、输入下拉
# `- S; F* x" n9 O6 j# f4 ]( B* B" O4 D4、模拟输入) i$ Z) [5 T* |
5、开漏输出
6 O, _& O$ @4 O. [+ J8 N* b6、推挽输出
2 O/ z4 `+ ?& b$ d/ `+ s& F7、推挽式复用功能4 ]6 Z5 U D% q2 I9 m3 \
8、开漏复用功能
+ b& z2 j7 b# w! |8 W
8 ^! k* F+ m" ?6 q) b6 U6 `9 a
6 `( Z3 {$ d9 e- ^* D8 y I! ~4 d- ?
STM32 的 CRL 控制着每组 IO 端口(A~G)的低 8 位的模式。每个 IO 端口的位占用 CRL 的 4 个位,高两位为 CNF,低两位为 MODE。这里我们可以记住几个常用的配置,比如 0X0 表示模拟输入模式(ADC 用)、0X3 表示推挽输出模式(做输出口用,50M 速率)、0X8 表示上/下拉输入模式(做输入口用)、0XB 表示复用输出(使用 IO 口的第二3 w" u! R$ ~1 E
功能,50M 速率)。CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8位输出口。$ }" G. z7 ?( {
这里我们点亮流水灯是使用库函数进行配置GPIO口的相关参数。
. V4 [9 R" @' [) n* D2 x [! m0 e, V, n
5 W& n9 _, Z6 D: G
基于固件库函数配置GPIO口$ }; M" {9 h( o" F& z# W! {4 h2 ^
GPIO 相关的函数和定义分布在固件库文件 stm32f10x_gpio.c 和头文件 stm32f10x_gpio.h 文件中。
; P4 b7 M! E+ s. [$ W. Q0 s在固件库开发中,操作寄存器 CRH 和 CRL 来配置 IO 口的模式和速度是通过 GPIO 初始化函数完成:8 M! G2 y( d: i
- void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
" E$ U; A/ H& F/ k
复制代码 * N+ x, n4 x3 I) T
这个函数有两个参数:
8 c, S. c3 L \" d+ p# H- V- L第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。; C8 i, ^5 ^- u: f( n% Z; l
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。结构体的具体定义如下。
$ L3 r$ U& v% a- typedef struct& u2 E9 F! ?5 H5 j
- { ( g* @# I: y$ c- r( _( f- w R
- int16_t GPIO_Pin; 8 k z/ ]" a7 ?1 F! K
- GPIOSpeed_TypeDef GPIO_Speed; ; Q: F3 h" F" e( j) b ]/ K8 b
- GPIOMode_TypeDef GPIO_Mode;
- n3 g& h4 [' g; M* } - }GPIO_InitTypeDef;
4 e# v* R: g& m4 x; J( f: _+ L+ i, K& x
复制代码
. q6 _. ?, A. o5 b9 Q# j n ]% T那么如何具体配置一个IO口呢?
! o2 X( |6 L9 Q! h初始化结构体初始化 GPIO 的常用格式是:; j9 b7 J0 S6 T4 d
- GPIO_InitTypeDef GPIO_InitStructure;# ?/ d- @2 {$ t* n
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置
; h& @" [; w, G7 V, a& g+ N: \3 L - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
. J- n K; _2 q) H* Z- o - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz, W0 k+ N9 E, V v% W! p
- GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数配置 GPIO
9 n3 S2 | T9 r0 _0 n
复制代码
; R! C B: S6 g/ j/ i: v上面代码的意思是设置 GPIOB 的第 5 个端口为推挽输出模式,同时速度为 50M。从上面初始化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来设置是要初始化哪个或者哪些 IO 口;第二个成员变量 GPIO_Mode 是用来设置对应 IO 端口的输出输入模式,第三个参数是 IO 口速度设置,有三个可选值。GPIO_Mode和GPIO_Speed均是由枚举类型定义。具体定义分别为
. @0 t4 _! S v' A8 t" Y. g i1 `! j6 @+ P8 ^# A
GPIO_Mode:
' @; q6 X1 f* H1 ~4 A9 p- typedef enum
: Y8 a" S# {9 W1 s; u7 L) H. A - { ; u1 i- s9 C- A4 R' {/ z
- GPIO_Mode_AIN = 0x0, //模拟输入
$ F& U0 H: Q$ B" ~; r$ @ - GPIO_Mode_IN_FLOATING = 0x04, //浮空输入8 i+ F* g7 |9 s% c" {
- GPIO_Mode_IPD = 0x28, //下拉输入( ~4 [/ n- ~( t# j3 R4 O F1 [
- GPIO_Mode_IPU = 0x48, //上拉输入4 R" V! K; @7 O
- GPIO_Mode_Out_OD = 0x14, //开漏输出8 o. Z7 E; o& ~9 |9 L
- GPIO_Mode_Out_PP = 0x10, //通用推挽输出4 c; j3 p0 k$ l x
- GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
% R/ L5 u, i @6 F7 R; w - GPIO_Mode_AF_PP = 0x18 //复用推挽) |6 Z& @' {* v9 O% K1 F1 _
- }GPIOMode_TypeDef;) ~) ^) C4 \1 ]+ _# V3 U9 r1 W8 o. [
复制代码 . K$ l& J( @* }" g: f. f, y
GPIO_Speed:
1 A1 {1 t, t5 _: o- typedef enum: o2 m; i9 ?, u5 Z+ ]
- {
?9 J! x9 ~/ {5 H4 G6 Q% z - GPIO_Speed_10MHz = 1,
+ V ^. a( A* }7 [. Z6 u: z, U ` - GPIO_Speed_2MHz,
/ Z9 Y1 C5 d& {9 d: y' H - GPIO_Speed_50MHz
# q1 `4 y, y4 W k2 }+ X - }GPIOSpeed_TypeDef;
: Y& k; j! \/ X; S" h" X
复制代码 * t% _) C( C8 o n, X; {! J' y* w
这样我们就可以初始化好一个GPIO口。5 `* @+ q% _2 S" g! H1 M6 }9 v
. H2 H* C3 O3 n: w/ ~8 h }2 B
; ^+ w+ ]0 S/ A$ c实现流水灯的基本代码
7 z* [$ R% _) t* \想要实现流水灯,要先了解控制GPIO口输出高低电平的函数。1 z" F, u8 n& o9 O+ u: \
* b9 W6 \6 R" z! W
& i& P n5 G. |: m1 J
输出高电平- GPIO_SetBits(GPIOB, GPIO_Pin_5);//PB.5输出高电平
- ?8 |" J2 n/ }; l6 }" D; z
复制代码
9 `2 f" d9 r2 u6 f) t0 @& R6 ]输出低电平$ N8 h. Y u/ Y% |6 Y
- GPIO_ResetBits(GPIOB, GPIO_Pin_5);//PB.5输出低电平
5 F9 c, V s7 Z( _# S
复制代码
6 _$ k# r" u+ T0 R- e: t. p使用IO口的基本操作为
' d3 W/ v% a n; z: ?& K- P1) 使能 IO 口时钟。调用函数为 RCC_APB2PeriphClockCmd()。 2) 初始化 IO 参数。调用函数 GPIO_Init();
- q7 Q* W; B7 c3) 操作 IO。
: I. ^' ~! J% T' }$ m! i
4 D* B& O1 D6 e$ v1 [. y, s4 {* A# A( n( t: V! G( Y
实操一波
2 F1 I4 i7 {8 ^( K先建立led.c和led.h文件; V5 Z6 V* a% z# y* m$ O0 X0 c
- #include "led.h"
) ~! F) p0 S5 j - //初始化 PB5 和 PE5 为输出口.并使能这两个口的时钟 ! d! N8 [5 v4 q+ _( N* @
- //LED IO 初始化
5 K2 C/ G2 T. v0 N3 }* `3 R! f - void LED_Init(void)
: _# O9 {( F* y0 p - {
' ^( j4 _8 Q- c- l) u - GPIO_InitTypeDef GPIO_InitStructure;% u- B K. v* d, U0 y3 J, s
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|3 b% d1 d) V) E! O
- RCC_APB2Periph_GPIOE, ENABLE); //使能 PB,PE 端口时钟
. c6 C4 q/ k) p% } - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 推挽输出
9 j! m R5 ?' @ - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出& a; f' ?' Z. ^- a/ I
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;* o0 b; @: P. D7 h$ n4 A# c! u
- GPIO_Init(GPIOB, &GPIO_InitStructure);
& c8 |) q7 B$ e; ~/ H7 y - GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高6 F( X, h9 P# z" Y
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 推挽输出8 W6 G; X4 i+ O0 v4 \! q" [
- GPIO_Init(GPIOE, &GPIO_InitStructure);2 l! E/ ~8 h* p$ S N- G# }
- GPIO_SetBits(GPIOE,GPIO_Pin_5); /PE.5 输出高4 Z M% Y+ t! \* o! x
- }
) }1 P% C0 ]& C3 i( c7 B5 u
复制代码 * h# V& x7 i9 Y# R- Z- N
在led.h文件里,可以使用如下宏定义/ A3 b$ E/ g l
- #ifndef __LED_H% G8 q$ Z: \& H8 C! ?
- #define __LED_H. R) Q) q* ?' K
- #include "sys.h"
$ m/ K+ _8 z$ c" A8 J. c! H, ` - //LED 端口定义
" N, ?# r0 o l8 b5 h. t7 ` - #define LED0 PBout(5)// DS0
. t5 c' Z( w- v) x8 X& }3 x6 K8 O+ B - #define LED1 PEout(5)// DS18 w4 A9 k* d3 n9 H3 U; l: Z
- void LED_Init(void);//初始化 # m3 ]1 w& K& O) v( e
- #endif
5 Q; a- D/ s! ]& P
复制代码
4 t6 H1 s& g8 _* W这样就可以实现位带操作来实现操作某个 IO 口的 1 个位
9 a9 W3 W6 m, p) I' p
/ E. C4 @2 U$ M4 d1 F我们在main.c里写下主函数) F, z, S! U# ^7 F
- #include "led.h"
0 l5 O0 H% ]5 M - #include "delay.h"7 m) `+ P7 t) U4 ?2 d* i; _
- #include "sys.h"
2 P! k1 v) a* k - //ALIENTEK 精英 STM32 开发板实验 1
% ?. B9 a5 v Y6 g0 [ - //跑马灯实验 " z7 b( z$ a+ P: D* P; f: _9 x
- int main(void)
8 T4 |2 u/ v. V# _ - {) S2 K5 L6 e% d8 |# Y+ H& `
- delay_init(); //延时函数初始化 / l& P. r5 X: V3 ?5 g
- LED_Init(); //初始化与 LED 连接的硬件接口/ V% c }4 b0 Z
- while(1)1 O# B: G& a/ [$ r" p9 x
- {
( I6 W1 h- o9 D - LED0=0; 5 g4 }: _& |6 j% ?9 K3 a. y
- LED1=1;+ h. T- k& V& f) t6 h
- delay_ms(300); //延时 300ms" ]% {7 @5 b/ P7 {
- LED0=1;
3 c9 q9 I6 _9 @( |( L4 ^) E# E - LED1=0;
" B2 P; Q/ l! F - delay_ms(300); //延时 300ms
4 s$ J$ F- g8 N6 k( s - } % E+ d% E0 h: F1 b4 L
- }
. T5 ]% z, F; y9 {2 l, |3 |1 j
复制代码
9 Q) o0 T1 R0 f如果没有使用宏定义,也可以使用输出电平函数进行设计
4 V5 j% `$ D) h& C8 ?- GPIO_SetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 1,等同 LED0=1;
& H; C3 b+ p6 |+ V9 a3 H/ W - GPIO_ResetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 0,等同 LED0=0;
8 F8 u+ [0 C7 T; [& G3 X2 Z! U
复制代码 5 i+ M) h' }) B* ~7 X
注:
+ K! O1 @$ ~! p9 Z( z由于stm32精英板的led灯只有两个,如果点亮外设的led灯,最好注意一下IO口的选择,许多IO口有多种功能,在选择IO口上可以考虑那些,未接任何外设,完全独立的IO口。
& _% X9 A g# C9 s————————————————
$ P2 b+ Z- u$ ]6 j版权声明:Pessimist~
" ?! B$ `4 g }( U* q. ]如有侵权请联系删除/ B" G& f# n6 z" W: Y
/ M! U& C) `4 m3 r$ \" K$ m6 h" m! x) E" h; p8 _+ E! Z0 }
7 v" v: P3 m& K$ H6 F2 m |