【51/STM32】详解单片机GPIO口输入输出的各种模式

摘要:通用型输入输出端口,简称GPIO口,是单片机与其他外围设备和电路进行信息交换和控制的桥梁。本文从复习模电中所学的场效应管开始,逐步引出开漏输出与推挽输出的概念及阐述其原理,并结合点亮一盏LED灯的实例进一步探讨IO口的输出模式;随后介绍单片机的输入电路;在此基础上,结合IO口输入与输出模式的性质介绍标准双向IO口和准双向IO口的特点;最后分别对51单片机和STM32单片机的IO口结构进行讲解,其中重点对STM32单片机的GPIO寄存器和工作原理进行介绍。

本文参考资料有
● GPIO输入输出各种模式(推挽、开漏、准双向端口)详解 https://blog.csdn.net/techexchangeischeap/article/details/72569999
● 单片机小白学步(20) IO口原理 https://m.sohu.com/a/76703738_119709
● GPIO工作模式之开漏输出 https://mp.weixin.qq.com/s/ly1AAlVlxB2x1hmF7w-aAg
● 开漏输出与推挽输出 https://www.zhihu.com/tardis/sogou/art/41942876
● 详解开漏输出,推挽输出,上拉电阻 https://mp.weixin.qq.com/s/eEOr44wq8v5u_Cjxy7EVGA



一、预备知识

        首先我们需要复习一下模电里面所学的场效应管的基本知识。
        场效应管主要分为两种类型:结型场效应管(JFET)和绝缘栅型场效应管(MOS - FET),这里我们介绍的主要是绝缘栅性场效应管。
        绝缘栅性场效应管(MOS管)有两种结构形式,可以分为N沟道型和P沟道型,每一种沟道又可以分为增强型和耗尽型两种。增强型和耗尽型的主要区别,是当Ugs = 0时,增强型MOS管漏极和源极之间不存在导电沟道,而耗尽型MOS管漏极和源极之间存在导电沟道。以N沟道增强型MOS管为例,栅源之间必须加正向电压Ugs > 某一开启电压,才能形成导电沟道。

Alt
        下面引入反相器的概念,反相器可以实现对输入信号的反相。下图电路模型即代表反相器(右端有一个小圆圈)。
在这里插入图片描述
        反相器通常由一对互补的PMOS管和NMOS管制成。由于PMOS管和NMOS成对出现在电路中,且二者在工作时总是互补,当其中一个管导通时,另一个管总是截止,故称为CMOS管(意为互补)。
在这里插入图片描述
        数字电路中多采用增强型MOS管,上图即为增强型PMOS管和NMOS管组成的非门电路。两个互补的MOS管开启电压相同,且需小于VDD。
        增强型PMOS管的导通条件为Ug < Us,增强型NMOS管的导通条件为Ug > Us。
        所以当输入信号为高电平时,上面的PMOS管截止,下面的NMOS管导通,此时Uo输出低电平;当输入信号为低电平时,上面的PMOS管导通,下面的NMOS管截止,此时Uo输出高电平。这样就实现了信号的反相。

二、开漏输出(Open Drain Output)

在这里插入图片描述
        开漏输出的全称是漏极开路输出电路,与开集输出十分类似,只是将三极管中的集电极替换成了场效应管中的漏极,上图就是一个漏极开路输出的电路模型。从输入端INT输入低电平后,经过反相器的作用变成高电平,那么NOMS管的栅极就是高电平,由于源极接地,此时Ug > Us,故NMOS管导通,这样输出端OUT就能输出低电平;而从输入端INT输入高电平后,经过反相器的作用变成低电平,此时Ug = Us,故NMOS管截止,这时输出端OUT悬空。悬空的意思,就是指输出既不是高电平也不是低电平,是一种很特殊的状态,称为高阻态。
        也就是说,在开漏电路中,输入低电平可以从输出端得到低电平,而输入高电平却不能从输出端得到高电平,得到的只是高阻态。要想从输出端得到高电平,输出端必须通过一个上拉电阻连接到VCC(即图中虚线框部分)。具体可以拉高到多少伏,是由外部所接的VCC所决定的。所以开漏输出电路具有电平转化的能力,在需要进行电平转化的地方,非常适合使用开漏输出。

三、推挽输出(Push-Pull Output)

在这里插入图片描述
        推挽结构是指两个MOS管或者三极管收到互补控制的信号控制,两个管子总是一个在导通时另一个截止。
        与开漏输出不同,推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。
        但推挽输出的一个缺点是,如果将两个推挽输出结构的引脚相连在一起,并且其中一个引脚输出高电平(即上面的MOS导通,下面的MOS管截止),同时另一个引脚输出低电平(即上面的MOS闭合,下面的MOS导通)。此时电流会从第一个引脚的VCC通过上端MOS管再经过第二个引脚的下端MOS管直接流向GND。整条通路上电阻很小,会发生短路,进而可能造成端口的损害。所以推挽输出结构的IO口不能用来实现“线与”,而“线与逻辑”的特点会在使用矩阵键盘时大有用处。
        下面是用51单片机连接4 × 4矩阵键盘的电路原理图,由于51单片机的IO口均为开漏输出,所以可以通过“线与逻辑”来实现矩阵键盘。关于矩阵键盘扫描的具体原理,这里就不再介绍,有兴趣的同学可以查阅相关资料。
在这里插入图片描述

四、IO口的输出实例:点亮一盏LED灯

        以51单片机为例,电路关键部分如下图所示,VCC通过1k电阻连接到LED的正极,LED的负极接到P1.0口。只要控制单片机P1.0口输出低电平,LED灯就会由于二极管两端的压差而点亮,如果P1.0口输出的是高电平,二极管两端压差为0,二极管就不会导通,从而LED灯不会被点亮。电流方向是从外部电路经过P1.0引脚流进单片机内部的,这种由外部电路流进单片机内部的电流,称为灌电流。
在这里插入图片描述
        程序实现也十分简单,由于51单片机并不需要对IO口进行初始化配置,所以P1.0口直接输出低电平即可。

#include <reg51.h>
sbit LED = P1^0;
void main()
{
	LED = 0; //P1.0口输出低电平
	while(1);
}

        下面再来介绍另外一种点亮LED灯的电路结构,简化电路模型如下图所示:
在这里插入图片描述
        这是开漏输出+上拉电阻的简化电路,蓝色框的部分表示在单片机内部,开关S相当于开漏输出电路的NMOS管,与开漏输出不同的是,P1.0引脚通过10k电阻上拉到VCC,所以引脚能够通过程序写1或写0输出高低电平。当单片机内部执行P1^0 = 0的时候,NMOS管导通,相当于开关S闭合,P1.0引脚接地输出低电平,LED由于两端没有压差而不会点亮;当单片机内部执行P1^0 = 1的时候,NMOS管截止,相当于开关S断开,P1.0引脚通过上拉电阻获得高电平输出,二极管由于两端正向压差而导通,从而LED灯点亮发光。该电路的电流方向是从单片机内部流向外部电路的,所以称为拉电流 。对于51单片机,灌电流接法电流较大,拉电流接法由于受到上拉电阻的限制,电流较小,导致LED灯不亮或者亮度很微弱,所以通常不采用这种接法。
        根据STC官方的芯片手册,对于STC单片机,建议单个IO口灌电流建议不超过20mA,所有IO口灌电流之和不超55mA,否则容易烧坏IO口。而拉电流大小只有230uA左右。

五、浮空输入电路(高阻态输入)与施密特触发器

在这里插入图片描述
        为了简单化,这里所说的输入IO,指的是只作为输入,不具有输出功能。此时对于输入引脚的要求就是高阻。
        图示中的缓冲器U1是具有控制输入端,且具有高阻抗特性的三态缓冲器。通俗地说就是这个缓冲器对外来说是高阻的,相当于在控制输入端不使能的情况下,物理引脚与内部总线之间是完全隔离的,完全不会影响内部电路。而控制输入端的作用就是可以发出读Pin状态的操作指令。其过程下图所示:
在这里插入图片描述
        这种基本电路的一个缺点是在读取外部信号的跳变沿时会出现抖动,为了解决这一问题,可以加入施密特触发器用于整形,获得外部信号较理想的矩形脉冲。
        利用施密特触发器的滞回特性,可以获得较为理想的矩形脉冲。下面我们以带施密特触发器的非门结构为例进行说明:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
        一般的逻辑门对输入电压在阈值附近的波动敏感,容易造成GPIO口对输入电平的读取错误,而在输入端加入了施密特触发器后,很大程度上克服了输入电压的波动,增强了输入的抗干扰能力。

六、带弱上拉的输入电路

        浮空输入状态下,IO的电平状态完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的。为了防止输入端悬空,减少外部电流对芯片的干扰,限流,增加(准)双向IO口高 / 低电平输出时的驱动能力,可以在输入引脚处加入一个上拉电阻或下拉电阻。当GPIO引脚无输入时,上拉输入在默认状态下为高电平,下拉输入在默认状态下为低电平。一般来讲,上拉电阻为1K­Ω ~ 10KΩ,电阻越小,驱动能力越强,下面主要介绍带弱上拉的输入电路。
        如下图所示,浮空输入模式下,引脚悬空时会受到外部信号的干扰,读取到不确定的电平状态。
在这里插入图片描述
        如下图所示,在输入引脚处加入一个上拉电阻Rp后,默认状态下为高电平,只有当输入引脚被低电平驱动时读到的才是0,对1的读取方式为“读取非0”,即除非有低电平输入,否则读脉冲信号就为1。
在这里插入图片描述
        但这种电路的一个问题是引脚对外呈现的不是高阻态,在某种意义上来说也在向外输出,当外部驱动电路不同时可能出现错误的检测结果。例如一个简化的外部驱动电路是如下图所示的结构,当开关K打到上端时,Test Point输出高电平;当开关K打到下端时,Test Point输出低电平。
在这里插入图片描述
        如果让这个外部驱动电路输出低电平,并将此电路与单片机带有弱上拉电阻(10kΩ)的输入引脚相连,其结构如下所示:
在这里插入图片描述
        由欧姆定律知,测试点处的电平是5V * (100K / 110K) = 4.545V,于是单片机的IO口测得的输入信号为高,而外部驱动电路实际输出的电平为低。这种错误的原因就在于单片机这种结构的输入电路并不是真正的高阻,或者说这个输入IO其实也在输出,而且影响了外部输入电路。
        这种情况的发生也说明了:信号前后两级传递,为什么需要输出阻抗小,输入阻抗大的原因。在这个例子中,外部驱动电路的输出阻抗很大,达到了100kΩ;而输入端的阻抗又不够大,只有10kΩ,于是就出现了问题。如果输入端的输入阻抗真正做到高阻(无穷大),如下图所示,K1和K2断开使得输入引脚悬空呈现高阻态,就不会出现输入电平检测错误的问题。
在这里插入图片描述

七、标准双向IO口的特点

  7.1 一个IO口可以既用作输出,又用作输入,但标准的双向IO口要满足以下两个特点:

✔① 在输出模式下,可以输出高低电平
✔② 在输入模式下,如果没有接外部电路,应呈现高阻态

  7.2 开漏输出、推挽输出结构是否可以作为双向IO?

✔① 由于开漏输出电路不能正常输出高电平,所以不能作为双向IO。
✔② 虽然推挽电路在输出模式下可以输出高低电平,但在输入模式下,必须将推挽输出结构的PMOS管和NMOS管都断开,才不会因某一MOS管的导通而锁死Pin引脚的电平状态。但推挽电路的特性,是两个晶体管收到互补信号的控制,总是一管导通时另一管截止。如果通过控制使得两个晶体管都断开,就能使得这个IO口作为输入,并且此时在没有外部电路时呈现高阻态,所以推挽输出结构有可能成为双向IO口。

八、准双向IO:开漏输出 + 上拉电阻 = (准) 输入输出端口

在这里插入图片描述
✔① 作为输出引脚使用时,能通过向锁存器U1中写入“1”或“0”,而从Pin引脚获得高 /低电平;作为输入引脚使用时,必须先向U1中写“1”,已达到断开T1的目的,否则U1为0会使得T1导通,从而导致Pin引脚的电平始终为GND,即外部无论输入什么信号,U2读回的全部为低电平。(具体原理可参考本文对开漏输出的介绍)
✔② 单片机内部以该电路连接的IO作为输入端口时,由于上拉电阻的存在,Pin引脚不悬空,即输入阻抗不为高阻,不满足标准双向IO口的第二点条件,所以称为准双向IO口。
✔③ 准双向端口读取输入状态,默认为高。也就是判断外部输入信号的方法是"非低则为高"。即该结构只能准确的识别外部的低电平,无法区分悬空和真正的高。于是只要读到的不是0,都认为外部为1。

九、51单片机的IO口结构

        51单片机的IO口用作普通输入输出功能,而不是工作在第二功能状态下时,都是开漏结构。对于51单片机的P1、P2、P3口,由于有内部上拉电阻,输入模式下不可能出现高阻态,所以称之为准双向IO口。而P0口默认不接上拉电阻,所以无法输出高电平,而如果外部加了上拉电阻连接到VCC,输入时又不会出现高阻态,所以也是准双向IO口。51单片机在使用IO口之前不需要配置输入还是输出状态,直接往引脚写入“0”或“1”就能输出相应的电平,而用作输入时,必须要向该引脚写“1”置成高电平,才能去读取引脚的电平状态。

十、STM32单片机的IO口结构

        STM32单片机的IO口与51单片机相比较为复杂,使用前必须操作相关寄存器完成对IO口的初始化配置(单片机内部会选择不同的硬件电路连接),以选择不同的输入 / 输出模式,配置完成后,共有八种输入 / 输出模式:浮空输入、上拉输入、下拉输入、模拟输入、开漏输出、推挽输出、复用开漏输出、复用推挽输出。这里除了模拟输入、复用开漏输出和复用推挽输出,其他的输入 / 输出模式都已经在前面介绍了,这里就主要对STM32单片机的GPIO寄存器及其配置成输入和输出模式下的内部工作原理进行详解。

10.1 GPIO寄存器

        STM32单片机一共有10个与GPIO口相关的寄存器,但GPIO口的初始化只用到了其中的四个,分别为端口模式寄存器MODER、输出类型寄存器OTYPER、输出速度寄存器OSPEEDR、上拉/下拉寄存器PUPDR,通过软件操作这四个寄存器就可以完成单片机对GPIO口输入 / 输出模式的配置。
        以下对寄存器的说明参考《STM32F4xx中文参考手册》第7章第4小节:
7.4.1 GPIO 端口模式寄存器 (GPIOx_MODER) (x = A…I)
复位状态
● PA13、PA14、PA15、PB3、PB4:复用功能模式
● 其它GPIO口:输入模式
复位值:
● 0xA800 0000(端口 A)
● 0x0000 0280(端口 B)
● 0x0000 0000(其它端口)
位 2y:2y+1 MODERy[1:0] :端口 x 配置位 (Port x configuration bits) (y = 0…15)
这些位通过软件写入,用于配置 I/O 方向模式。
● 00:输入(复位状态)
● 01:通用输出模式
● 10:复用功能模式
● 11:模拟模式

7.4.2 GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x = A…I)
● 复位状态:所有GPIO口配置成输出推挽
● 复位值:0x0000 0000
位 31:16 保留,必须保持复位值。
位 15:0 OTy[1:0] :端口 x 配置位 (Port x configuration bits) (y = 0…15)
这些位通过软件写入,用于配置 I/O 端口的输出类型。
● 0:输出推挽(复位状态)
● 1:输出开漏

7.4.3 GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x = A…I/)
复位状态
● PB3:11:30 pF 时为 100 MHz(高速)(15 pF 时为 80 MHz 输出(最大速度))
● 其它GPIO口:2MHz(低速)
复位值:
● 0x0000 00C0(端口 B)
● 0x0000 0000(其它端口)
位 2y:2y+1 OSPEEDRy[1:0] :端口 x 配置位 (Port x configuration bits) (y = 0…15)
这些位通过软件写入,用于配置 I/O 输出速度。
● 00:2 MHz(低速)
● 01:25 MHz(中速)
● 10:50 MHz(快速)
● 11:30 pF 时为 100 MHz(高速)(15 pF 时为 80 MHz 输出(最大速度))

7.4.4 GPIO 端口上拉/ 下拉寄存器 (GPIOx_PUPDR) (x = A…I/)
复位状态
● PA13、PA15、PB4:上拉 ;PA14:下拉
● 其它GPIO口:无上拉或下拉
复位值:
● 0x6400 0000(端口 A)
● 0x0000 0100(端口 B)
● 0x0000 0000(其它端口)
位 2y:2y+1 PUPDRy[1:0] :端口 x 配置位 (Port x configuration bits) (y = 0…15)
这些位通过软件写入,用于配置 I/O 上拉或下拉。
● 00:无上拉或下拉
● 01:上拉
● 10:下拉
● 11:保留

10.2 输入 / 输出模式的寄存器端口位配置表

        GP输出 = 通用输出    PP = 推挽    OD = 开漏    PU = 上拉    PD = 下拉
        GPIO口的初始化一般需要配置四个寄存器:MODER寄存器、OTYPER寄存器、OSPEEDR寄存器、PUPDR寄存器,但如果选择GPIO口作为输入功能,可以只配置MODER寄存器和PUPDR寄存器,OTYPER寄存器和OSPEEDR寄存器的状态不会影响输入。
在这里插入图片描述
在这里插入图片描述
        与51单片机类似,如果将stm32的某一IO口配置成开漏输出,且端口带上拉电阻,该IO口就成为一个准双向IO口,同样地,用作输入前,必须要向该引脚写“1”置成高电平,才能去读取引脚的电平状态。(或者需要切换使用输入功能时,直接操作MODER寄存器配置成输入)
      ● stm32的GPIO口配置成准双向IO口的步骤:
✔ MODER寄存器配置成通用输出模式,OTYPER寄存器配置成输出开漏,PUPDR寄存器配置成上拉。
✔ 输出时: GPIOx­>ODR =输出值;
✔ 输入时: 读时先输出高电平(否则有可能因先前低电平输出锁死IO口为低电平),然后读。
      GPIOx­>ODR = 0xFFFF;
      变量 = GPIOx­>IDR;

10.3 GPIO口通过MODER寄存器配置成输入模式的工作原理

● 输出缓冲器被关闭
● 施密特触发器输入被打开
● 根据 GPIOx_PUPDR 寄存器中的值决定是否打开上拉和下拉电阻
● 输入数据寄存器每隔 1 个 AHB1 时钟周期对 I/O 引脚上的数据进行一次采样
● 对输入数据寄存器的读访问可获取 I/O 状态
在这里插入图片描述

10.4 GPIO口通过MODER寄存器配置成输出模式的工作原理

● 输出缓冲器被打开:
— 开漏模式:输出寄存器中的“0”可激活 N-MOS,而输出寄存器中的“1”会使端口保持高阻态 (Hi-Z)(P-MOS 始终不激活)。
— 推挽模式:输出寄存器中的“0”可激活 N-MOS,而输出寄存器中的“1”可激活P-MOS。
● 施密特触发器输入被打开
● 根据 GPIOx_PUPDR 寄存器中的值决定是否打开弱上拉电阻和下拉电阻
● 输入数据寄存器每隔 1 个 AHB1 时钟周期对 I/O 引脚上的数据进行一次采样
● 对输入数据寄存器的读访问可获取 I/O 状态
● 对输出数据寄存器的读访问可获取最后的写入值
        注意:虽然在输出模式下施密特触发器输入也打开,且输入数据寄存器每隔一个AHB1时钟周期对IO引脚上的数据进行一次采样,但IO口的电平状态很有可能通过推挽 / 开漏结构中的P-MOS管被钳制在高电平,或通过N-MOS管被钳制在低电平,此时对输入数据寄存器的读访问反映的只是IO口此时输出电平的高低,并不能用来检测外部电路输入的电平状态。要想使用该IO口作为输入,需要将MODER寄存器配置成输入模式,此时输出缓冲器被关闭,不会使电平检测发生错误。或者,可以在输出开漏+上拉电阻的配置模式下,向输出数据寄存器中写“1”,此时N-MOS管截止,IO口不会因为N-MOS管被钳制在低电平,从而可以进行外部电路输入电平的检测。
在这里插入图片描述

  • 40
    点赞
  • 218
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
首先,需要连接数码管到STM32F103R6开发板上,并确定数码管的引脚连接正确。然后,需要在代码中配置GPIO为输出模式,并设置输出低电平。 以下是示例代码: ```c #include "stm32f10x_gpio.h" int main(void) { // 初始化GPIO GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 使数码管转动 int i; for(i=0; i<8; i++) { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 数码管第一位亮 GPIO_SetBits(GPIOA, GPIO_Pin_1); // 数码管第二位灭 GPIO_SetBits(GPIOA, GPIO_Pin_2); // 数码管第三位灭 GPIO_SetBits(GPIOA, GPIO_Pin_3); // 数码管第四位灭 // 等待一段时间 delay(1000); GPIO_SetBits(GPIOA, GPIO_Pin_0); // 数码管第一位灭 GPIO_ResetBits(GPIOA, GPIO_Pin_1); // 数码管第二位亮 GPIO_SetBits(GPIOA, GPIO_Pin_2); // 数码管第三位灭 GPIO_SetBits(GPIOA, GPIO_Pin_3); // 数码管第四位灭 // 等待一段时间 delay(1000); GPIO_SetBits(GPIOA, GPIO_Pin_0); // 数码管第一位灭 GPIO_SetBits(GPIOA, GPIO_Pin_1); // 数码管第二位灭 GPIO_ResetBits(GPIOA, GPIO_Pin_2); // 数码管第三位亮 GPIO_SetBits(GPIOA, GPIO_Pin_3); // 数码管第四位灭 // 等待一段时间 delay(1000); GPIO_SetBits(GPIOA, GPIO_Pin_0); // 数码管第一位灭 GPIO_SetBits(GPIOA, GPIO_Pin_1); // 数码管第二位灭 GPIO_SetBits(GPIOA, GPIO_Pin_2); // 数码管第三位灭 GPIO_ResetBits(GPIOA, GPIO_Pin_3); // 数码管第四位亮 // 等待一段时间 delay(1000); } } // 等待一段时间的延时函数 void delay(int t) { int i, j; for(i=0; i<t; i++) for(j=0; j<10000; j++); } ``` 在这个示例代码中,我们使用了GPIOA的四个引脚分别控制数码管的四个位,通过依次使每个位亮起来,从而达到数码管转圈的效果。同时,我们使用了一个简单的延时函数`delay()`来控制每个位亮起来的时间间隔。您可以根据实际需要修改延时时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值