一、GPIO口的简介
每个通用 I/O 端口包括 4 个 32 位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、 GPIOx_OSPEEDR 和 GPIOx_PUPDR)、2 个 32 位数据寄存器(GPIOx_IDR 和 GPIOx_ODR)、1 个 32 位置位/复位寄存器 (GPIOx_BSRR)、1 个 32 位锁定寄存器 (GPIOx_LCKR) 和 2 个 32 位复用功能选择寄存器(GPIOx_AFRH 和 GPIOx_AFRL)。
二、GPIO口的主要模式
GPIO模式可以分为以下几种:
-
输入模式:
- 浮空输入:引脚的电平状态不确定,完全由外部输入决定。
- 上拉输入:在无输入的情况下,引脚保持高电平。
- 下拉输入:在无输入的情况下,引脚保持低电平。
- 模拟输入:输入信号不经施密特触发器直接接入,输入信号为模拟量而非数字量。
-
输出模式:
- 推挽输出:可以输出高、低电平,连接数字器件。
- 开漏输出:输出端相当于三极管的集电极,需要上拉电阻才能输出高电平。
- 复用推挽输出:IO受内部外设控制,如定时器的PWM,SPI的MOSI等。
-
其他模式:
- GPIO_Mode_AIN:模拟输入,主要用于读取外部模拟信号。
- GPIO_Mode_IN_FLOATING:浮空输入,数据通道中仅接入TTL触发器。
- GPIO_Mode_IPD:下拉输入,内部连接下拉电阻,悬空时默认为低电平。
- GPIO_Mode_IPU:上拉输入,内部连接上拉电阻,悬空时默认为高电平。
每种模式都有其特定的应用场景,例如模拟输入模式主要用于读取外部模拟信号,而浮空输入模式则适用于需要外部控制信号的引脚。输出模式中,推挽输出和开漏输出是常见的,它们可以驱动数字器件,而复用推挽输出则用于连接内部外设模块。
在配置GPIO引脚时,需要根据具体的应用场景选择合适的模式,以确保引脚的功能能够满足需求。
三、GPIO 框图剖析:
1.保护二极管及上、下拉电阻:可以通过“上拉/下拉寄存器 GPIOx_PUPDR”控制引脚的上、下拉以及浮空模式。
2.P-MOS 管和 N-MOS 管 :
GPIO 引脚线路经过两个上、下拉电阻结构后,向上流向“输入模式”结构,向下流向“输出模 式”结构。先看输出模式部分,线路经过一个由 P-MOS 和 N-MOS 管组成的单元电路。这个结构 使 GPIO 具有了“推挽输出”和“开漏输出”两种模式。在该结构中输入高电平时,上 方的 P-MOS 导通,下方的 N-MOS 关闭,对外输出高电平;而在该结构中输入低电平时,N-MOS 管导通,P-MOS 关闭,对外输出低电平。
3.复用功能输出:
“复用功能输出”中的“复用”是指 STM32 的其它片上外设对 GPIO 引脚进行控制,此时 GPIO 引 脚用作该外设功能的一部分,算是第二用途。从其它外设引出来的“复用功能输出信号”与 GPIO 本身的数据据寄存器都连接到双 MOS 管结构的输入中,通过图中的梯形结构作为开关切换选择。 例如我们使用 USART 串口通讯时,需要用到某个 GPIO 引脚作为通讯发送引脚,这个时候就可 以把该 GPIO 引脚配置成 USART 串口复用功能,由串口外设控制该引脚,发送数据。
4.输入数据寄存器:
看 GPIO 结构框图的上半部分,它是 GPIO 引脚经过上、下拉电阻后引入的,它连接到施密特 触发器,信号经过触发器后,模拟信号转化为 0、1 的数字信号,然后存储在“输入数据寄存器 GPIOx_IDR”中,通过读取该寄存器就可以了解 GPIO 引脚的电平状态。
5.复用功能输入:
当 GPIO 引脚用于 ADC 采集电压的输入通道时,用作“模拟输入”功能,此时信号是不经过施 密特触发器的,因为经过施密特触发器后信号只有 0、1 两种状态,所以 ADC 外设要采集到原始 的模拟信号,信号源输入必须在施密特触发器之前。类似地,当 GPIO 引脚用于 DAC 作为模拟 电压输出通道时,此时作为“模拟输出”功能,DAC 的模拟信号输出就不经过双 MOS 管结构了, 在 GPIO 结构框图的右下角处,模拟信号直接输出到引脚。同时,当 GPIO 用于模拟功能时 (包括 输入输出),引脚的上、下拉电阻是不起作用的,这个时候即使在寄存器配置了上拉或下拉模式, 也不会影响到模拟信号的输入输出。
四、硬件连接
1.图LED 灯电路连接图 _ 中从 3 个 LED 灯的阳极引出连接到 3.3V 电源,阴极各经过 1 个电阻引入 至 STM32 的 3 个 GPIO 引脚 PH10、PH11、PH12 中,所以我们只要控制这三个引脚输出高低电 平,即可控制其所连接 LED 灯的亮灭。如果您的实验板 STM32 连接到 LED 灯的引脚或极性不 一样,只需要修改程序到对应的 GPIO 引脚即可,工作原理都是一样的。
2.把 GPIO 的引脚设置成推挽输出模式并且默认下拉,输出低电平,这样就能让 LED 灯亮起来,开启时钟,配置引脚模式,控制电平,经过这三步,我们就可以控制一个 LED 了。
五、main 文件
#include "stm32F4xx.h"
#include "stm32f4xx_conf.h"
#include "stdio.h"
int couter = 0;
uint8_t key1;
uint8_t key2;
int8_t color = 0;
void delay(__IO uint32_t ms)
{
for(;ms!=0;ms--);
}
void LED_init()
{
GPIO_InitTypeDef gpio_info;
//PH0
//初始化GPIOH时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,ENABLE);
gpio_info.GPIO_Mode = GPIO_Mode_OUT;
gpio_info.GPIO_OType = GPIO_OType_PP;
gpio_info.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12;
gpio_info.GPIO_PuPd = GPIO_PuPd_UP;
gpio_info.GPIO_Speed = GPIO_Low_Speed;
GPIO_Init(GPIOH,&gpio_info);
}
void key_init()
{
GPIO_InitTypeDef gpio_info;
EXTI_InitTypeDef exti_info;
NVIC_InitTypeDef NVIC_InitStructure;
//PA0
//初始化GPIOA的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
//初始化中断时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
//配置按键
gpio_info.GPIO_Mode = GPIO_Mode_IN;
gpio_info.GPIO_OType = GPIO_OType_PP;
gpio_info.GPIO_Pin = GPIO_Pin_0;
gpio_info.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_info.GPIO_Speed = GPIO_Low_Speed;
GPIO_Init(GPIOA,&gpio_info);
//配置按键的外部中断
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,EXTI_PinSource0);
exti_info.EXTI_Line = EXTI_Line0;
exti_info.EXTI_LineCmd = ENABLE;
exti_info.EXTI_Mode = EXTI_Mode_Interrupt;
exti_info.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&exti_info);
//配置这个外部中断的优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //PA0 对应的是EXTI0_IRQn
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStructure);
}
void key2_init()
{
GPIO_InitTypeDef gpio_info;
EXTI_InitTypeDef exti_info;
NVIC_InitTypeDef NVIC_InitStructure;
//PA0
//初始化GPIOA的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
//初始化中断时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
//配置按键
gpio_info.GPIO_Mode = GPIO_Mode_IN;
gpio_info.GPIO_OType = GPIO_OType_PP;
gpio_info.GPIO_Pin = GPIO_Pin_13;
gpio_info.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_info.GPIO_Speed = GPIO_Low_Speed;
GPIO_Init(GPIOC,&gpio_info);
//配置按键的外部中断
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,EXTI_PinSource0);
exti_info.EXTI_Line = EXTI_Line13;
exti_info.EXTI_LineCmd = ENABLE;
exti_info.EXTI_Mode = EXTI_Mode_Interrupt;
exti_info.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&exti_info);
//配置这个外部中断的优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //PA0 对应的是EXTI0_IRQn
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI0_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line1)!=RESET)//证明触发了中断
{
//控制灯进行颜色切换
color ++;
if(color > 2)
color=0;
//清除中断标记
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
//编写中断服务函数
void EXTI15_10_IRQHandler()
{
if(EXTI_GetITStatus(EXTI_Line13)!=RESET)//证明触发了中断
{
//控制灯进行颜色切换
color --;
if(color<3)
//清除中断标记
EXTI_ClearITPendingBit(EXTI_Line13);
}
}
#define LED_RED GPIO_Pin_10
#define LED_GREEN GPIO_Pin_11
#define LED_BLUE GPIO_Pin_12
//按下KEY1按键---红蓝绿、红蓝绿
//按下KEY2按键---绿蓝红、绿蓝红
int main(void)
{
LED_init();
key_init();
key2_init();
//关闭11、12
GPIO_SetBits(GPIOH,GPIO_Pin_11|GPIO_Pin_12);
while(1)
{
//读取按键状态
if(color ==0)//亮红灯
{
GPIO_ResetBits(GPIOH,LED_RED);
GPIO_SetBits(GPIOH,LED_GREEN|LED_BLUE);
}
else if(color ==1)//亮蓝灯
{
GPIO_ResetBits(GPIOH,LED_GREEN);
GPIO_SetBits(GPIOH,LED_RED|LED_BLUE);
}
else if(color ==2)//亮绿灯
{
GPIO_ResetBits(GPIOH,LED_BLUE);
GPIO_SetBits(GPIOH,LED_RED|LED_GREEN);
}
}
}