STM32单片机初学3-GPIO详解

GPIO(General-purpose input/output)即通用输入输出。GPIO是单片机最基本的外设,任何其他外设都离不开GPIO。本文将详细讲解GPIO的内部的构造及其工作原理,并附以简单的程序以供初学者学习。

与51单片机不同(51单片机直接写P0=0xff或者P0^1=1就能对IO口进行高低电平的控制),STM32的IO在使用前必须进行IO口的初始化并启用对应的时钟,否则IO口是不会输出的。

首先看GPIO内部结构图(如下)

 IO口有两条路径,一条是输出(I:in),一条是输入(O:out)。

我们把控制IO输入的部分称为输入驱动器(如上图的上虚线框所示),它由一个肖特基触发器和一组上下拉电阻及其控制开关组成。肖特基触发器用于数字量输入(即1和0)时维持电平的稳定(其原理暂不细说),如果是作为模拟量输入,则会跳过肖特基触发器。右边的开关及上拉电阻状态取决于IO口的模式,下文将详细讲解。

同样,我们把控制IO输出的部分称为输出驱动器(如上图的下虚线框所示),他由输出控制器和两个MOS管(上管为PMOS,与VCC相连;下管为NMOS,与GND相连)组成。输出控制器用以控制两个mos管的开关状态以输出高电平或者低电平,其状态也取决于IO的模式,下文详细讲解。

保护二极管用于将IO口的电平限制在Vss与Vdd之间。

接下来详细介绍GPIO在各个模式下的工作状态。

 STM32的GPIO共有8种模式(4种输入,4种输出),如下表:

 1.模拟输入 AIN

首先说明一点,在输入模式(不管哪种输入模式)下,输出驱动器的两个MOS管全部处于断开状态。

模拟量输入模式用于将0-3.3V(3.3V是基于单片机的供电电压,如果电压为3.2V,则是0-3.2V)的电压模拟量转换成0-2^12(以STM32F103C8T6为例,其ADC为12位,所以是0到2的12次方)的数字量供CPU使用。模拟量信号采集需要用到该模式,比如温湿度采集,电压电流采集等。

 该模式下,输入驱动器的上下两个开关全部处于断开状态,模拟量路径如上图箭头所示。

IO的电压直接输入到片内的ADC上。

2.浮空输入IN_FLOATING

该模式用于数字量输入,CPU会采集IO口的电平高低。

该模式下输入驱动器的上下两个开关全部处于断开状态(此时IO口呈现高组态,可认为IO与单片机内部断路),信号路径如上图箭头所示。为了研究方便,现将输入驱动器简化如下。

在该模式下,如果IO口处于悬空状态(即IO口既不是高电平,也不是低电平),CPU采集到的值也会处于一个不确定的状态,所以这种输入模式下需要外接上拉电阻或者下拉电阻才能有稳定输入(如下)。

 对于外接上拉电阻法:在开关断开时,IO的电平会被拉到VCC,也就是高电平,而开关闭合时,IO直接与GND相连,强制变成低电平。(断1闭0)

 对于外接下拉电阻法:在开关断开时,IO的电平会被拉到GND,也就是低电平,而开关闭合时,IO直接与VCC相连,强制变成高电平。(断0闭1)

两种接法的开关逻辑正好相反。上拉(下拉)电阻的阻值可取1K-10K(不能小于VCC/引脚最大灌电流)。

这两种接法的上拉(下拉)电阻的作用是限制开关闭合时的电流。没有这两个电阻,开关闭合时,VCC与GND直接连接会短路。

3.下拉输入 IPD

该模式与浮空输入的下拉电阻法原理相同,只是省去了外接下拉电阻,而使用了片内的下拉电阻。

该模式下,片内下拉开关保持闭合,上拉开关保持断开。

 在外部的开关断开时,IO的电平会被拉到VSS,也就是低电平,而开关闭合时,IO直接与VCC相连,强制变成高电平。(断0闭1)

4.上拉输入IPU

这种模式与下拉输入相对,与浮空输入的上拉电阻接法原理一样。即片内上拉开关保持闭合,下拉开关保持断开。

  在外部的开关断开时,IO的电平会被拉到VDD,也就是高电平,而开关闭合时,IO直接与GND相连,强制变成低电平。(断1闭0)

其开关逻辑与上拉输入相反。

5.开漏输出OUT_OD

再次说明一点,在输出模式(不管哪种输入模式)下,输入驱动器的两个开关全部处于断开状态。

为了研究方便,现将输出驱动器电路简化如下(两个MOS管简化成开关):

 在开漏模式下,上管保持断开。

当IO口输出1,下管断开,IO与单片机内部断开;

当IO输出0,下管闭合,IO与VSS直接相连,钳置为低电平。

 观察就会发现,IO口输出1时,其状态并不是输出高电平(IO与单片机内部断开,可认为是高阻态),输出0时,状态倒是输出低电平。这就是开漏输出的特点:开漏输出模式不能直接输出高电平,只能输出低电平(从电流的角度看,电流只能流入单片机IO口)。这就是开漏输出的得名原因:只有开路或者电流流入两种状态。

所以如果想用该模式驱动一个LED灯,有两种接法:外接上拉电阻、漏型接法。

 当IO口输出1,IO本身是没有输出高电平的,但IO口通过上拉电阻能输出高电平,所以LED被点亮。上拉电阻也是LED的限流电阻,在这种状态下,没有上拉电阻,VCC将直接加在LED两端,会烧毁LED。

当IO输出0,下管导通,IO口与VSS相连,LED阳极为低电平,LED两端没电压,熄灭。电流从VCC经过上拉电阻流入IO口再到VSS。

可以发现,在这种模式下,不管输出IO输出1还是0,也不管LED是亮还是灭,都会有电流从上拉电阻流过,也就是都会消耗电流,而且LED灭的时候电流反而大。

 这种模式相比上拉电阻接法看起来电路简单些。LED阳极与VCC相连,阴极接IO口。

当IO口输出1,下管断开,LED的环路开路,电流无法流过LED,LED灭。

当IO口输出0,下管闭合,LED的环路闭合,电流从VCC经LED,再经LED流进单片机IO口,再流向VSS,LED点亮

这种接法下,LED的状态与程序里IO口的逻辑相反(1灭0亮)。LED灭的时候,完全不消耗能量,只有亮的时候才消耗电流。

限流电阻的作用是在LED点亮时限制流过LED的电流,以免烧坏,其大小可按Vcc/LED工作电流计算。

6.推挽输出OUT_PP

推挽输出是最常用的模式之一。它既能输出高电平也能输出低电平,具有强大的驱动能力。

在这种模式下,上管与下管的状态永远相反。

IO口输出1时,上管闭合,下管断开,IO口直接与VDD相连,是为高电平;

IO口输出0时,上管断开,下管闭合,IO口直接与VSS相连,是为低电平。

这种模式下,有源型漏型两种接法。

源型接法,IO口输出高电平时,LED点亮。电流从IO口流出,经过限流电阻、LED流向GNG

 

 漏型接法,IO口输出低电平时,LED点亮。电流经过限流电阻、LED从IO口流入,再流向VSS。

这种模式下,切勿将IO口引脚直接连在一起,因为在IO口输出状态不同时,IO口会发生短路(如下所示),容易烧坏IO口。

7.复用开漏输出AF_OD

复用开漏输出是指复用功能的开漏输出,工作原理与开漏输出相同。STM32的外设接口(如IIC,SPI通信接口等)都是复用IO口,假设使用PB10,PB11作为IIC通信接口,使用复用开漏输出模式后,这两个的IO的状态将完全由内部外设输出决定,而不再受GPIO输出数据寄存器控制。可以有效避免对GPIO的误操作而影响IIC的正常通信。

 8.复用推挽输出AF_PP

复用开漏输出是指复用功能的推挽输出,工作原理与推挽输出相同。同样他也是用于复用功能的输出。具体使用复用推挽还是复用开漏视情况而定。同样,复用开漏也无法直接输出高电平,需外接上拉电阻。所以实际使用中,复用推挽还是用的比较多。

GPIO的8种模式已经介绍完了,接下来以简单的程序演示,如何使用STM32的GPIO。

查看STM32外设说明书,GPIO有很多函数。

 但本文只讲GPIO的基本使用:GPIO初始化,GPIO的置位复位。

前文讲到,GPIO使用前需要进行初始化。

下面是一个简单的GPIO初始化函数,下面以此解析各行程序作用。

void GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;        //定义一个GPIO初始化参数的结构体
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //启用GPIOB的时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;     //选择要初始化的引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;      //设置IO速度
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_PP;       //设置IO模式
	GPIO_Init(GPIOA,&GPIO_InitStructure);	        //初始化
}

这里我定义一个函数void GPIO_Init(void),用来做GPIO的初始化。只要在主函数调用该函数就能对相应的IO口做初始化。

GPIO_InitTypeDef GPIO_InitStructure:

这行程序定义了一个名为GPIO_InitStructure的结构体(这个名字可以自由取),这个结构体原型为GPIO_InitTypeDef(拼写需完全一致且不可更改)。GPIO_InitTypeDef是在STM32外设库中的stm32f10x_gpio.h中定义好了,它包含GPIO_Pin、GPIO_Speed、GPIO_Mode三个成员组成,分别对应Pin脚值、速度、模式三个参数。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE):

与51单片机不同,STM32每种外设都有专门的时钟控制器,要想使用对应的外设,则需要打开对应的时钟控制器(默认是不开的,目的是降低功耗),否则对GPIO的任何操作都是无效的。查看STM32内部结构图,GPIO口的时钟控制器为APB2,所以这行程序就是用来打开GPIO的时钟控制器APB2。

如果想同时打开多组,则可以写成RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE),多组GPIO之间用|隔开。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; 

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;     

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_PP;    

前面已经定义了一个GPIO_InitStructure 的结构体,这3行就是用来依次给里面的3个成员赋值。

首先是Pin脚,想要初始化哪个引脚,则选择哪个引脚。注意,这里的Pin脚的标识并不是单片机芯片的引脚标识,而是该引脚在它所在组的序号。比如STM32F103C8T6的10号脚是PA0,如果想对该引脚初始化,则Pin值应赋值为GPIO_Pin_0。同样,如果想对多个IO口进行同样参数的初始化,则可以写成 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;  每两个Pin之间用|隔开。

如果想对该组所有的IO口进行同样参数的初始化,则可以写成GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;

然后是Speed,他是一个频率值,代表IO口的电平翻转速度。有2MHz,10MHz,50MHz可以选择。如果该IO口是用于数据传输(IIC,SPI通信等)或者PWM发波等需要高频信号的场景,一般将速度选最大,即50MHz。当然,普通的应用场景也可以用50MHz。

最后是IO口模式,共有8种模式可供选择,已经在上文详细介绍。

GPIO_Init(GPIOA,&GPIO_InitStructure);

三个成员赋值之后,就可以开始初始化了。GPIO_Init是GPIO外设库中已经封装好的程序,他有两个输入变量,一个是GPIO,一个是GPIO_InitStructure中的参数。作用是按照GPIO_InitStructure中的参数将GPIOA初始化。

如果还需要给其他IO口初始化,则同样按照上述程序写即可。

初始化完成之后,就可以对IO口进行置位复位操作了。

下面列举了几种置位复位的方法,可根据自己的习惯使用。

GPIO_SetBits(GPIOA, GPIO_Pin_0);         //PA0输出1
GPIO_ResetBits(GPIOA, GPIO_Pin_0);       //PA0输出0


GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);     //PA0输出1
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);   //PA0输出0


GPIO_Write(GPIOA, 0x1);             //PA0输出1
GPIO_Write(GPIOA, 0x0);             //PA0输出1

其中GPIO_Write函数是对整组GPIO进行操作。

下面是一个简单的LED灯循环闪烁的程序,可供初学者参考学习、

#include<stm32f10x.h>                //STM32头文件
#include<stm32f10x_gpio.h>           //GPIO头文件
#include<stm32f10x_rcc.h>            //RCC时钟头文件

void GPIO_Init(void)            //GPIO初始化子函数
{
	GPIO_InitTypeDef GPIO_InitStructure;                    //定义一个GPIO初始化参数的结构体
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //启用GPIOB的时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;               //选择要初始化的引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;       //设置IO速度
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_PP;            //设置IO模式
	GPIO_Init(GPIOA,&GPIO_InitStructure);	                //按以上参数初始化GPIO
}

void Delay_ms(int Time)        //延时函数
{
	int i=0;
	while(Time--)
	{
		i=8000;
		while(i--);
	}
	return;
}

void main(void)            //主函数
{
    GPIO_Init;                                    //初始化GPIO
    while(1)
    {
         GPIO_SetBits(GPIOA, GPIO_Pin_0);         //PA0输出1
         Delay_ms(500);                            //延时500ms
         GPIO_ResetBits(GPIOA, GPIO_Pin_0);       //PA0输出0
         Delay_ms(500);                            //延时500ms
    }    
}

基础的GPIO置复位学会了,其他的流水灯跑马灯就很简单了,这里就不演示了。

以上内容都是个人理解,如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_55203246/article/details/123723102