STM32单片机GPIO口点亮流水灯
( d9 Y5 R) B/ W任何一个单片机,最简单的外设莫过于 IO 口的高低电平控制了,本文将通过一个经典的流水灯程序,了解 STM32F1 的 IO 口作为输出LED:DS0 和 DS1 交替闪烁,实现类似流水灯的效果。# [4 ~9 C# \+ g6 f$ O9 q
+ ^! W8 J. r5 F9 _
STM32IO口简介
6 r! {3 L8 i: u8 u, f1 L本文将要实现的是控制ALIENTEK 精英 STM32开发板上的两个LED实现一个类似流水灯的效果,该实验的关键在于如何控制 STM32 的 IO 口输出。了解了 STM32 的 IO 口如何输出的,就可以实现流水灯了。% ?1 t1 o t" L4 t! B `
STM32上有众多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 电平兼容的)。+ s0 |* c$ j- U; T
: _9 c# x/ w. |+ e" \ Z8 V+ P Y
9 b. X& L2 j) U$ k4 @) E" p: o" L5 i0 H; Y1 D1 y# J
STM32 的 IO 口相比 51 而言要复杂得多,所以使用起来也困难很多。首先 STM32 的 IO 口6 \( B1 S' ^/ b6 E+ x3 n
可以由软件配置成如下 8 种模式:5 w$ U' U3 E1 C5 c) }. J
1、输入浮空
0 [8 E! [% y+ K7 N1 n. ^2、输入上拉
+ W3 Z% L1 |3 b+ }6 f, ]: D3、输入下拉
3 ]& Y4 L& q+ Q; b& v4、模拟输入) Y/ m4 ]3 M0 F" } L
5、开漏输出* x7 z- m: x0 p/ ]2 R8 Q/ K5 s
6、推挽输出
! Y1 n8 ~2 w2 Z$ G- w& b! t7、推挽式复用功能- X/ T- q8 r e
8、开漏复用功能
% V" r5 [# a" V4 ?8 }$ L6 Q! B( {. B6 g7 d' m
9 Q; w; Z, I; w
/ R, z' g4 V+ c% tSTM32 的 CRL 控制着每组 IO 端口(A~G)的低 8 位的模式。每个 IO 端口的位占用 CRL 的 4 个位,高两位为 CNF,低两位为 MODE。这里我们可以记住几个常用的配置,比如 0X0 表示模拟输入模式(ADC 用)、0X3 表示推挽输出模式(做输出口用,50M 速率)、0X8 表示上/下拉输入模式(做输入口用)、0XB 表示复用输出(使用 IO 口的第二* S+ {- |( u( @$ P/ B- Y$ X
功能,50M 速率)。CRH 的作用和 CRL 完全一样,只是 CRL 控制的是低 8 位输出口,而 CRH 控制的是高 8位输出口。# {* x# t8 O: E) ~, h
这里我们点亮流水灯是使用库函数进行配置GPIO口的相关参数。6 W- i! ~' `. [. ^* V
) ~$ B5 ?" Y0 _7 e; y- @% w
" O, W* E7 }( E: I+ m. K3 |* X+ k基于固件库函数配置GPIO口
3 k/ B7 y; T; x. e* gGPIO 相关的函数和定义分布在固件库文件 stm32f10x_gpio.c 和头文件 stm32f10x_gpio.h 文件中。
$ X; Z- k0 R5 y9 F/ ]4 x2 v在固件库开发中,操作寄存器 CRH 和 CRL 来配置 IO 口的模式和速度是通过 GPIO 初始化函数完成:
" U7 ?; ?5 O/ ]1 Y$ `- void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
( c" d. b2 n7 X
复制代码
5 [" e" n0 x- O6 w* R这个函数有两个参数:
; O- r# ~6 X/ G& O第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。5 [# ]& l, ^% y+ d) Z
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。结构体的具体定义如下。
# p0 K4 x4 m+ f7 Z, [! c1 @- typedef struct
( [( U" E5 c5 M M- I - {
! b) W4 f( c/ I- ?7 O - int16_t GPIO_Pin;
9 G m6 _" F+ [ n - GPIOSpeed_TypeDef GPIO_Speed;
' c. B1 [' P. K% a- J7 I6 d - GPIOMode_TypeDef GPIO_Mode;
8 @7 q5 C4 Y6 e- d" N' F4 Y+ o; j - }GPIO_InitTypeDef;
; H4 `9 B* _! G1 u2 H
复制代码 6 ]+ ?4 l4 F d
那么如何具体配置一个IO口呢?
, X9 Z- E8 b( N" u4 N4 T) k初始化结构体初始化 GPIO 的常用格式是:. ]1 R$ P) D) X$ ?9 W2 @
- GPIO_InitTypeDef GPIO_InitStructure;
% L7 W$ l0 k& M! L7 a: o- J; Z- t - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 端口配置$ G: w3 j( s( [, ?7 }
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出4 {- T) E: ?8 E. H/ R
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz, U4 J: {6 Z; w" ^2 T/ [5 G; J8 s( u
- GPIO_Init(GPIOB, &GPIO_InitStructure);//根据设定参数配置 GPIO/ [' s, I- g3 L7 _; W
复制代码
. w2 T( F/ b# \' G上面代码的意思是设置 GPIOB 的第 5 个端口为推挽输出模式,同时速度为 50M。从上面初始化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来设置是要初始化哪个或者哪些 IO 口;第二个成员变量 GPIO_Mode 是用来设置对应 IO 端口的输出输入模式,第三个参数是 IO 口速度设置,有三个可选值。GPIO_Mode和GPIO_Speed均是由枚举类型定义。具体定义分别为* t. [9 q7 t( K
6 x5 v8 L1 \1 P* N fGPIO_Mode:% e. D/ ^1 m9 \
- typedef enum
/ y0 i0 d% o& J3 ~5 h7 o) U - {
7 i. a4 M% c4 _/ O - GPIO_Mode_AIN = 0x0, //模拟输入! \2 w& ]. z) a7 ~. Z% n
- GPIO_Mode_IN_FLOATING = 0x04, //浮空输入7 f% c; Y) k( g7 M# Z
- GPIO_Mode_IPD = 0x28, //下拉输入1 g) o S' G# Q
- GPIO_Mode_IPU = 0x48, //上拉输入6 H q9 H; W; i4 I# w
- GPIO_Mode_Out_OD = 0x14, //开漏输出4 R: Q5 r: m Z% v. e
- GPIO_Mode_Out_PP = 0x10, //通用推挽输出
4 {' }' B/ F& v# e+ { - GPIO_Mode_AF_OD = 0x1C, //复用开漏输出
W" p' k* `* J; m# J# R - GPIO_Mode_AF_PP = 0x18 //复用推挽
4 f* Q: d7 H$ r - }GPIOMode_TypeDef;
/ o+ [" h" }* b% b- U% j# e9 Y
复制代码 / }2 M1 Z) [ d @$ \
GPIO_Speed:
7 p4 E0 C! }7 M( g* A7 ~" |- typedef enum
; Q% U$ Z4 g: O - { * w. l$ C+ i" Q/ f, n S
- GPIO_Speed_10MHz = 1,
+ ^$ n; h: l) d - GPIO_Speed_2MHz,
- H. h; I7 ?, a% T1 F) H, D4 X7 ^! J - GPIO_Speed_50MHz4 i4 X* ~0 }$ a H# a/ ~
- }GPIOSpeed_TypeDef;% h( E% R& L- N2 b7 F! A9 F
复制代码 ! c/ }2 j# l+ K6 S/ E) ^8 S
这样我们就可以初始化好一个GPIO口。
: B, f% q. M7 x% [$ n% v4 o+ M
- n( v9 w) q3 _! i6 w M
7 N/ J4 f: u7 ?( t" @) `7 u实现流水灯的基本代码& X% H5 _3 M* f/ E' ?
想要实现流水灯,要先了解控制GPIO口输出高低电平的函数。
& }* f7 n) f+ S4 y. T
) }" @1 L# ?' q. P% v7 S
$ D- }6 a. z# j0 n- w输出高电平- GPIO_SetBits(GPIOB, GPIO_Pin_5);//PB.5输出高电平6 S' e+ y2 U- l: _ D4 ?! E1 y; g$ ]
复制代码 ' g% r$ y7 c2 T s8 S5 J9 ?
输出低电平1 t7 s0 T. x/ Z4 G7 o( J6 {# l/ }
- GPIO_ResetBits(GPIOB, GPIO_Pin_5);//PB.5输出低电平9 p$ `) _$ c; G6 B" y* r
复制代码 - A! H; Z& f% _4 |, t/ P Q
使用IO口的基本操作为
9 l$ B: S# o$ N8 [- R# ~0 S4 J( ^1) 使能 IO 口时钟。调用函数为 RCC_APB2PeriphClockCmd()。 2) 初始化 IO 参数。调用函数 GPIO_Init();) v8 ~5 m7 I' D a+ H
3) 操作 IO。+ p* [, A( J `7 d! e
9 l7 w. N; \9 V' L
$ v1 m4 B; v1 Q
实操一波1 D! R2 A; y' j8 l( x
先建立led.c和led.h文件
O4 f4 [+ C" F- \2 \4 `- #include "led.h"
2 X* t( w w3 ^ - //初始化 PB5 和 PE5 为输出口.并使能这两个口的时钟 2 t. O9 w+ {0 ?/ l6 p! I# {, V
- //LED IO 初始化
+ u- E' R$ ~! [# C4 I - void LED_Init(void)
0 p: T3 { g" \' N - {" @$ x% @) Q& I! ^
- GPIO_InitTypeDef GPIO_InitStructure;+ @+ \! s* f3 T' ]
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|
2 |" ^6 M4 f$ I) z# ] - RCC_APB2Periph_GPIOE, ENABLE); //使能 PB,PE 端口时钟# j" Q; f% c% [6 D
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED0-->PB.5 推挽输出( f3 v; R t9 T' {, T
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出. L9 L$ ^) m: f8 U
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
9 H6 {7 E) g6 X1 w. j! G% ^& s9 Q - GPIO_Init(GPIOB, &GPIO_InitStructure);7 [- g4 [ c/ O2 J: m9 N% C. B
- GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高* ~ l7 M# @! x7 o. M: p; Z
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5 推挽输出
& @/ U A) J3 ~# m - GPIO_Init(GPIOE, &GPIO_InitStructure);
6 I% a C- M* f' @6 u - GPIO_SetBits(GPIOE,GPIO_Pin_5); /PE.5 输出高
/ R7 u6 P4 B s- Y! A - }
! c# @" b5 i, _( u% [
复制代码 1 o, |+ H- b5 g2 j
在led.h文件里,可以使用如下宏定义3 M( @3 ` `) Z+ D
- #ifndef __LED_H' W' K- D4 P: F- c$ B) S$ Z
- #define __LED_H
' k, Q! X$ x+ U+ ~- y9 s3 s - #include "sys.h"
+ E, N$ M8 ^7 p; \4 ~/ A - //LED 端口定义
* v4 X/ V8 K8 }( x/ f - #define LED0 PBout(5)// DS0
/ h+ h2 d- w/ N, V - #define LED1 PEout(5)// DS1
# V1 V; t5 ~3 L: G( n7 y - void LED_Init(void);//初始化 ) K- Z2 h+ p% [" U; n# }& U
- #endif
$ |" X) ]$ C( V Y: p: N2 _, A/ |
复制代码 # C! g0 J/ s. T1 I3 e6 p
这样就可以实现位带操作来实现操作某个 IO 口的 1 个位2 u3 b' s* G X- C& v. B+ d0 d
4 M$ T: z, x1 O" o我们在main.c里写下主函数
# P% {& f0 g x- #include "led.h"1 F9 n! ?6 ?- j
- #include "delay.h"+ I* R& O# }0 b; i' v! y! V
- #include "sys.h"1 s0 J( f. M* h; F# ~5 F7 f. p
- //ALIENTEK 精英 STM32 开发板实验 1
A# k! U, _+ a6 o- q" U - //跑马灯实验
, [" Z0 v4 Q* Q& J0 `/ ^. O - int main(void)! m- k. B. n- \0 R G$ z
- {
! {: c& v8 X+ I p - delay_init(); //延时函数初始化 ; E B/ i* \. W _. T5 ^- j1 @
- LED_Init(); //初始化与 LED 连接的硬件接口/ V' K/ D; P3 R* ^/ j! L
- while(1)1 O( D) u% u2 N: `2 |" r
- { # \# O8 ~! e+ _# K- b* L1 \3 F
- LED0=0;
d. _4 ]1 t" N - LED1=1; s' K, ~' f: C) g- W
- delay_ms(300); //延时 300ms5 }0 ^, Y* I# {. o; j6 l: ? c) i' i0 T
- LED0=1;7 P& P" D5 Y* N+ A4 ~3 |9 f
- LED1=0;( u& J, n2 n& ?0 V! j c
- delay_ms(300); //延时 300ms3 r3 E4 [+ A9 X$ y; j
- }
; N( ] V3 {' ~# Q2 o, ~# U! O3 { - }
2 D* P6 P" u! w% B
复制代码
% Z4 c5 j8 t6 V: O9 R6 D如果没有使用宏定义,也可以使用输出电平函数进行设计2 i! ~3 ?; H3 |- \. r) U: f
- GPIO_SetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 1,等同 LED0=1;2 ]9 v9 s! v1 q% y& i, ?9 m M, s
- GPIO_ResetBits(GPIOB, GPIO_Pin_5); //设置 GPIOB.5 输出 0,等同 LED0=0;
0 E7 P" F3 o+ D; y& q! L' k
复制代码
1 m7 e# i9 Y: A% n注:( q- l& g" @, m) i. y6 Y
由于stm32精英板的led灯只有两个,如果点亮外设的led灯,最好注意一下IO口的选择,许多IO口有多种功能,在选择IO口上可以考虑那些,未接任何外设,完全独立的IO口。
2 i2 b0 [, i0 b& q/ a. Z: D————————————————& P) Q7 U% B. B( R" B w
版权声明:Pessimist~9 ?$ e3 {7 X8 f `
如有侵权请联系删除5 Y, U; L2 k% p- _; {
0 F- ?! ?* p C: X- m7 ]
8 ]& M% i# Z. o
# d3 ]: n: k6 H8 i6 | |