1. 什么是GPIO:
: t1 Q( D7 H1 k" bGPIO(General-Purpose IO ports,通用输入/输出接口),用于感知外界信号(输入模式)和控制外部设备(输出模式)。, Y1 B: H8 ^+ K, p
STM32F103C8T6一共有48个引脚,除去电源引脚、晶振时钟引脚、复位引脚、启动选择引脚、程序下载引脚(大部分为最小系统必须引脚),剩下的则是GPIO引脚。
% e( r( K$ x5 G F下图为STM32F103系列GPIO的基本结构,左侧连接MCU内部,中间上半部分为输入,中间下半部分为输出,右侧为MCU引出的外设I/O引脚。
4 o2 c( \" i) E9 d z% N
! _3 N; H+ L/ }2 y, \7 m( B
; u2 F( p; J! S9 n0 h6 @
/ k, K& k5 s9 D2 @4 O) S
2. GPIO工作模式5 I- ~( g, ^6 E0 \0 H( ?: R
STM32F103系列的I/O引脚共有8种工作模式,其中输出模式有四种:推挽输出、开漏输出、复用推挽输出、复用开漏输出;输入模式有四种:上拉输入、下拉输入、浮空输入、模拟输入。; }& a7 T+ \$ v1 O" x
. U# B! Q3 b# d4 M) b- f4 _- A
. k2 H% f) U7 R1. 推挽输出(Push-Pull,PP)
" C3 `. B4 a2 D! n6 g7 d6 D& m1 I' l& z& [
推挽结构由两个MOS管按互补对称的方式连接,任意时刻总是其中一个三极管导通,另一个三极管截止。
6 s, P. X: S9 |7 `( e8 }如上图①所示,MOS管作为开关使用,“输出控制”向两个MOS管栅极加一定电压,P-MOS管源极和漏极之间导通,VDD经过P-MOS管的S->G->D输出,N-MOS管处于高阻态(电阻很大,近似开路),整体对外为高电平;“输出控制”取消向两个MOS管栅极施加电压,P-MOS管源极和栅极截止,P-MOS管处于高阻态,N-MOS管源极和漏极导通,整体对外为低电平。6 c, A; Z+ o# [$ k/ \; B$ o" G% R
推挽模式,让“输出控制”变为了VDD/Vss输出,使得输出电流增大,提高了输出引脚的驱动能力,提高了电路的负载能力和开关的动作速度。
6 S3 |. M5 r' z% X9 I1 i
0 i; t5 k; l3 j8 |$ W* Q0 |2. 开漏输出(Open-Drain,OD); }1 C+ A7 b0 n* C2 G5 \7 ]9 m6 N) r: v* z
& g1 T+ r8 H7 G: B开漏模式下,“输出控制”不会控制P-MOS管,“输出控制”只会向N-MOS管栅极加一定电压,两个MOS管都处于截止状态,两个漏极处于悬空状态,称之为漏极开路。“输出控制”取消栅极的施加电压,P-MOS管依旧处于高阻态,N-MOS管导通,整体对外为低电平。
1 \; } z5 z0 T$ C% X: {9 `开漏输出模式可以等效将下图中灰色框的P-MOS管看作不存在。即该模式下只能输出低电平,若要输出高电平,则需要外接电阻,所接的电阻称为上拉电阻,此时输出电平取决于此时上拉电阻的外部电源电压情况,如下图中蓝色框的外部电路。, [# p& o7 s2 [: E' v$ }
# U/ a% q, b) O' `% O7 p
( P4 _! @- i' t1 A
% E& H) y- e& c1 {! \ _/ J1 V7 [推挽输出模式可以直接输出高电平,开漏输出模式需要外接上拉电阻才能输出高电平,但开漏输出拥有一些推挽输出不具有的特性:
l. Y: n8 \- X }
( @# S8 p; |; ~1.利用外部电路驱动能力。如图 8.1.2 所示,“输出控制”只需要提供一个很小的栅极驱动电流,VCC 经过上拉电阻为外部负载提高驱动电流;2.实电平转换。推挽输出模式由VDD提供,即只能提供3.3V电平。使用开漏输出模式后,VCC可以为 5V,从而实现了电平转换的效果。/ ^& v' [/ F( G2 ^
3.方便实现“逻辑与”功能。多个开漏的引脚可以直接并在一起使用,统一接一个合适的上拉电阻,就可以实现“逻辑与”关系,即当所有引脚均输出高电平时,输出才为高电平,若任一引脚输出低电平,则输 出低电平。在I²C、SMBUS等总线电路中经常会用到。# V+ G8 |8 j8 f
# `" p+ U: x) T: x9 C
; Q! n i" O0 ~; j3. 复用功能推挽/开漏输出(Alternate Function,AF)4 Z2 K3 r9 P( e F
+ T7 _& l- ?: {, VGPIO引脚除了作为通用输入/输出引脚使用外,还可以作为片上外设(USART、I²C、SPI等)专用引脚,即一个引脚可以有多种用途,但同一时刻一个引脚只能使用复用功能中的一个。
8 {% U# o/ N' q3 } g. S- |% Q当引脚设置为复用功能时,可选择复用推挽输出模式或复用开漏输出模式,在设置为复用开漏输出模式时,需要外接上拉电阻。
+ o d Q* @& }+ ?; z5 c; A% X1 e
4. 上拉输入模式(Input Pull-up): ~% z. f, x, q
如上图中②所示,VDD经过开关、上拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入高电平。该模式的典型应用就是外接按键,当没有按键按下时候,MCU的引脚为确定的高电平,当按键按下时候,引脚电平被拉为低电平。
; G, M9 r5 K+ h2 z/ k" Y5 @
3 @# w/ h @" z6 t8 X' P( f! j5. 下拉输入模式(Input Pull-down)
" { r. M/ G+ Z ?如上图 中②所示,Vss经过开关、下拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入低电平。; z) N% H# u: V) F1 z* o9 ]# d
5 H5 l, ~% X1 T! U$ H1 _% `
6. 浮空输入模式(Floating Input)
/ q$ l0 ~8 M# G7 V如上图中②所示,两个上/下拉电阻开关均断开,既无上拉也无下拉,I/O引脚直接连接TTL肖特基触发器,此时I/O引脚浮空,读取的电平是不确定的,外部信号是什么电平,MCU引脚就输入什么电平。MCU复位上电后,默认为浮空输入模式。
) I% J q& ~; @' s% w' z9 c7 g K4 ^
7. 模拟输入模式(Analog mode)
% I1 X- o+ A1 J1 c* [% \ N: a如上图中②所示,两个上/下拉电阻开关均断开,同时TTL肖特基触发器开关也断开,引脚信号直接连接模拟输入,实现对外部信号的采集。
+ u! Q o8 f3 T' H5 N2 u9 \2 m, q6 s8 k7 L# u' c, h: g( u
GPIO 输出速度
. N" s# `4 G( v! [/ z- `& J$ S+ V1.STM32的I/O引脚工作在输出模式下时,需要配置I/O引脚的输出速度。该输出速度不是输出信号的速度,而是I/O口驱动电路的响应速度。
* r1 B+ K9 k& P. v2.STM32提供三个输出速度:2MHz、10MHz、50MHz。实际开发中需要结合实际情况选择合适的相应速度,以兼顾信号的稳定性和低功耗。通常,当设置为高速时,功耗高、噪声大、电磁干扰强;当设备为低速 时,功耗低、噪声小、电磁干扰弱。! L8 V' s$ ?; t* H/ g0 B
3.通常简单外设,比如LED灯、蜂鸣器灯,建议使用2MHz的输出速度,而复用为I²C、SPI等通信信号引脚时,建议使用10MHz或50MHz以提高响应速度。4 K- h+ C) E- b9 \
0 ~, j5 a0 s# B; y- X
3. 硬件方面 p: m. p2 z# m+ T. ^8 M5 F$ P
- G9 D& v; O, ] d0 h: Y
5 r" j2 s( C2 ~, K6 v/ Y
# K9 A. j& j- A& P0 V常见LED灯(图片来源百问网)
5 D' d# G4 \& R; @2 k2 ^: s) G1 I' w9 I# a4 t
( y2 C! ^3 i2 F, Q% c" `7 @
# L/ n0 o% z9 Q4 xLED灯原理图(图片来源百问网): \, n$ {/ Z; y) Z
% e( C% N6 G. f* {
4. 软件方面
9 m- @8 s U7 J$ o# ydriver_led.h4 R" i( _1 D5 D
( w5 V9 L9 h" s2 i* G; ^- #ifndef __DRIVER_LED_H
" z* Y& ^4 w X* d6 V - #define __DRIVER_LED_H
9 K( Y7 r% [$ f: n8 V - ! |: R) M/ |1 E2 m0 }& `% B
- #include "stm32f1xx_hal.h"! G% A1 [' r7 g. _3 K {$ ?4 L
- ) n5 Z6 j9 K7 r- B
- /*********************5 c* ~9 E" f- C9 C8 S0 d
- * 引脚宏定义
5 v9 @5 o! K( W* T - **********************/4 N1 m* e+ K$ @8 M" m
- #define B_LED_GPIO_PIN GPIO_PIN_1
% Z0 q# v7 n: A P - #define B_LED_GPIO_PORT GPIOA
4 ?, b1 \- s) J4 n5 K+ G - #define B_LED_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
# t8 T: Y7 M/ z) C2 K$ K* x9 Z/ q# p - / _1 c# Z! @( ~- z4 D0 b0 z; t
- /*********************: i: A7 U0 M' W/ W! F
- * 函数宏定义
- ^. n0 `1 T) j) N: c - **********************/
8 `/ f8 w* b4 h5 Z6 b9 i8 u - /*
9 O' E X. j9 \# L5 ~ - * LED亮灭函数宏定义! v& O% _1 J9 i2 j8 {" q( H
- */
$ v7 P6 b! N! W% O - #define ON GPIO_PIN_RESET9 J5 M% ]5 l$ `# u' n
- #define OFF GPIO_PIN_SET
' B# @- P4 i: k$ s! p) k T - / c8 l/ @+ G. }0 M" \/ w! B
- #define BLED(flag) HAL_GPIO_WritePin(B_LED_GPIO_PORT, B_LED_GPIO_PIN, flag)
3 B7 D: `2 J9 y6 o - + Q. K# F7 m# V4 r2 r# g1 W) ?7 R* D
- /*
3 U' E1 o" _% x5 o! k- [- U- t - * 函数名:void LedGpioInit(void)
& ~, G. ?7 R7 C) k. S# k - * 输入参数:无4 K: y4 o/ N: j) ~' b. v
- * 输出参数:无
1 B6 a3 n7 t0 c( i1 [/ M- A) U - * 返回值:无( j/ i: _5 }- T7 y, I
- * 函数作用:初始化LED的引脚,配置为上拉推挽输出; ~& D, s6 N# ?) ^! B+ W! p
- */3 A6 N" |( D$ Q
- extern void LedGpioInit(void);7 b4 T- d' ]4 R4 a
: T: H0 W- s3 t n6 r- #endif
0 G3 J0 n4 r2 i6 ~
复制代码 ( v. c) [! E$ U) j# j3 z+ u
driver_led.c6 Z- L# z) r: r1 S& B% d
/ A1 s- G$ o% p& \
- #include "driver_led.h"
" P0 Q1 E1 ^* W
& l. ]8 V9 v" t. X" I& P$ D' Y- /*
# P- o# z1 J2 q# C0 ^/ A9 | - * 函数名:void LedGpioInit(void)1 `$ c/ k1 r6 @. n6 J ?
- * 输入参数:无% U! }: m: q7 T6 g) U& A x% j
- * 输出参数:无
$ M! J: x3 N8 f" _ - * 返回值:无
; u2 j/ n" |1 ^$ Y ^" h - * 函数作用:初始化LED的引脚,配置为上拉推挽输出
! o" @( C& u. B - */2 t. [/ i* @8 N8 G+ C" H9 w2 T! h
- void LedGpioInit(void)1 @, t% v q* T6 g! o' k9 P1 w8 k
- {
' F* f: W. j0 N& s - // 定义GPIO的结构体变量
, {: y. [* V; C0 M4 O( a% l - GPIO_InitTypeDef GPIO_InitStruct = {0};* r1 l/ Z+ W7 A# S
- // 使能LED的GPIO对应的时钟
/ Y0 E( l- O8 K9 {% k - B_LED_GPIO_CLK_EN();. [4 n9 B Y0 q1 B
' e4 u* C. C; i- GPIO_InitStruct.Pin = B_LED_GPIO_PIN; // 选择LED的引脚; m' ~8 \0 W; r; t. c4 d
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式
! j, U2 I& V4 N/ d! F1 T1 [ l - GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉8 Q& l2 i3 k+ t
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 引脚输出速度设置为慢% m. C# H' W; y+ X9 w/ U
$ X+ u/ q8 w- N( U- // 初始化引脚配置
5 q5 ~: i4 T! R9 I4 ? - HAL_GPIO_Init(B_LED_GPIO_PORT, &GPIO_InitStruct);( T R; V" E$ G* {# W7 t
- , e7 |( o$ f" C1 G
- // 默认LED灭:OFF-灭,ON-亮
/ S; u7 z" C8 ?& L4 k - BLED(OFF);4 |9 V6 a3 Q' |* o! D
- }
4 Q- D) N* g8 j4 d' Q
复制代码
" i6 W( P- y3 r# m1 ]main.c
5 B' v7 O% d! t$ c1 |
2 G' ]" I9 }( u/ ?8 F- #include "main.h"
: L9 D5 f" ]5 [! b, X6 y! o - #include "driver_led.h"0 ?7 Y5 M2 g& S/ @
- : T7 Q# a) O: n2 D" E
- int main(void)
6 z) f- y- t8 I0 J$ V - { . n+ N i" M+ n! `# w$ B; }9 J
- // 初始化HAL库函数必须要调用此函数
; C1 H4 G+ m9 m# F9 K8 }6 r! H - HAL_Init();% n0 p$ m1 P0 O' r5 C9 H+ K+ l; J
- // 系统时钟即AHB/APB时钟配置* `/ A: z* m' m# i; r% c/ O; X9 G
- SystemClock_Config(); " C9 r2 B# H" c! ?5 m6 `6 p
- // 初始化LED* p" r. ^6 h$ p: q" i2 Z) A+ Z
- LedGpioInit();
" [& r2 x4 b/ O - // 循环闪烁) R+ \8 }+ B: z9 O
- while(1)
" ^) L, z$ K& l7 Q, `8 w - {
7 R% O; w& y) N( y, z% I - BLED(ON);
- E9 V+ N6 K5 o' @3 W3 W7 d - HAL_Delay(1000);
& P% `* j) z5 F O8 B& R2 _ - BLED(OFF);
! z0 V6 h/ @( f: X - HAL_Delay(1000);9 @9 n+ o+ X( _! }+ W4 @
- }, A9 q# h H. ]# T1 `8 n
- }
复制代码
2 R! @8 i+ \' \1 X# t$ V, X8 ~ a0 D6 S
|