一、GPIO概述
GPIO(General Purpose Input Output)通用输入输出口 可配置为8种输入输出模式
引脚电平:0V~3.3V,部分引脚可容忍5V(有FT标记),输出最大只能输出3.3V,因为供电只有3.3V
输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等(比如I2C/SPI);只要是可以用高低电平控制的地方都可以用GPIO来完成,如果控制的是功率比较大的设备,只需要再加入驱动电路即可。
输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等
主要用于连接微控制器、嵌入式系统或其他电子设备与外部世界进行交互。它通过引脚(Pin)来提供数字输入和输出功能
- 数字输入:GPIO可以读取外部设备或传感器的数字信号。例如,读取按键状态、检测外部开关状态、接收来自其他设备的数据等。
- 数字输出:GPIO可以控制外部设备或执行器的状态。例如,控制LED灯的亮灭、驱动蜂鸣器发声、控制继电器开关等。
- 中断功能:通过将GPIO配置为中断模式,可以在外部事件触发时产生中断请求,以实现实时响应或处理特定事件。
- 模拟输入/输出:某些GPIO引脚支持模拟信号输入输出,可以读取或输出模拟量信号,如读取光强传感器的光照值、控制电机的转速等。
- 多功能引脚:有些GPIO引脚具有多种功能,可以通过配置选择不同的工作模式,如串行通信接口(SPI、I2C、UART)、定时器输入/输出等。
1、GPIO基本结构
Cortex-M3系统架构:
从上面的内核架构图中可以看出在Stm32中所有的GPIO都是挂载在APB2外设总线上的,其中GPIO外设的名称是按照GPIOA、GPIOB、GPIOC等这样的方式命名的,每个GPIO外设总共有16个引脚编号是从0-15(PA0、PA1…)
寄存器:特殊的存储器,内核通过APB2总线对寄存器进行读写,这样就可以完成输出电平和读取电平的功能了(寄存器的每一位对应一个引脚,输入/出寄存器写1就是高电平写0就是低电平)。Stm32是32位的单片机所以内部寄存器都是32位,可是引脚端口只有十六位所以这个寄存器只有低16对应的有引脚端口高16位是没有用到的
驱动器:是用来增加信号的驱动能力
2、GPIO位结构
上下可分为两个部门,上面是输入部分下面是输出部分
输入
- 保护二极管:对输入电压进行限幅,如果输入电压比3.3v还搞那么上方的保护二极管就会导通,输入电压产生的电流会直接流入Vdd而不会流入内部电路;反之如果输入的是负电压下发的保护二极管就会导通电流会从Vss直接流出去
- 上/下拉电阻:开关是可以通过程序进行配置,开关对应上/下拉输入模式,如果上下两个开关都断开就是浮空输入模式
- 肖特基触发器:主要作用是对输入电压进行整形的,他的执行逻辑是如果输入电压大于某个阀值输出就会瞬间升为高电平,如果输入电压小于某个阀值输出就会瞬间降为低电平;这样两个阀值就会产生一个区间在这个区间内即使输入电压有波动但是没有超过上下两个阀值对外输出不会有变化,相当于提高了对外输出的容错率,将经过整形后的数据给到寄存器。
- 模拟输入:连接到ADC上,因为ADC需要接收模拟量,所以是接到肖特基触发器前面
- 复用功能输入:连接到其他需要读取端口的外设上,接收的数字量所以在肖特基触发器后面
输出
P-MOS/N-MOS(电子开关):开关负责将IO口接到Vdd或者Vss,通过电子开关可以选择推挽、开漏或关闭三种输出模式。
推挽模式下P-MOS和N-MOS均有效,数据为1时候上管导通下关断开,输出接到Vdd高电平反之相反。这种情况高低电平均有较强的驱动能力所以也可以叫强推输出模式对IO口有绝对的控制权高低电平有Stm32说了算。
开漏模式下P-MOS是无效的只有N-MOS在工作,数据为1时下管断开,这时输入相当于断开也就是高阻模式;数据为0时下管导通直接接到Vss输出电平。这种情况下只有低电平有驱动能力高电平时没有驱动能力的。
关闭模式两个MOS管都无效也就是输出关闭,端口电平有外部信号来控制。
3、GPIO模式
通过上面对GPIO位结构解析可以看到GPIO输入输出的8中模式:
浮空/上拉/下拉输入
*注:在输入模式下,输出驱动器是断开的。所以端口在输入的时候只能输入不能输出
模拟输入
*注:在模拟输入模式下,输出驱动器是断开的,肖特基触发器也是关闭的无效状态,所以整个GPIO都是无用的只剩下模拟输入这根线了
开漏/推挽输出
*注:在输出模式下,输入驱动器是连接的,肖特基触发器也是有效状态整个输入都是有效的,所以一个端口只能有一个输出,但可以有多个输入
复用开漏/推挽输出
*注:可以看到通用输出是没有连接的,引脚的控制权转移到片上外设。所以引脚电平时又片上外设控制的
8种模式中,7种模式输入都是有效的只有模拟输入模式会关闭数字输入功能。
二、输出实例
1、二极管
LED:发光二极管,正向通电点亮,反向通电不亮
硬件电路
低电平驱动的电路:
LED正极接3.3v,负极通过一个限流电阻(防止电流过大烧毁led另一方面可以通过阻值控制led亮度)接到PA0上。当PA0输出低电平时,LED两段就会产生电压差形成正向导通的电流LED点亮;反之PA0输出高电平时两端都是3.3v不会形成电流所以LED熄灭。
高电平驱动的电路:
推挽模式两种接线都可以,在推挽模式下Stm32对端口引脚的高低电平有绝对控制权,单在单片机电路里一般倾向于第一种接法因为很多单片机或芯片都使用了高电平弱驱动,电平强驱动的规则
GPIO控制发光二极管(LED)实例
操作Stm32的GPIO大体有三个步骤:
第一步:使用RCC开启GPIO时钟
第二步:GPIO_Init函数初始GPIO
第三波:使用输出或输入函数控制GPIO引脚端口
以上三步总共设计了RCC和GPIO两个外设,接下来看一下这两个外设都有哪些库函数
在Function栏找到rcc.h头文件并打开拖到最下面可以看到rcc的库函数都在这个头文件里声明了
同样的找到GPIO头文件
下面控制Stm32板子电源指示灯旁边的PC13知识点闪烁:
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
//第一步:使用RCC开启GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
//第二步:GPIO_Init函数初始GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;//GPIOC第13个引脚端口
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//Speed 50MHz
GPIO_Init(GPIOC,&GPIO_InitStruct);
while(1){
//第三步:使用输出或输入函数控制GPIO引脚端口
GPIO_SetBits(GPIOC,GPIO_Pin_13);//指定端口设置为高电平
Delay_ms(500);
GPIO_ResetBits(GPIOC,GPIO_Pin_13);//指定端口设置为低电平
Delay_ms(500);
// GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//Bit_SET: to set the port pin 高电平
// Delay_ms(500);
// GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);//Bit_RESET: to clear the port pin 低电平
// Delay_ms(500);
}
}
流水灯code
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //GPIO引脚,赋值为所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化
/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0020); //0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
}
}
2、蜂鸣器(有源蜂鸣器)
- 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
- 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音
硬件电路
下面两种接线方式都是使用三极管开关驱动方案,对于功率稍微大一些的设备直接用IO口驱动会导致Stm32负担过重,这时就可以用一个三极管驱动电路来完成驱动任务。
PNP三极管驱动电路:
左边的基极给低电平,三极管就会导通,通过3.3v和GND就可以给蜂鸣器提供驱动电流了
NPN三极管驱动电路:
基极给高电平导通,低电平断开(需要注意的是:PNP三级管最好接在上面,NPN三极管最好接在下面,这是因为三极管的通断是需要在发射极和基极直接产生一定的开启电压)
GPIO控制蜂鸣器实例
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO引脚,赋值为第12号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOB的初始化
/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0); //将PB12引脚设置为低电平,蜂鸣器鸣叫
Delay_ms(100); //延时100ms
GPIO_SetBits(GPIOB, GPIO_Pin_0); //将PB12引脚设置为高电平,蜂鸣器停止
Delay_ms(100); //延时100ms
GPIO_ResetBits(GPIOB, GPIO_Pin_0); //将PB12引脚设置为低电平,蜂鸣器鸣叫
Delay_ms(100); //延时100ms
GPIO_SetBits(GPIOB, GPIO_Pin_0); //将PB12引脚设置为高电平,蜂鸣器停止
Delay_ms(700); //延时700ms
}
}
三、输入实例
1、按键
按键:常见的输入设备,按下导通,松手断开
按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动
按键按下去和松开的时候都会有一小段连续抖动的情况,这种抖动在程序中需要注意过滤一下(加一段延迟,把抖动时间耗过去)
硬件电路
上面两个下接按键方式,下面两个是上接按键方式。
通常使用第一种下接按键方式
最终的目的是通过按键开关控制引脚端口高低电平
- ①必须要求PA0是上拉输入模式,否则会出现引脚电压不确定错误。这种情况按下按键引脚为低电平,松开按键为高电平
- ②相比第一个,这里接了一个外部上拉电阻。当松开按键,引脚由于上拉作用保持为高电平,按键按下接入GND此时为低电平
- ③必须要求PA0是下拉输入模式。当按键按下为高电平反之低电平
- ④相比第三个个,这里接了一个外部下拉电阻。当松开按键,引脚由于下拉作用保持为低电平,按键按下接入3.3v此时为高电平电平
通过开关信号输入控制二极管(Stm最小版电源指示灯旁边)实例
在keil5工程里新建一个驱动文件夹组Hardware改组下面新建Add new item to group 四个文件LED.h、LED.c、KEY.h、KEY.c文件(模块化设计)
LED.h:
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED_ON(void);
void LED_OFF(void);
void LED_Turn(void);
#endif
LED.c:
#include "stm32f10x.h" // Device header
/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure); //将PC13引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOC, GPIO_Pin_13); //设置PC13引脚为高电平
}
/**
* 函 数:LED开启
* 参 数:无
* 返 回 值:无
*/
void LED_ON(void)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13); //设置PC13引脚为低电平
}
/**
* 函 数:LED关闭
* 参 数:无
* 返 回 值:无
*/
void LED_OFF(void)
{
GPIO_SetBits(GPIOC, GPIO_Pin_13); //设置PC13引脚为高电平
}
/**
* 函 数:LED状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOC, GPIO_Pin_13); //则设置PC13引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13); //则设置PC13引脚为低电平
}
}
KEY.h:
#ifndef __KEY_H
#define __KEY_H
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
KEY.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA15引脚初始化为上拉输入
}
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15) == 0) //读PA15输入寄存器的状态,如果为0,则代表按键按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}
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) //按键按下
{
LED_Turn(); //LED翻转
}
}
}
2、传感器
传感器元件(光敏电阻/热敏电阻/红外接收管【对射式/反射式红外传感器】等)的电阻会随外界模拟量的变化而变化(比如光线越强光敏电阻的阻值越小;温度越高热敏电阻的阻值越小;红外光线越强红外接受管的阻值越小),通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出
硬件电路
红外传感器控制蜂鸣器实例
蜂鸣器头文件Buzzer.h:
#ifndef __BUZZER_H
#define __BUZZER_H
void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
void Buzzer_Turn(void);
#endif
Buzzer.c:
#include "stm32f10x.h" // Device header
/**
* 函 数:蜂鸣器初始化
* 参 数:无
* 返 回 值:无
*/
void Buzzer_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_ResetBits(GPIOB, GPIO_Pin_0); //设置PB0引脚为低电平
}
/**
* 函 数:蜂鸣器开启
* 参 数:无
* 返 回 值:无
*/
void Buzzer_ON(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_0); //设置PB0引脚为高电平
}
/**
* 函 数:蜂鸣器关闭
* 参 数:无
* 返 回 值:无
*/
void Buzzer_OFF(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0); //设置PB0引脚为低电平
}
/**
* 函 数:蜂鸣器状态翻转
* 参 数:无
* 返 回 值:无
*/
void Buzzer_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOB, GPIO_Pin_0); //则设置PB0引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0); //则设置PB0引脚为低电平
}
}
传感器头文件Sensor.h:
#ifndef __LIGHT_SENSOR_H
#define __LIGHT_SENSOR_H
void Sensor_Init(void);
uint8_t Sensor_Get(void);
#endif
Sensor.c:
#include "stm32f10x.h" // Device header
/**
* 函 数:感器初始化
* 参 数:无
* 返 回 值:无
*/
void Sensor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA12引脚初始化为上拉输入
}
/**
* 函 数:获取当前感器输出的高低电平
* 参 数:无
* 返 回 值:传感器输出的高低电平,范围:0/1
*/
uint8_t Sensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12); //返回PA12输入寄存器的状态
}
main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "Buzzer.h"
#include "Sensor.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
uint8_t SensorNum; //定义用于接受传感器返回的变量
int main(void)
{
/*模块初始化*/
LED_Init(); //LED初始化
Key_Init(); //按键初始化
Buzzer_Init(); //蜂鸣器初始化
Sensor_Init(); //传感器初始化
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键按下
{
LED_Turn(); //LED翻转
}
SensorNum = Sensor_Get();
if (SensorNum == 1) //如果当前传感器输出1
{
Buzzer_ON(); //蜂鸣器开启
}
else //否则
{
Buzzer_OFF(); //蜂鸣器关闭
}
}
}