51单片机的疑问与见解(江科大版)

学习到这里总觉得要写些什么东西,或者要做些什么东西出来,但是发现51单片机老师教程(江科大版)讲的太详细了视频来源-bilibili,将比较复杂的东西都模块化了,我所能做的就是课下自己练习,以及分享一下,目前自己所学到的一些东西。(如有不对,请大家指正)

目录

1.LED灯管点亮()

课堂先知:

课外知识:

1.1LED点亮

1.2LED闪烁 

 1.3LED流水灯

2.独立按键 

课堂先知:

2.1独立按键控制LED状态

2.2独立按键实现LED二进制闪烁

2.3独立按键实现LED移位闪烁

3.数码管

课堂先知:     数码管连接方式,由下图可知,

 3.1静态数码管显示

3.2动态数码管

4.模块化编程

课堂先知:

5.LCD1602功能模块

 6.矩阵键盘

6.1原理图

6.2矩阵键盘密码锁


1.LED灯管点亮()

课堂先知:

所有IO口通电后默认是高电平,单片机的IO口输出是弱上拉类型的,输出低电平能接收很大的电流,输出高电平电流比较小。(“弱1”“强0”输出由于上拉电阻的存在,输出高电平达不到“VCC”所以输出1时候就“弱”,而输出低电平等于GND,是“强0”)

课外知识:

在这里说一下弱上拉和强下拉,弱上拉,强下拉的区别(出处:http://bbs.eeworld.com.cn/thread-1070283-1-1.html)根据电阻的阻值区分

“强”输出
“强1”指的是输出电平的高电平等于或接近于VCC,“强0”指的是输出的低电平等于或接近于GND。如下图。可以很直观的看出什么是“强”和“弱”。

51单片机的疑问与见解(江科大版)_第1张图片

“弱0”、“强1”输出
很明显,这是下拉电阻的接法,当输出低电平时,由于下拉电阻的存在,低电平是高于GND的,而当输出高电平时,输出电平可以达到VCC。

51单片机的疑问与见解(江科大版)_第2张图片


4、“弱1”“强0”输出
由于上拉电阻的存在,输出高电平达不到“VCC”所以输出1时候就“弱”,而输出低电平等于GND,是“强0”
 

51单片机的疑问与见解(江科大版)_第3张图片

单片机的插入方式

51单片机的疑问与见解(江科大版)_第4张图片        51单片机的疑问与见解(江科大版)_第5张图片         51单片机的疑问与见解(江科大版)_第6张图片               

插入方式我们发现单片机上有一个凹口,单片机的卡槽中有一个开关是对应的 ,凹槽对应开关 ,如果插错了会导致烧毁的。

LED原理图(根据板子资料而定) 

 51单片机的疑问与见解(江科大版)_第7张图片

1.1LED点亮

 在这里需要注意一下:REGX52.H 和REG52H的区别:其中是否内置了sbit和sfr函数

#include 
void main()
{
		P2_0=0;
    while(1);
	//且使用小端书写1111 1110使用二进制表示容易被当成十进制,所以使用是用十六进制表示
}

1.2LED闪烁 

这里注意 void Delay500ms()        //@12.000MHz 这个函数延时频率要与自己的开发板一致,以及_nop_();这个语句,添加之后需要 #include 头函数

#include
#include 
void Delay500ms()		//@12.000MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 205;
	k = 187;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main()
{
	while(1)
	{
		P2=0xfe;//1111 1110
		Delay500ms();
		P2=0x7f;
		Delay500ms();		
	}		
}

 1.3LED流水灯

除此之外对于流水的的展示还可以通过左右移位结合取反的方式实现,此外还可以通过取反实现流水灯的二进制显示。

#include
#include 
void Delay500ms()		//@12.000MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 205;
	k = 187;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main()
{
	while(1)
	{
		P2=0xfe;//1111 1110
		Delay500ms();
		P2=0xfd;//1111 1101
		Delay500ms();
		P2=0xfb;//1111 1011
		Delay500ms();
		P2=0xf7;//1111 0111
		Delay500ms();
		P2=0xef;//1110 1111
		Delay500ms();
		P2=0xdf;//1101 1111
		Delay500ms();
		P2=0xbf;//1011 1111
		Delay500ms();
		P2=0x7f;//0111 1111
		Delay500ms();
		
		
	}
}

 在这里的代码是对于延迟函数的自定义,可以重复使用

#include 
void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;

	while(xms)
	{
		i = 11;
		j = 190;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}

void main()
{
		while(1)
	{
		P2=0xfe;//1111 1110
		Delay1ms(500);
		P2=0xfd;//1111 1101
		Delay1ms(500);
		P2=0xfb;//1111 1011
		Delay1ms(500);
		P2=0xf7;//1111 0111
		Delay1ms(500);
		P2=0xef;//1110 1111
		Delay1ms(500);
		P2=0xdf;//1101 1111
		Delay1ms(500);
		P2=0xbf;//1011 1111
		Delay1ms(100);
		P2=0x7f;//0111 1111
		Delay1ms(100);	
	}
}

2.独立按键 

课堂先知:

  1. 单片机通电时所有IO口都是高电平
  2. 寄存器写一个值会送到IO口上,同样会检测IO口的电平读回来按键松开,读寄存器,值为1;
  3. 按下时读寄存器为0

大家可以自行查询一下寄存器的可位寻址与不可位寻址(是否可以对于单独的某一位进行操作)

51单片机的疑问与见解(江科大版)_第8张图片

这里需要注意的是K1,K2按键连接的IO端口,在这里大家可以将IO口定义为符合程序的代号

可以有效地避免后期端口冲突事件的发生。

2.1独立按键控制LED状态

#include 
void main()
{
//	P2=0xef;
//在对于单片机的编程中要注意的一点就是模块连接的是哪一端(vcc/end)
//如果连接的是vcc那一端(0是低电平,1是高电平)gnd那一端的话只能接负
//正接正,负接负,
	while(1)
	{
		if(P3_1==0)
		{
			P2_0=0;
		}
		else
		{
				P2_0=1;
		}
	}
	
}

2.2独立按键实现LED二进制闪烁

#include 
unsigned char LEDNum=0;//使用这个类型的原因是因为unsigned char的表示范围0~255
void Delay(unsigned int xms)//@11.0592MHz
{
	while(xms--)
	{
	unsigned char i, j;
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
  }
}

void main()
{
	while(1)
	{
		if(P3_1==0)
		{
			Delay(15);//消除按下时的抖动
			while(P3_1==0);//判断是否松开
			Delay(15);//如果松开消除松开时的抖动
			/*P2--;这是第一种方法,当8位二进制数减为0的时候,会借位重新变为1111 1111  不建议对P2端口直接进行P2++,上电之后P2=1111 1111,当再相加就会溢出,变为0000 0000,取反之后又会变为1111 1111,会进入死循环。*/
			LEDNum++;//因此使用LEDNum,他能进行是因为会自动转换为十六进制在这里可以进行对于LEDNum的越界判断,当LEDNum>16置0
			P2=~LEDNum;
		}
	}		
}

在这里大家可以思考一下为什么要使用   while(P3_1==0);   Delay(15); 这两个语句呢。

首先了解一下按键的抖动

对于机械开关,当机械触电断开、闭合时,由于机械触电的弹性作用,一个开关闭合时不会马上稳定地接通,在断开时也不会一下子就断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动

51单片机的疑问与见解(江科大版)_第9张图片

肉眼可能无法观察,比如日常生活中的开关灯,但单片机执行速率是MHZ级别的,会有明显影响,如何消除这个影响呢?

  1. 硬件消抖:通过电路过滤掉
  2. 软件处理:按键按下时延时20ms,松手时也延时20ms

2.3独立按键实现LED移位闪烁

//#include 
//unsigned char LEDNum=0x01;
//unsigned char start=1;
//void Delay1ms(xms)		//@12.000MHz
//{
//	unsigned char i, j;
//	while(xms)
//	{
//		i = 2;
//		j = 239;
//		do
//		{
//			while (--j);
//		} while (--i);
//		xms--;
//	}
//}

//void main()
//{

//	while(1)
//	{
//		if(P3_1==0)
//		{
//			Delay1ms(20);
//			while(P3_1==0);
//			Delay1ms(20);
//			
//      if(start==0) 
//			{
//				if(LEDNum==0x01)  LEDNum=0x80;
//				else LEDNum<<=1;
//				P2=~LEDNum;	
//			}
//			
//			if(start==1) 
//			{
//				P2=~LEDNum; start=0;
//			}
//		}
//		
//		if(P3_0==0)
//		{
//			Delay1ms(20);
//			while(P3_0==0);
//			Delay1ms(20);
//			if(start==0) 
//			{				
//			if(LEDNum==0x80)  LEDNum=0x01;
//			else LEDNum>>=1;
//			P2=~LEDNum;		
//			}
//			if(start==1) 
//			{
//				P2=~LEDNum; start=0;
//			}

//			
//		}
//		
//	}
//}




#include 
void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
while(xms--)
{	
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}
}
unsigned int num;
void main()
{
	P2=~(0x80);
while(1)
{
		if(P3_1==0)
		{
			Delay1ms(20);
			while(P3_1==0);
			Delay1ms(20);
			num++;
			if(num>=8)
				num=0;
			P2=~(0X80>>num);	
		}
		if(P3_0==0)
		{
			Delay1ms(20);
			while(P3_0==0);
			Delay1ms(20);
			if(num==0)
				num=7;
			else
				num--;
			P2=~(0X80>>num);	
		}
	}
}

在这里我给出了两种写法:一种是使用一个变量去控制去实现移位(这种方法是后期江科大UP主常用的一种实现多个功能的写法,推荐理解),先定义start=1,LEDNum=0x01,无论时K1是第一次按下,还是K2第一次按下,都是先让D1亮,反转start=0,接下来在判断K1按下还是K2按下,LEDNum=0x01,当K1按下时将LEDNum重新赋值为LEDNum=0x80,在取反。K2按下就照常右移。当LEDNum=0x80的时候,K1正常左移,取反。 对于K2则需要将LEDNum重新赋值为LEDNum=0x01;(对于这个程序不是所有板子通用,因为我的LED从左向右的顺序为D1~D8)

一种是UP课上讲解的(非常巧妙)利用一个全局变量,实现左右移位,当进行右移3位时,不就是将0x80右移三位吗,在取反吗,此时的num是不是为3。

而此时进行左移一位不就变成了将0x80右移2位 ,再取反。

3.数码管

课堂先知:
     数码管连接方式,由下图可知,

首先我们知道每个数码管都有 abcdefg dp 八个段。

如果要显示数字6,那么就需要把AFGEDC点亮:

对应共阴极接法图中,3,8接地,然后A~DP输入10111110(也叫段码),把这八个数据给单片机的IO口上,即可显示"6"

对应共阳极接法图中,3,8接Vcc,然后A~DP输入01000001(与共阴极相反)

51单片机的疑问与见解(江科大版)_第10张图片

8个数码管那我们按理来说是需要8*7个引脚,很浪费。需要7连接7个IO口,对于单片机而言太浪费空间,因此引入74HC138译码器,74译码器使用3位 bit 输入表示8种状态,调整 LED1~8 哪一个输出低电平,代表要启动8个数码管的哪一个的公共端。51单片机的疑问与见解(江科大版)_第11张图片

 四位一体数码管(四个大公共端引出,其余字母相同的管子连在一块,如所有A连一块,所有B连一块):51单片机的疑问与见解(江科大版)_第12张图片

 3.1静态数码管显示

注意数码管的原理图,LED的顺序依次为LED8~LED1

#include 

//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

//数码管显示子函数
void Nixie(unsigned char Location,Number)
{
	switch(Location)		//位码输出
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number];	//段码输出
}

void main()
{
	Nixie(2,3);	//在数码管的第2位置显示3
	while(1)
	{
		
	}
}

3.2动态数码管

动态数码管就是不断的扫描,这是一个循环过程,可以将Nixie()函数多放几段,会发现数据会发生紊乱,这是什么原因呢?(这是数码管本身存在的问题)

对于动态数码管的显示过程应该是 位码 段码 位码 段码 位码 段码 。。。

但是在此过程中可能会发生数据读取错误,就是上一位的段码被下一阶段的显示过程读取为位码(数据相隔非常近),我们可以在,读取一次之后,加入延时,让数据显示一段时间,使下一段数据无法读取上一段的数据,这个过程叫做消影

#include 

//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

//延时子函数
void Delay(unsigned int xms)
{
    unsigned char i, j;
    while(xms--)
    {
        i = 2;
        j = 239;
        do
        {
            while (--j);
        } while (--i);
    }
}

//数码管显示子函数
void Nixie(unsigned char Location,Number)
{
    switch(Location)        //位码输出
    {
        case 1:P2_4=1;P2_3=1;P2_2=1;break;
        case 2:P2_4=1;P2_3=1;P2_2=0;break;
        case 3:P2_4=1;P2_3=0;P2_2=1;break;
        case 4:P2_4=1;P2_3=0;P2_2=0;break;
        case 5:P2_4=0;P2_3=1;P2_2=1;break;
        case 6:P2_4=0;P2_3=1;P2_2=0;break;
        case 7:P2_4=0;P2_3=0;P2_2=1;break;
        case 8:P2_4=0;P2_3=0;P2_2=0;break;
    }
    P0=NixieTable[Number];    //段码输出
    Delay(1);                //显示一段时间
    P0=0x00;                //段码清0,消影
}

void main()
{
    while(1)
    {
        Nixie(1,1);        //在数码管的第1位置显示1
        Delay(20);
        Nixie(2,2);        //在数码管的第2位置显示2
        Delay(20);
        Nixie(3,3);        //在数码管的第3位置显示3
        Delay(20);
    }
}

4.模块化编程

课堂先知:

  1. 传统方式编程:所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路

  2. 模块化编程:把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等

51单片机的疑问与见解(江科大版)_第13张图片

之前是吧Delay函数放在main函数前面,现在将Delay函数模块化。Delay.c给主函数并不需要把所有的东西都包含进去,只需要把声明包含进去。所以在.h文件中都是提供一个接口 

51单片机的疑问与见解(江科大版)_第14张图片

预编译(程序的运行过程:预处理(预编译)-编译-汇编-链接)这里可以了解一下(#define 和 const 定义常量的区别https://blog.csdn.net/ZhaDeNianQu/article/details/120195045?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168397717316782425145415%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168397717316782425145415&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-4-120195045-null-null.142^v87^control_2,239^v2^insert_chatgpt&utm_term=const%E5%92%8Cdefine%E7%9A%84%E5%8C%BA%E5%88%AB&spm=1018.2226.3001.4187)

51单片机的疑问与见解(江科大版)_第15张图片

  1. 此外还有#ifdef,#if,#else,#elif,#undef等

  2. 其实#ifndef等语句是对程序的某些部分是否编译进行选择

这个方面观看江科大UP主的视频会更加清晰。

5.LCD1602功能模块

51单片机的疑问与见解(江科大版)_第16张图片

下面是代码展示(江科大UP资源包) 

#include 

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

这部分的代码示例就不再演示,大家可以自行去实验这些功能模块,后期的使用频率非常高。有兴趣可以自己编写LCD功能模块。  原理图如下:(开发板芯片数据手册:https://pan.baidu.com/pfile/docview?path=%2F%E6%88%91%E7%9A%84%E8%B5%84%E6%BA%90%2F%E6%9D%BF%E5%AD%90%E8%B5%84%E6%96%99%2F6--%E8%8A%AF%E7%89%87%E8%B5%84%E6%96%99%2F%E5%BC%80%E5%8F%91%E6%9D%BF%E8%8A%AF%E7%89%87%E6%95%B0%E6%8D%AE%E6%89%8B%E5%86%8C%2FLCD1602%E6%B6%B2%E6%99%B6%E5%AE%8C%E6%95%B4%E4%B8%AD%E6%96%87%E8%B5%84%E6%96%99.pdf&client=web&scene=main

 对于本节课无法切换到函数所定义的位置的方法,是将程序退出文件

51单片机的疑问与见解(江科大版)_第17张图片

 6.矩阵键盘

6.1原理图

51单片机的疑问与见解(江科大版)_第18张图片

51单片机的疑问与见解(江科大版)_第19张图片

对数码管来说,在同一时间不能同时控制多位数码管显示不同数字,但可以利用扫描解决。

为了减少 IO 口的占用,用4个 IO 口代表行,4个 IO 口代表列。

类似动态数码管快速扫描实现几乎同时点亮的效果,矩阵键盘也是快速扫描

矩阵连接的越多,节省I/O口越明显。比如1080P的比例为1920*1080=2073600,显示屏需要2073600个像素点才能显示1080P的视频,且因为RGB通道,还需要乘3,共需6220800个LED。单独判断需要600多万个I/O口,但是如果连接成矩阵形式,只需要1920+1080=3000,再乘3为9000个,大幅减少了I/O口。

51单片机的疑问与见解(江科大版)_第20张图片

主要有两种扫描方法

行列式扫描法:每次给某一列赋值为0,然后检测这一列有无按钮按下。

​ 按行扫描:通过设置 P17 16 15 14 中的一个为低电平来选择扫描哪一行。根据 P10 P11 P12 P13 的输入判断是哪一列。但是 P15 口是蜂鸣器,不断反转会响。所以最好还是用按列扫描注意查看自己的板子蜂鸣器使用的是哪一个端口 (本次的代码示例是行扫描)

线翻转扫描方法:给所有列赋1,给所有行赋0,先判断在哪一行;然后用同样的方法判断在哪一列。

这里有一点问题就是:本单片机是准双向口输出,每个口既能做输入也能做输出而不用重新配置口线输出状态。其实这样相当于单片机一个引脚输出高电平,直接与另一个为低电平的引脚相连接。不会短路吗?

#include 
#include "Delay.h"
/**
  * @brief  矩阵键盘读取按键键码
  * @param  无
  * @retval KeyNumber 按下按键的键码值
			如果按键按下不放,程序会停留在此函数,松手的一瞬间返回按键键码,没有按键按下时返回零
  */
unsigned char MatrixKey()
{
	unsigned char Keynum=0;
	P1=0xff;
	P1_7=0;
	if(P1_3==0){Delay(15);while(P1_3==0);Delay(15);Keynum=1;}
	if(P1_2==0){Delay(15);while(P1_2==0);Delay(15);Keynum=2;}
	if(P1_1==0){Delay(15);while(P1_1==0);Delay(15);Keynum=3;}
	if(P1_0==0){Delay(15);while(P1_0==0);Delay(15);Keynum=4;}
	
	P1=0xff;
	P1_6=0;
	if(P1_3==0){Delay(15);while(P1_3==0);Delay(15);Keynum=5;}
	if(P1_2==0){Delay(15);while(P1_2==0);Delay(15);Keynum=6;}
	if(P1_1==0){Delay(15);while(P1_1==0);Delay(15);Keynum=7;}
	if(P1_0==0){Delay(15);while(P1_0==0);Delay(15);Keynum=8;}	
	
	
	P1=0xff;
	P1_5=0;
	if(P1_3==0){Delay(15);while(P1_3==0);Delay(15);Keynum=9;}
	if(P1_2==0){Delay(15);while(P1_2==0);Delay(15);Keynum=10;}
	if(P1_1==0){Delay(15);while(P1_1==0);Delay(15);Keynum=11;}
	if(P1_0==0){Delay(15);while(P1_0==0);Delay(15);Keynum=12;}
	
	P1=0xff;
	P1_4=0;
	if(P1_3==0){Delay(15);while(P1_3==0);Delay(15);Keynum=13;}
	if(P1_2==0){Delay(15);while(P1_2==0);Delay(15);Keynum=14;}
	if(P1_1==0){Delay(15);while(P1_1==0);Delay(15);Keynum=15;}
	if(P1_0==0){Delay(15);while(P1_0==0);Delay(15);Keynum=16;}
	
	return Keynum;
}

对于机械开关,当机械触电断开、闭合时,由于机械触电的弹性作用,一个开关闭合时不会马上稳定地接通,在断开时也不会一下子就断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动

51单片机的疑问与见解(江科大版)_第21张图片

肉眼可能无法观察,比如日常生活中的开关灯,但单片机执行速率是MHZ级别的,会有明显影响,如何消除这个影响呢?

  1. 硬件消抖:通过电路过滤掉
  2. 软件处理:按键按下时延时20ms,松手时也延时20ms

 在本次示例中,不难发现我们又对按键进行了消抖,主要是对其进行模块化操作。

6.2矩阵键盘密码锁

在进行试验之前要将,矩阵键盘模块化,这里就不给出示例,

首先我们要定义按键功能:S1-S9定义为数字的1-9,S10定义为0,S11为确认键,S12为取消键,S13-S16按键不用,定义一个Count变量来记录输入的次数。

#include 
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNum;
unsigned int Password,Count;

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Password:");
	while(1)
	{
		KeyNum=MatrixKey();
		if(KeyNum)
		{
			if(KeyNum<=10)	//如果S1~S10按键按下,输入密码
			{
				if(Count<4)	//如果输入次数小于4
				{
					Password*=10;				//密码左移一位
					Password+=KeyNum%10;		//获取一位密码
					Count++;	//计次加一
				}
				LCD_ShowNum(2,1,Password,4);	//更新显示
			}
			if(KeyNum==11)	//如果S11按键按下,确认
			{
				if(Password==2345)	//如果密码等于正确密码
				{
					LCD_ShowString(1,14,"OK ");	//显示OK
					Password=0;		//密码清零
					Count=0;		//计次清零
					LCD_ShowNum(2,1,Password,4);	//更新显示
				}
				else				//否则
				{
					LCD_ShowString(1,14,"ERR");	//显示ERR
					Password=0;		//密码清零
					Count=0;		//计次清零
					LCD_ShowNum(2,1,Password,4);	//更新显示
				}
			}
			if(KeyNum==12)	//如果S12按键按下,取消
			{
				Password=0;		//密码清零
				Count=0;		//计次清零
				LCD_ShowNum(2,1,Password,4);	//更新显示
			}
		}
	}
}

这次矩阵键盘的密码设置为4位,0000,而对于密码的输入就是一个问题,输入的密码应该是从右向左 ,在这里我们将得到的Passwore*=10,因为Password是全局变量且未赋初值,默认值为0;

而对于KeyNum%10的目的是为了获取一位密码。

你可能感兴趣的:(51单片机,嵌入式硬件,单片机)