stm32矩阵键盘学习笔记_嵌入式矩阵键盘串口显示代码-程序员宅基地

技术标签: 单片机  

矩阵键盘简介

  1. 什么是矩阵键盘
    矩阵键盘是单片机外部设备中所使用的排布类似于矩阵的键盘组,由于电路设计时需要更多的外部输入,单独的控制一个按键需要浪费很多的IO资源,所以就有了矩阵键盘,常用的矩阵键盘有4X4和8X8,其中用的最多的是4X4。
  2. 矩阵键盘的原理
    矩阵键盘又称为行列式键盘,它是用4条I/O线作为行线,4条I/O线作为列线组成的键盘。
    在行线和列线的每一个交叉点上,设置一个按键。这样键盘中按键的个数是4×4个。
    这种行列式键盘结构能够有效地提高单片机系统中I/O口的利用率。由于单片机IO端口具有线与的功能,因此当任意一个按键按下时,行和列都有一根线被线与,通过运算就可以得出按键的坐标从而判断按键键值。

矩阵键盘扫描原理

在这里插入图片描述

  1. 行扫描的原理:因为如果有按键按下的话,某一个输入的引脚就会跟对应的输出引脚连接,因为输出为高电平,所以对应的输入引脚会被拉高,读取引脚的状态,判断哪个引脚被拉高就可以知道哪一行有按键按下了;总的来说是通过高四位输出高电平来对矩阵键盘进行逐行扫描,当低四位接收到的数据不全为1的时候,说明有按键按下,然后通过接收到的数据是哪一位为0来判断是哪一行按键被按下。
  2. 再通过列操作模块:
#define KEY_CLO0_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_RESET)
#define KEY_CLO1_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_13,Bit_RESET)
#define KEY_CLO2_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_14,Bit_RESET)
#define KEY_CLO3_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_15,Bit_RESET)

#define KEY_CLO0_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_SET) 
#define KEY_CLO1_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_13,Bit_SET)
#define KEY_CLO2_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_14,Bit_SET)
#define KEY_CLO3_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_15,Bit_SET)

来进行列扫描。

端口的配置

介绍完矩阵键盘的扫描原理了我们该通过keli来实现我们想要的功能啦,首先我们要进行的就是对我们所需要的端口进行配置,如下:


//端口的配置
void key_init(){
    

	GPIO_InitTypeDef GPIO_InitStruture;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //打开PB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); //打开PE时钟
	
	//定义PB12、PB13、PB14、PB15为推挽输出 
	GPIO_InitStruture.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruture.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
	GPIO_InitStruture.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruture);
	
	//定义PD8、PD9、PD10、PD11为上拉输入 分别定义为四行
	GPIO_InitStruture.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruture.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11;
	GPIO_Init(GPIOD,&GPIO_InitStruture);
}

相关函数的编写

配置完我们所需要的端口,接下来我们就应该编写相关函数啦。

  1. 行扫描函数:
//如果为1,代表没有按键被按下,如果为0,代表有按键被按下
char KEY_ROW_SCAN(void)
{
    
    //读出行扫描状态
    Key_row[0] = GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_8)<<3;
    Key_row[0] = Key_row[0] | (GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_9)<<2);
    Key_row[0] = Key_row[0] | (GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_10)<<1);
    Key_row[0] = Key_row[0] | (GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_11));
    
	if(Key_row[0] != 0x0f)         //不是1111,代表肯定有一个0行
    {
    
      delay_ms(10);                    //消抖
      if(Key_row[0] != 0x0f)
		  //0111 1011 1101 1110
        {
       
                //printf("Key_Row_DATA = 0x%x\r\n",Key_row[0]);
                switch(Key_row[0])
                {
    
                    case 0x07:         //0111 判断为该列第1行的按键按下
                        return 1;
                    case 0x0b:         //1011 判断为该列第2行的按键按下
                        return 2;
                    case 0x0d:         //1101 判断为该列第3行的按键按下
                        return 3;
                    case 0x0e:         //1110 判断为该列第4行的按键按下
                        return 4;
                    default :
                        return 0;
                }
        }
        else return 0;
    }
    else return 0;
}
  1. 按键扫描函数:
char KEY_SCAN(void)
{
        
    char Key_Num=0;            //1-16对应的按键数
    char key_row_num=0;        //行扫描结果记录
    
    KEY_CLO0_OUT_LOW;        
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
     
        while(KEY_ROW_SCAN() != 0);  //消抖
        Key_Num = 0 + key_row_num;
    }
    KEY_CLO0_OUT_HIGH;
    
    KEY_CLO1_OUT_LOW;        
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
     
        while(KEY_ROW_SCAN() != 0);
        Key_Num = 4 + key_row_num;
    }
    KEY_CLO1_OUT_HIGH;
    
    KEY_CLO2_OUT_LOW;    
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
     
        while(KEY_ROW_SCAN() != 0);
    Key_Num = 8 + key_row_num;
    }
    KEY_CLO2_OUT_HIGH;
    
    KEY_CLO3_OUT_LOW;    
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
    
        while(KEY_ROW_SCAN() != 0);
        Key_Num = 12 + key_row_num;
    }
    KEY_CLO3_OUT_HIGH;
    
    return Key_Num;
}

主函数与其他

  1. 主函数:
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key16.h"
#include "stdio.h"
#include "usart.h"


int main(void)
{
    
	vu8 key=0;
	char key_confirm;
	led_init();
	delay_init();
	key_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(115200);
	
	while(1)
	{
    
		key_confirm = KEY_SCAN();
		if(key_confirm>0&&key_confirm<17){
    
			printf("Key_NUM = %d \r\n",key_confirm); //按下1-16个按键的操作
      printf("= = = = = = = = = = = \r\n");		
		}	
	}	

}
  1. key.c:
#include "key16.h"
#include "delay.h"


uint8_t Key_row[1]={
    0xff};   //定义一个数组,存放行扫描状态

//端口的配置
void key_init(){
    

	GPIO_InitTypeDef GPIO_InitStruture;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //打开PB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); //打开PE时钟
	
	//定义PB12、PB13、PB14、PB15为推挽输出 
	GPIO_InitStruture.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruture.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
	GPIO_InitStruture.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruture);
	
	//定义PD8、PD9、PD10、PD11为上拉输入 分别定义为四行
	GPIO_InitStruture.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruture.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11;
	GPIO_Init(GPIOD,&GPIO_InitStruture);
	

}

/***
 *函数名:KEY_ROW_SCAN
 *功  能:按键行扫描
 *返回值:1~4,对应1~4行按键位置
 */
//如果为1,代表没有按键被按下,如果为0,代表有按键被按下
char KEY_ROW_SCAN(void)
{
    
    //读出行扫描状态
    Key_row[0] = GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_8)<<3;
    Key_row[0] = Key_row[0] | (GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_9)<<2);
    Key_row[0] = Key_row[0] | (GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_10)<<1);
    Key_row[0] = Key_row[0] | (GPIO_ReadInputDataBit(GPIOD,GPIO_Pin_11));
    
	if(Key_row[0] != 0x0f)         //不是1111,代表肯定有一个0行
    {
    
      delay_ms(10);                    //消抖
      if(Key_row[0] != 0x0f)
		  //0111 1011 1101 1110
        {
       
                //printf("Key_Row_DATA = 0x%x\r\n",Key_row[0]);
                switch(Key_row[0])
                {
    
                    case 0x07:         //0111 判断为该列第1行的按键按下
                        return 1;
                    case 0x0b:         //1011 判断为该列第2行的按键按下
                        return 2;
                    case 0x0d:         //1101 判断为该列第3行的按键按下
                        return 3;
                    case 0x0e:         //1110 判断为该列第4行的按键按下
                        return 4;
                    default :
                        return 0;
                }
        }
        else return 0;
    }
    else return 0;
}

/***
 *函数名:KEY_SCAN
 *功  能:4*4按键扫描
 *返回值:0~16,对应16个按键
 */
char KEY_SCAN(void)
{
        
    char Key_Num=0;            //1-16对应的按键数
    char key_row_num=0;        //行扫描结果记录
    
    KEY_CLO0_OUT_LOW;        
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
     
        while(KEY_ROW_SCAN() != 0);  //消抖
        Key_Num = 0 + key_row_num;
    }
    KEY_CLO0_OUT_HIGH;
    
    KEY_CLO1_OUT_LOW;        
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
     
        while(KEY_ROW_SCAN() != 0);
        Key_Num = 4 + key_row_num;
        //printf("Key_Clo_2\r\n");
    }
    KEY_CLO1_OUT_HIGH;
    
    KEY_CLO2_OUT_LOW;    
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
     
        while(KEY_ROW_SCAN() != 0);
    Key_Num = 8 + key_row_num;
        //printf("Key_Clo_3\r\n");
    }
    KEY_CLO2_OUT_HIGH;
    
    KEY_CLO3_OUT_LOW;    
    if( (key_row_num=KEY_ROW_SCAN()) != 0 )
    {
    
        while(KEY_ROW_SCAN() != 0);
        Key_Num = 12 + key_row_num;
    }
    KEY_CLO3_OUT_HIGH;
    
    return Key_Num;
}

  1. key.h:
#ifndef _KEY16_H
#define _KEY16_H

#include "sys.h"
#include "stm32f10x.h"

#include <string.h>


void key_init();
char KEY_SCAN(void);
char KEY_ROW_SCAN(void);
void HW_KEY_FUNCTION(void);


#define KEY_CLO0_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_RESET)
#define KEY_CLO1_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_13,Bit_RESET)
#define KEY_CLO2_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_14,Bit_RESET)
#define KEY_CLO3_OUT_LOW  GPIO_WriteBit(GPIOB,GPIO_Pin_15,Bit_RESET)

#define KEY_CLO0_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_12,Bit_SET) 
#define KEY_CLO1_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_13,Bit_SET)
#define KEY_CLO2_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_14,Bit_SET)
#define KEY_CLO3_OUT_HIGH  GPIO_WriteBit(GPIOB,GPIO_Pin_15,Bit_SET)


#endif

串口的接线与矩阵键盘的接线

  1. 首先我们先接串口,把串口的VCC和GND分别接到主板上的3.3V和GND上(注意要把串口上的插线帽调整到3.3V,不然容易把板子烧坏,板子烧坏了那可就成大xx了),然后再把串口上的TXD和RXD分别连到主板的RXD和TXD。
  2. 然后我们来接矩阵键盘,要用到八根线,R1 ~ R4分别连到主板上的PD8 ~ PD11,C1 ~ C4分别连到主板上的PB12 ~ PB15.
    在这里插入图片描述

load与运行调试

好啦,所有准备工作都完成啦!!又到了我们激动人心的时刻啦!!
运行成功图如下:
在这里插入图片描述
over。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_54733828/article/details/113061639

智能推荐

微软超融合私有云测试06-StorageSpaceDirect(S2D)分布式存储配置-程序员宅基地

Start1. 创建分布式存储可故障转移群集创建完毕后,接下来在群集中启用分布式存储(Storage Space Direct),来作为群集存储使用。1) 在启用分布式存储之前,首先需要处理所有磁盘,使所有磁盘处于可用状态磁盘要求为:用于存储空间直通的磁盘必须为空且没有分区或其他数据。 如果磁盘有分区或其他数据,那么它不会包含在存储空间直通系统中。2)下面,通过一个脚本,来清..._s2d 声明磁盘

SpringBoot系列—Spring Cloud快速入门-程序员宅基地

为了解决单块式架构可用性低,可伸缩性差,集中发布的生命周期以及违反单一功能原则,微服务(Microservice)应运而生了,将功能按照边界拆分为单个服务,但是每个服务之间的通讯如何解决?Spring Cloud 的出现为我们解决分布式开发中常用的问题给出了完整的方案。Spring Cloud 基于Spring Boot ,为我们提供了配置管理,服务发现,断路器,代理服务等。基于Spring

JDK12安装配置(Win10)-程序员宅基地

JDK12安装配置(Win10)一、JDK12下载JDK12下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk12-downloads-5295953.html1、选择Accept License Agreement2、点击下载对应系统安装文件二、JDK12安装右键点击下载文件,以管理员身份运行点击下...

1033:计算线段长度-程序员宅基地

时间限制: 1000 ms 内存限制: 32768 KB提交数: 8910 通过数: 5063【题目描述】已知线段的两个端点的坐标A(Xa,Ya),B(Xb,Yb),求线段AB的长度,保留到小数点后3位。【输入】第一行是两个实数Xa,Ya,即A的坐标。第二行是两个实数Xb,Yb,即B的坐标。输入中所有实数的绝对值均不超过1000010000。【输出】一个实...

自相关函数怎么理解,为什么定义中有共轭,卷积呢。定义中的卷积,共轭有什么意义?尤其是在信号处理方面-程序员宅基地

简洁地解释如下:1) 首先我们仅考虑实信号。自相关的直观含义就是:把一个信号平移一段距离,跟原来有多相似。于是就有了自相关的定义:它代表了“移、乘、积”这三步操作。 如果只谈自相关,其实到此就可以结束了。只不过,在信号处理领域中还有一个叫“卷积”的东西,在别的地方(已知线性时不变系统的冲激响应和输入,求响应)有用。它跟自相关的定义很相似,包含了“卷、移、乘、积”..._自相关函数

FPGA项目五:数码管动态扫描_da..的博客-程序员宅基地

文章目录第五章 数码管动态扫描第 1 节 项目背景第 2 节 设计目标第 3 节 设计实现3.1 顶层信号3.2 信号设计3.3 信号定义第四节 综合和上板4.1 新建工程4.2 综合4.3 配置管脚4.4 再次综合4.5 连接开发板4.6 上板第五章 数码管动态扫描第 1 节 项目背景led 数码管(LED Segment Displays)是由多个发光二极管封装在一起的器件,这些二极管组成“8”字型,在内部完成引线连接,只引出它们的各个笔划和公共电极。一般来说,led 数码管常用段数为 7 段_数码管动态扫描

随便推点

按钮控制android progressbar,Android ProgressBar手动控制开始和停止-程序员宅基地

这两天有个需求,点击按钮从SD卡解压压缩包,并读取压缩包内txt文档内容,然后在街面上显示出来。毕竟IO操作很耗时,如果文件较大会花费不少时间。所以,在处理数据的时候能给个进度就好了。我们通常的做法就是,点击按钮后弹出一个加载框或者加载进度条,等数据处理结束后,再让对话框消失。但是现在的需求是,用一个布局,左侧显示刷新列表,右侧显示ProgressBar。那么问题来了,ProgressBar显示的...

kettle 7.1 找不到 connect的原因以及解决办法_webspoon idea 运行没有connect-程序员宅基地

kettle是一个比较好用的ETL开源工具,之前一直使用的是6.1版本,最近项目组有小伙伴第一次使用,直接下载了7.1版本。在使用中关于资源库的使用和6.1版本略有不同,如何创建资源库这里就不赘述了,大家可以参考网上的方法。但是小伙伴反馈资源库每次在kettle关闭重新打开后就不能用了,甚至连右上角那个connect按钮也不见了。聪明的小伙伴已经找到原因了,是由于资源库中包含中文,但是在repositories.xml(系统盘用户目录.kettle目录下)中中文显示的是乱码,解决办法是删除这个文件里的内_webspoon idea 运行没有connect

选择ATEasy的十个原因-程序员宅基地

ATEasy为测试工程师提供了多种好处,包括:1.综合框架Integrated FrameworkATEasy提供了一个简化的,易于遵循的框架,指导用户创建在实际的测试系统之后建模的可重用组件。 组件包括系统,驱动程序,程序,测试,命令(System, Drivers, Programs, Tests, Commands)等等。2.重用性和可扩展性Re-usability and ScalabilityATEasy框架为用户提供了创建可重复使用的软件组件的能力,如仪器驱动程序,系统组件和测试程序。 _ateasy

mysql中CONCAT()嵌套update的用法_mysql update concat-程序员宅基地

先讲讲在工作上的一个用法。生产上,用户晒单后,其中一个表中一个字段没有维护,打算手动用sql把线上一个月的数据维护一下。用到了concat()函数,拼接生成update语句:SELECT CONCAT('update gshop_order_product set isSun=1 where id=',order_product_id,';') FROM gshop_comments W..._mysql update concat

一切都是对象---JAVA编程思想_一切都是对象by-程序员宅基地

一切都是对象“如果我们说另一种不同的语言,那么我们就会发觉一个有些不同的世界。”——Luduing Wittgerstein(1889-1951)尽管Java是基于C++的,但是相比之下,Java是一种更“纯粹”的面向对象设计语言。Java语言假设我们只进行面向对象的程序设计;而C++不同,因为C++是C的一个超集,所以,C++在某些方面显得过于复杂。2.1 用引用操纵对象..._一切都是对象by

空心菱形(易懂) 和 实心菱形的比较记忆_空心菱形什么样-程序员宅基地

#include<stdio.h>int main(){ int n; int i,j,k; scanf("%d",&n);//此时输入的是上半部分的行数 下半部分的行数比下半部分少一行 故下半部分可以表示为n-1 for(i=1;i<=n;i++)//代表每一行的情况 { for(j=1;j<=n-i;j++)//按照行数依次可知前面空格的个数与行数成 n-i 的关系 pri..._空心菱形什么样

推荐文章

热门文章

相关标签