1. 什么是GPIO:# T* @. H0 @" L7 |. U
GPIO(General-Purpose IO ports,通用输入/输出接口),用于感知外界信号(输入模式)和控制外部设备(输出模式)。: p+ \7 u/ J2 w* ]/ x
STM32F103C8T6一共有48个引脚,除去电源引脚、晶振时钟引脚、复位引脚、启动选择引脚、程序下载引脚(大部分为最小系统必须引脚),剩下的则是GPIO引脚。
7 N# A% c4 y2 u1 F+ ]2 W, `1 E下图为STM32F103系列GPIO的基本结构,左侧连接MCU内部,中间上半部分为输入,中间下半部分为输出,右侧为MCU引出的外设I/O引脚。
- s" _" d" c- V$ H- o: i8 N E
$ x; y) ^ D+ Z0 W
# X; G0 A" P5 N4 w& U: A& L. O" s' l1 Q( p% c h
2. GPIO工作模式( N0 R. S- j% n4 ~/ r
STM32F103系列的I/O引脚共有8种工作模式,其中输出模式有四种:推挽输出、开漏输出、复用推挽输出、复用开漏输出;输入模式有四种:上拉输入、下拉输入、浮空输入、模拟输入。
/ B' D, M6 e0 O& {7 }; W( j0 a" d
6 W; p1 T5 A0 c+ W
2 F. S+ o3 O; p% ^1 M8 n( a
1. 推挽输出(Push-Pull,PP)2 W9 Y$ Z5 l: D" G3 z: h- `5 S
5 y, r8 s1 n3 A# ~+ D1 j
推挽结构由两个MOS管按互补对称的方式连接,任意时刻总是其中一个三极管导通,另一个三极管截止。
& F4 f: ^# G# W( C/ [如上图①所示,MOS管作为开关使用,“输出控制”向两个MOS管栅极加一定电压,P-MOS管源极和漏极之间导通,VDD经过P-MOS管的S->G->D输出,N-MOS管处于高阻态(电阻很大,近似开路),整体对外为高电平;“输出控制”取消向两个MOS管栅极施加电压,P-MOS管源极和栅极截止,P-MOS管处于高阻态,N-MOS管源极和漏极导通,整体对外为低电平。. K- f; d( U# z- u+ `2 R0 F6 U
推挽模式,让“输出控制”变为了VDD/Vss输出,使得输出电流增大,提高了输出引脚的驱动能力,提高了电路的负载能力和开关的动作速度。2 F' \& H. g3 Z, w$ P
: ` H* b( f# T* {2. 开漏输出(Open-Drain,OD)% ~! l* j5 C* l! }+ P: S
" d: C" v5 k5 K) {" ~
开漏模式下,“输出控制”不会控制P-MOS管,“输出控制”只会向N-MOS管栅极加一定电压,两个MOS管都处于截止状态,两个漏极处于悬空状态,称之为漏极开路。“输出控制”取消栅极的施加电压,P-MOS管依旧处于高阻态,N-MOS管导通,整体对外为低电平。 o5 k' ^- z s- n. S0 l, i
开漏输出模式可以等效将下图中灰色框的P-MOS管看作不存在。即该模式下只能输出低电平,若要输出高电平,则需要外接电阻,所接的电阻称为上拉电阻,此时输出电平取决于此时上拉电阻的外部电源电压情况,如下图中蓝色框的外部电路。
2 M! _' p/ S' G7 N
4 e7 u: _5 S [0 z) I
! t" I+ U$ X7 x
2 m0 Z* u4 m2 O' G$ w5 b- Z! k
推挽输出模式可以直接输出高电平,开漏输出模式需要外接上拉电阻才能输出高电平,但开漏输出拥有一些推挽输出不具有的特性:
% P0 w. E, [) O8 s6 s# Y# K5 z) r$ g/ M! p0 ?
1.利用外部电路驱动能力。如图 8.1.2 所示,“输出控制”只需要提供一个很小的栅极驱动电流,VCC 经过上拉电阻为外部负载提高驱动电流;2.实电平转换。推挽输出模式由VDD提供,即只能提供3.3V电平。使用开漏输出模式后,VCC可以为 5V,从而实现了电平转换的效果。- Y( u/ y- _) v. \: {7 i
3.方便实现“逻辑与”功能。多个开漏的引脚可以直接并在一起使用,统一接一个合适的上拉电阻,就可以实现“逻辑与”关系,即当所有引脚均输出高电平时,输出才为高电平,若任一引脚输出低电平,则输 出低电平。在I²C、SMBUS等总线电路中经常会用到。, x3 i( a( L o A
5 g8 V* G4 G6 ]3 j" q- l6 I
5 H) ~+ b1 N a7 |' w3. 复用功能推挽/开漏输出(Alternate Function,AF)) R7 c6 Q2 z, @, V' p/ ]* H! m; v
6 x7 V; p3 ]' t, dGPIO引脚除了作为通用输入/输出引脚使用外,还可以作为片上外设(USART、I²C、SPI等)专用引脚,即一个引脚可以有多种用途,但同一时刻一个引脚只能使用复用功能中的一个。/ c. C/ ^- R3 \* y/ f& d7 f6 J: ?
当引脚设置为复用功能时,可选择复用推挽输出模式或复用开漏输出模式,在设置为复用开漏输出模式时,需要外接上拉电阻。
" R8 [9 P8 {9 {! D. V. w& w n- A* p! y& H
4. 上拉输入模式(Input Pull-up)2 D2 y* ]. ?7 g2 H5 e8 |0 }$ G' A
如上图中②所示,VDD经过开关、上拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入高电平。该模式的典型应用就是外接按键,当没有按键按下时候,MCU的引脚为确定的高电平,当按键按下时候,引脚电平被拉为低电平。# O# n9 Z. j8 J& t$ `2 H1 a
( F# {; E9 M2 V2 m+ X5. 下拉输入模式(Input Pull-down)
- [: W: @1 s# ]1 P% i5 X如上图 中②所示,Vss经过开关、下拉电阻,连接外部I/O引脚。当开关闭合,外部I/O无输入信号时,默认输入低电平。& m- @5 @! F0 `
0 w7 K: Z* H, T+ U
6. 浮空输入模式(Floating Input)6 b! O7 E$ } v- A
如上图中②所示,两个上/下拉电阻开关均断开,既无上拉也无下拉,I/O引脚直接连接TTL肖特基触发器,此时I/O引脚浮空,读取的电平是不确定的,外部信号是什么电平,MCU引脚就输入什么电平。MCU复位上电后,默认为浮空输入模式。% w" T6 x9 R% f1 B0 Z
6 l7 A0 V0 t/ s5 t7 \ N2 w) K7. 模拟输入模式(Analog mode)
' g7 |( n" [2 F* k. V0 L如上图中②所示,两个上/下拉电阻开关均断开,同时TTL肖特基触发器开关也断开,引脚信号直接连接模拟输入,实现对外部信号的采集。" t" G ~; A* r) y( S- M4 O
$ ~# l, V e, o% G1 h. Y% n
GPIO 输出速度( `0 s( h/ b. R0 e; D0 U6 D+ A
1.STM32的I/O引脚工作在输出模式下时,需要配置I/O引脚的输出速度。该输出速度不是输出信号的速度,而是I/O口驱动电路的响应速度。% ^7 G2 f8 F2 ~! P! ^. I2 B, { h
2.STM32提供三个输出速度:2MHz、10MHz、50MHz。实际开发中需要结合实际情况选择合适的相应速度,以兼顾信号的稳定性和低功耗。通常,当设置为高速时,功耗高、噪声大、电磁干扰强;当设备为低速 时,功耗低、噪声小、电磁干扰弱。) u! ~* V6 d; @4 A# @) W3 t8 F: B
3.通常简单外设,比如LED灯、蜂鸣器灯,建议使用2MHz的输出速度,而复用为I²C、SPI等通信信号引脚时,建议使用10MHz或50MHz以提高响应速度。
" a7 n' [8 s: {% T. [6 C0 ^6 ?- @* B- [. q* J) U" F* a
3. 硬件方面
* U0 F. f0 }5 G
3 ~1 ?9 o* G/ n U
# h/ [* O# C, P4 W- \: y
% R( t/ N' c# y5 p2 \0 z常见LED灯(图片来源百问网)
/ ]! R9 S9 {$ H. Y! s$ x. e9 U! J+ b* j- ~$ f4 P' k# Z8 S
7 c$ B' `* [% ?# O
: S3 T3 m0 k. \- a- H6 Z/ i1 G# tLED灯原理图(图片来源百问网)' o% u& ]& P) G2 {, z$ z
3 Q1 H+ ~! M6 W2 p) `# A2 r
4. 软件方面
4 \; v! o- M; }4 d4 R* @driver_led.h6 F( S$ Y, A! N: E7 _ N0 g. Y
: c: Y# G- ~$ D: w- }
- #ifndef __DRIVER_LED_H
9 k$ O/ ?: u V- N* T4 `7 t, G% {) J - #define __DRIVER_LED_H
# Q. r) T. I1 h - " @- _6 ?4 E3 v: i+ n( O* x/ e* v4 {6 @
- #include "stm32f1xx_hal.h"0 F: h* E( S: M6 |( [% V3 h( d& O
- + X( }: I4 O7 y% q# T
- /*********************
: x: v* J. }$ m4 L3 Y }# | - * 引脚宏定义8 M3 N6 d! l, c8 Y
- **********************/
1 M% d: Y7 d4 E# M, H0 c0 d. `% b' w - #define B_LED_GPIO_PIN GPIO_PIN_13 [+ g( D1 { l6 f/ D. v
- #define B_LED_GPIO_PORT GPIOA- c# u( E z' G% R0 N- n) A+ r* a( O
- #define B_LED_GPIO_CLK_EN() __HAL_RCC_GPIOA_CLK_ENABLE()
! t5 P& f+ b3 p0 h$ A: j& ~+ b! ^ - 3 [' {" D" |& ^0 y4 N
- /*********************
- n3 z! w; ]+ C- [ - * 函数宏定义
, M6 B4 {3 D4 Y# r6 i/ [( E2 Y - **********************/ e0 ?2 Q. W4 c+ u, j
- /*
; u: t) ]% v6 N% G4 Y - * LED亮灭函数宏定义' c) d* M9 h9 P* _
- */; i) B) w6 c9 w. ?8 I
- #define ON GPIO_PIN_RESET1 \% B" q4 A% g' x2 F0 B3 o1 p% c
- #define OFF GPIO_PIN_SET
7 }7 W- C% l7 B* B$ ]0 E* A - 0 e' s4 B" }6 ?( D/ J
- #define BLED(flag) HAL_GPIO_WritePin(B_LED_GPIO_PORT, B_LED_GPIO_PIN, flag)
3 ~ y* N0 a6 J5 o6 f7 T
9 k7 k, n+ H5 k/ D3 H" ^- /*
9 {( T' k1 X! d9 i, ~' Q8 _ - * 函数名:void LedGpioInit(void)8 T/ u0 |+ g, P# j+ e3 F! \
- * 输入参数:无: F( e3 n5 P7 A+ l- M3 e$ b
- * 输出参数:无
# {: v1 r+ O4 V* x& s } - * 返回值:无2 o4 _- J6 O2 h" Z3 \6 L( @
- * 函数作用:初始化LED的引脚,配置为上拉推挽输出
0 m. j+ T& }3 m$ H9 P8 W - */
3 N h8 l$ e! B: H$ ?+ s - extern void LedGpioInit(void);6 L/ U; W3 U& ~& R! B
$ E9 N' o9 A" V5 Z7 E) ? H- #endif
5 c* C. t `1 U# G
复制代码
' X! O' \/ v/ Wdriver_led.c
! B+ ]3 n: G7 G" m0 P: E7 \
: G! p- T& G( n# F7 p( z" _5 o$ K- #include "driver_led.h"8 T7 d* Q, q& y: g8 F8 I2 {) Y
+ F. `; p m# {' U2 v: q4 f6 ^/ b7 c- /*
; f+ W: [0 I6 \1 o0 W3 Z - * 函数名:void LedGpioInit(void)/ f$ Q+ R6 Y& y5 M5 N5 @. z' H" d" z
- * 输入参数:无$ R& g7 A* r% H3 H) n
- * 输出参数:无0 O; W2 j! Q+ `
- * 返回值:无, H1 r. m# M# Y% J4 }. N8 c4 s
- * 函数作用:初始化LED的引脚,配置为上拉推挽输出4 a* V5 O1 P- p7 D- U
- */& r4 ~, t' {* m+ S2 z5 { b
- void LedGpioInit(void)
( J' s& Q; N. e) O0 H3 `# P& U - {
* W/ B0 Y& C; u& H- S5 |8 \ - // 定义GPIO的结构体变量 e, N3 y+ @( ]. m+ {# b, @
- GPIO_InitTypeDef GPIO_InitStruct = {0};
2 A9 ^5 |. o1 n0 u - // 使能LED的GPIO对应的时钟; e& Z4 K, B% z/ f) Z: K* C6 M, b
- B_LED_GPIO_CLK_EN();0 `$ J* T- K2 N1 y8 g2 J
/ G& Z2 n( S' V9 D+ k! l6 |" a! u- GPIO_InitStruct.Pin = B_LED_GPIO_PIN; // 选择LED的引脚4 @7 f: v. G4 y. b6 R" \
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 设置为推挽输出模式
) N) I; f( C2 p0 e) K( T- u - GPIO_InitStruct.Pull = GPIO_PULLUP; // 默认上拉2 v. I) ~/ x4 @: s" d
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 引脚输出速度设置为慢
6 _* T# O7 ], }- }0 Y$ b* P* a
4 F0 \ E5 e' D/ q# g5 I' m) ^- // 初始化引脚配置
* }9 r- T: ~' A, O5 d; r - HAL_GPIO_Init(B_LED_GPIO_PORT, &GPIO_InitStruct);
. e$ j! L1 W/ e3 y$ a - ( p! s+ A0 W9 L7 M. M) U
- // 默认LED灭:OFF-灭,ON-亮
% K9 ^) I0 b9 O. Z! d+ T( I - BLED(OFF);0 ^1 c' n1 z1 o1 w2 B, X* \- v& ?/ c
- }
( s* R. [7 A* w, ?' ]
复制代码
7 ], O2 `! [" W$ Hmain.c: f) i( k! N& n7 h5 J2 i2 r J
( L: q/ Y* q6 U* B' b7 z, p
- #include "main.h"
: J2 r# X. ]) Z" @( q' L" v - #include "driver_led.h" r4 i, a4 C3 C( j2 m' Y( v5 Z/ C
* r3 L- X: k+ g9 ]$ X9 N( [- int main(void)
! ~6 B/ k4 ?" t/ l, F - { : E, C, I3 n- J7 Z
- // 初始化HAL库函数必须要调用此函数
2 o1 m' G4 j/ G0 b) q7 j - HAL_Init();
+ M. |8 z3 t! F# s f& r5 M* M- g - // 系统时钟即AHB/APB时钟配置 u# ]* w, E8 B
- SystemClock_Config(); / l7 a/ z X9 }, B: `1 }: A
- // 初始化LED: v0 K4 |9 e; n9 O, ^
- LedGpioInit();
* k( x* Q: w1 C - // 循环闪烁/ k: x, O. D( O1 J1 ~' _
- while(1)7 a& k* \6 x7 i6 I) Z) R4 _% s
- {
2 @0 `2 d- p6 v2 A. ]7 e - BLED(ON);
. R: _" K: e! V. |4 } - HAL_Delay(1000);
* N& Z4 m1 u& o* Q1 o/ c5 c - BLED(OFF);
# F6 |! ?( I- }3 V% J7 Y - HAL_Delay(1000);: n* v8 k ^6 [ @2 ^& j7 h
- }
% e: {. N6 K: ^ - }
复制代码 0 X& q2 l2 W( D* f; {1 u1 q+ W* E
9 `( U1 y& R9 d/ Y+ ]1 _9 D
|