mm
1 GPIO简介
GPIO(General Purpose Input Output)通用输入输出口
可配置为8种输入输出模式
引脚电平:0V~3.3V,部分引脚可容忍5V
输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等
输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等
2 GPIO基本结构
左边为APB2外设总线,每个GPIO外设总共有16个引脚。每个模块内包括了寄存器和驱动器,寄存器就是特殊的存储器,内核通过APB2总线对寄存器进行读写,寄存器每一位对应一个引脚。因为STM32都是32位的,而GPIO只有16位,所以只能表示低十六位。驱动器增加信号驱动能力,寄存器只负责存储数据
3 GPIO位结构
左边三个寄存器,中间为驱动器,右边为某一个IO引脚,上面输入下面输出
输入部分:右边的IO引脚接两个保护二极管对输入电压进行限幅,产生的电流直接流入VDD/流出VSS而不会流入内部电路,避免过高电压。若电压在0-3.3V之间则两个二极管不会导通。然后连接了上拉和下拉电阻,可以通过程序配置,避免输入数据不确定。上拉默认高电平,下拉默认低电平,阻值较大,为弱上拉/下拉,尽量不影响正常的输入操作。
施密特触发器对输入电压进行整形(如果输入电压大于某一阈值则输出为高电平,小于则输出低电平),整形后直接写入输入数据寄存器。模拟输入连接到ADC,接在触发器前;复用功能输入连接到其他需要读取端口的外设上,接收数字量
输出部分:输出数据寄存器和片上外设的复用功能输出通过数据选择器接到输出控制部分。如果选择输出数据寄存器,就是IO口输出。位设置/清除寄存器用来单独操作输出数据寄存器的某一位。输出控制后接到两个MOS管,是一种电子开关,有三种输出方式(推挽、开漏、关闭)(推挽模式P_MOS、N_MOS均有效,高低电平有较强驱动能力;开漏输出P_MOS无效,只有低电平有驱动能力;关闭则两个MOS都无效,引脚配置为输入模式)
4 GPIO模式
4.1 浮空/上拉/下拉输入
都可以读取端口高低电平,而浮空不固定。浮空模式时一定要接上连续驱动源不能悬空。
输入时输出寄存器开关断开,输入寄存器可选上拉下拉或悬空,通过施密特触发器就能输入
4.2 模拟输入
输出断开,施密特触发器关闭无效,从引脚直接接上片上外设,ADC常用
4.3 开漏/推挽输出
开漏输出为高阻态没有驱动能力,推挽输出高低电平都有驱动能力。
输出由输出数据寄存器控制,P-MOS无效为开漏输出,P-MOS和N-MOS都有效为推挽输出。输出模式下输入模式也有效
4.4 复用开漏/推挽输出
复用输出由片上外设控制,引脚控制权转移到片上外设。输入部分外设可以读取引脚电平,普通输入也有效
5 操作GPIO三步骤
(1)使用RCC开启GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
(2)使用GPIO_Init函数初始化GPIO
与初始化GPIO有关库函数:
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO类型
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //工作模式
GPIO_Mode_AIN 模拟输入,GPIO_Mode_IN_FLOATING 浮空输入,
GPIO_Mode_IPD 下拉输入,GPIO_Mode_IPU 上拉输入,
GPIO_Mode_Out_OD 开漏输出,GPIO_Mode_Out_PP 推挽输出,
GPIO_Mode_AF_OD 复用开漏,GPIO_Mode_AF_PP 复用推挽
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //选择引脚
GPIO_InitStructure.GPIO_Speed = //输出速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
(3)使用输出或输入的函数控制GPIO口
与GPIO有关库函数:
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); //PortVal可以同时对16个端口进行写入
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//锁定GPIO配置
6 GPIO读取
与GPIO有关库函数:
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);//读取整个输出数据寄存器
二极管/蜂鸣器硬件电路
二极管:上方低电平驱动,下方高电平驱动
蜂鸣器:三极管驱动减少STM32负担。上方为PNP三极管,左边为基极,带箭头为发射极,剩下的是集电极,基极给低电平三极管导通,给蜂鸣器提供电流。下方为NPN三极管,左边为基极,带箭头为发射极,剩下的是集电极,基极给高电平导通低电平断开。最好不要隔开基极和发射极,两边之间需要开启电压。
传感器模块介绍
传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出
图三:N1代表传感器元件所代表的可变电阻,R1是和N1进行分压的定值电阻,C2为滤波电容,给中间的电压输出进行滤波,滤出干扰保证电路稳定。N1阻值变小时下拉作用增强,AO端电压拉低,极端情况下N1阻值为0,AO输出完全下拉输出0V。当N1变大下拉作用减弱,中间引脚由于R1上拉作用电压升高,极端情况下N1阻值无穷大相当于断路,AO输出完全拉高输出VCC。
图一:LM393为电压比较器芯片,可用作数字输出,对AO二值化。电容为电路的滤波电容
图二:电阻为电位器
图四:LED1为电源指示灯,LED2为DO输出指示灯,低电平点亮,R5上拉电阻默认输出高电平
常用上面两种接法
图一:按键按下时PA0直接下拉到GND,PA0口电压为低电平。松手时PA0悬空,引脚电压不稳定,此时必须要求PA0是上拉输入模式(此时引脚悬空则为高电平)
图二:按键松手时自动接高电平,按下为低电平,此时不会产生按键悬空
图三:PA0需配置成下拉输入
图四:PA0需要配置成下拉输入模式或浮空输入模式
图五:传感器模块电路,DO数字输出接引脚,AO模拟输出
程序源码
LED.c
#include "stm32f10x.h" // Device header
/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平
}
/**
* 函 数:LED1开启
* 参 数:无
* 返 回 值:无
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平
}
/**
* 函 数:LED1关闭
* 参 数:无
* 返 回 值:无
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平
}
/**
* 函 数:LED1状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}
/**
* 函 数:LED2开启
* 参 数:无
* 返 回 值:无
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平
}
/**
* 函 数:LED2关闭
* 参 数:无
* 返 回 值:无
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平
}
/**
* 函 数:LED2状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为低电平
}
}
Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 2; //置键码为2
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
int main(void)
{
/*模块初始化*/
LED_Init(); //LED初始化
Key_Init(); //按键初始化
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
LED1_Turn(); //LED1翻转
}
if (KeyNum == 2) //按键2按下
{
LED2_Turn(); //LED2翻转
}
}
}