STM32 学习笔记_4 GPIO:LED,蜂鸣器,按键,传感器的使用

GPIO 通用 IO

General Purpose Input Output.

可配置为8种输入输出模式。通常0~3.3V,部分引脚允许 5V。

image-20230419020637994

上面的虚线方框是输入模块,下面的是输出模块。

推挽输出是1输出高电平,0输出低电平。开漏输出正好相反,因此没有高电平驱动能力。开漏输出通常用于一些通信协议,如 I2C 的引脚,这样各个引脚之间不会互相干扰。

模式名称 性质 特征
浮空输入 数字输入 可读取引脚电平,若引脚悬空,则电平不确定
上拉输入 数字输入 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
下拉输入 数字输入 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
模拟输入 模拟输入 GPIO无效,引脚直接接入内部ADC
开漏输出 数字输出 可输出引脚电平,高电平为高阻态,低电平接VSS
推挽输出 数字输出 可输出引脚电平,高电平接VDD,低电平接VSS
复用开漏输出 数字输出 由片上外设控制,高电平为高阻态,低电平接VSS
复用推挽输出 数字输出 由片上外设控制,高电平接VDD,低电平接VSS

三种输入区别就是浮空输入引脚悬空的时候电平不确定(因此尽量接一个连续的驱动源),而上下拉输入就会设置为默认高低电平。

image-20230419144107702

如图,输出部分被断开了。输入部分接上 VDD 开关就是下拉,接上 VSS 就是上拉。

模拟输入用不到图里的任何器件,输入信号直接接入 ADC 模块。

开漏输出和推挽输出就在于高电平是高阻态还是 VDD。

复用输出就是又可以读取,又可以接到外设。

image-20230419145406005

上半部分可见,输入信号经过触发器处理后给两个出口输出了。

代码:驱动LED灯

操纵 GPIO 口驱动点亮LED灯。

首先建好项目模板:

image-20230420024857375

控制gpio需要三个步骤:开启rcc时钟,初始化,输入输出函数控制。根据架构图我们可知,GPIO 在 APB2 总线上。我们看看 RCC 里面有什么函数:

image-20230420025053559

这个是控制 APB2 时钟的,我们看一下其内容:

image-20230420025148413

参数1是要操作的 APB2 口,参数2是启动或禁用。挺简单的。

然后再看一下 GPIO 初始化函数:

image-20230420025345860

第一个参数选口,第二个参数是一个GPIO_InitTypeDef 结构体。我们知道要对其设置三个变量:输入输出模式,pin 脚,速度。

image-20230420040211907

最后一个是输入输出函数控制,这个主要介绍 GPIO 四个函数:SetBits ResetBits WriteBit Write

我们让 GPIOA Pin0 输出高或低电平,LED 另一个脚接在 GND 或 3.3 上。

image-20230420143426678
#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 抖动。

按键可以有四种处理方式:接低电平(上拉输入),接高电平(下拉输入,不常见),以及加上上下拉电阻。
image-20230421025011700

这次的项目我们新建一个 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 上,控制电压稳定(左侧)。

image-20230420205300753

上下拉电阻阻值越小拉力越强。

数字输出 DO:AO 输出的调整,一个电压比较器,同向>反向输出VCC,反之输出 GND。

image-20230421024424317

其中 IN+ 是模拟输入,我们的传感器。IN- 是一个可调节的电位器。

image-20230421024607421

整体电路简化就是这样的:

image-20230421024728192

左边的是一个电源指示灯。

第二个是 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就实现了小夜灯。

猜你喜欢

转载自blog.csdn.net/jtwqwq/article/details/130299783