学习到这里总觉得要写些什么东西,或者要做些什么东西出来,但是发现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矩阵键盘密码锁
所有IO口通电后默认是高电平,单片机的IO口输出是弱上拉类型的,输出低电平能接收很大的电流,输出高电平电流比较小。(“弱1”“强0”输出由于上拉电阻的存在,输出高电平达不到“VCC”所以输出1时候就“弱”,而输出低电平等于GND,是“强0”)
)
在这里说一下弱上拉和强下拉,弱上拉,强下拉的区别(出处:http://bbs.eeworld.com.cn/thread-1070283-1-1.html)根据电阻的阻值区分
“强”输出
“强1”指的是输出电平的高电平等于或接近于VCC,“强0”指的是输出的低电平等于或接近于GND。如下图。可以很直观的看出什么是“强”和“弱”。
“弱0”、“强1”输出
很明显,这是下拉电阻的接法,当输出低电平时,由于下拉电阻的存在,低电平是高于GND的,而当输出高电平时,输出电平可以达到VCC。
4、“弱1”“强0”输出
由于上拉电阻的存在,输出高电平达不到“VCC”所以输出1时候就“弱”,而输出低电平等于GND,是“强0”
插入方式我们发现单片机上有一个凹口,单片机的卡槽中有一个开关是对应的 ,凹槽对应开关 ,如果插错了会导致烧毁的。
LED原理图(根据板子资料而定)
在这里需要注意一下:REGX52.H 和REG52H的区别:其中是否内置了sbit和sfr函数
#include
void main()
{
P2_0=0;
while(1);
//且使用小端书写1111 1110使用二进制表示容易被当成十进制,所以使用是用十六进制表示
}
这里注意 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();
}
}
除此之外对于流水的的展示还可以通过左右移位结合取反的方式实现,此外还可以通过取反实现流水灯的二进制显示。
#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);
}
}
大家可以自行查询一下寄存器的可位寻址与不可位寻址(是否可以对于单独的某一位进行操作)
这里需要注意的是K1,K2按键连接的IO端口,在这里大家可以将IO口定义为符合程序的代号
可以有效地避免后期端口冲突事件的发生。
#include
void main()
{
// P2=0xef;
//在对于单片机的编程中要注意的一点就是模块连接的是哪一端(vcc/end)
//如果连接的是vcc那一端(0是低电平,1是高电平)gnd那一端的话只能接负
//正接正,负接负,
while(1)
{
if(P3_1==0)
{
P2_0=0;
}
else
{
P2_0=1;
}
}
}
#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); 这两个语句呢。
首先了解一下按键的抖动
对于机械开关,当机械触电断开、闭合时,由于机械触电的弹性作用,一个开关闭合时不会马上稳定地接通,在断开时也不会一下子就断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动
肉眼可能无法观察,比如日常生活中的开关灯,但单片机执行速率是MHZ级别的,会有明显影响,如何消除这个影响呢?
//#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位 ,再取反。
首先我们知道每个数码管都有 abcdefg dp 八个段。
如果要显示数字6,那么就需要把AFGEDC点亮:
对应共阴极接法图中,3,8接地,然后A~DP输入10111110(也叫段码),把这八个数据给单片机的IO口上,即可显示"6"
对应共阳极接法图中,3,8接Vcc,然后A~DP输入01000001(与共阴极相反)
8个数码管那我们按理来说是需要8*7个引脚,很浪费。需要7连接7个IO口,对于单片机而言太浪费空间,因此引入74HC138译码器,74译码器使用3位 bit 输入表示8种状态,调整 LED1~8 哪一个输出低电平,代表要启动8个数码管的哪一个的公共端。
四位一体数码管(四个大公共端引出,其余字母相同的管子连在一块,如所有A连一块,所有B连一块):
注意数码管的原理图,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)
{
}
}
动态数码管就是不断的扫描,这是一个循环过程,可以将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);
}
}
传统方式编程:所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路
模块化编程:把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等
之前是吧Delay函数放在main函数前面,现在将Delay函数模块化。Delay.c给主函数并不需要把所有的东西都包含进去,只需要把声明包含进去。所以在.h文件中都是提供一个接口
预编译(程序的运行过程:预处理(预编译)-编译-汇编-链接)这里可以了解一下(#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)
此外还有#ifdef,#if,#else,#elif,#undef等
其实#ifndef
等语句是对程序的某些部分是否编译进行选择
这个方面观看江科大UP主的视频会更加清晰。
下面是代码展示(江科大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
对于本节课无法切换到函数所定义的位置的方法,是将程序退出文件
对数码管来说,在同一时间不能同时控制多位数码管显示不同数字,但可以利用扫描解决。
为了减少 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口。
主要有两种扫描方法
行列式扫描法:每次给某一列赋值为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;
}
对于机械开关,当机械触电断开、闭合时,由于机械触电的弹性作用,一个开关闭合时不会马上稳定地接通,在断开时也不会一下子就断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动
肉眼可能无法观察,比如日常生活中的开关灯,但单片机执行速率是MHZ级别的,会有明显影响,如何消除这个影响呢?
在本次示例中,不难发现我们又对按键进行了消抖,主要是对其进行模块化操作。
在进行试验之前要将,矩阵键盘模块化,这里就不给出示例,
首先我们要定义按键功能: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的目的是为了获取一位密码。