1. 什么是GPIO:( D5 B3 J' m/ e! D1 ?% F
GPIO(General-Purpose IO ports,通用输入/输出接口),用于感知外界信号(输入模式)和控制外部设备(输出模式)。 W% G. M7 W- z1 f% b$ {: m
STM32F103C8T6一共有48个引脚,除去电源引脚、晶振时钟引脚、复位引脚、启动选择引脚、程序下载引脚(大部分为最小系统必须引脚),剩下的则是GPIO引脚。
6 R+ ?$ K) n' ]0 A下图为STM32F103系列GPIO的基本结构,左侧连接MCU内部,中间上半部分为输入,中间下半部分为输出,右侧为MCU引出的外设I/O引脚。2 a- @' b C- c
! n. R& G% P' @( L$ h. y
6 g2 x3 a- o; {5 U: S2 i
0 S) ^1 p$ k, L5 u. K* b2. GPIO工作模式
8 P7 u0 W7 g1 @1 f# t7 {& h# }STM32F103系列的I/O引脚共有8种工作模式,其中输出模式有四种:推挽输出、开漏输出、复用推挽输出、复用开漏输出;输入模式有四种:上拉输入、下拉输入、浮空输入、模拟输入。 C' G4 h! ? z6 J
$ D" O& K, \+ @+ Y$ H6 Y+ z* A
, |2 l* G0 b0 f& L4 e/ V( Y' b& @1. 推挽输出(Push-Pull,PP): J/ ?: \. F# T
$ A4 N( I, o0 g/ A推挽结构由两个MOS管按互补对称的方式连接,任意时刻总是其中一个三极管导通,另一个三极管截止。
- _# \. }% [0 t, o$ C! x* ]$ F如上图①所示,MOS管作为开关使用,“输出控制”向两个MOS管栅极加一定电压,P-MOS管源极和漏极之间导通,VDD经过P-MOS管的S->G->D输出,N-MOS管处于高阻态(电阻很大,近似开路),整体对外为高电平;“输出控制”取消向两个MOS管栅极施加电压,P-MOS管源极和栅极截止,P-MOS管处于高阻态,N-MOS管源极和漏极导通,整体对外为低电平。
' m# K( b; M. B4 \推挽模式,让“输出控制”变为了VDD/Vss输出,使得输出电流增大,提高了输出引脚的驱动能力,提高了电路的负载能力和开关的动作速度。( u' j- j% Q6 }, m$ C: [
1 @2 T) Y8 x' Y) E" ~; e2. 开漏输出(Open-Drain,OD)
- u. m9 q/ K K" @% h
! W: i$ k3 E3 j开漏模式下,“输出控制”不会控制P-MOS管,“输出控制”只会向N-MOS管栅极加一定电压,两个MOS管都处于截止状态,两个漏极处于悬空状态,称之为漏极开路。“输出控制”取消栅极的施加电压,P-MOS管依旧处于高阻态,N-MOS管导通,整体对外为低电平。
8 r+ r- {" o6 C; x& h6 n6 M开漏输出模式可以等效将下图中灰色框的P-MOS管看作不存在。即该模式下只能输出低电平,若要输出高电平,则需要外接电阻,所接的电阻称为上拉电阻,此时输出电平取决于此时上拉电阻的外部电源电压情况,如下图中蓝色框的外部电路。
0 q! O; A/ P R7 k- j# y$ ^: c5 Q
* G3 H A( U) V* U! @6 w; s4 \
6 h! m0 J+ S, g) x/ L" g2 F4 [
8 a9 F8 C' |* g. }" y. O推挽输出模式可以直接输出高电平,开漏输出模式需要外接上拉电阻才能输出高电平,但开漏输出拥有一些推挽输出不具有的特性: H/ F$ k/ C6 v2 Y
. J8 L; v; B! z+ R9 f8 A1.利用外部电路驱动能力。如图 8.1.2 所示,“输出控制”只需要提供一个很小的栅极驱动电流,VCC 经过上拉电阻为外部负载提高驱动电流;2.实电平转换。推挽输出模式由VDD提供,即只能提供3.3V电平。使用开漏输出模式后,VCC可以为 5V,从而实现了电平转换的效果。, ^3 q# {5 y5 c$ F6 o2 G
3.方便实现“逻辑与”功能。多个开漏的引脚可以直接并在一起使用,统一接一个合适的上拉电阻,就可以实现“逻辑与”关系,即当所有引脚均输出高电平时,输出才为高电平,若任一引脚输出低电平,则输 出低电平。在I²C、SMBUS等总线电路中经常会用到。
- v4 u# B5 c! n( X9 X( f `* Z/ j7 E0 R; \
* J4 b/ p* F/ d! A0 B/ y1 q- N3. 复用功能推挽/开漏输出(Alternate Function,AF)
" e# C {+ j$ @+ {' _5 g
0 g% ?$ Z3 n) }( q) GGPIO引脚除了作为通用输入/输出引脚使用外,还可以作为片上外设(USART、I²C、SPI等)专用引脚,即一个引脚可以有多种用途,但同一时刻一个引脚只能使用复用功能中的一个。" d; \1 N) C1 c1 o+ ]
当引脚设置为复用功能时,可选择复用推挽输出模式或复用开漏输出模式,在设置为复用开漏输出模式时,需要外接上拉电阻。% x3 r! m) H) S' a! O. L0 X
" b4 j ?% E$ X! C; T
4. 上拉输入模式(Input Pull-up)* h; t7 _' o! Q& }
如上图中②所示,VDD经过开关、上拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入高电平。该模式的典型应用就是外接按键,当没有按键按下时候,MCU的引脚为确定的高电平,当按键按下时候,引脚电平被拉为低电平。0 D; P0 P" s( A. r, l
8 W" L+ m: u" q) R
5. 下拉输入模式(Input Pull-down)
- } B9 X/ @( i; g! \如上图 中②所示,Vss经过开关、下拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入低电平。. w" S! r' c+ d( y8 N
+ I' _7 ^+ {/ I& V. Z4 t/ @6. 浮空输入模式(Floating Input)
, k e, W" ^. r% Q4 \9 F" w0 [如上图中②所示,两个上/下拉电阻开关均断开,既无上拉也无下拉,I/O引脚直接连接TTL肖特基触发器,此时I/O引脚浮空,读取的电平是不确定的,外部信号是什么电平,MCU引脚就输入什么电平。MCU复位上电后,默认为浮空输入模式。
; Y) o. q' p/ U* E* J) l. g! C. x6 D/ [* J& {6 L* {- W
7. 模拟输入模式(Analog mode)
. S8 _. a- `8 o" `如上图中②所示,两个上/下拉电阻开关均断开,同时TTL肖特基触发器开关也断开,引脚信号直接连接模拟输入,实现对外部信号的采集。
* m' m1 O G5 M: V! n, _2 F4 ] ^! d5 v, P0 ~
GPIO 输出速度3 r) O5 f# V0 r6 Y7 x: r
1.STM32的I/O引脚工作在输出模式下时,需要配置I/O引脚的输出速度。该输出速度不是输出信号的速度,而是I/O口驱动电路的响应速度。
! E0 O, {, d& e+ W. r6 y( m2.STM32提供三个输出速度:2MHz、10MHz、50MHz。实际开发中需要结合实际情况选择合适的相应速度,以兼顾信号的稳定性和低功耗。通常,当设置为高速时,功耗高、噪声大、电磁干扰强;当设备为低速 时,功耗低、噪声小、电磁干扰弱。
' n) c k2 k7 [( F3.通常简单外设,比如LED灯、蜂鸣器灯,建议使用2MHz的输出速度,而复用为I²C、SPI等通信信号引脚时,建议使用10MHz或50MHz以提高响应速度。
' q7 ]2 o2 E. [) f
/ R; b# B( h2 I) Y r8 m3. 硬件方面1 f% Q, n' P9 Z6 H
, t2 D, Z! j8 U; w1 `/ u% v
+ z4 V0 I2 m2 P! d
+ k8 y$ k0 ^1 Q$ h+ m6 u+ n( I f常见LED灯(图片来源百问网)
/ y9 o) U* y) B! j' v8 I* A& ?" O6 q
/ h' E- Z2 g9 h) F" c& D
J! r3 c1 y2 s7 J6 w. g* M4 W; G+ JLED灯原理图(图片来源百问网)
8 R y4 r% I1 T* Z' \" U, c' P6 R
8 G7 A3 T- \- p( Y3 W# U9 n8 D4. 软件方面
2 z% g+ L7 U3 H+ H7 Z, i9 ddriver_led.h/ j0 M$ g- a9 }8 f+ l. Q
! m* Z' k# `% v8 k6 O
- #ifndef __DRIVER_LED_H
, m# m' g$ N- V1 s6 V - #define __DRIVER_LED_H
/ y- _" q+ }: f0 s2 _
0 Q* X4 ~; G5 ?* c0 u- #include "stm32f1xx_hal.h"7 h4 z7 s. z+ Z" r' t$ j7 E
$ x" _6 l2 z( q) C- /*********************, F. j7 U6 x$ s
- * 引脚宏定义7 P/ S( \# S& O
- **********************/6 p/ R; z) B- v1 C9 S( R
- #define B_LED_GPIO_PIN GPIO_PIN_14 F8 p- Y* t6 F$ d; `, c4 W
- #define B_LED_GPIO_PORT GPIOA
' g k3 }+ n; H3 S - #define B_LED_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
$ u& i, G4 y/ N+ K% H! p. Y
9 y. {6 X) Y3 [! `4 ~( s* T2 f X- /*********************
, B9 `2 N/ k/ ?# g - * 函数宏定义+ I8 A; |6 ^1 r
- **********************/
. Q& o* P7 ?. \# w1 t" y( v8 I - /*2 ^! Z/ [9 b$ S V d$ j/ F& [
- * LED亮灭函数宏定义
1 ?* b. e; v2 Z - */
: k2 i2 \! l5 m! K - #define ON GPIO_PIN_RESET
0 C1 o- R8 M5 q' K# v, } - #define OFF GPIO_PIN_SET
/ s* ^( o! ~# v; _ - # a9 S3 D3 S& [0 U5 V
- #define BLED(flag) HAL_GPIO_WritePin(B_LED_GPIO_PORT, B_LED_GPIO_PIN, flag) ; ]- X0 f1 }- c" S6 d# g4 |9 P
- 7 ]$ W8 A' J" t2 t' q8 a
- /*
( W7 o1 G+ Y; S - * 函数名:void LedGpioInit(void)
M8 F( [) ?" t4 b. T- B8 H - * 输入参数:无
- j( M$ Y+ ~5 f1 j% W) E- J - * 输出参数:无
9 t2 d+ Q5 @ ~4 A - * 返回值:无
: C- M+ `3 i: o) h# T0 T - * 函数作用:初始化LED的引脚,配置为上拉推挽输出
+ d# F6 ]4 n( j; w) ^' |' c" h2 ~) u - */& K8 q# h4 g; E: F. l7 c2 u
- extern void LedGpioInit(void);
9 h+ b1 J) I; T, s7 E) K - D2 Q& s9 P! q- ^
- #endif0 b Z! T; z' v" {# h% g3 v1 O
复制代码 + p1 W7 `/ X, `4 d" C' x! |
driver_led.c H+ A( H- q8 u! G" c3 ~* e8 ^
$ G# {8 l+ N ]3 a% n6 L- #include "driver_led.h"
. l+ z' M( S3 c# t: d) b O* g - 3 e: _- s3 t$ U. f1 \0 V
- /*
9 p7 J" q: F+ A( `. ~) }8 g2 R% u. E - * 函数名:void LedGpioInit(void)
; w, z' e. ^& K9 I" K: N1 Z7 ^0 C - * 输入参数:无, ^, U/ ]+ g1 a; p
- * 输出参数:无
* t" R% h2 V. m' X: Z3 f - * 返回值:无
}, ~, k3 Y- Z5 `+ J/ m* Z - * 函数作用:初始化LED的引脚,配置为上拉推挽输出
) w8 ~/ g! F7 ^1 U3 R, }: i8 V- X - */
" m2 ?7 l E1 X. N9 h - void LedGpioInit(void)
7 D( v! B$ h2 V. l8 U# a' v - {/ O% v$ Z6 S& N5 C$ [8 L& r
- // 定义GPIO的结构体变量0 Q8 q; C1 ~* C L) g
- GPIO_InitTypeDef GPIO_InitStruct = {0};% }$ N5 r1 d- N+ f$ z! t* B) ?
- // 使能LED的GPIO对应的时钟
C* H( W- ~ N8 e5 c' @ - B_LED_GPIO_CLK_EN();. N y1 p7 j" q2 ?0 i0 S* |
, T9 O2 g6 F+ ]/ |- GPIO_InitStruct.Pin = B_LED_GPIO_PIN; // 选择LED的引脚' Z+ b: X% ?# }' f) r. a! T7 j: y; S
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式+ X2 f& a5 c) D/ r" b/ T( D
- GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉
6 m! {, R6 k ? - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 引脚输出速度设置为慢! S; z2 b# c: W/ F
0 {% t4 N; D" ^+ d/ b- // 初始化引脚配置
9 P4 C, @: ]& b+ h - HAL_GPIO_Init(B_LED_GPIO_PORT, &GPIO_InitStruct);
% m! X0 N' g5 F2 }2 e' y9 s1 z - $ `, i) A9 k7 |9 {# c
- // 默认LED灭:OFF-灭,ON-亮
7 n+ Q/ B: b) [! s! c# [ - BLED(OFF);
% w2 }0 `7 [# ]9 t - }
% K! V; |* O& }) O& i
复制代码 0 [1 v7 H9 e" T% A3 o4 }. [/ a4 ]
main.c
, i9 y3 C5 J: i4 t. Q6 A4 q3 g/ T B+ W* g0 D
- #include "main.h"$ q+ D) Q( C* N+ i+ g
- #include "driver_led.h"2 E3 W. n& l: ^. M1 N& F* G4 Z
- " @" w) d1 Y# d! b: t: d
- int main(void), ^' T$ @5 u% h" D* k6 [6 v. @) O
- {
$ t, i4 x" e+ k. M3 n - // 初始化HAL库函数必须要调用此函数" w0 S( O* j$ a' @, Z+ l9 N
- HAL_Init();
+ D7 k: _; v' ] - // 系统时钟即AHB/APB时钟配置0 i' q7 L8 ]4 }, { {8 f8 t
- SystemClock_Config();
' Y: o9 J5 Q p! |0 T; ^: t - // 初始化LED
/ J- U' A1 Z6 A5 }: u) E7 B - LedGpioInit(); ( ?. A# V0 b# f B$ e
- // 循环闪烁
9 _: b: N: W8 H4 y - while(1) Q* Z$ |# w+ W# t2 H' ?/ H6 o
- {+ _: j- P# r q4 W; g$ q' a, X
- BLED(ON);
. r* ]6 K# t# g# c* X, q0 Q - HAL_Delay(1000);# S4 Z" n# o$ k# a- i( ~
- BLED(OFF);/ u9 F' m4 G2 W3 P8 j
- HAL_Delay(1000);
+ b1 ~4 a/ z9 }8 e" v+ R - }
! I6 W4 V' C$ a7 |9 D. J - }
复制代码
6 k* S6 }9 ^$ ^7 D4 P1 U; Z; n6 z( k1 ?5 V, h7 i
|