GPIO(General-Purpose IO ports) ,也就是通用 IO 接口。在嵌入式系统中常常有数量众多,但是结构却比较简单的外部设备/电路,对这些设备/电路,有的需要 CPU 为之提供控制手段(输出),有的则需要被 CPU 用做输入信号。而且,许多这样的设备/电路只要求一位,即只要有开/关两种状态就够了。比如,控制某个 LED 灯亮与灭,或者通过获取某个引脚的电平属性来达到判断外围设备的状态。对这些设备/电路的控制,使用传统的串行口或并行口都不合适。所以在微控制器芯片上一般都会提供一个"通用可编程 IO 接口",即 GPIO。接口至少有两个寄存器,即"通
用 IO 控制寄存器"与"通用 IO 数据寄存器"。数据寄存器的各位都直接引到芯片外部,而对这种寄存器中每一位的作用,即每一位的信号流通方向,则可以通过控制寄存器中对应位独立地加以设置。比如,可以设置某个引脚的属性为输入、输出或其他特殊功能。
在实际的 MCU 中,GPIO 是有多种形式的。比如,有的数据寄存器可以按照位寻址,有些却不能按照位寻址,这在编程时就要区分了。比如传统的 8051 系列,就区分成可位寻址和不可位寻址两种寄存器。另外,为了使用的方便,很多 MCU 的 GPIO 接口除必须具备两个标准寄存器外,还提供上拉寄存器,可以设置 IO 的输出模式是高阻,还是带上拉的电平输出,或者不带上拉的电平输出。这在电路设计中,外围电路就可以简化不少。
GPIO工作方式
在后续的操作中,我们将通过配置寄存器来设置GPIO的不同工作方式,以控制各种外设以及与外设之间的通讯。例如,要点亮LED灯,需要将GPIO配置为输出模式;而要检测按键,则需要将GPIO配置为输入模式。当然,这里的输入和输出是相对于cpu而言的。接下来,我们将介绍一些GPIO的工作方式
浮空输入
![](https://i-blog.csdnimg.cn/blog_migrate/0c567cb5fbc7de79bb54c428824687a7.png)
浮空输入是指在I/O端口没有接输入时,此时输入端口是悬空在空气中,没有连接到任何电源或地。它的电平是一个不确定的值,也就是我们所说的浮空,电平会受到周围环境的电磁干扰或其他因素的影响而随机变化,会处于一个跳变的状态,一会高,一会低。只有输入了一个高(1)/低(0)电平才会确定下来。
这么做的优势在于输入模式的电平会完全取决于外部电路而与内部电路无关。但是在没有外部
电路接入的时候,IO 脚浮空会使得电平不确定
TTL施密特触发器被用来处理数字信号中的噪声和抖动。这有助于确保信号的稳定性和清晰度。一旦信号经过处理,它可以被存储在输入数据寄存器中,以便后续的处理和使用。这种处理方法有助于在数字系统中获得可靠和稳定的信号
输入上拉
输入上拉 VDD 所在上拉电阻开关闭合,VSS所在下拉电阻的开关断开。
补充:VDD接的是电源,VSS接地
输入上拉使得I/O端口没有电压输入时,电平也是高电平
![](https://i-blog.csdnimg.cn/blog_migrate/00c9aeafc2cc7867cd297193849ed988.png)
如果输入了一个高电平,VDD 和 O点(上拉电阻和下拉电阻中间)之间就几乎没有电势差,此时 O 点(上拉电阻和下拉电阻中间)的电平就仍然是高电平,读取到的电平就是高电平。但是由于在没有电压输入的时候,电平也是高电平,所以这一种输入情况下是没有办法确定信号是否输入了
如果输入了一个低电平,此时 O 点(上拉电阻和下拉电阻中间)的电平的电平就会变成低电平,那么 VDD 和 O 点(上拉电阻和下拉电阻中间)之间形成了电势差,但是因为上拉电阻的存在,所以不会出现一个大电流。此时主控制器读取到的一个电平就是一个低电平。在上拉输入的情况下,低电平的是能够非常明显的读取到的。
输入下拉
状态:VDD 所在上拉电阻开关断开,VSS所在下拉电阻的开关闭合
此时I/O端口没有输入时, O 点(上拉电阻和下拉电阻中间)电平就是 VSS 的电平,此时读取到的电平就是低电平。
![](https://i-blog.csdnimg.cn/blog_migrate/88d21580359992c3e7544a4bc8dc5874.png)
此时输入的电平如果是一个低电平,就没有办法和之前的情况进行区分。
如果输入的是一个高电平,O 点(上拉电阻和下拉电阻中间)和 VSS 之间同样形成了电势差,O 点(上拉电阻和下拉电阻中间)的电平会变成外部的高电平,那么主控得到的就是一个高电平信号。
注意:上拉和下拉电阻电路的开关在实际应用中一般使用 MOS 管来代替开关。
模拟输入
![](https://i-blog.csdnimg.cn/blog_migrate/04f6ff70f5927bf03d43715541791fcb.png)
模拟输入不经过TTL施密特触发器。有时候我们需要用 AD 采集 IO 口上面的真实电压。这就有了我们所需要的模拟输入。为了让外部的电压真实的读取到单片机的 AD 模块,我们既不能闭合上拉和下拉的开关,也不能让信号经过施密特触发器。
开漏输出
![](https://i-blog.csdnimg.cn/blog_migrate/54d61c5c1ce272a367bf725aa1727059.png)
开漏输出时, P-MOS 不工作,这里我们只考虑 N-MOS(图中3) 。
输出控制电路(图中2)的作用:当通过控制器给一个高电平的时候,信号经过输出控制电路会对该信号取反。既当控制器给出高电平时会输出低。
N-MOS的作用:我们可以把这一个 MOS 管当成一个三极管,对于图中所示的这种三极管我们可以简单的理解成一个水龙头,左侧就是一个水龙头开关,当左侧输出控制电路输出是高电平时,O 点(P-MOS 和 N-MOS 中间)和 VSS (接地)就会导通。当左侧输出控制电路输出是底电平时,N-MOS截止。
所以说,开漏输出就很好理解了。当我们CPU给一个高电平的时候,经过输出控制电路取反,N-MOS 管关闭,此时输出的电压就是一个浮空,即不确定的电压。如果给一个低电平,那么 MOS 管导通,相当于 IO 口与 VSS 相连,此处就输出了一个低电平电压。
这样做有以下两个好处
一、虽然我们可以看到开漏输出是没有办法在内部输出一个高电平,但是这一个看似是缺点。
其实实际上是一种优点。我们可以得到,当给一个高电平的时候,MOS 管没有导通,此时电压不确定导致无法输出高电平,但是一旦我们在外部增加一个上拉,那么这一个缺点就会被有效避免。并且,因为是我们自己设计一个上拉,这个上拉的电压是由我们自己确定,这样我们就可以根据外部电路需要多少 V 的高电平来给这一个上拉的电压
二、开漏输出的实质其实就是一个 OD 门(OD:漏极输出(Open Drain))。而在数电中,OD 门
有一个非常重要的特性就是可以实现线与的功能,简单来说,就是在像 IIC 这样的总线协议中,只要有一个给低电平,那么总线都会被拉低。
推挽输出
推挽输出就是可以需要利用两个不同的 MOS 管来实现输出。
![](https://i-blog.csdnimg.cn/blog_migrate/658cf8bb3caf6683d510808d632d0811.png)
P-MOS 和 N-MOS 是不同的控制方式,当给一个高电平的时候经过输出控制电路后被转换为低
电平,P-MOS 导通,N-MOS 不导通,此时 IO 口接通在 VDD(电源),此时输出的是高电平。当给一个低电平的时候,则是 N-MOS 导通,P-MOS 不导通,此时 IO 口接通在 VSS 电源上面,此时输出的是底电平。使用 MOS 管的优势在于带载能力强
推挽复用 功能和 开漏 复 用
用推挽和复用开漏其实很简单,在你理解了开漏和推挽的原理之后,如果你不想用单片机内
部来输出,那么你可以进行复用,将输出转移到其他外设上面
复用开漏输出
复用推挽输出
![](https://i-blog.csdnimg.cn/blog_migrate/150504eda5380605dafdc27b8c3f2b78.png)
常用寄存器
每个通用 I/O 端口有四个 32 位配置寄存器(GPIOx_MODER,GPIOx_OTYPE,GPIOx_OSPEEDR 和GPIOx_PUPDR),两个 32 位数据寄存器(GPIOx_IDR 和 GPIOx_ODR)和 32 位设置/重置寄存器(GPIOx_BSRR)。此外,所有 GPIO 都
有一个 32 位的锁定寄存器(GPIOx_LCKR)和两个 32 位的备用函数选择寄存器
(GPIOx_AFRH 和 GPIOx_AFRL)。
补充基址寄存器知识点基址寄存器(Base Register)是一种在计算机体系结构中用于寻址的寄存器。它通常用于存储某个存储区域(通常是内存)的基地址,从而在进行内存访问时,通过基址寄存器与偏移值的相加,可以方便地计算出要访问的具体内存地址。
基址寄存器的作用是提供一个相对地址的起始点,而不是直接使用绝对的内存地址。通过基址寄存器,程序可以更加灵活地处理数据结构、数组和函数调用等。
在汇编语言中,基址寄存器通常用于形成有效地址(Effective Address)。有效地址是计算得到的内存地址,它由基址寄存器的值和一个偏移值相加而得。这个偏移值可以是常数,也可以是另一个寄存器中的值。
例如,在ARM汇编语言中,可以使用基址寄存器和偏移值来进行内存访问,如下所示:
LDR r0, [r1, #4] ; 从地址 (r1 + 4) 处加载数据到寄存器 r0
在这个例子中,
r1
就是基址寄存器,#4
是偏移值。实际访问的内存地址是r1 + 4
。基址寄存器在访问数组、结构体和其他数据结构时非常有用,因为它允许程序员使用相对地址而不是绝对地址,从而使代码更加清晰和可维护。
GPIOx_MODER
GPIOx_MODER(+0x00):用于设置对应IO的模式(输入、输出、模拟输入、复用)
(+0x00)含义:相对于基址寄存器位移偏差
![](https://i-blog.csdnimg.cn/blog_migrate/81f54e82daf7dce529a5fcc2b3391022.png)
该图中的MODER1--MODER15都对应着一个IO端口 ,00设置为输入模式,01设置为输出模式,10设置为模拟输入模式,11设置为复用模式
GPIOx_OTYPER
GPIOx_OTYPER(+0x04):用于设置对应IO的输出类型(推挽、开漏)
![](https://i-blog.csdnimg.cn/blog_migrate/6df43f17e5ff41bd76078f24c36c1572.png)
GPIOx_OSPEEDR
GPIOx_OSPEEDR(+0x08):用于设置对应1O的状态反正速度(低、中、高、非常高)
![](https://i-blog.csdnimg.cn/blog_migrate/d2393632c5c3b06dd15d87ac4e95b8a0.png)
GPIOx_PUPDR
GPIOx_PUPDR(+0xOC):用于设置对应IO的上下拉电阻(浮空、上拉、下拉)
![](https://i-blog.csdnimg.cn/blog_migrate/0c72c54feb7ef56198004037407f8929.png)
GPIOx_IDR
GPIOx_IDR(+0x10):用于反应IO口实际的高低电平
status=PZn [n]
GPIOx-ODR
GPIOx-ODR(+0x14):用于设置对应I0口的高低电平
![](https://i-blog.csdnimg.cn/blog_migrate/d930b6e74befd50ecba77260f0f8eec5.png)
点灯实验
汇编语言版
实验目的
利用汇编语言控制 STM32MP157 开发板的三个 LED 发光二极管,使其有规律闪烁。通过本实例熟悉、掌握汇编语言的使用方法
![](https://i-blog.csdnimg.cn/blog_migrate/c9b61859af37f5bc67b9b8fcf639ea56.png)
LED侧原理图中LED1,LED2,LED3代表电平输入
CPU侧原理图中PZ5连接了LED1;PZ6--LED2;PZ7--LED3
寄存器设置
查看原理图可得, LED1 对应可由 PZ5 引脚控制,为了实现控制 LED 灯的目的,首先使能对应
GPIO 时钟,需要通过配置 MODER 寄存器将对应的端口配置成输出模式,通过 OTYPER 设置输出类型,然后可以通过 ODR 寄存器实现 LED 灯的点亮与熄灭。
相关代码
.equ MODER, 0x54004000 ; GPIO端口模式寄存器的地址
.equ OTYPER, MODER+0X04 ; GPIO端口输出类型寄存器的地址
.equ ODR, MODER+0X14 ; GPIO端口输出数据寄存器的地址
.text
.global _start
_start:
ldr r0, =MODER ; 将MODER的地址加载到寄存器r0
mov r1, #0x0 ; 设置r1为0
str r1, [r0] ; 将r1的值写入MODER寄存器,将MODER寄存器清零
ldr r0, =MODER ; 将MODER的地址加载到寄存器r0
mov r1, #(0x15 << 10) ; 设置r1,配置特定的GPIO端口为输出模式
str r1, [r0] ; 将r1的值写入MODER寄存器,配置GPIO端口为输出模式
ldr r0, =OTYPER ; 将OTYPER的地址加载到寄存器r0
mov r1, #0x0 ; 设置r1为0
str r1, [r0] ; 将r1的值写入OTYPER寄存器,配置输出类型为推挽输出
loop:
ldr r0, =ODR ; 将ODR的地址加载到寄存器r0
mov r1, #(0x7 << 5) ; 设置r1,将指定的GPIO端口置为高电平
str r1, [r0] ; 将r1的值写入ODR寄存器,设置GPIO端口为高电平
bl delay ; 调用延时函数
ldr r0, =ODR ; 将ODR的地址加载到寄存器r0
mov r1, #0x0 ; 设置r1为0,将指定的GPIO端口置为低电平
str r1, [r0] ; 将r1的值写入ODR寄存器,设置GPIO端口为低电平
bl delay ; 调用延时函数
b loop ; 无条件分支跳转到loop标签,循环执行
delay:
ldr r2, =50000000 ; 设置延时计数器的初始值
del:
sub r2, r2, #1 ; 计数器减1
nop ; 空指令,用于增加延时
cmp r2, #0 ; 比较计数器是否为0
bne del ; 如果计数器不为0,则继续循环延时
mov pc, lr ; 返回到调用延时函数的地方
stop: ; 程序的结束标签
b stop
.end
C语言版
实验目的
利用 STM32MP157 的 PZ5 这个 I/O 引脚控制开发板中 LED 发光二极管,使其有规律闪
烁。
相关代码
void mydelay_ms(int ms)
{
volatile int i = 0, j = 0 ;
while(ms--) {
for (i = 0; i < 1000; i++)
for (j = 0; j < 1000; j++);
}
}
int main()
{
GPIOZ->MODER = GPIOZ->MODER &= ~(0x3 << 10); //位清零重置
GPIOZ->MODER = GPIOZ->MODER | (0x1 << 10); //设置为输出模式
GPIOZ->OTYPER &= ~(0x1 << 5); //推娩输出
GPIOZ->OSPEEDR &= ~(0x3 << 10); //低速
while(1) {
GPIOZ->ODR = (GPIOZ->ODR &= ~(0x1 << 5)) | 0x1 << 5;
mydelay_ms(30);
GPIOZ->ODR &= ~(0x1 << 5);
mydelay_ms(30);
}
return 0;
}