GPIO简介
通用输入输出端口,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。GPIO被分成很多组,每组有 16 个引脚(也有可能少于 16 个),基本的输出功能是输出高低电平,基本的输入功能是检测外部输入电平。
框图解析
保护二极管
我们先看到最右边的芯片引脚连接的两个二极管,这两个二极管主要是保护芯片,防止外部过高或过低的电压输入,当引脚电压高于Vdd时,上方二极管导通,输入芯片的电压就被钳制在Vdd电压,下面的也是同样的道理。
输出模式
输出电路要看到下面的电路,线路经过一个由 P-MOS 和 N-MOS 管组成的单元电路。这个结构使 GPIO 具有了“推挽输出”和“开漏输出”两种模式。
推挽输出
该结构中输入高电平时,经过反相器反向后,上方的 P-MOS 导通,下方的 N-MOS 关闭,对外输出高电平,也即Out直接接到了Vdd,P 管负责灌电流,N 管负责拉电流,推挽输出的低电平为 0 伏,高电平为 3.3 伏,值得注意的是这个输出电压是很低的,不足以驱动电机等外设,所以要驱动电机还必须接上一个驱动电路。
开漏输出
在开漏输出模式时,上方的 P-MOS 管完全不工作。若控制输出为 1 (它无法直接输出高电平) 时,则 P-MOS管和 N-MOS 管都关闭,所以引脚既不输出高电平,也不输出低电平,为高阻态。值得一提的是:**它具有“线与”特性,也就是说,若有很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的
电压为外部上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电平,0 伏。**我们平常很少需要用到开漏输出模式,除了向I2C这些需要线与的总线电路,以及必须输出5v高电平的场合,可以外接一个上拉电阻和5v电源,并配置成开漏模式
那么问题来了:什么是上拉电阻?
上拉电阻主要接在电源和引脚之间,用于拉高某个节点的电压,从而提高电路驱动能力以及将不确定信号钳制在高电平。
具体讲解看这里:
复用功能输出
这里指的是其他片上外设对GPIO引脚进行控制,从其它外设引出来的“复用功能输出信号”与 GPIO本身的数据据寄存器都连接到双 MOS 管结构的输入中,例如我们用串口通讯时需要把某个GPIO引脚复用为发送引脚或接收引脚
模拟输出
DAC模拟信号不经过双MOS结构直接输出到引脚
输入模式
在输入模式时,施密特触发器打开,输出被禁止,可通过输入数据寄存器 GPIOx_IDR 读取 I/O 状态
上拉输入
上拉输入模式下上拉电阻支路导通,当无外部信号时,读到的是Vdd电压,也即高电平,当输入高电平时,读到的是高电平,输入低电平读到低电平,上拉输入的好处是信号较为稳定,无信号输入时稳定在高电平,下拉输入则相反
浮空输入
浮空输入模式下上下拉电阻皆断开,读到的电平完全取决于外部,但是外部无输入时,电平会不确定
模拟输入
不经过触发器,读到的即是模拟信号
寄存器操作GPIO
这里先讲解一下如何寄存器操作GPIO,以便大家可以更好的了解寄存器,我们需要用到参考手册以及原理图,我用的是正点原子精英版。
首先看到参考手册里的GPIO寄存器描述这一节,我们可以清楚的看到手册为我们把GPIO这个外设的寄存器都描述详尽,跟着大家解读一下:
我们首先看到这两个端口配置寄存器,一个是负责低八位,也即是负责PIn0-7,另一个高八位,也即是pin8-15,这样就刚好对应每个GPIO的16个IO(一些GPIO没有16个引脚的除外),这里的每一个单元都是两位的,比如MODE0[1:0]就可以写入 00 01 10 11,看到下面的说明,那我们要配置推挽输出且速度为10MHZ的时候,只需在CNF0[1:0]写入00 ,在 MODE0[1:0]写入01即可,值得一提的是区分输入还是输出看的是MODE这个单元,当这个单元有写入的时候那便是输出,否则为输入,因为输入不需要速度。
接下来这些寄存器也是同样的道理,看着说明就好了,这里的每个单元便是对应着16个IO,所以高16位用不上了。r表示只可读,w表示只可写,rw是可读写
我们以拉低PA4为例子:步骤如下
-
开启对应外设的时钟(时钟将会在下一章做讲解,这里先跳过)
-
可以看到我们只需要把2那个位 置1
-
清除PA4的端口位
-
可以看到这里我们需要把BR4置1
-
设置PA4的输出模式
代码如下:
int main(void) { // 开启 GPIOB 端口 时钟 RCC_APB2ENR |= (1<<3); // 清空控制 PA4 的端口位 GPIOA_BSRR |= (1<<5); // 配置 PA4 为通用推挽输出,速度为 10M GPIOC_CRL |= (1<<4*2); // PA4 输出低电平 GPIOA_ODR &= ~(1<<4); while (1); }
下面是用Cubemx配置的流程:
然后点右上方的start project
搜索对应的引脚
ok,下面就来讲怎么写main里面的函数:
比如这个的,其实GPIO的相关函数并不多,可以简单记一下加上有代码补全功能,你敲到HAL_GPIO_基本就会看到整个函数了,直接回车选中就行,或者我们可以在文件里面找:
在这个文件里往下翻也能找到
函数放入的变量怎么办呢?首先我们右键函数,选择”go to definition of…“,就会跳到如下
在断言里面继续右键,就可以看到我们可填入的变量名称了。
实战
实战部分将演示GPIO相关的实验
1 LED灯
跑马灯配蜂鸣器
首先看原理图
我们只需要输出引脚拉低LED就可以亮,蜂鸣器那里我们需要拉高,main里我是写了一个红灯亮蜂鸣器就响的逻辑,PB5 PE5是LED的,PB8是蜂鸣器的
2 按键输入
按键输入这里涉及到了一个消抖的问题,按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,所以我们需要采用延时函数嵌套判定来解决这一问题。
按键这部分的实验,本质上就是我们要通过GPIO的输人模式,读取引脚外部的电平变化。
/*按下翻转电平*/
void Key_Scan()
{
if(KEY0==0)
{
HAL_Delay(100);//延时消抖
if(KEY0==0)
{
/* 等待按键松开*/
while (KEY0==0);//这句循环要加,也是用于解决抖动产生的误判问题。
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
}
}
if(KEY1==0)
{
HAL_Delay(100);//延时消抖
if(KEY1==0)
{
while (KEY1==0);
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
}
}
}
按下亮 松开灭:
这里typora的代码块莫名嵌入后变成乱码了,这里直接截个图哈(懒得改了),具体代码我会放Github上的,到时候给个链接