【STM32F103笔记】3、按键与矩阵键盘

上一篇说完了STM32库开发的引脚输出控制,这一篇对其引脚输入控制方法进行说明,引脚设置为输入功能时能够感知引脚上的电平高低,具有模拟输入复用功能的引脚还可以结合芯片内部的A/D准确测量其电平值,后续在ADC章节再进行讨论。

这里通过按键改变引脚的输入电平高低,当引脚上接入高电平时,其输入数据寄存器对应的位将置1,而当引脚上接入低电平时,将清0;程序对引脚的输入数据进行判断,并控制LED的亮灭。

由于笔者蓝色的最小系统板没有设置按键,因此用黑色的最小系统板进行按键程序的演示。

按键电路原理图

在这里插入图片描述
从图中可以看出,黑色系统板的LED由GPIOC13引脚控制,低电平点亮;按键接在GPIOA0引脚,按键按下则引脚与地连接,即引脚接入低电平,在上一篇的基础上,可以开始写程序了。

程序设计

程序原理

程序思路是,初始化GPIOA0为上拉输入模式,即在STM32芯片内部使能上拉电阻,如图所示:
在这里插入图片描述
在STM32引脚内部,有输出驱动与输入驱动电路,通过Port Configuration Register(CRL、CRH)寄存器设置引脚输入还是输出,并设置相应的输入输出模式。

在这里将GPIOA0引脚设置为上拉输入模式,即上拉电阻的“开关”闭合,引脚在默认状态下,通过内部的上拉电阻接到VDD,也就是默认为高电平;这样,当按键按下时,GPIOA0通过按键接地,引脚切换为低电平

检测到按键按下时,读取GPIOC13引脚的输出数据(0或1),并对其取反,重新写入GPIOC13引脚数据输出寄存器,改变引脚输出状态,控制LED状态反转。

同理复制模板工程文件到新工程文件夹Key下,打开Keil uVision5工程文件。这里直接给出核心程序:

/**
  * @brief  Main program.
  * @param  None
  * @retval None
  */
int main(void)
{
	// BitAction是库文件提供的enum枚举类型
	BitAction status;
	
	// 配置引脚
	GPIOConfig();
	
	while(1)
	{
		// 读取GPIOA0输入状态
		if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==0)
		{
			// 延时去抖
			delay_u(1000);
			if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==0)
			{
				// 等待按键释放,即松手检测
				while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)==0);
				// 读取LED状态并取反
				status = (BitAction)(1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13));
				// 设置LED反转
				GPIO_WriteBit(GPIOC, GPIO_Pin_13, status);
			}
		}
	}
}

/**
  * @brief  Configure GPIO
  * @param  None
  * @retval None
  */
void GPIOConfig(void)
{
	GPIO_InitTypeDef GPIOInitStruct;
	
	// GPIOA与GPIOC均挂载在APB2总线上
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	// 设置GPIOA为上拉输入模式,输入模式下无需设置引脚输出速率
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &GPIOInitStruct);
	
	// 设置GPIOC13为推挽输出模式
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_13;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIOInitStruct);
	GPIO_SetBits(GPIOC, GPIOInitStruct.GPIO_Pin);
}

其中BitAction是库文件中提供的枚举类型,表示引脚的位清0与置1:

/** 
  * @brief  Bit_SET and Bit_RESET enumeration  
  */

typedef enum
{ Bit_RESET = 0,
  Bit_SET
}BitAction;

uint8_t GPIO_ReadOutputDataBit( GPIO_TypeDef * GPIOx, uint16_t GPIO_Pin ) 为引脚输入数据读取函数,可以在库文件帮助文档中找到其用法。

在利用官方库进行STM32程序开发时,需要时刻结合帮助文档,查找相关函数。

运行结果

编译程序并下载运行,可以看到按键改变LED亮灭状态:
在这里插入图片描述

矩阵键盘

这里用的矩阵键盘规格为4x4,即4行4列共16个按键,通过8个端子就可以进行控制和读取,电路原理图及实物如下:
在这里插入图片描述
从电路原理图可以看出,按键的每一行、每一列均分别相连并接入P1的一个端子,其控制方式为扫描读取(行扫描列读取或者列扫描行读取),这里以行扫描为例:
在这里插入图片描述
P1的1-4端子为列,用于读取数据,5-8端子为行,进行扫描;假设K4被按下:

控制STM32的引脚,首先使P1的5端子输出低电平,6-8端子为高电平,并读取1-4端子的电平数据,可以知道1端子为低电平,2-4端子为高电平;由此可以推知K4按下;

依次使P1的5-8端子分别输出低电平,并读取列数据,可以获取按下的按键位置;

但进行行扫描时矩阵键盘无法识别同一列同时按下的两个按键,举例若K4、K8同时按下:

  • 第一行扫描时,P1的5端子输出低电平,6端子输出高电平
  • 此时6端子的高电平通过K8、K4进入5端子,形成回路;
  • 而由上面的引脚内部结构可以看出,引脚配置成推挽输出时,输出高电平时P-MOS导通,而输出低电平时N-MOS导通,且这两个MOS管采用的是对称设计,即参数近似,故此时VDD通过两个MOS管接到VSS,引脚输出的电压为 1 2 V D D \frac{1}{2}V_{DD} ,近似于高电平;
  • 从而1端子的输入引脚的电平被干扰,导致无法正确读取数据
程序

矩阵键盘列接到PA0-PA3,行接到PA4-PA7,而上一篇中的流水灯接到PB8-PB15

矩阵键盘扫描程序:

/**
  * @brief  Key Scan
  * @param  None
  * @retval sum of key values
  */
uint8_t MatrixKeyScan(void)
{
	uint8_t i, temp, pin = 0;
	
	// 进行行扫描
	for(i=0; i<4; i++)
	{
		// 首先将所有行都置高电平,然后按照行扫描顺序将第i行输出低电平
		GPIO_SetBits(GPIOA, 0xF0);
		// 由于PA4-PA7接行,故0x10左移i位为当前行控制引脚编号
		GPIO_ResetBits(GPIOA, 0x10<<i);
		
		// 读取列数据,由于GPIO_ReadInputData返回uint16_t,因此只取低4位
		temp = (uint8_t)(GPIO_ReadInputData(GPIOA) & 0x000F);
		if(temp != 0x0F)
		{
			// 延时去抖
			delay_u(1000);
			temp = (uint8_t)(GPIO_ReadInputData(GPIOA) & 0x000F);
			// 这里不进行松手检测,直接将该行中所有按下的键值相加
			if(temp !=0x0F)
			{
				pin += ~(temp|0xF0)+i*4;
			}
		}
	}
	// 返回计算的键值
	return pin;
}

这里通过8个LED以二进制的方式显示按下的键的值的和,如按下K1、K3,则LED1、LED3亮,二进制值为0b00000101,即4

完整程序

(不含上一篇中写好的延时程序)如下:

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"

/* Private functions Declaration ---------------------------------------------*/
void GPIOConfig(void);
uint8_t MatrixKeyScan(void);

void delay_m(uint32_t t);
void delay_u(uint32_t t);

/**
  * @brief  Main program.
  * @param  None
  * @retval None
  */
int main(void)
{
	uint8_t keys;
	
	GPIOConfig();
	while(1)
	{
		keys = MatrixKeyScan();
		GPIO_SetBits(GPIOB, 0xFF00);
		GPIO_ResetBits(GPIOB, keys<<8);
	}
}

/**
  * @brief  Key Scan
  * @param  None
  * @retval sum of key values
  */
uint8_t MatrixKeyScan(void)
{
	uint8_t i, temp, pin = 0;
	
	for(i=0; i<4; i++)
	{
		GPIO_SetBits(GPIOA, 0xF0);
		GPIO_ResetBits(GPIOA, 0x10<<i);
		
		temp = (uint8_t)(GPIO_ReadInputData(GPIOA) & 0x000F);
		if(temp != 0x0F)
		{
			delay_u(1000);
			temp = (uint8_t)(GPIO_ReadInputData(GPIOA) & 0x000F);
			if(temp !=0x0F)
			{
				pin += ~(temp|0xF0)+i*4;
			}
		}
	}
	return pin;
}

/**
  * @brief  Configure GPIO
  * @param  None
  * @retval None
  */
void GPIOConfig(void)
{
	GPIO_InitTypeDef GPIOInitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	//GPIOInitStruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIOInitStruct.GPIO_Pin = 0x00F0;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIOInitStruct);
	//GPIOInitStruct.GPIO_Pin = |GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
	GPIOInitStruct.GPIO_Pin = 0x0F;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &GPIOInitStruct);
	
	//GPIOInitStruct.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
	GPIOInitStruct.GPIO_Pin = 0xFF00;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIOInitStruct);
	GPIO_ResetBits(GPIOC, GPIOInitStruct.GPIO_Pin);
	delay_m(500);
	GPIO_SetBits(GPIOC, GPIOInitStruct.GPIO_Pin);
}

注意在GPIO初始化和控制GPIO输出时,并没有用GPIO_Pin_x的形式(被注释的部分),查看GPIO_Pin_x的定义(如在GPIO_Pin_0上右键Go To Definition…)可知,是用二进制位表示的GPIO引脚编号,因此可以直接用二进制数来表示引脚,更为方便(虽然没有那么直观)。

运行结果

编译程序并下载,现象如下(一根手指按下了K1-K4,难道可以用这个练大横按~啊哈哈哈哈哈):
在这里插入图片描述

完结撒花✿✿ヽ(°▽°)ノ✿

发布了4 篇原创文章 · 获赞 8 · 访问量 1966

猜你喜欢

转载自blog.csdn.net/Keep_moving_tzw/article/details/104461700
今日推荐