尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

2.0GPIO输出

2.0GPIO输出 GPIO简介GPIOGeneral Purpose Input Output通用输入输出口可配置为8种输入输出模式引脚电平0V~3.3V部分引脚可容忍5V输出模式下可控制端口输出高低电平用于驱动LED、控制蜂鸣器、模拟通信协议输出时序等应用。输入模式下可读取端口的高低电平或电压用于读取按键状态、接收模块电平信号、进行ADC电压采集以及模拟通信协议的数据接收等场景。右边有PA0 PA1 PA2 ... PA15这表示GPIOA 是一个“端口”GPIOA 里面最多有16 个引脚GPIOA ├── PA0 ├── PA1 ├── PA2 ├── ... └── PA15同理GPIOB ├── PB0 ├── PB1 ├── ... └── PB15所以你可以把 GPIOA 想成一个16 个房间的大房子。每个房间就是一个 IO 引脚。图中间的“寄存器”是什么这是你现在最应该搞明白的地方。图里寄存器 驱动器我们先只看“寄存器”。你可以把寄存器理解成STM32 芯片内部的一排控制小盒子。CPU 想控制 GPIO就要往这些“盒子”里写数据或者从里面读数据。比如CPU ↓ 寄存器 ↓ GPIO硬件 ↓ PA0或者反过来PA0 ↓ GPIO硬件 ↓ 寄存器 ↓ CPU读取那黄色的 APB2 是什么这个就是总线。你现在完全可以把它理解成CPU和GPIO之间的高速公路。比如 CPU 想告诉 GPIO“PC13 给我输出低电平。”CPU 不能凭空说一句话给 PC13。它要通过芯片内部的总线把数据送到 GPIO 外设。你的图里就是CPU ↓ APB2总线 ↓ GPIO外设 ↓ 寄存器 ↓ 驱动器 ↓ GPIO引脚所以看到 APB2你现在先不要研究复杂的总线协议。你就记APB2 CPU访问 GPIO 这一类外设的通道之一。GPIO结构输出路径:CPU → ODR输出数据寄存器存状态 → 输出控制逻辑做决定→ P-MOS N-MOSPMOS和NMOS就是两种晶体管,可以把引脚接到电源或接到地。简单说,当要输出高电平时,PMOS 导通把引脚拉到 VDD。当要输出低电平时,NMOS 导通把引脚拉到 VSS。这样就能用互补的方式,让引脚要么高、要么低,不会卡在中间。配合控制电路后就能安全决定什么时候开哪一个,避免短路直接打架。→ VDD/VSS 电源/地→ I/O 引脚 → 外部设备。输入路径”:外部设备 → I/O 引脚 → 保护电路 → TTL 施密特触发器你把它理解成一个输入信号的质检员就行了。它在输入路径上,负责把外部有点抖动、不干净的电压变成稳定的0 或 1,再交给 CPU→ 输入缓冲→ IDR输入数据寄存器→ CPUGPIO模式这两个模式其实不用死记名字,只要记住它们控制引脚的方式不同就好。先说推挽输出(Push-Pull)。你还记得我们说过的 P-MOS 和 N-MOS 吗?推挽输出就是这两个管子都参与工作:需要输出高电平时,P-MOS 导通,N-MOS关断,引脚被拉到 3.3V;需要低电平时,P-MOS 关断,N-MOS 导通,引脚被拉到 0V(GND)。所以推挽可以主动输出高和低两种电平,很适合点亮 LED 这类常见的数字外设。再来看开漏输出(Open Drain)。开漏和推挽最大的区别就是 P-MOS 不参与,只用 N-MOS。N-MOS 导通时,引脚被拉到低电平; N-MOS 截止时,引脚其实是悬空的,它本身不能主动输出高电平,需要外部上拉电阻把它拉到高电平。比如在 3.3V 系统上接一个10kΩ 的上拉电阻,N-MOS 松开时电平被拉到3.3V,拉低时则是 0V。所以开漏实际上只能主动拉低,高电平得靠上拉电阻提供。它的好处是多个设备可以共用一根线,例如 I2C,不会出现一个想拉高、另一个拉低的冲突情况。总结一下:推挽输出,高低电平都能主动输出,适合点 LED 这种常规数字外设;开漏输出只能主动拉低,高电平靠上拉电阻提供,适合像 I2C 这种需要多设备共线的场景。输入模式:浮空输入、上拉输入和下拉输入。它们三个其实在回答同一个问题:当外面没有信号的时候,这个引脚默认应该是什么电平。这就是最浮空输入就是没有任何电阻接到 VDD 或 VSS,外面给高就读到1,给低就读到 0,但如果外面什么都不接,它其实是悬空的,不确定,可能会受干扰乱跳。上拉输入呢,就是内部帮你接了一颗上拉电阻,没信号的时候默认就是高电平。有信号拉低时才变成低电平,适合按键这种场合。下拉输入相反,默认是低,信号拉高时才变高。这样做的好处就是,引脚状态永远有定义,不会飘来飘去。而开漏输出之所以要外接上拉电阻,就是它只能主动拉低,高电平要靠电阻帮它拉上去。比如 I2C这类总线,就需要开漏加上拉,避免设备之间抢线。它们的驱动方式跟普通的开漏和推挽基本一样,都是看是谁控制这个引脚:普通模式是 GPIO 自己控制,通过 ODR 改变电平;而复用模式是交给其他外设,比如串口、SPI、定时器等来控制。具体到驱动方式,普通推挽和复用推挽都能推高、也能拉低,只是一个是你手动控制,另一个是外设帮你控制。普通开漏和复用开漏则都只能主动拉低,高电平要靠上拉电阻提供,主要用在需要多设备共用一根线的场合,比如 I2C。总结来看,复用说的是谁来控制引脚,开漏或推挽说的是引脚怎么输出电平。把这两个维度分开理解,就不会把八种 GPIO 模式当成八个完全不同的东西,而是知道它们只是在“控制者”和“驱动方式”这两个概念上的不同组合而已。开漏如何上拉因为 GPIO 引脚本身不会决定自己的电平平,如果悬空就会乱跳,不能让它悬空。所以我们加一颗“上拉电阻”,就是把引脚通过电阻接到3.3V,没人管它时默认就是高电平;有按键拉下来时就变成低电平。这就是为什么上拉电阻常用在按键输入上。下拉电阻则完全相反,默认低电平,有外部信号拉高时才变成高电平,给电平一个明确的默认状态,避免信号飘忽不定。所以总结一下,上拉负责默认高,下拉负责默认低,都是为了让信号稳定可控。至于开漏为什么能够得到高电平,关键要澄清一点:开漏本身其实不能主动输出高电平,它要加上外部的上拉电阻,把引脚拉到 VDD,才会有高电平。没有上拉时,它只是悬空,什么都不是。所以准确的说法是:开漏只能主动把引脚拉低,而高电平是由上拉电阻“帮”它实现的。高阻抗的模拟输入配置:在模拟输入模式下,引脚尽可能不去影响外部电压,而是把这个电压直接送给 ADC(模数转换器)去测量。首先最右边还是 I/O 引脚,外面的传感器或电位器的电压,比如说1.8V,就接到这里。平时引脚上下各有一个保护二极管,分别连到 VDD 和 VSS,主要是保护用的,不负责测量。然后你会看到 TTL 施密特触发器旁边标了“关”,这表示在模拟输入模式时,数字输入这条路是被关闭的。因为施密特触发器只能把电压判断成 0 或 1,会丢掉真实电压,所以要关掉。IDR 这个输入数据寄存器在模拟输入时也基本不用,因为 ADC 需要的是像 2.37V 这样的数值,而不是 0 或1。最后CPU 是通过 ADC 模块去读取的。路径大致是外部模拟电压→ I/O 引脚→ 保护二极管(只是保护作用)→模拟通道→ ADC→CPU。你会发现,数字输入那套用于判断 0 和 1 的电路,在这里都被关掉了,只保留保护电路,把引脚直接交给 ADC 去量实际电压。所以模拟输入其实比数字输入的图更简单,因为它绕开了好几级数字输入电路,直接连接到 ADC 来测量真实的电压。ADC 就是模数转换器(Analog-to-Digital Converter,中文叫“模数转换器”。它的作用是把像 1.8V 这样的模拟电压,转换成 CPU 能读懂的数字值。比如说,你有一个电位器,输出可能是 0.8V、1.6V、2.5V、3.3V这些模拟量,CPU 本身看不懂,它只能看懂数字。所以中间就要靠 ADC 来翻译,把这些电压变成数字告诉 CPU。你刚才看到那张模拟输入的图,本质上就是在说:把外部的模拟电压直接送进 ADC 里,而不是变成 0 或 1 那种数字信号,也不用 IDR 那套数字输入电路。之后你学到 ADC 外设的时候,就会看到程序是怎么启动 ADC 、读取转换结果,再把这个数字换算成实际电压,那就会和今天这张模拟输入电路图一一对应上了。LED闪烁时钟RCC库较为常用的3个函数void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState); void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState); void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);LED灯APB2来实现。配置GPIO暂时所需函数void GPIO_DeInit(GPIO_TypeDef* GPIOx); void GPIO_AFIODeInit(void); void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct); uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);1. GPIO_Init()void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);作用配置GPIO工作模式比如GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA,GPIO_InitStructure);意思把PA0配置成 推挽输出 50MHz2. GPIO_DeInit()void GPIO_DeInit(GPIO_TypeDef* GPIOx);作用恢复默认状态例如GPIO_DeInit(GPIOA);相当于把GPIOA恢复出厂设置平时基本不用。3. GPIO_StructInit()void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);作用给结构体赋默认值例如GPIO_InitTypeDef GPIO_InitStructure; GPIO_StructInit(GPIO_InitStructure);执行后Mode Speed Pin都会变成默认配置。实际项目用得不多。二、输入读取类4. GPIO_ReadInputDataBit()uint8_t GPIO_ReadInputDataBit( GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin );作用读取一个引脚例如GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);返回0 或者 1相当于看看PA0现在高还是低按键实验经常用。5. GPIO_ReadInputData()uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);作用读取整个端口例如GPIO_ReadInputData(GPIOA);返回PA0~PA15 全部状态相当于一次性读取16个引脚。三、输出控制类LED重点6. GPIO_SetBits()void GPIO_SetBits( GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin );作用输出高电平例如GPIO_SetBits(GPIOA,GPIO_Pin_0);结果PA0 1如果LED低电平点亮LED灭7. GPIO_ResetBits()void GPIO_ResetBits( GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin );作用输出低电平例如GPIO_ResetBits(GPIOA,GPIO_Pin_0);结果PA0 0这两个函数是江科大 LED 实验最常用的。四、读输出状态8. GPIO_ReadOutputDataBit()uint8_t GPIO_ReadOutputDataBit(...)作用读取输出状态例如GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_0);看看PA0当前输出的是0还是19. GPIO_ReadOutputData()uint16_t GPIO_ReadOutputData(...)作用读取整个GPIO端口输出状态例如GPIO_ReadOutputData(GPIOA);返回PA0~PA15 当前输出值五、直接写值10. GPIO_WriteBit()void GPIO_WriteBit( GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal );作用指定输出0或1例如GPIO_WriteBit( GPIOA, GPIO_Pin_0, Bit_SET );等价于GPIO_SetBits(GPIOA,GPIO_Pin_0);GPIO_WriteBit( GPIOA, GPIO_Pin_0, Bit_RESET );等价于GPIO_ResetBits(GPIOA,GPIO_Pin_0);11. GPIO_Write()void GPIO_Write( GPIO_TypeDef* GPIOx, uint16_t PortVal );作用一次写16个引脚例如GPIO_Write(GPIOA,0xFFFF);结果PA0~PA15全部输出1点亮一颗LED灯(低电平点亮)#include stm32f10x.h // Device header int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//此时时钟被激活了 //配置GPIO口 分别对GPIO,引脚频率进行配置。 GPIO_InitTypeDef MYGPIO; MYGPIO.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出 MYGPIO.GPIO_PinGPIO_Pin_0; MYGPIO.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init(GPIOA,MYGPIO); //GPIO_SetBits(GPIOA,GPIO_Pin_0); GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET); while(1) { } }LED灯闪烁#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//此时时钟被激活了 //配置GPIO口 分别对GPIO,引脚频率进行配置。 GPIO_InitTypeDef MYGPIO; MYGPIO.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出 MYGPIO.GPIO_PinGPIO_Pin_0; MYGPIO.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init(GPIOA,MYGPIO); //GPIO_SetBits(GPIOA,GPIO_Pin_0); while(1) { GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET); Delay_ms(100); GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET); Delay_ms(100); } }LED流水灯我这里只使用了4个灯。#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//此时时钟被激活了 //配置GPIO口 分别对GPIO,引脚频率进行配置。 GPIO_InitTypeDef MYGPIO; MYGPIO.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出 MYGPIO.GPIO_PinGPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; MYGPIO.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init(GPIOA,MYGPIO); while(1) { GPIO_Write(GPIOA,~0x0001);//0000 0000 0000 0001 Delay_ms(500); GPIO_Write(GPIOA,~0x0002);//0000 0000 0000 0010 Delay_ms(500); GPIO_Write(GPIOA,~0x0004);//0000 0000 0000 0100 Delay_ms(500); GPIO_Write(GPIOA,~0x0008);//0000 0000 0000 1000 Delay_ms(500); } }可以用移位机制for循环替换代码#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//此时时钟被激活了 //配置GPIO口 分别对GPIO,引脚频率进行配置。 GPIO_InitTypeDef MYGPIO; MYGPIO.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出 MYGPIO.GPIO_PinGPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; MYGPIO.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init(GPIOA,MYGPIO); while(1) { for(uint8_t i0;i4;i) { GPIO_Write(GPIOA,~(0x0001i));//0000 0000 0000 0001 Delay_ms(500); } } }蜂鸣器实现倒数5秒记时#include stm32f10x.h // Device header #include Delay.h int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//此时时钟被激活了 //配置GPIO口 分别对GPIO,引脚频率进行配置。 GPIO_InitTypeDef MYGPIO; MYGPIO.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出 MYGPIO.GPIO_PinGPIO_Pin_0; MYGPIO.GPIO_SpeedGPIO_Speed_50MHz; GPIO_Init(GPIOA,MYGPIO); for(uint8_t i0;i5;i) { GPIO_ResetBits(GPIOA,GPIO_Pin_0); Delay_ms(200); GPIO_SetBits(GPIOA,GPIO_Pin_0); if(i4) { Delay_ms(1000); } } }
返回列表