STM32之GPIO输入

66 阅读4分钟

核心知识

相关函数

学习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);

其命名简明扼要,四个函数其实就是在读取输入/输出数据寄存器一位/所有位

这里只以uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)来举例说明用法:

  • 这个函数用来读取指定引脚的高低电平,比如读取PA12引脚,就是GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12)
  • 注意第二个参数并不能用位或的方式同时选择多个引脚,不同于GPIO_SetBits(其实从名字上可以看到这种差别,Bit真就是Bit)。

模块化编程

在这一节的视频中,我们新建了Hardware文件夹来存放功能代码,分别新建了LED.cLED.h文件:

  • LED.c用来存放驱动程序的主体代码;
  • LED.h用来存放这个驱动程序可以对外提供的函数或变量的声明。

.h文件有规范格式,如下:

#ifndef __LED_H
#define __LED_H


#endif

注:__LED_H就是 双下划线 + 文件名大写 + 下划线

再把.c文件中定义的函数头加入到文件中,最终形态如下:

#ifndef __LED_H
#define __LED_H

void LED_Init(void);
void LED_Turn(char KeyNum);

#endif

代码讲解

按键控制 LED

实现思路

按键按下时联通电路,其连接的GPIOB口电压发生变化,可以用GPIO的读函数来获得对应引脚的高低电平状态,来捕获按键按下这个事件,然后调用控制LED的函数,就实现了通过按键来控制对应的LED灯的操作

思考

问:GPIO引脚的电压发生变化,内部的输入寄存器的值会自动变化吗?

答:会,而且是硬件自动完成的,MCU硬件自动把电平结果映射到 GPIOx_IDR(Input Data Register) 对应的bit

实现代码

LED.c

#include "stm32f10x.h"                  // Device header

void LED_Init(void)
{
	// 1. 使用`RCC`开启`GPIO`的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2. 使用`GPIO_Init`函数初始化`GPIO`
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化默认低电平,所以如果不设置LED会亮
	
	// 3. 使用输出或者输入的函数控制`GPIO`口
	GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
	
}	

// LED状态切换,KeyNum用来指定切换哪个LED
void LED_Turn(uint8_t KeyNum)
{
	if(KeyNum == 1)
	{
		if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1))
		{
			GPIO_ResetBits(GPIOA, GPIO_Pin_1);
		}
		else
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_1);
		}
	}
	if(KeyNum == 2)
	{
		if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2))
		{
			GPIO_ResetBits(GPIOA, GPIO_Pin_2);
		}
		else
		{
			GPIO_SetBits(GPIOA, GPIO_Pin_2);
		}
	}
}
  • 主要涉及初始化函数和功能函数两种,初始化就是固定的使用GPIO外设的三部曲的前两部:1)使用RCC开启GPIO的时钟 2)使用GPIO_Init函数初始化GPIO

  • 重点是LED_Turn,这里使用了GPIO的读函数中的GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1),它读取了输出寄存器中对应PA1位的高低电平,如果是1(高电平)就使用GPIO_ResetBits(GPIOA, GPIO_Pin_1)设置为低电平,从而达到LED亮灭的切换。


Key.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 为什么是上拉输入的模式?
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;  // 这个设置的是GPIO的输出速度,在输入模式下其实没用
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;
	
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
		Delay_ms(20);
		KeyNum = 1;
	}
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
	{
		Delay_ms(20);
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
		Delay_ms(20);
		KeyNum = 2;
	}
	
	return KeyNum;
}

这里有一个问题,为什么设置成上拉输入模式?是由我们的按键连接电路决定的,但是内部是什么情况呢?


main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"

uint8_t KeyNum;

int main(void)
{
	LED_Init();
	Key_Init();
	
	while (1)
	{
		KeyNum = Key_GetNum(); //得到需要切换的LED编号
		LED_Turn(KeyNum);
	}
}

模块化编程的好处,主函数非常简洁,逻辑清晰。