导读
GPIO(General Purpose Input Output)通用输入输出口,俗称IO口。
GPIO根据使用场景,可配置8种输入输出模式。
GPIO的引脚电平:0V~3.3V(0为低电平,1为高电平),部分引脚可容忍5V(意思是在这个端口输入5V电压,也认为是高电平,但是对于输出而言,最大只能输出3.3V,因为供电就只有3.3V)。
意思是在这个端口输入5V电压,也认为是高电平,但是对于输出而言,最大只能输出3.3V,因为供电就只有3.3V。具体哪些端口可以容忍5V,可以参考STM32的引脚定义,带FT(Five Tolerate)的是可以容忍5V输入的引脚。

GPIO在输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等。如果控制的是功率比较大的设备,只需要再加入驱动电路即可。
GPIO在输入模式下可读取端口的高低电平或电压,用于读取按键输入、外界模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。
GPIO的基本结构

GPIO的外设名称是按照GPIOA、GPIOB、GPIOC等这样的名称来命名的,每个GPIO外设,总共有16引脚,编号是从0到15,例如,GPIOA的第0号引脚,我们把它称作PA0。
在每个GPIO模块内,主要包含了寄存器和驱动器,寄存器就是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,这样就可以完成输出电平和读取电平的功能了。
GPIO模块内寄存器
寄存器的每一位对应一个引脚,其中输出寄存器写1,对应的引脚就会输出高电平,写0,就输出低电平;输入寄存器读取为1,就证明对应的端口目前是高电平,读取为0,就是低电平。因为STM32是32位单片机,所以STM32内部寄存器都是32位,但这个端口只有16位,所以这个寄存器只有低16位对应的有端口,高16位是没有用到的。
GPIO模块内驱动器
驱动器是用来增加信号的驱动能力的,寄存器只负责存储数据,如果要进行点灯这样的操作的话,还是需要驱动器来负责增大驱动能力的。
GPIO中每一位的具体电路结构


IO引脚,这里接了两个保护二极管,这个是对输入电压进行限幅的,上面这个二极管接VDD,3.3V,下面接VSS,0V。
如果输入电压比3.3V还要高,那上方这个二极管就会导通,输入电压产生的电流就会直接流入VDD而不会流入内部电路,这样就可以避免过高的电压对内部这些电路产生伤害。
如果输入电压比0V还要低,这个电压是相对于VSS的电压,所以是可以有负电压的,那这时下方这个二极管就会导通,电流会从VSS直接流出去,而不会从内部汲取电流,也是可以保护内部电路的。
如果输入电压在0~3.3V之间,那两个二极管均不会导通,这时二极管对电路没有影响,这就是保护二极管的用途。

上拉电阻和下拉电阻,上拉电阻至VDD,下拉电阻至VSS,这个开关是可以通过程序进行配置的,如果上面导通、下面断开,就是上拉输入模式;如果下面导通、是上面断开,就是下拉输入模式,如果两个都断开,就是浮空输入模式。
上拉和下拉有什么作用呢?
这个其实是为了给输入提供一个默认的输入电平的,因为对应一个数字的端口,输入不是高电平就是低电平,那如果输入引脚啥都不接,那到底算是高电平还是低电平呢?这就不好说了,实际情况是,如果输入啥都不接,这时输入就会处于一种浮空的状态,引脚的输入电平极易受外界干扰而改变,就像是一个物体悬浮在太空一样,它的位置是不确定的,受到一点扰动就会变化,为了避免引脚悬空导致的输入数据不确定,我们就需要在这里加上上拉或者下拉电阻,如果接入上拉电阻,当引脚悬空时,还有上拉电阻来保证引脚的高电平,所以上拉输入又可以称作默认为高电平的输入方式,下拉也同理,就是默认为低电平的输入方式。
这个上拉或者下拉电阻的阻值都是比较大的,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作。

这个施密特触发器的作用就是对输入电压进行整形的,它的执行逻辑是,如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输出电压小于某一阈值,输出就会瞬间将为低电平。

举个例子,因为这个引脚的波形是外界输入的,虽然是数字信号,实际情况下可能会产生各种失真,如果没有施密特触发器,那很有可能因为干扰而导致误判,如果有了施密特触发器,定一个这样的阈值上下限,高于上限输出高,低于下限输出低,中间对电压留有一定的变化范围,这样可以有效的因信号波动造成的输出抖动现象。
经过施密特触发器整形的波形就可以直接写入输入数据寄存器了,我们再用程序读取输入数据寄存器对应的某一位的数据,就可以知道端口的输入电平了。

最后上面还有两路线路,这些就是连接到片上外设的一些端口。
其中有模拟输入,这个是连接到ADC上的,因为ADC需要接收模拟量,所以这个线是接到施密特触发器前面的。
另一个是复用功能输入,这个是连接到其他需要读取端口的外设上的,比如串口的输入引脚等,这根线接受的数字量,所以在施密特触发器后面。
数字部分可以由输出数据寄存器或片上外设控制,两种方式通过数据选择器接到了输出控制部分。

如果选择通过输出数据寄存器进行控制,就是普通的IO口输出,写这个数据寄存器的某一位就可以操作对应的端口了,那左边还有个叫做位设置/清除寄存器,这个可以用来单独操作输出数据寄存器的某一位,而不影响其他位,因为输出数据寄存器可以同时控制16个端口,并且这个寄存器只能整体读写,所以想单独控制其中某一个端口而不影响其他端口的话,就需要一些特殊的操作方式,第一种方式实现读出这个寄存器,然后用按位与和按位或的方式更改某一位,最后再将更改后的数据写回去,在C语言中就是&=和|=的操作,这种方法比较麻烦,效率不高,对于IO口的操作而言不太合适;

第二种方式是通过设置这个位设置/位清除寄存器,如果我们要对某一位进行置1的操作,在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,这样它内部就会有电路,自动将输出数据寄存器中的对应位置置为1,而剩下写0的位则保持不变,这样就保证了只操作其中某一位而不影响其他位,并且这是一步到位的操作;如果我们要对某一位进行清0的操作,就在清除寄存器的对应位写1即可,这样内部电路就会把这一位清0了,这就是第二种方式,这也就是这个位设置和位清除寄存器的作用。
另外还有第三种操作方式,就是读写STM32中的“位带”区域,在STM32中,专门分配的有一段地址区域,这段地址区域映射了RAM和外设寄存器所有的位,读写这段地址中的数据,就相当于读写所映射位置的某一位,这就是位带的操作方式。

接下来输出控制之后就接到了两个MOS管,上面是P-MOS,下面是N-MOS,这个MOS管就是一种电子开关,我们的信号来控制开关的导通和关闭,开关负责将IO口接到VDD或者VSS,在这里可以选择推挽、开漏或者关闭三种输出方式。
在推挽输出模式下,P-MOS和N-MOS均有效,数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平。数据寄存器为0时,上管断开,下管导通,输出直接接到VSS,就是输出低电平。这种模式下,高低电平均有较强的驱动能力,所以推挽输出模式可以叫做强推输出模式。在推挽输出模式下,STM32对IO口具有绝对的控制权,高低电平都由STM32决定。
在开漏输出模式下,这个P-MOS是无效的,只有N-MOS在工作,数据寄存器为1时,下管断开,这时输出就相当于断开,也就是高阻模式。数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平。这种模式下,只有低电平有驱动能力,高电平是没有驱动能力的。 开漏输出模式有什么用呢?这个模式可以作为通信协议的驱动方式,比如I2C通信的引脚,就是使用的开漏模式,在多机通信的情况下,这个模式可以避免各个设备的相互干扰,另外开漏模式还可以用于输出5V的电平信号,比如在IO口外界一个上拉电阻到5V的电源,当输出低电平时,由内部的N-MOS直接接VSS,到输出高电平时,由外部的上拉电阻拉高至5V,这样就可以输出5V的电平信号,用于兼容一些5V电平的设备,这就是开漏输出的主要用途。
最后一种模式就是关闭,这个是当引脚配置为输入模式的时候,这两个MOS管都无效,也就是输出关闭,端口的电平由外部信号来控制。
GPIO的8种工作模式

浮空/上拉/下拉输入的电路结构

这里可以看到,在输入模式下,输出寄存器是断开的,端口只能输入而不能输出。上拉下拉电阻可以配置为上拉输入、下拉输入和浮空输入,然后输入通过施密特触发器进行波形整形后,连接到输入数据寄存器。
模拟输入的电路结构

模拟输入是ADC模数转换器的专属配置,这里的输出是断开的,输入的施密特触发器也是关闭的无效状态,只剩下一根线了,也就是从引脚直接接入片上外设,也就是ADC,所以当我们使用ADC的时候,将引脚配置为模拟输入就行了,其他时候一般用不到模拟输入。
开漏输出和推挽输出的电路结构

这时候,输出是由输出数据寄存器控制的,这个P-MOS如果无效,就是开漏输出,如果P-MOS和N-MOS都有效,就是推挽输出。另外,我们可以看到,在输出模式下,输入模式也是有效的,但是在输入模式下,输出都是无效的,这是因为一个端口只能有一个输出,但可以有多个输入,所以当配置成输出模式的时候,内部也可以顺便输入一下,这个是没啥影响的。
复用开漏输出和复用推挽输出
这俩模式跟普通的开漏输出和推挽输出差不多,只不过复用的输出,引脚电平是由片上外设控制的。

可以看到输出数据寄存器是没有连接输出控制的,引脚的控制权转移到的片上外设,由片上外设来控制。在输入部分,片上外设也可以读取引脚的电平,同时普通的输入也是有效的,顺便接收一下电平信号。
其实在GPIO的8种模式中,除了模拟输入这个模式会关闭数字的输入功能,其他的7种模式种,所有的输入都是有效的。
这些就是STM32 GPIO的全部介绍了。
LED和蜂鸣器介绍

如果是引脚没有修剪过的LED,其中长脚是正极,短脚是负极,通过LED内部也可以看到正负极。