一、GPIO的简介
1.1.GPIO的介绍
GPIO的全称是General Purpose Input Output,即通用输入输出端口,它和51单片机里的IO口一样,只是一个输入输出的端口,负责采集外部器件的信息或者控制外部期间工作。
1.2.GPIO的特点
- 不同型号,IO数量可能不一样,可通过选型手册快速查询
- 快速反转,每次翻转最快只要两个时钟周期
- 每个IO口都可以做中断(STM32最可贵的特点)
- 支持8种工作模式
1.3.GPIO的电气特性
STM32工作电压范围在2V ~ 3.6V内,一般选3.3V为工作电压,要是接上了5V的电压,不通过特定的电路,会导致烧毁。GPIO识别的电压范围有两种类型的端口:第一种是COMS端口,电压在-0.3V ~ 1.164V判定为逻辑0,1.833V ~ 3.6V判定为逻辑1,设计电压的时候不要把电压设计在1.164V ~ 1.833V之间;第二种是TTL端口,它的端口能够兼容3.3V和5V的电压。想了解该GPIO到底是输出哪一种电平类型的端口,可以通过查询STM32的数据手册,I/O电平的框框里,有FT的是TTL端口,空白的是COMS端口。单个IO口最大的输出电流为25mA。
二、GPIO引脚分布及结构
2.1.引脚分布
STM32引脚类型:电源引脚(以V开头的引脚)、晶振引脚(8号引脚是低速输入,9号引脚是低速输出;23号引脚是高速输入,24号引脚是高速输出)、复位引脚(25号引脚)、下载引脚(具体的引脚需要参考手册)、BOOT引脚(138号引脚和PB2_BOOT1)、GPIO引脚(以P开头的引脚):
2.2.GPIO基本结构(F1系列)
根据红线划分控制器的外部和内部,外部的IO引脚是看得见的银色引脚。
① 保护二极管:保护二极管共有两个,用于保护引脚外部过高或过低的电压输入。当引脚输入电压高于VDD 时,上面的二极管导通,当引脚输入电压低于 VSS 时,下面的二极管导通,从而使输入芯片内部的电压处于比较稳定的值。虽然有二极管的保护,但这样的保护却很有限,大电压大电流的接入很容易烧坏芯片。
② 上拉、下拉电阻:它们阻值大概在 30~50K 欧之间,可以通过上、下两个对应的开关控制,这两个开关由寄存器控制。当引脚外部的器件没有干扰引脚的电压时,即没有外部的上、下拉电压,引脚的电平由引脚内部上、下拉决定,开启内部上拉电阻工作,引脚电平为高;开启内部下拉电阻工作,则引脚电平为低。需要注意的是,STM32 的内部上拉是一种“弱上拉”,这样的上拉电流很弱,如果有要求大电流还是得外部上拉。
③ 施密特触发器:可以将非标准方波,整形成方波。当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;当输入在正负向阈值电压之间,输出不改变。
④ P-MOS 管和 N-MOS 管:这个结构控制 GPIO 的开漏输出和推挽输出两种模式。开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。推挽输出:这两只对称的 MOS 管每次只有一只导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载拉电流。推拉式输出既能提高电路的负载能力,又能提高开关速度。
三、GPIO的八种模式
GPIO八种模式 | 特点及引用 |
---|---|
输入浮空 | 输入用,完全浮空,状态不定 |
输入上拉 | 输入用,用内部上拉,默认为高电平 |
输入下拉 | 输入用,用内部下拉,默认为低电平 |
模拟功能 | ADC、DAC |
开漏输出 | 软件IIC的SDL、SCL等 |
推挽输出 | 驱动能力强,25mA,通用输出 |
开漏式复用功能 | 片上外设功能(硬件IIC的SDL、SCL引脚等) |
推挽式复用功能 | 片上外设功能(SPI的SCK、MISO、MOSI引脚等) |
3.1.输入浮空
上拉/下拉电阻为断开状态,施密特触发器打开,输出被禁止。输入浮空模式下,IO 口的电平完全是由外部电路决定。如果 IO 引脚没有连接其他的设备,那么检测其输入电平是不确定的。该模式可以用于按键检测等场景。
3.2.输入上拉
上拉电阻导通,施密特触发器打开,输出被禁止。在需要外部上拉电阻的时候,可以使用内部上拉电阻,这样可以节省一个外部电阻,但是内部上拉电阻的阻值较大,所以只是“弱上拉”,不适合做电流型驱动。
3.3.输入下拉
下拉电阻导通,施密特触发器打开,输出被禁止。在需要外部下拉电阻的时候,可以使用内部下拉电阻,这样可以节省一个外部电阻,但是内部下拉电阻的阻值较大,所以不适合做电流型驱动。
3.4.模拟功能
上下拉电阻断开,施密特触发器关闭,双 MOS 管也关闭。其他外设可以通过模拟通道输入输出。该模式下需要用到芯片内部的模拟电路单元单元,用于 ADC、DAC、MCO这类操作模拟信号的外设。
3.5.开漏输出
STM32 的开漏输出模式是数字电路输出的一种,从结果上看它只能输出低电平 Vss 或者高阻态,常用于 IIC 通讯(IIC_SDA)或其它需要进行电平转换的场景。
3.6.推挽输出
STM32 的推挽输出模式,从结果上看它会输出低电平 VSS 或者高电平VDD。推挽输出跟开漏输出不同的是,推挽输出模式 P-MOS 管和 N-MOS 管都用上。
3.7.开漏式复用功能
一个 IO 口可以是通用的 IO 口功能,还可以是其他外设的特殊功能引脚,这就是 IO 口的复用功能。一个 IO 口可以是多个外设的功能引脚,需要选择作为其中一个外设的功能引脚。当选择复用功能时,引脚的状态是由对应的外设控制,而不是输出数据寄存器。除了复用功能外,其他的结构分析请参考开漏输出模式。
3.8.推挽式复用功能
复用功能介绍请查看开漏式复用功能,结构分析请参考推挽输出模式。
四、GPIO寄存器
4.1.GPIO寄存器介绍
CRL、CRH | IDR | ODR | BSRR | LCK |
---|---|---|---|---|
配置工作模式,输出速度 | 输入数据 | 输出数据 | 设置ODR寄存器的值 | 配置锁定 |
4.2.端口配置寄存器(GPIOx_CRL 和 GPIOx_CRH)
这两个寄存器都是 GPIO 口配置寄存器,不过 CRL 控制端口的低八位,CRH 控制端口的高八位。寄存器的作用是控制 GPIO 口的工作模式和工作速度,一共有32各框,每两个对应一个GPIO引脚,如控制GPIOA的第3个引脚,就控制CNF3和MODE3的框,寄存器描述如下图所示:
想要控制B~G的GPIO也是类似操作。再通过在特定位置输入特定值,来达到需要的效果:
图里的CNFy[1:0]如果输入了10,该如何区分上拉模式还是下拉模式,通过ODR寄存器来设置:
4.3.端口输出数据寄存器(GPIOx_ODR)
该寄存器用于控制 GPIOx 的输出高电平或者低电平,低 16 位有效,分别对应每一组 GPIO 的 16 个引脚。当 CPU 写访问该寄存器,如果对应的某位写 0(ODRy=0),则表示设置该 IO 口输出的是低电平,如果写 1(ODRy=1),则表示设置该 IO 口输出的是高电平,y=0~15。此外,除了 ODR 寄存器,还有一个寄存器也是用于控制 GPIO 输出的,它就是 BSRR 寄存器。
4.4.端口置位/复位寄存器(GPIOx_BSRR)
该寄存器也用于控制 GPIOx 的输出高电平或者低电平,BSRR 是只写权限,而 ODR 是可读可写权限。BSRR 寄存器 32 位有效,在低 16 位(0-15)相应的位写 1(BSy=1),那么对应的 IO 口会输出高电平,往相应的位写 0(BSy=0),对 IO 口没有任何影响;高 16 位(16-31)作用是:对相应的位写 1(BRy=1)会输出低电平,写 0(BRy=0)没有任何影响,y=0~15。总结:对于 BSRR 寄存器,在高16位写1会置0;在低16位写1会置1,写 0 的话,对 IO 口电平是没有任何影响的。
首先需要读出来 ODR 寄存器的值,然后对整个 ODR 寄存器重新赋值来达到设置某个或者某些 IO 口的目的,而 BSRR 寄存器直接设置即可,这在多任务实时操作系统中作用很大。。BSRR寄存器还有一个好处,就是 BSRR 寄存器改变引脚状态的时候,不会被中断打断,而 ODR 寄存器有被中断打断的风险。
五、GPIO配置
5.1.通用外设驱动模型
- 初始化:时钟设置、参数设置、IO设置(可选)、中断设置(开中断、设NVIC)(可选)
- 读函数:从外设读取数据(可选)
- 写函数:往外设写入数据(可选)
- 中断服务函数:根据中断标志,处理外设各种中断事务(可选)
5.2.使能时钟
Hal库驱动函数:__HAL_RCC_GPIOx_CLK_ENABLE();
它主要操作的寄存器是RCC_APB2ENR
(为什么会操作APB2总线呢,因为根据手册可知,GPIO挂在APB2总线上),功能是开启GPIO时钟。这个函数是在stm32f1xx_hal_rcc.h
里面,例如:打开GPIOA时钟的时候,它的寄存器里面的操作是:RCC->APB2ENR |= RCC_APB2ENR_IOPAEN
将控制GPIOA的时钟开关置1(打开),图三是偏移到控制GPIOA时钟开关的位置。
5.3.设置工作模式
Hal库驱动函数:HAL_GPIO_Init(...);
它主要操作的寄存器是CRL、CRH、ODR
,功能是初始化GPIO。该函数有两个参数,第一个是确定GPIOA~G组的结构体指针,里面操控的都是GPIO的寄存器CRL、CRH、IDR、ODR、BSRR 、LCK
等;第二个是操作功能的结构体指针,例如在PB5和PE5都设置为推挽输出,高速传输。
5.4.设置输出状态(可选)
Hal库驱动函数:HAL_GPIO_WritePin(...);
它主要操作的寄存器是BSRR
,作用是控制IO输出高/低电平;HAL_GPIO_TogglePin(...);
它主要操作的寄存器也是BSRR
,作用是每次调用,IO输出的电平就会翻转一次。
5.5.读取输入状态(可选)
Hal库驱动函数:HAL_GPIO_ReadPin(...);
它主要操作的寄存器是IDR
作用是读取IO的电平。
六、实验
使两个LED灯闪烁,下面是主函数:
#include "./SYSTEM/delay/delay.h"
void led_init(void);
int main(void)
{
HAL_Init();
sys_stm32_clock_init(RCC_PLL_MUL9);
delay_init(72);
led_init();
while(1)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);
delay_ms(500);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);
delay_ms(500);
}
}
void led_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
gpio_initstruct.Pin = GPIO_PIN_5;
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_initstruct.Pull = GPIO_PULLUP;
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
gpio_initstruct.Pin = GPIO_PIN_5;
HAL_GPIO_Init(GPIOE, &gpio_initstruct);
}