GPIO 通用 IO
General Purpose Input Output.
可配置为8种输入输出模式。通常0~3.3V,部分引脚允许 5V。
上面的虚线方框是输入模块,下面的是输出模块。
推挽输出是1输出高电平,0输出低电平。开漏输出正好相反,因此没有高电平驱动能力。开漏输出通常用于一些通信协议,如 I2C 的引脚,这样各个引脚之间不会互相干扰。
模式名称 | 性质 | 特征 |
---|---|---|
浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空,则电平不确定 |
上拉输入 | 数字输入 | 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 |
下拉输入 | 数字输入 | 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 |
模拟输入 | 模拟输入 | GPIO无效,引脚直接接入内部ADC |
开漏输出 | 数字输出 | 可输出引脚电平,高电平为高阻态,低电平接VSS |
推挽输出 | 数字输出 | 可输出引脚电平,高电平接VDD,低电平接VSS |
复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态,低电平接VSS |
复用推挽输出 | 数字输出 | 由片上外设控制,高电平接VDD,低电平接VSS |
三种输入区别就是浮空输入引脚悬空的时候电平不确定(因此尽量接一个连续的驱动源),而上下拉输入就会设置为默认高低电平。
如图,输出部分被断开了。输入部分接上 VDD 开关就是下拉,接上 VSS 就是上拉。
模拟输入用不到图里的任何器件,输入信号直接接入 ADC 模块。
开漏输出和推挽输出就在于高电平是高阻态还是 VDD。
复用输出就是又可以读取,又可以接到外设。
上半部分可见,输入信号经过触发器处理后给两个出口输出了。
代码:驱动LED灯
操纵 GPIO 口驱动点亮LED灯。
首先建好项目模板:
控制gpio需要三个步骤:开启rcc时钟,初始化,输入输出函数控制。根据架构图我们可知,GPIO 在 APB2 总线上。我们看看 RCC 里面有什么函数:
这个是控制 APB2 时钟的,我们看一下其内容:
参数1是要操作的 APB2 口,参数2是启动或禁用。挺简单的。
然后再看一下 GPIO 初始化函数:
第一个参数选口,第二个参数是一个GPIO_InitTypeDef 结构体。我们知道要对其设置三个变量:输入输出模式,pin 脚,速度。
最后一个是输入输出函数控制,这个主要介绍 GPIO 四个函数:SetBits ResetBits WriteBit Write
我们让 GPIOA Pin0 输出高或低电平,LED 另一个脚接在 GND 或 3.3 上。
#include "stm32f10x.h"
int main(void){
/* 控制gpio需要三个步骤:开启rcc时钟,初始化,输入输出函数控制 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
while(1){}
}
reset 置为0,我们的电路设计是0亮,因此 reset 亮,set 灭。
也可以通过 write 函数写。GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
进而可以借助江协科技老师提供的 delay 函数做出led灯闪烁的效果:
while(1){
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(500);
}
其实 Bit_SET 就是1,RESET 就是0,但是我们不能直接给函数赋值0或1,要加一个类型转化。
while(1){
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)1);
Delay_ms(500);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)0);
Delay_ms(500);
}
这里因为我们用的是推挽输出,所以高低电平都有驱动能力。如果改成开漏输出,高电平就没有驱动能力了。开漏输出就不能让 LED 一端接地,一端接输出,因为高电平驱动不了。只能一端接 3.3,一端接 输出。
所以大多数时候推挽输出更方便,开漏输出应用场景不多。
然后我们试一下流水灯。这个就是多个 led 灯接在pin01234567上,while里轮流点亮即可。没啥好说的,就是一个地方:初始化的时候,可以这么写:
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
同时初始化多个。这么做的原理是这些引脚对应值是 0000 0001, 0000 0010, 0000 0100, 0000 1000……可以或运算。
类似的,我们也可以用 write 函数替代 writebit 函数:
GPIO_WriteBit(GPIOA,~00000010);
只点亮第二位,pin1。
代码:蜂鸣器
这里使用的蜂鸣器比51简单,不用不停反转,给电就响。但是调不了频率。
蜂鸣器上面提示3个引脚,GND,VCC,IO。IO 我们随便挑一个引脚输入(不要挑 JTAG 要用的三个引脚!A15 B3 B4)
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
代码:按键
按键是机械弹簧结构, 按下松开会有 5-10ms 抖动。
按键可以有四种处理方式:接低电平(上拉输入),接高电平(下拉输入,不常见),以及加上上下拉电阻。
这次的项目我们新建一个 HardWare 文件夹,里面添加一些模块化函数。比如这次我们要写的按键用例是两个按键独立控制两个 LED 灯,那么 LED.c 代码:
#include "stm32f10x.h" // Device header
void LED_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0 | GPIO_Pin_1;//两个灯泡接在 A0 A1上
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
void LED1_On(void){
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
}
void LED1_Off(void){
GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
void LED2_On(void){
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
void LED2_Off(void){
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
并且补充响应 .h 文件。
当然可以定义一个 LED_Set 函数,传入两个参数决定点亮哪几个灯。
或者其实一个就行了,传入 11 10 01 00 代表灯的状态。
对按键输入的处理:首先初始化 GPIOB 用于读取按键输入,模式设置为上拉输入
void Key_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//其实输入模式下这个参数没用
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
对于输入数据如何处理?参考 GPIO.h 里面的文件:
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);
后两个是读取输入端口,看看自己输出了什么东西。
我们读取按键输入,就是读取输入模式下的某一位。
简单的按键点亮 LED 只需要第一个函数读取一下即可,记得消抖。
uint8_t Key_GetNum(void){
uint8_t KeyNum=0;//按下按键时=1
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0)==0){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0)==0);
Delay_ms(20);
KeyNum+=1;
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)==0){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)==0);
Delay_ms(20);
KeyNum+=2;
}
return KeyNum;
}
这里我用二进制来判断两个按键按下方式。
int main(void){
LED_Init();
Key_Init();
LED1_Off();
LED2_Off();
uint8_t KeyNum;
while(1){
KeyNum=Key_GetNum();
if(KeyNum>0){
if(KeyNum%2==1)LED1_On();
if(KeyNum>=2)LED2_On();
}
}
}
这个只是一个测试代码,跑起来的效果就是按下按键后 LED 永久点亮。
如何让按下按键后 LED 反转状态呢?这时候我们就可以用到输入里介绍的读取当前输出了。我们不再使用 LED_On 和 Off,而是使用 Turn。
void LED1_Turn(void){
if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0)==1)GPIO_ResetBits(GPIOA, GPIO_Pin_0);
else GPIO_SetBits(GPIOA, GPIO_Pin_0);
}
void LED2_Turn(void){
if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1)==1)GPIO_ResetBits(GPIOA, GPIO_Pin_1);
else GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
代码:传感器
原理:内部有一个受传感因素的值改变而变的电阻,一个定值电阻,根据分压我们可以判断当前阻值。光敏电阻,热敏电阻,红外接收管都类似。
模拟输出 AO:根据传感器电阻的值得到的模拟电压值输出。图中 N1 是传感器电阻。
可能还带有滤波电容,两端接在 VDD GND 上,控制电压稳定(左侧)。
上下拉电阻阻值越小拉力越强。
数字输出 DO:AO 输出的调整,一个电压比较器,同向>反向输出VCC,反之输出 GND。
其中 IN+ 是模拟输入,我们的传感器。IN- 是一个可调节的电位器。
整体电路简化就是这样的:
左边的是一个电源指示灯。
第二个是 DO 指示灯。
第三个是一个确保默认高电平的上拉电阻。
接下来我们写一个用例,光敏传感器控制蜂鸣器。只用 DO 即可。
代码和按键其实非常相似,DO 读取输入,给蜂鸣器输出。
int main(void){
Buzzer_Init();
while(1){
if(LightSensor_Get()==1)Buzzer_On();
else Buzzer_Off();
}
}
void LightSensor_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t LightSensor_Get(void){
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
}
void Buzzer_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void Buzzer_On(void){
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_Off(void){
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
光敏传感器是光线少的时候阻值大,读到的值赋1.可以拧电位器调节水平。
蜂鸣器和LED还都是低电平响/点亮的,所以我把蜂鸣器拔下来换个高电平接 3.3 的LED就实现了小夜灯。