目录
一、矩阵键盘
1、矩阵按键原理
1.1工作方式
1.2工作原理
1.3单片机IO口
2、矩阵键盘数字显示代码
3、矩阵键盘密码锁
二、定时器(工作模式1)
1、定时器的原理
2、寄存器
2.1模式选择寄存器TMOD(不可位寻址)
2.2中断控制寄存器TCON(可位寻址)
3、使用独立按键点亮流水灯
4、简易秒表的实现
在键盘中按键数量较多时,将按键排列成矩阵可以减少IO口的占用。采用逐行或逐列的扫描,就可以读出任意位置按键的状态。
和数码管一样,矩阵按键实现多个按键同时检测的方式为:读取第一行/列,读取第二行/列,读取第三行/列······不断循环这一过程,达到多个按键同时检测的效果。
矩阵按键越多,节约的IO口也会越多。
51单片机的矩阵按键使用8个引脚来控制16个按键(4*4)。
通过原理图不难发现P14-P17这4个引脚是控制矩阵按键横向的电平的,而P10-P13这4个引脚是控制矩阵按键纵向的电平的。
逐行扫描:逐行扫描就是让P13-P10循环执行0111,1011,1101,1110。例如我给P17-P14分别赋值1011,P13-P10分别检测各自引脚电平,如果P13检测到低电平,那么说明按键S5被按下了。
逐列扫描:对P10-P13进行赋值,由P14-P17根据引脚电平检测按键是否被按下。
上拉是指通过一个连接在IO口与电源之间的电阻将不确定或高电平但驱动能力不够的电位控制在高电平。上拉电阻越大,上拉能力越弱。51系列单片机P1、P2、P3为准双向口,均为弱上拉强下拉模式,当输出高电平时,能够输出的电流很小,很容易被别的强下拉拉低。
//按列扫描
uchar MatrixKey()
{uchar KeyNumber=0;P1=0XFF;//P1口整体赋为高电平P1_3=0;//P1的3号口给低电平//P1的14-17口循环检测按键状态if(P1_7==0){delay_ms(20);//延时20ms消抖while(P1_7==0);//如果消抖完还没松手,就空循环delay_ms(20);//延时20ms消抖KeyNumber=1;//松手了}if(P1_6==0){delay_ms(20);while(P1_6==0);delay_ms(20);KeyNumber=5;}if(P1_5==0){delay_ms(20);while(P1_5==0);delay_ms(20);KeyNumber=9;}if(P1_4==0){delay_ms(20);while(P1_4==0);delay_ms(20);KeyNumber=13;}P1=0XFF;P1_2=0;if(P1_7==0){delay_ms(20);while(P1_7==0);delay_ms(20);KeyNumber=2;}if(P1_6==0){delay_ms(20);while(P1_6==0);delay_ms(20);KeyNumber=6;}if(P1_5==0){delay_ms(20);while(P1_5==0);delay_ms(20);KeyNumber=10;}if(P1_4==0){delay_ms(20);while(P1_4==0);delay_ms(20);KeyNumber=14;}P1=0XFF;P1_1=0;if(P1_7==0){delay_ms(20);while(P1_7==0);delay_ms(20);KeyNumber=3;}if(P1_6==0){delay_ms(20);while(P1_6==0);delay_ms(20);KeyNumber=7;}if(P1_5==0){delay_ms(20);while(P1_5==0);delay_ms(20);KeyNumber=11;}if(P1_4==0){delay_ms(20);while(P1_4==0);delay_ms(20);KeyNumber=15;}P1=0XFF;P1_0=0;if(P1_7==0){delay_ms(20);while(P1_7==0);delay_ms(20);KeyNumber=4;}if(P1_6==0){delay_ms(20);while(P1_6==0);delay_ms(20);KeyNumber=8;}if(P1_5==0){delay_ms(20);while(P1_5==0);delay_ms(20);KeyNumber=12;}if(P1_4==0){delay_ms(20);while(P1_4==0);delay_ms(20);KeyNumber=16;}return KeyNumber;
}
void main()
{LCD_Init();//初始化while(1){uchar KeyNumber=0;KeyNumber=MatrixKey();if(KeyNumber!=0)//说明有按键被按下,下一次按键被按下才进入if中更改显示屏数据{LCD_ShowNum(1,1,KeyNumber,5);}}
}
#include "LCD1602.h"
#include "MatrixKey.h"
typedef unsigned int uint;
uint KeyNumber=0,password=0,cnt=0;
void main()
{LCD_Init();//初始化LCD_ShowString(1,1,"password");while(1){KeyNumber=MatrixKey();if(KeyNumber!=0)//说明有按键被按下,下一次按键被按下才进入if中更改显示屏数据{//输入密码if(KeyNumber<=10&&cnt<4)//如果KeyNumber小于等于10,认为是在输入密码{password*=10;password+=KeyNumber%10;++cnt;LCD_ShowNum(2,1,password,4);}//确认密码if(KeyNumber==11)//把11定义为确认密码键{if(password==1234){LCD_ShowString(1,14,"OK ");}else{LCD_ShowString(1,14,"ERR");}password=cnt=0;//确认后清零LCD_ShowNum(2,1,password,4);//更新显示为0000}//按位取消已输入的密码if(KeyNumber==12)//把12定义为确认密码键{password/=10;--cnt;LCD_ShowNum(2,1,password,4);//更新显示为0000}}}
}
STC89C52有三个定时器(P1.0和P1.1会多一组定时器T2)。
T0和T1均有四种工作模式:
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器(T1定时器在该模式停止计数)
工作模式1简图:
从上图可以看出,中断系统能够暂停主程序,去运行另一个程序。(有点像系统编程里父进程调用wait函数,阻塞父进程,父进程等待子进程变成僵尸后,父进程拿到子进程的退出码和退出信号后,继续执行父进程。当然只是有点像,因为单片机是通过数据溢出发送中断,执行中断服务函数)
图上这些英文标识就是51单片机的寄存器或寄存器的位的名字,所以写程序的时候控制这些寄存器就可以完成电路的闭合和优先级等操作。
GATE | 0 | 仅由TRx(TR1或TR0)控制定时器/计数器,TRx=1,允许计数/定时 |
1 | 由外引脚ITNx(INT0或INT1)和TRx均为1时,允许计数/定时 | |
C/T | 0 | 定时器工作模式,对单片机的晶体振荡器分频后的脉冲进行计数。 |
1 | 计数器工作模式,计数器对外部输入引脚T0(P3.4)或T1(P3.5)的外部脉冲(负跳变)计数。 | |
M1和M2 | 00 | 模式0:13位定时器/计数器 |
01 | 模式1:16位定时器/计数器(常用) | |
10 | 模式2:8位自动重装模式 | |
11 | 模式3:两个8位计数器(T1定时器在该模式停止计数) |
TF0 | 0 | 定时器/计数器T0溢出中断标志。该位为0表示未满 |
1 | 定时器/计数器T0溢出中断标志。溢出时,该位由硬件置1,并向CPU请求中断,响应后硬件清0 | |
TR0 | 0 | 停止定时器/计数器工作 |
1 | 允许定时器/计数器工作 |
//Timer0.c
//初始化
void Timer0_Init()
{//TMOD=0X01;//选择工作模式1,只需将TMOD比特位置为0000 0001即可,不过这样会修改T1的状态//TMOD&=0XF0;//把TMOD的低四位清零,高四位保持不变TMOD|=0X01;//把TMOD的最低位置1,其他位不变TR0=1;//允许定时器开始计数TF0=0;//该位为0表示计数未满TH0=64535/256;//拿到64535的高8位TL0=64535%256;//拿到64535的低8位//控制电路的开关ET0=1;EA=1;PT0=0;//PT0用于设置优先级,默认是0,不写也行
}
//key.c用于返回按键按下所对应的值
unsigned char Key()
{unsigned char KeyNumber=0;if(P3_1==0){delay_ms(20);while(P3_1==0);//空循环delay_ms(20);KeyNumber=1;}if(P3_0==0){delay_ms(20);while(P3_0==0);KeyNumber=2;}if(P3_2==0){delay_ms(20);while(P3_2==0);KeyNumber=3;}if(P3_3==0){delay_ms(20);while(P3_3==0);KeyNumber=4;}return KeyNumber;
}
//main.c
#include //左右移函数头文件
unsigned char KeyNumber,LedMode;
//定时器数据溢出发生中断,执行中断函数
//interrupt告诉编译器这是中断服务函数
//0:外部中断0;1:T0定时器/计数器;2:外部中断1;3:T1定时器/计数器;4:串行口
//数字越小,代表优先级越高
void Timer0_Routine() interrupt 1
{static unsigned int T0Count;//函数栈帧结束后保留T0CountTH0=64535/256;//每次进中断函数后,让这两个寄存器重新回到初值TL0=64535%256;T0Count++;//每中断一次T0Count++if(T0Count>=(500/(12/11)))//达到了定时0.5秒的作用(晶振11.0592){T0Count=0;//重置为0if(LedMode==0)P2=_crol_(P2,1);//P2左移1位if(LedMode==1)P2=_cror_(P2,1);//P2右移1位}
}
void main()
{P2=0XFE;Timer0_Init();while(1){KeyNumber=Key();if(KeyNumber==1) {LedMode++;if(LedMode>=2){LedMode=0;}}}
}
调用初始化函数,定时器开始计数,约1毫秒后发生溢出,每次发生溢出将会调用中断服务函数,中断服务函数运行完毕后,继续执行main函数。通过代码可以看到,这个中断服务函数将在约500次中断(0.5秒)后才会执行移位LED灯的操作,这个时候main函数已经运行至while循环中了,每隔0.5秒,LED灯将会循环右移(代码明明是写的左移呀,你为什么说LED在右移?代码确实在控制P2引脚低电平那一位左移,但是单片机的低位LED灯在最左边,所以看起来是在右移,不影响)当按下K1键时,LedMode变为1,流水灯将向反方向移动;再次按下K1,LedMode重新被置为0,流水灯的方向再次逆转。
#include
#include "LCD1602.h"
#include "delay.h"
#include "Timer0.h"
unsigned char sec,min,hour;
void Timer0_Routine() interrupt 1
{static unsigned int T0Count;//函数栈帧结束后保留T0CountTH0=64535/256;//每次进中断函数后,让这两个寄存器重新回到初值TL0=64535%256;T0Count++;//每中断一次T0Count++if(T0Count>=(1000/(12/11)))//达到了定时1秒的作用(晶振11.0592){T0Count=0;sec++;if(sec>=60){sec=0;min++;if(min>=60){min=0;hour++;}}}
}
void main()
{LCD_Init();Timer0_Init();LCD_ShowString(1,1,"Clock:");while(1){LCD_ShowNum(2,1,hour,2);LCD_ShowString(2,3,":");LCD_ShowNum(2,4,min,2);LCD_ShowString(2,6,":");LCD_ShowNum(2,7,sec,2);}
}