【Part_04】单片机的IO口输入功能:全栈计划

文章目录

  • 前言
  • 独立按键实验
  • 💒项目代码及效果
  • 🌟刨根问底环节
  • 🌻浅识按键
  • 代码设计
  • 矩阵按键实验
  • 💒项目代码及效果
  • 🌟刨根问底环节
  • 🌻浅识矩阵按键
  • 代码设计
  • 浅读代码
  • 总结
  • 前言

    杨枝的单片机专栏,多图多阐述,争取让大家在学习单片机的路上减少些许的痛苦

    虽说是14天,但是我自己快更了半个月了,也才更了一半,我看了一下后面的知识,可以拎出来说的不是特别多啦,我已经在买STM32的板子啦,咱们重心还是得放在32~
    14天了,是对于各位看这个篇文章的小伙伴来说嗷,一天看一篇,轻松上手,不再害怕单片机。
    因为我自己现在呢,抬头是算法题海,低头是作业项目,很多时候只是写得完草稿,没有及时发出来,抱歉哈,原谅我🌹🌹🌹

    独立按键实验

    💒项目代码及效果

    #include "reg52.h"
    
    //使用宏定义来定义每个独立按键按下的键值
    #define KEY1_PRESS 1
    #define KEY2_PRESS 2
    #define KEY3_PRESS 3
    #define KEY4_PRESS 4
    #define KEY_UNPRESS 0
    
    typedef unsigned int u16;
    typedef unsigned char u8;
    
    //根据电路图来定义需要操控的管脚
    sbit KEY1 = P3^1;
    sbit KEY2 = P3^0;
    sbit KEY3 = P3^2;
    sbit KEY4 = P3^3;
    
    //定义咱们要操作的LED1
    sbit LED1 = P2^0;
    
    //编写用于消抖的延时函数
    void delay_10us(u16 ten_us) 
    {
    	while(ten_us--);
    }
    
    
    //检测独立按键是否按下,按下则返回对应键值
    //mode = 0,单次扫描按键,只有当按键松开后才能触发下次的扫描,防止按下一次出现多次触发情况 
    //mode = 1,连续扫描按键 ,会一直返回这个按键的键值,好处是可以很方便实现连按操作 
    u8 key_scan(u8 mode) 
    {
    	static u8 key = 1;
    	
    	//支持连续扫描按键
    	if(mode) key = 1;
    	//常规的检查是否有按键按下
    	if(key && (!KEY1 || !KEY2 || !KEY3 || !KEY4)) 
    	{
    		//通过延时函数消抖
    		delay_10us(1000);
    		//修改键值,在mode = 0的情况
    		key = 0;
    		if(!KEY1) 
    			return KEY1_PRESS;
    		else if(!KEY2)
    			return KEY2_PRESS;
    		else if(!KEY3)
    			return KEY3_PRESS;
    		else if(!KEY4)
    			return KEY4_PRESS;	
    	}else if(KEY1 && KEY2 && KEY3 && KEY4)//这些按键都还是高电平,即没有按键按下
    		key = 1;
    
    	return KEY_UNPRESS; 
    }
    
    int main()
    {
    	//接受按键处理函数返回的结果 
    	u8 key = 0;
    	while(1) 
    	{
    		//此时传入的mode值为0,表示单次扫描按键
    		//然后将扫描按键的值保存在变量key中,最后通过if 判断语句控制LED1状态。 
    		key = key_scan(0);
    		if(key == KEY1_PRESS) //按键1确实是按下了
    			LED1 = !LED1; 
    	}
    	return 0;
    }
    
    

    实验的效果是:当按下K1 键,D1 指示灯亮,再按下K1 键,D1 指示灯灭,如此循环。这个不好放图片,就不放啦~

    🌟刨根问底环节

    🌻浅识按键

    看了之前的文章的小伙伴现在心里可能已经清楚了,想要把代码写出来呢,主要是要会看电路图,这个题也是一样的,主要依赖于电路图,但是基本的硬件知识也是需要积累的。


    上面是51开发板上的按键的基本样子吧,浅有个印象,看到了知道是它就好。

    51单片机的独立按键电路构成是由各个按键的一个管脚来连接在一起接地,按键的其他引脚分别接到单片机的IO口上。

    之前的文章都介绍了IO口输出电流支撑其他设备的输出功能,在按键这儿了,使用的就是它的输入功能啦~

    工作原理阐述:

    按键的一端接地,另一端与单片机的IO口相连,开始时先给该IO口赋一高电平(其实默认就是高电平),然后让单片机不断的检查IO口是否变成了低电平,当按键闭合时,相当于该IO口通过按键与地相连了,即变成了低电平,程序检查到IO口的输入变成低电平则说明按键被按下,就会执行相应的指令。

    下面这个电压信号图比较重要,因为涉及到消抖的事儿
    通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,电压信号如下图所示:

    按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为5ms 到10ms。出现这种抖动肯定是会影响实验的准确度,因此需要进行消抖的操作。
    一、硬件消抖
    硬件消抖是通过在电路中并联一个电容,大小了,电容容量为0.1uf即可。

    消抖原理:因为电容需要一定的时间进行充电和放电,充放时间大于抖动时间,依次实现硬件消抖。
    51开发板为了使电路更加简单,是没有自行配置这种用于消抖的电路的,因此需要咱们进行软件消抖,其实原理也是差不多,将抖动时间忽视就好。
    二、软件消抖
    ① 先设置IO 口为高电平(由于开发板IO 都有上拉电阻,所以默认IO 为高电平)。
    ② 读取IO 口电平确认是否有按键按下。
    ③ 如有IO 电平为低电平后,延时几个毫秒。这里的延时就是在对抖动进行发现以及忽视
    ④ 再读取该IO 电平,如果仍然为低电平,说明按键按下。
    ⑤ 确实不在抖动区间,执行按键控制程序。

    代码设计

    代码要怎么写主要是要看这个电路图

    从这个图中可以看到,4个独立按键的控制管脚分别链接在51单片机的P31、P30、P32、P33上,另一端都是连接在GND上。至于咱们想作为LED1这个灯了,大家是否还记得它是通过P20端口来控制的了。

    咱们演示的时候是通过LED1进行演示的,倘若想通过按键来控制其他LED灯的点亮或者熄灭,只需要修改相应的按键按下之后的执行逻辑。

    矩阵按键实验

    💒项目代码及效果

    #include "reg52.h"
    typedef unsigned int u16; //对系统默认数据类型进行重定义
    typedef unsigned char u8;
    #define KEY_MATRIX_PORT P1 //使用宏定义矩阵按键控制口
    #define SMG_A_DP_PORT P0  //使用宏定义数码管段码口
    
    //共阴极数码管显示0~F 的段码数据
    u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
    0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
    
    /********************************************************************
    ***********
    * 函数名: delay_10us
    * 函数功能: 延时函数,ten_us=1 时,大约延时10us
    * 输入: ten_us
    * 输出: 无
    *********************************************************************
    **********/
    void delay_10us(u16 ten_us)
    {
    	while(ten_us--);
    }
    
    /********************************************************************
    ***********
    * 函数名: key_matrix_ranks_scan
    * 函数功能: 使用行列式扫描方法,检测矩阵按键是否按下,按下则返回对应键值
    * 输入: 无
    * 输出: key_value:1-16,对应S1-S16 键,
    *					0:按键未按下
    *********************************************************************
    **********/
    
    u8 key_matrix_ranks_scan(void)
    {
    	u8 key_value=0;
    
    	KEY_MATRIX_PORT=0xf7;//给第一列赋值0,其余全为1
    	if(KEY_MATRIX_PORT!=0xf7)//判断第一列按键是否按下
    	{
    		delay_10us(1000);//消抖
    		switch(KEY_MATRIX_PORT)//保存第一列按键按下后的键值
    		{
    			case 0x77: key_value=1;break;
    			case 0xb7: key_value=5;break;
    			case 0xd7: key_value=9;break;
    			case 0xe7: key_value=13;break;
    		}
    	}while(KEY_MATRIX_PORT != 0xf7);//等待按键松开
    
    	KEY_MATRIX_PORT=0xfb;//给第二列赋值0,其余全为1
    	if(KEY_MATRIX_PORT!=0xfb)//判断第二列按键是否按下
    	{
    		delay_10us(1000);//消抖
    		switch(KEY_MATRIX_PORT)//保存第二列按键按下后的键值
    		{
    			case 0x7b: key_value=2;break;
    			case 0xbb: key_value=6;break;
    			case 0xdb: key_value=10;break;
    			case 0xeb: key_value=14;break;
    		}
    	}while(KEY_MATRIX_PORT!=0xfb);//等待按键松开
    
    	KEY_MATRIX_PORT=0xfd;//给第三列赋值0,其余全为1
    	if(KEY_MATRIX_PORT! = 0xfd)//判断第三列按键是否按下
    	{
    		delay_10us(1000);//消抖
    		switch(KEY_MATRIX_PORT)//保存第三列按键按下后的键值
    		{
    			case 0x7d: key_value=3;break;
    			case 0xbd: key_value=7;break;
    			case 0xdd: key_value=11;break;
    			case 0xed: key_value=15;break;
    		}
    	}while(KEY_MATRIX_PORT!=0xfd);//等待按键松开
    
    	KEY_MATRIX_PORT = 0xfe;//给第四列赋值0,其余全为1
    	if(KEY_MATRIX_PORT! = 0xfe)//判断第四列按键是否按下
    	{
    		delay_10us(1000);//消抖
    		switch(KEY_MATRIX_PORT)//保存第四列按键按下后的键值
    		{
    			case 0x7e: key_value=4;break;
    			case 0xbe: key_value=8;break;
    			case 0xde: key_value=12;break;
    			case 0xee: key_value=16;break;
    		}
    	}while(KEY_MATRIX_PORT != 0xfe);//等待按键松开
    
    	return key_value;
    }
    //这个可爱的软件会对没用的模块进行警告...,先注释线翻转法
    /********************************************************************
    ***********
    * 函数名: key_matrix_flip_scan
    * 函数功能: 使用线翻转扫描方法,检测矩阵按键是否按下,按下则返回对应键值
    * 输入: 无
    * 输出: key_value:1-16,对应S1-S16 键,
    *					0:按键未按下
    *********************************************************************
    **********/
    /*
    u8 key_matrix_flip_scan(void)
    {
    	static u8 key_value=0;
    
    	KEY_MATRIX_PORT = 0x0f;//给所有行赋值0,列全为1
    	if(KEY_MATRIX_PORT != 0x0f)//判断按键是否按下
    	{
    		delay_10us(1000);//消抖
    		if(KEY_MATRIX_PORT!=0x0f)
    		{
    			//测试列
    			KEY_MATRIX_PORT = 0x0f;
    			switch(KEY_MATRIX_PORT)//保存行为0,按键按下后的列值
    			{
    				case 0x07: key_value=1;break;
    				case 0x0b: key_value=2;break;
    				case 0x0d: key_value=3;break;
    				case 0x0e: key_value=4;break;
    			}
    		
    			//测试行
    			KEY_MATRIX_PORT=0xf0;
    
    			switch(KEY_MATRIX_PORT)//保存列为0,按键按下后的键值
    			{
    				case 0x70: key_value=key_value;break;
    				case 0xb0: key_value=key_value+4;break;
    				case 0xd0: key_value=key_value+8;break;
    				case 0xe0: key_value=key_value+12;break;
    			}
    	
    			while(KEY_MATRIX_PORT!=0xf0);//等待按键松开
    		}
    	}else 
    		key_value=0;
    
    	return key_value;
    }
    */
    /********************************************************************
    ***********
    * 函数名: main
    * 函数功能: 主函数
    * 输入: 无
    * 输出: 无
    *********************************************************************
    **********/
    void main()
    {
    	u8 key=0;
    	while(1)
    	{
    		key = key_matrix_ranks_scan();
    		if(key!=0)
    		//得到的按键值减1 换算成数组下标段码
    		SMG_A_DP_PORT=gsmg_code[key-1];
    	}
    }
    

    实验效果了,当按下S1-S16 键,最左边数码管对应显示0 - F

    🌟刨根问底环节

    矩阵按键这块,我感觉有点小有趣

    🌻浅识矩阵按键

    独立按键与单片机连接时,每一个按键都需要单片机的一个I/O 口,若某单片机系统需较多按键,如果用独立按键便会占用过多的I/O 口资源。因为单片机系统中I/O口资源往往比较稀缺,当用到多个按键时为了减少I/O 口引脚,因此就引入了矩阵按键。

    51单片机上的矩阵按键以4*4的16 个按键排成4 行4 列,
    ·第一行将每个按键的一端连接在一起构成行线;
    第一列将每个按键的另一端连接在一起构成列线

    这样便一共有4 行4 列共8 根线, 我们将这8根线连接到单片机的8 个I/O 口上,通过程序扫描键盘就可检测16 个键。
    用这种方法我们也可实现3 行3 列9 个键、5 行5 列25个键、6 行6 列36 个键甚至更多。

    换汤不换药吧,无论是独立按键,还是矩阵按键,单片机检测其是否被按下的依据都是一样的,都是去检测某个按键对应的IO口是否为低电平。
    独立按键了,有一段固定是低电平,整体实现还好蛮简单的。矩阵按键两端都和单片机的IO口相连了,因此需要在检测时需要编程通过单片机IO口送出低电平。检测的方式中常用的是行列扫描法线翻转法

    只用看看下面关于这两个方法的文字描述就好,看看就好,没有必要急着去理解,因为文字描述挺抽象的,我待会结合着那个巧妙的代码带着大家理解

    ① 行列扫描法
    先送一列为低电平,其余几列全部是高电平(通过这种方式来确定列数),然后立即轮流检测一次各行是否有低电平,若检测到某行为低电平(这时我们又确定行数),则我们便可确认当前被按下的键是哪行哪列的,用同样的方法轮流送各列一次低电平,再轮流检测一次各行是否变成低电平,这样即可检测完所有的按键,当有按键被按下时便可以 判断出安喜爱的键是哪一个键。这个没有定论,也可以先将行先设置为低电平,扫描列是否有低电平。
    ② 线翻转法
    线翻转法是使所有行线变成低电平,检测所有列线是否有低电平,如果有,就记录列线值;然后再翻转,使所有的列线变成低电平,检测所有行线的值,由于按键会有按键按下,行线的值也会有变化,记录行线的值,从而可以检测到所有的按键。

    代码设计

    想要把单片机的代码写出来,还是需要看这个可爱的路图

    从上图中可以看出:
    4*4 矩阵按键引出的8 根控制线直接连接到51 单片机的P1 口上。
    电路中的P17 连接矩阵键盘的第1 行,P13 连接矩阵键盘第1 列。

    浅读代码

    因为矩阵按键实验的代码量有点大了,假如直接让大家去接触的话,可能有点打脑壳,就我带着大家理解其中的逻辑吧~

    读代码都是从主函数开始读的。
    主函数中,变量的声明,以及最后根据行列式扫描函数返回的结果来显示数码管就不用赘述了吧,因为是之前的知识点啦~

    重点是咱们结合着代码来理解行列式扫描法函数吧

    这个代码很长,看起来很吓人,但其实是一个思想循环使用了几次而已。

    行列扫描法是这种阐述的:
    先送一列为低电平,其余几列全部是高电平(通过这种方式来确定列数),
    然后立即轮流检测一次各行是否有低电平,若检测到某行为低电平(这时我们又确定行数)

    落实到代码上,就是这种玩的啦:
    让P1端口的八个串口变成描述中的高低电平的状态(0低1高),有点蒙吗,那咱们来再具体一点

    想按照注释中写的,让第一列为0,其他全部为1,

    电路图中可以看到,P13、P12、P11、P10控制的是列,那么咱们操作的时候,也按照电路图进行操作。
    让某一列为低电平已经实现了。

    老规矩,该消抖的还是要消抖,然后在当前列的情况下,去检测是否有某一行被摁了。也是同样的逻辑喔


    其他15个数字的显示也是类似的逻辑,反复造轮子啦,那我就不在赘述喽~。
    然后线翻转法也是类似的理解,友友们可以试着独立去模拟一下,假如有不清楚的小伙伴可以私信我嗷

    总结

    对于IO口作为输入的这个板块的知识点了,不能说不重要,其思想也就是将高低电平通过其他外界设备(按键)来体现出来了。
    我观念里,本篇的重点了:
    ① 是知道IO口确实可以输出电流支撑其他外界设备,也可以通过输入的方式来帮助其他设备的运转。
    ② 可以很明显的看到,按键这块知识点的的代码量上去了,对IO口的操作也不再是简单的一个或者三个了,假如反复练习,是加深对IO口使用以及理解的最好途径了。

    ③ 积累一些消除误差的小知识,比如这块的消抖

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Part_04】单片机的IO口输入功能:全栈计划

    发表评论