51单片机键盘设置
51单片机汇编语言教程:25课单片机键盘接口程序设计

51单片机汇编语言教程:第25课-单片机键盘接口程序设计关S未被按下时,P1。
0输入为高电平,S闭合后,P1。
0输入为低电平。
由于按钮是机械触点,当机械触点断开、闭合时,会有抖动动,P1。
0输入端的波形如图2所示。
这种抖动对于人来说是感觉不到的,但对计算机来说,则是完全能感应到的,因为计算机处理的速度是在微秒级,而机械抖动的时间至少是毫秒级,对计算机而言,这已是一个“漫长”的时间了。
前面我们讲到中断时曾有个问题,就是说按钮有时灵,有时不灵,其实就是这个原因,你只按了一次按钮,可是计算机却已执行了多次中断的过程,如果执行的次数正好是奇数次,那么结果正如你所料,如果执行的次数是偶数次,那就不对了。
为使CPU能正确地读出P1口的状态,对每一次按钮只作一次响应,就必须考虑如何去除抖动,常用的去抖动的办法有两种:硬件办法和软件办法。
单片机中常用软件法,因此,对于硬件办法我们不介绍。
软件法其实很简单,就是在单片机获得P1。
0口为低的信息后,不是立即认定S1已被按下,而是延时10毫秒或更长一些时间后再次检测P1。
0口,如果仍为低,说明S1的确按下了,这实际上是避开了按钮按下时的抖动时间。
而在检测到按钮释放后(P1。
0为高)再延时5-10个毫秒,消除后沿的抖动,然后再对键值处理。
不过一般情况下,我们常常不对按钮释放的后沿进行处理,实践证明,也能满足一定的要求。
当然,实际应用中,对按钮的要求也是千差万别,要根据不一样的需要来编制处理程序,但以上是消除键抖动的原则。
键盘与单片机的连接<键盘连接>图3<单片机与键盘接口图>图41、通过1/0口连接。
将每个按钮的一端接到单片机的I/O 口,另一端接地,这是最简单的办法,如图3所示是实验板上按钮的接法,四个按钮分别接到P3.2、P3.3、P3.4和P3.5。
对于这种键各程序能采用持续查询的办法,功能就是:检测是否有键闭合,如有键闭合,则去除键抖动,判断键号并转入对应的键处理。
51单片机 PS2口电脑键盘输入液晶显示

#include <reg52.h>sbit LCM_RS= P2^0;//定义LCD引脚sbit LCM_RW= P2^1;sbit LCM_E= P2^2;sbit Key_Data = P3^4; //定义Keyboard引脚sbit Key_CLK = P3^3;#define LCM_Data P0#define Busy 0x80 //用于检测LCM状态字中的Busy标识unsigned char code UnShifted[59][2] = {0x1C, 'a',0x32, 'b',0x21, 'c',0x23, 'd',0x24, 'e',0x2B, 'f',0x34, 'g',0x33, 'h',0x43, 'i',0x3B, 'j',0x42, 'k',0x4B, 'l',0x3A, 'm',0x31, 'n',0x44, 'o',0x4D, 'p',0x15, 'q',0x2D, 'r',0x1B, 's',0x2C, 't',0x3C, 'u',0x2A, 'v',0x1D, 'w',0x22, 'x',0x35, 'y',0x1A, 'z',0x45, '0',0x16, '1',0x1E, '2',0x26, '3',0x25, '4',0x2E, '5',0x36, '6',0x3D, '7',0x46, '9',0x0E, '`',0x4E, '-',0x55, '=',0x5D, '\\',0x29, ' ',0x54, '[',0x5B, ']',0x4C, ';',0x52, '\'',0x41, ',',0x49, '.',0x4A, '/',0x71, '.',0x70, '0',0x69, '1',0x72, '2',0x7A, '3',0x6B, '4',0x73, '5',0x74, '6',0x6C, '7',0x75, '8',0x7D, '9',};unsigned char code Shifted[59][2] = { 0x1C, 'A',0x32, 'B',0x21, 'C',0x23, 'D',0x24, 'E',0x2B, 'F',0x34, 'G',0x33, 'H',0x43, 'I',0x3B, 'J',0x42, 'K',0x4B, 'L',0x3A, 'M',0x31, 'N',0x44, 'O',0x4D, 'P',0x15, 'Q',0x1B, 'S',0x2C, 'T',0x3C, 'U',0x2A, 'V',0x1D, 'W',0x22, 'X',0x35, 'Y',0x1A, 'Z',0x45, '0',0x16, '1',0x1E, '2',0x26, '3',0x25, '4',0x2E, '5',0x36, '6',0x3D, '7',0x3E, '8',0x46, '9',0x0E, '~',0x4E, '_',0x55, '+',0x5D, '|',0x29, ' ',0x54, '{',0x5B, '}',0x4C, ':',0x52, '"',0x41, '<',0x49, '>',0x4A, '?',0x71, '.',0x70, '0',0x69, '1',0x72, '2',0x7A, '3',0x6B, '4',0x73, '5',0x74, '6',0x6C, '7',0x75, '8',0x7D, '9',};/*void Delay5Ms(void);void LCMInit(void);void DisplayOneChar(unsigned char X,unsigned char Y,unsigned char DData);void DisplayListChar(unsigned char X,unsigned char Y,unsigned char code *DData);void Decode(unsigned char ScanCode);void WriteDataLCM(unsigned char WDLCM);void WriteCommandLCM(unsigned char WCLCM,BuysC);unsigned char ReadDataLCM(void);unsigned char ReadStatusLCM(void);*/unsigned char code cdle_net[] = {"1602LCD PS2 TEST"};unsigned char code email[] = {"fjbysj@"};unsigned char code Cls[] = {" "};static unsigned char IntNum = 0; //中断次数计数static unsigned char KeyV; //键值static unsigned char DisNum = 0; //显示用指针static unsigned char Key_UP=0, Shift = 0;//Key_UP是键松开标识,Shift是Shift键按下标识static unsigned char BF = 0; //标识是否有字符被收到//5ms延时void Delay5Ms(void){unsigned int TempCyc = 5552;while(TempCyc--);}//400ms延时void Delay400Ms(void){unsigned char TempCycA = 5;unsigned int TempCycB;while(TempCycA--) {TempCycB=7269;while(TempCycB--); }}//读状态unsigned char ReadStatusLCM(void){ LCM_Data = 0xFF;LCM_RS = 0;LCM_RW = 1;LCM_E = 0;LCM_E = 1;while(LCM_Data & Busy); //检测忙信号return(LCM_Data);}//写数据{ReadStatusLCM(); //检测忙LCM_Data = WDLCM;LCM_RS = 1;LCM_RW = 0;LCM_E = 0; //若晶振速度太高可以在这后加小的延时LCM_E = 0; //延时LCM_E = 1;}//写指令void WriteCommandLCM(unsigned char WCLCM,BuysC) //BuysC为0时忽略忙检测{ if(BuysC) ReadStatusLCM(); //根据需要检测忙LCM_Data = WCLCM;LCM_RS = 0;LCM_RW = 0;LCM_E = 0;LCM_E = 0;LCM_E = 1;}/*//读数据unsigned char ReadDataLCM(void){ LCM_RS = 1;LCM_RW = 1;LCM_E = 0;LCM_E = 1;return(LCM_Data);}*/void LCMInit(void) //LCM初始化{ LCM_Data = 0;WriteCommandLCM(0x38,0); //三次显示模式设置,不检测忙信号Delay5Ms();Delay5Ms();WriteCommandLCM(0x38,0);Delay5Ms();Delay5Ms();WriteCommandLCM(0x38,0);Delay5Ms();Delay5Ms();WriteCommandLCM(0x38,1); //显示模式设置,开始要求每次检测忙信号WriteCommandLCM(0x08,1); //关闭显示WriteCommandLCM(0x01,1); //显示清屏WriteCommandLCM(0x06,1); // 显示光标移动设置WriteCommandLCM(0x0F,1); // 显示开及光标设置}//按指定位置显示一个字符void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData){Y &= 0x1;X &= 0xF; //限制X不能大于15,Y不能大于1if(Y)X |= 0x40; //当要显示第二行时地址码+0x40;X |= 0x80; //算出指令码WriteCommandLCM(X, 1); //发命令字WriteDataLCM(DData); //发数据}//按指定位置显示一串字符void DisplayListChar(unsigned char X, unsigned char Y, unsigned char code *DData){unsigned char ListLength;ListLength = 0;Y &= 0x1;X &= 0xF; //限制X不能大于15,Y不能大于1while (DData[ListLength]>0x19) {//若到达字串尾则退出if(X <= 0xF) {//X坐标应小于0xFDisplayOneChar(X, Y, DData[ListLength]); //显示单个字符ListLength++;X++;}}}void Keyboard_out(void) interrupt 2{if((IntNum > 0) && (IntNum < 9)) {KeyV >>= 1; //因键盘数据是低>>高,结合上一句所以右移一位if(Key_Data) {KeyV |= 0x80; //当键盘数据线为1时为1到最高位}}IntNum++;while(!Key_CLK); //等待PS/2CLK拉高if(IntNum > 10) {IntNum = 0; //当中断10次后表示一帧数据收完,清变量准备下一次接收BF = 1; //标识有字符输入完了EA = 0; //关中断等显示完后再开中断(注:如这里不用BF和关中断直接调Decode()则所Decode中所调用的所有函数要声明为再入函数)}}void Decode(unsigned char ScanCode) //注意:如SHIFT+G为12H 34H F0H 34H F0H 12H,也就是说shift的通码+G的通码+shift的断码+G的断码{unsigned char TempCyc;if(!Key_UP) { //当键盘松开时switch(ScanCode) {case 0xF0 : //当收到0xF0,Key_UP置1表示断码开始Key_UP = 1;break;case 0x12: // 左SHIFTShift = 1;break;case 0x59: // 右SHIFTShift = 1;break;default:if(DisNum > 15) {DisplayListChar(0,1,Cls); //清LCD第二行DisNum = 0;}if(Shift == 1) { //如果按下SHIFTfor(TempCyc = 0;(Shifted[TempCyc][0]!=ScanCode)&&(TempCyc<59); TempCyc++); //查表显示if(Shifted[TempCyc][0] == ScanCode) {DisplayOneChar(DisNum,1,Shifted[TempCyc][1]);}DisNum++;}else { //没有按下SHIFTfor(TempCyc = 0; (UnShifted[TempCyc][0]!=ScanCode)&&(TempCyc<59);TempCyc++); //查表显示if(UnShifted[TempCyc][0] == ScanCode) {DisplayOneChar(DisNum,1,UnShifted[TempCyc][1]);}DisNum++;}break;}}Key_UP = 0;switch(ScanCode) { //当键松开时不处理判码,如G 34H F0H 34H 那么第二个34H不会被处理case 0x12: // 左SHIFTShift = 0;break;case 0x59: // 右SHIFTShift = 0;break;default:break;}}BF = 0; //标识字符处理完了}void KeyScan(void)/* 键盘扫描*/{unsigned char i,j,kv0,kv1,kv2,kv3,kv4,kvn,kvc,kvo,CLEAR;i = 0;j = 0;P1 = 0xEF;kv0=P1; /* 读入键值*/P1 = 0xDF;kv1=P1;P1 = 0xBF;kv2=P1;P1 = 0x7F;kv3=P1;P1 = 0xFF;kv4=P1;kvn = 0;for(i=0; i<4; i++) { /* 键值分析*/if((kv0&0x01)==0) {kvn = i*4 + 1;j++;}if((kv1&0x01)==0) {kvn = i*4 + 2;j++;}if((kv2&0x01)==0) {kvn = i*4 + 3;}if((kv3&0x01)==0) {kvn = i*4 + 4;j++;}if((kv4&0x01)==0) {kvn = 17 + i;j++;}kv0 /= 2;kv1 /= 2;kv2 /= 2;kv3 /= 2;kv4 /= 2;}if(j>1) {kvn = 0; /* 同时按下多键则键值无效*/}if(kvn==0) {kvc = 0;kvo = 0;}else { /* 若键值有效*/if(kvn==kvo) { /* 若键值与上次键值相同*/if(kvc<253)kvc++; /* 同键值计数延时*/if(kvc>2)kvn=0; /* 以前键值已有效,则本次无效*/ if(kvo == CLEAR) {if(kvc==20)kvn = 0x55; /* 连按清零键达120*10ms 则置全清*/ }}else { /* 若键值与上次键值不同*/ kvo = kvn; /* 记下键值*/kvc = 1;kvn = 0; /* 本次键值无效*/}}if(kvc==0) {kvn=0;}}void main(void){ unsigned char TempCyc;Delay400Ms(); //启动等待,等LCM讲入工作状态LCMInit(); //LCM初始化//Delay5Ms(); //延时片刻(可不要)DisplayListChar(0, 0, cdle_net);DisplayListChar(0, 1, email);//ReadDataLCM();//测试用句无意义for(TempCyc=0; TempCyc<10; TempCyc++) {Delay400Ms(); //延时}DisplayListChar(0, 1, Cls);IT1 = 0; //设外部中断1为低电平触发EX1 = 1; //开中断EA = 1;while(1) { KeyScan();if(BF)Decode(KeyV);else {EA = 1; //开中断}}}。
基于C51单片机的键盘及LCD显示

}
}
}
1.实验报告格式:
一.实验名称
二.实验目的
三.实验内容
四.设计思想
五.硬件设计
六.程序代码
七.参考文献
2.硬件电路原理图用PROTEL等软件画出。
附录:程序源代码:
附录1
#include "reg51.h"
#include "intrins.h"
#define THCO 0xee
#define TLCO 0x0
i_data&=0xf0;
for(i=0;i<8;i++)
{
SID=(bit)(i_data&0x80);
SCLK=0;
SCLK=1;
i_data=i_data<<1;
}
CS=0;
}
void InitLCD() //液晶初始化
{
send_command(0x30); //功能设置:一次送8位数据,基本指令集
2)ST7920控制器系列中文图形液晶模块资料手册
三、设计指标
利用实验板上提供的键盘电路,LCD显示电路,设计一人机界面,能实现以下功能:
1.LCD上显示“重庆科技学院”
2.按键至少包括0-9的数字键
3.LCD显示按键值
4.电子钟显示:时,分,秒(选作)
四、实验要求
1.以单片机为核心,设计4*4非编码键盘及LCD的硬件电路,画出电路原理图。
{
unsigned char hi=0;//汉字显示
if(x==0) send_command(0x80+y);//
else if(x==1) send_command(0x90+y);
51单片机键盘数码管显示(带程序)

期中大作业学院:物理与电子信息工程学院课题:【利用8255和51单片机实现数码管显示按键数值的程序】要求:【4*4矩阵键盘,按0到15,数码管上分别显示0~9,A~F】芯片资料:8255:8255是Intel公司生产的可编程并行I/O接口芯片,有3个8位并行I/O口。
具有3个通道3种工作方式的可编程并行接口芯片(40引脚)。
其各口功能可由软件选择,使用灵活,通用性强。
8255可作为单片机与多种外设连接时的中间接口电路。
8255作为主机与外设的连接芯片,必须提供与主机相连的3个总线接口,即数据线、地址线、控制线接口。
同时必须具有与外设连接的接口A、B、C口。
由于8255可编程,所以必须具有逻辑控制部分,因而8255内部结构分为3个部分:与CPU连接部分、与外设连接部分、控制部分。
8255特性:1.一个并行输入/输出的LSI芯片,多功能的I/O器件,可作为CPU总线与外围的接口。
2.具有24个可编程设置的I/O口,即3组8位的I/O口,分别为PA口、PB口和PC 口。
它们又可分为两组12位的I/O口:A组包括A口及C口(高4位,PC4~PC7),B组包括B口及C口(低4位,PC0~PC3)。
A组可设置为基本的I/O口,闪控(STROBE)的I/O闪控式,双向I/O三种模式;B组只能设置为基本I/O或闪控式I/O两种模式,而这些操作模式完全由控制寄存器的控制字决定.引脚说明RESET:复位输入线,当该输入端处于高电平时,所有内部寄存器(包括控制寄存器)均被清除,所有I/O口均被置成输入方式。
CS:芯片选择信号线,当这个输入引脚为低电平时,即CS=0时,表示芯片被选中,允许8255与CPU进行通讯;CS=1时,8255无法与CPU做数据传输。
RD:读信号线,当这个输入引脚为低电平时,即CS=0且RD=0时,允许8255通过数据总线向CPU发送数据或状态信息,即CPU从8255读取信息或数据。
WR:写入信号,当这个输入引脚为低电平时,即CS=0且WR=0时,允许CPU将数据或控制字写入8255。
51单片机使用状态机的键盘程序

}
default://其它
{
step = _Key1_up;//单键抬起消抖
#define Key_Down 0x3D //下箭头
#define Key_Add 0x3B //加
#define Key_Sub 0x37 //减
#define Key_Enter 0x2F //回车
传入参数:无
返回参数:无
设 计:莫汉伟 amo73@
修改日期:2007-10-12
备 注:详细功能和处理算法请参照本文件相关的流程图和文档
**************************************************************************/
#define KeyBuffLen 8 //定义键值环形缓冲区长度为8(缓冲区大小可自由定义,只要大于0即可)
//定义一个键盘缓冲区结构体
struct Struct_KeyBoardBuff
{
u8 buff[KeyBuffLen];//键值环形缓冲区
u8 in; //写键值指示(定时器中断写)
u8 Read_Key(void)
{
u8 Value;
if(Key.out != Key.in)
{
Value=Key.buff[Key.out++];//"读"还没有追上"写",缓冲区有键值,读之
if(Key.out >= KeyBuffLen) //如果"读"跑到了队列尾部,则重新跳回原点
u8 out; //读键值指示(用户读)
用Protues学习51键盘

用Proteus学习51单片机之键盘键盘说开了,其实就是很多的按钮。
如果键盘数比较小的话,直接使用IO 口连接按钮就可以了,比如我要实现一个功能,按键使数字加1或减1,这样的话,只需要两个按键就可以了。
单个键盘的检测并不困难,先把IO口电平置为高电平,然后直接IO连按钮,再把按钮接地,当按下按钮时,检测到IO口电平为低即表示按下了按钮。
当然这是理论情况下,实际的情况是,按下按钮后,电平还会上下的跳变几次,所以如果只是单纯的检测电平的变化,是不准确的。
所以比较简单的方法是,检测到电平变化后,延时一段时间(比如10ms),再去检测电平,如果电平和先前检测到的一致,说明确实是按下去了。
下面是单独按钮的实验,简单起见,直接用LED灯来显示当前值的二进制(不考虑溢出等情况)当需要的的按键比较少的时候,可以直接把IO口和按钮直接相连,不过当需要的按钮很多的时候,则不能这样连接了。
像一般的电脑键盘100多个按键,难道直接和系统IO相连?这显然是不现实的。
矩阵键盘就是一种能够以较少的IO口,检测较多按键的键盘。
矩阵键盘的原理就不写了,网上很容易就找到,简单而言,就是检测按下的按键,是第几行,第几列。
具体检测的方法也很简单,首先给相连的所有IO口加高电平,然后把某一行的线置低电平,如果有按键按下的话,则对应的列连接的IO口也会变成低电平,这样,行和列就都知道了。
原理图如下,按下1-9时在数码管上显示数字,其他按键直接显示“-”号。
检测代码如下:charcheck(){uchar row=0,col=0;//定义行和列uchartmp = 0,tmp2 = 0;for(row=0;row<4;row++)//P2.0到P2.3表示是第几行,依次置低电位tmp = (0xFF & (~(1<<row)));//表示第一行是低电平P2 = tmp;tmp2 = tmp;//因为要逐个的检测列,tmp的值会改变,所以加了一个tmp2来记录行的值//P2.4到P2.7表示是第几列,依次检测是否低电平for(col=0;col<4;col++){tmp = tmp2 & (~(1<<(col+4))); //表示对应的行和列,表示到P2时,应该显示的值if(P2 == tmp){delay(); //去抖动if(P2 == tmp) //确认是第几行,第几列了,则返回对应的值{if(row==3 && col==1) //0在第四行,第二列return0;if(rowreturn(2-row)*3 + col + 1;return10;//表示按的键不在0到9之间}}}return-1;//表示没有按键}。
51单片机讲稿第九章键盘使用

;0号键功能程序
;0号键功能程序执行完返回 ;0号键功能程序
JMP START
……………………… PROM7: ……………………… JMP START …
;1号键功能程序执行完返回
;7号键功能程序 ;7号键功能程序执行完返回
键盘的使用
7.2.4 行列式键盘
行列式键盘又叫矩阵式键盘。用I/O口线组成行、列结构, 按键设置在行列的交点上。例如4×4的行列结构可组成16个键 的键盘。因此,在按键数量较多时,可以节省I/O口线。 1.行列式键盘的接口 行列式键盘的接口方法有许多,例如直接接口于单片机的 I/O口上;利用扩展的并行I/O接口;用串行口扩展并行I/O口接 口;利用一种可编程的键盘、显示接口芯片8279进行接口等。 其中,利用扩展的并行I/O接口方法方便灵活,在单片机应用系
统中比较常用。
键盘的使用
图7.2.6 8255扩展I/O口组成的行列式键盘
键盘的使用 2.键盘工作原理 按键设置在行、列线的交点上,行、列线分别连接到按键 开关的两端。行线通过上拉电阻接+5 V,被箝位在高电平状态。 对键盘的工作过程可分两步:第一步是CPU首先检测键盘 上是否有键按下;第二步是再识别是哪一个键按下。 检测键盘上有无键按下可采用查询工作方式、定时扫描工 作方式和中断工作方式。
键盘的使用
7.2.2 键盘接口和键输入软件中应解决的几个问题
1.消除键抖动
键按下
键稳定
前沿抖动 前沿抖动
图7.2.2 键合断时的电压抖动
键盘的使用
图7.2.3 消除键抖动电路
键盘的使用 2.键编码及键值
(1) 用键盘连接的I/O线的二进制组合表示键码。例如用4行、
4列线构成的16个键的键盘,可使用一个8位I/O口线的高、低4 位口线的二进制数的组合表示16个键的编码,如图5.4(a)所示。 各键相应的键值为88H、84H、82H、81H、48H、44H、42H、 41H、28H、24H、22H、21H、18H、14H、12H、11H。这种键 值编码软件较为简单直观,但离散性大,不便安排散转程序的 入口地址。
例说51单片机8-学习使用键盘

pzq@/csh@sparkcn8. 学习使用键盘前面章节我们主要了LED 、点阵、数码管等,这些我们都称之为输出设备,这一章我们就一起来看嵌入式中最常用的输入设备—按键和键盘。
8.1 简介说到按键大家都很熟悉,这个东西随处可见,其原理也比较简单,就是平时处于断路状态,一按下,就是短路状态了,达到一个状态切换的目的。
如下图8-1所示图 8-1在嵌入式中常见的键盘有三种1 独立键盘所谓独立按键就是每个按键单独连接在CPU 的一个I/O 口上,每个按键之间是独立的。
如图8-2的连接方式pzq@/csh@sparkcn图 8-2这种连接方式,每增加一个按键就要多使用一个I/O 口,所以在按键比较多的情况下,就不适合使用这种按键连接方式。
否则整个CPU 都被按键给占用。
这种按键的操作方式也比较简单,学完本章后面的内容,代码留给大家做动手题。
2 矩阵键盘当使用的键盘比较多时,使用这种方式连接可以节约很多IO 口,因为在原图图上排列成矩形,所以一般叫矩阵键盘,有时也叫行列键盘。
上图图 8-3这种方式方式使用n 行n 列可以扩展出n*n 个键盘,比使用独立按键大大减pzq@/csh@sparkcn少IO 口的使用。
适合于按键比较多的情况,这个也是这章主要负责消灭的问题。
3使用专用芯片来管理键盘,当单片机的I/O 口已经不够添加需要的按键数或单片机的程序本身很繁忙,没有功夫应付按键的计算时,就得考虑使用专用芯片了。
如周立功的zlg7289(请童鞋们自己上网搜索这个芯片的使用手册并了解其使用方法)。
抖动注意了,这里的抖动可不是说你按键时手的抖动。
这里的抖动指的是因为按键机械特性,固有的抖动。
当我们按下按键时,我们认为他的状态就是由开路直接变为短路了,从CPU 端看到就是0变1,或1变0了。
这是我们想的理想情况,实际情况并没有这么干脆利索,如果你手头有示波器的话,可以自己动手测量一下按下时的波形,这样印象会更深刻,如果没有,就看看前人的经验吧,也是一样的,看下图8-4。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
\\\§8.3 键盘接口技术一、键盘输入应解决的问题键盘是一组按键的集合,它是最常用的单片机输入设备.操作人员可以通过键盘输入数据或命令,实现简单的人机通讯。
键是一种常开型按钮开关,平时(常态)键的二个触点处于断开状态,按下键时它们才闭合(短路)。
键盘分编码键盘和非编码键盘。
键盘上闭合键的识别由专用的硬件译码器实现并产生编号或键值的称为编码键盘,如:ASCⅡ码键盘、BCD码键盘等;靠软件识别的称为非编码键盘。
在单片机组成的测控系统及智能化仪器中用得最多的是非编码键盘。
本节着重讨论非编码键盘的原理、接口技术和程序设计。
键盘中每个按键都是—个常开关电路,如图所示。
1.按键的确认:P1.7=1 无按键;P1.7=0 有按键;2.去抖动去抖动的方法:①硬件去抖动采用RS触发器:优点: 速度快,实时,缺点: 增加了硬件成本②软件去抖动采用延时方法延时5—10ms 延时5—10ms P1.7=0 确认P1.7=0 P1.7=1 (去前沿抖动) (去后沿抖动)二、独立式键盘每个I/O口连接一个按,S1 P1.0S2 P1.1……………………….S8 P1.7软件:START:MOV P1,#0FFH ;置P1口为高电平JNB P1.0, RS1 ; S1按下,程序去执行RS1JNB P1.1, RS2 ; S2按下,程序去执行RS2JNB P1.2, RS3 ; S3按下,程序去执行RS3JNB P1.3, RS4 ; S4按下,程序去执行RS4JNB P1.4, RS5 ; S5按下,程序去执行RS5JNB P1.5, RS6 ; S6按下,程序去执行RS6JNB P1.6, RS7 ; S7按下,程序去执行RS7JNB P1.7, RS8 ; S8按下,程序去执行RS8AJMP START ; 继续扫描按键………….RS1: AJMP PK1 ;RS2: AJMP PK2 ;RS3: AJMP PK3 ;RS4: AJMP PK4 ;RS5: AJMP PK5 ;RS6: AJMP PK6 ;RS7: AJMP PK7 ;RS8: AJMP PK8 ;AJMP START ; 无键按下,继续扫描…………………PK1: ……….. ;按键S1功能处理程序AJMP START ;处理S1按键后, 继续扫描PK2: ……….. ;按键S2功能处理程序AJMP START………………….PK8: ………………;按键S8功能处理程序AJMP START ; 处理S8按键后, 继续扫描优点: 连线简单,程序容易.缺点: 太浪费资源适用于按键较少、I/O口空闲的场合。
三、行列式非编码键盘接口方法按键较多时,一般采用行列式键盘.采用扫描方式,软件扫描方式有三种:程序扫描方式: 当CPU空闲时,扫描键盘,判断有无键按下.定时扫描方式: 利用CPU的定时器,每隔一定时间扫描一次键盘.中断扫描方式: 在硬件上采用中断,有键按下时,产生中断,由中断服务程序来处理.下面是16个按键,构成的4×4键盘行线: 四根,接P1.0---P1.3列线: 四根,接P1.4---P1.7程序扫描法原理:①P1.0---P1.3输出低电平,读P1.4—P1.7,若全为1,无键按下若不全为1,有键按下②在有键按下的情况下,进一步判断是那个键按下使P1.0---P1.3依次输出低电平,读P1.4----P1.7判断有无键按下子程序, 程序名: KAP键盘查询子程序KAP: MOV P1,#0F0H ;行线输出低电平,列线输出高电平MOV A,P1 ;P1口读入ACPL A ;取反ANL A,#0F0H ;取高四位,即P1.4---P1.7RET程序出口: A=0, 则无键按下.A≠0 则有键按下.去抖动-----------延时10ms子程序: 程序名D10MSD10MS: MOV R6, #14H;DL: MOV R7,#0FFH;DJNZ R7,$DJNZ R6,DLRETKINP: LCALL KAP ; 调键盘查询子程序,JNZ KP1 ; A≠0,有键按下SJMP END_KINP ; A=0,无键按下,退出按键查询程序KP1: LCALL D10MS ;延时10MS,去抖动LCALL KAP ;再次查询键盘,JNZ KP2 ; A≠0,确认有键按下SJMP END_KINP ; A=0,误操作,重新查询KP2为取键值子程序KP2: MOV R2, #0FEH ; R2为行扫描值MOV R4, #00H ; R4初值为第0行行首键号CLR F0 ; F0=0表示正在扫描键盘KP4: MOV P1, R2 ; 扫描行为低MOV A, P1 ;读P1JB ACC.4, L1 ;第0列不为低,则检查第1列MOV A, #00H ; 为低,则行首键值送入AAJMP KP5 ; KP5L1: JB ACC.5, L2 ; 检查第一列MOV A, #04H ;第一列行首键值送入AAJMP KP5L2: JB ACC.6 , L3 ; 检查第二列MOV A, #08H ;第二列行首键值送入AAJMP KP5L3: JB ACC.7 , NEXT ;检查第三列,若为1,则检查下一行MOV A, #0CH ; 第三列行首键值送入AKP5: ADD A , R4 ; A A+R4,键值调整PUSH A ;KP3: LCALL D10MS ; 后沿去抖动LCALL KAP ;查询按键是否释放JNZ KP3 ; A≠0 未释放,继续查询POP A ;键已释放,弹出键值RETNEXT: INC R4 ; 下一列,行键值加1MOV A , R2 ; 取扫描值JNB ACC.3, END_KINP ; 判断扫描是否结束?RL A ; 下一个扫描值MOV R2, A ;AJMP KP4 ;END_KINP: SETB F0RET51单片机电子日历(电子时钟)程序2007-08-03 21:01:05| 分类:默认分类|字号订阅程序代码:/****************************************************************************//* 电子日历,有时间显示、闹铃、日期、秒表及键盘设置功能*//* 功能键A: 设置位数字+1 闹钟模式下为闹钟开关秒表模式下为记时开关*/ /* 功能键B: 设置位数字-1 闹钟模式下为闹钟开关*//* 功能键C:设置模式及设置位选择秒表模式下为清零键*//* 功能键D:在四种工作模式下切换设置闹钟开关*//****************************************************************************/#include#include/***************这里设置程序初始化时显示的时间****************/#define SET_HOUR 12 /*设置初始化小时*/#define SET_MINUTE 00 /*设置初始化分钟*/#define SET_SECOND 00 /*设置初始化秒数*//*************************系统地址****************************/#define BASE_PORT 0x8000 /*选通基地址*/#define KEY_LINE BASE_PORT+1 /*键盘行线地址*/#define KEY_COLUMN BASE_PORT+2 /*键盘列线地址*/#define LED_SEG BASE_PORT+4 /*数码管段选地址*/#define LED_BIT BASE_PORT+2 /*数码管位选地址*/#define LED_ON(x) XBYTE[LED_BIT]=(0x01<<x) style="line-height: 25px; "> #define LED_OFF XBYTE[LED_SEG]=0x00 /*LED显示空*/</x) >/**************在设置模式下对秒分时的宏定义*****************/#define SECOND 0 /*对应数码管右边两位*/#define MINUTE 1 /*对应数码管中间两位*/#define HOUR 2 /*对应数码管左边两位*//********************定义四种工作模式***********************/#define CLOCK clockstr /*时钟模式*/#define ALART alartstr /*闹钟模式*/#define DATE datestr /*日期模式*/#define TIMER timerstr /*秒表模式*//****************以下是所有子函数的声明*********************/void sys_init(void); /*系统的初始化程序*/void display(void); /*动态刷新一次数码管子程序*/void clockplus(void); /*时间加1S的子程序*/void update_clockstr(void); /*更新时间显示编码*/void update_alartstr(void); /*更新闹钟时间的显示编码*/void update_datestr(void); /*更新日期显示编码*/void update_timerstr(void); /*更新秒表时间的显示编码*/void deley(int); /*延时子程序*/void update_dispbuf(unsigned char *); /*更新显示缓冲区*/unsigned char getkeycode(void); /*获取键值子程序*/void keyprocess(unsigned char); /*键值处理子程序*/unsigned char getmonthdays(unsigned int,unsigned char);/*计算某月的天数子程序*/ /*功能键功能子函数*/void Akey(void); /*当前设置位+1 开关闹钟开关秒表*/void Bkey(void); /*当前设置位-1 开关闹钟*/void Ckey(void); /*设置位选择秒表清零*/void Dkey(void); /*切换四种工作模式*//**********************全局变量声明部分*********************/unsigned char led[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};/*从0~9的LED编码*/unsigned char ledchar[3]={0x5c,0x54,0x71};/*o n f*///unsigned char key[24]={ /* 键值代码数组对应键位:*/// 0x70,0x71,0x72,0x73,0x74,0x75, /* 7 8 9 A TRACE RESET*/// 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5, /* 4 5 6 B STEP MON */// 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5, /* 1 2 3 C HERE LAST */// 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5}; /* 0 F E D EXEC NEXT */struct{ /*时间结构体变量*/unsigned char s;unsigned char m;unsigned char h;}clock={SET_SECOND,SET_MINUTE,SET_HOUR};struct{ /*闹铃时间结构体变量*/unsigned char m;unsigned char h;}alart={SET_MINUTE,SET_HOUR};struct{ /*日期结构体变量*/unsigned int year;unsigned char month;unsigned char day;}date={6,1,1};struct{ /*秒表时间结构体变量*/unsigned char ms;unsigned char s;unsigned char m;}timer={0,0,0};unsigned char dispbuf[6]; /*显示缓冲区数组*/unsigned char clockstr[6]; /*时间显示的数码管编码数组*/unsigned char alartstr[6]; /*闹钟显示的数码管编码数组*/unsigned char datestr[6]; /*日期显示的数码管编码数组*/unsigned char timerstr[6]; /*秒表显示的数码管编码数组*/unsigned int itime=0,idot; /*定时器0中断计数*/unsigned char itime1=0; /*定时器1中断计数*/sbit P3_1=P3^1; /*外接蜂鸣器的管脚*/bdata bit IsSet=0; /*设置模式标志位0:正常走时1:设置模式*/bdata bit Alart_EN=0; /*闹铃功能允许位0:禁止闹铃1:允许闹铃*/bdata bit IsBeep=0; /*响铃标志位0:未响铃1:正在响铃*/unsigned char SetSelect=0; /*在设置模式IsSet=1时,正在被设置的位,对应上面的宏*/unsigned char *CurrentMode; /*标志当前正设置的功能,如CurrentMode=CLOCK或CurrentMode=ALART等*/void timerplus(void);/**************************函数部分*************************/void main(void){sys_init();while(1){XBYTE[KEY_COLUMN,0x00]; /*给键盘列线赋全零扫描码,判断是否有键按下*/while((XBYTE[KEY_LINE]&0x0f)==0x0f) /*检测是否有键按下,无则一直进行LED 的刷新显示*/{if(Alart_EN&&(clock.h==alart.h)&&(clock.m==alart.m)) {IsBeep=1;}else{ IsBeep=0;P3_1=0;}display();}keyprocess(getkeycode()); /*有键按下时得到键值,并送入键值处理程序*/display(); /*可要可不要*/}}void sys_init(void){TMOD=0x22; /*定时器0和1都设置为工作方式2,基准定时250×2=500us=0.5ms*/TH0=6; /*定时器0中断服务用来产生1秒时钟定时及闹钟蜂鸣器蜂鸣脉冲*/TL0=6; /*定时器1中断服务留给秒表使用,产生1/100秒定时*/TH1=6;TL1=6;ET0=1;ET1=1;EA=1;TR0=1;update_clockstr(); /*初始化时钟显示编码数组*/update_alartstr(); /*初始化闹钟显示编码数组*/update_datestr(); /*初始化日期显示编码数组*/update_timerstr(); /*初始化秒表显示编码数组*/update_dispbuf(clockstr);/*初始化显示缓冲数组*/CurrentMode=CLOCK; /*默认的显示摸式为时钟*/P3_1=0; /*蜂鸣器接线引脚复位*/}void timer0(void) interrupt 1 using 1 /*定时器0中断服务器,用来产生1秒定时*/{itime++;if(itime==1000){if(IsSet) /*在设置模式下,对正在设置的位闪烁显示*/{dispbuf[SetSelect*2]=0; /*对正在设置的位所对应的显示缓冲区元素赋0,使LED灭*/dispbuf[SetSelect*2+1]=0;}if(IsBeep) P3_1=!P3_1; /*闹钟模式时,产生峰鸣器响脉冲*/if(CurrentMode==CLOCK){dispbuf[2]=dispbuf[2]&0x7f;dispbuf[4]=dispbuf[4]&0x7f;}}if(itime==2000) /*两千次计数为1S 2000×0.5ms=1s*/{itime=0; /*定时1s时间到,软计数清零*/clockplus(); /*时间结构体变量秒数加1 */update_clockstr(); /* 更新时间显示编码数组*/if(CurrentMode!=TIMER) update_dispbuf(CurrentMode); /* 用时间编码数组更新显示缓冲区*/}}void timer1(void) interrupt 3 using 2 /*定时器1中断服务器,用来产生1/100秒定时*/{idot++;if(++itime1==20) /*20*0.5ms=10ms*/{itime1=0;timerplus();update_timerstr();if(CurrentMode==TIMER){update_dispbuf(timerstr);dispbuf[2]=dispbuf[2]&0x7f; /*关闭小数点的显示*/dispbuf[4]=dispbuf[4]&0x7f;if(idot<1000) /*闪烁显示小数点*/{dispbuf[2]=dispbuf[2]|0x80;dispbuf[4]=dispbuf[4]|0x80;}else{dispbuf[2]=dispbuf[2]&0x7f;dispbuf[4]=dispbuf[4]&0x7f;}}}if(idot==2000) idot=0;}/*功能模块子函数*/void clockplus(void) /*时间加1s判断分,时子函数*/{if(++clock.s==60) /*秒位判断*/{clock.s=0;if(++clock.m==60) /*分位判断*/{clock.m=0;if(++clock.h==24) /*时位判断*/{clock.h=0;if(++date.day==(getmonthdays(date.year,date.month)+1)){date.day=1;if(++date.month==13) date.month=1;}}}}}void timerplus() /*秒表1/100秒位加1,判断秒、分子程序*/ {if(++timer.ms==100){timer.ms=0;if(++timer.s==60){timer.s=0;if(++timer.m==60){timer.m=0;}}}}void update_clockstr(void) /*更新时钟显示代码数组clockstr*/{clockstr[0]=led[clock.s%10]; /*给元素0赋相应数码管显示编码,编码序号是秒数的个位*/clockstr[1]=led[(int)(clock.s/10)]; /*给元素1赋相应数码管显示编码,编码序号是秒数的十位*/clockstr[2]=led[clock.m%10]; /*以下类推*/clockstr[3]=led[(int)(clock.m/10)];clockstr[4]=led[clock.h%10];clockstr[5]=led[(int)(clock.h/10)];}void update_alartstr(void) /*更新闹钟显示代码数组alartstr*/{ /*右边两位显示on:闹钟开启of:闹钟关闭*/if(Alart_EN) alartstr[0]=ledchar[1];/*显示字母n*/else alartstr[0]=ledchar[2]; /*显示字母f*/alartstr[1]=ledchar[0]; /*显示字母o*/alartstr[2]=led[alart.m%10];alartstr[3]=led[(int)(alart.m/10)];alartstr[4]=led[alart.h%10];alartstr[5]=led[(int)(alart.h/10)];}void update_datestr(void) /*更新日期显示代码数组datestr*/{datestr[0]=led[date.day%10];datestr[1]=led[(int)(date.day/10)];datestr[2]=led[date.month%10];datestr[3]=led[(int)(date.month/10)];datestr[4]=led[date.year%10];datestr[5]=led[(int)(date.year/10)];}void update_timerstr(void) /*更新秒表显示代码数组timerstr*/{timerstr[0]=led[timer.ms%10];timerstr[1]=led[(int)(timer.ms/10)];timerstr[2]=led[timer.s%10];timerstr[3]=led[(int)(timer.s/10)];timerstr[4]=led[timer.m%10];timerstr[5]=led[(int)(timer.m/10)];}void display(void) /*刷新显示六位LED一次*/{unsigned char i;for(i=0;i<6;i++){LED_ON(i); /*选通相应位*/XBYTE[LED_SEG]=dispbuf[i]; /*写显示段码*/deley(50); /*延时显示*/LED_OFF; /*写LED全灭段码*/}}void update_dispbuf(unsigned char *str) /*更新显示缓冲区子函数,参数为要用来更新缓冲区的源字符数组的首地址*/{dispbuf[0]=str[0]; /*将要更新的源字符数组内容COPY至dispbuf数组,用作显示缓冲区*/dispbuf[1]=str[1];dispbuf[2]=str[2]|0x80; /*默认把时位和分位后面的小数点显示出来,根据需要再取舍*/dispbuf[3]=str[3];dispbuf[4]=str[4]|0x80;dispbuf[5]=str[5];}void deley(int i) /*延时子函数*/{while(i--);}unsigned char getkeycode(void) /*键盘扫描子程序,返回获得的键码*/{unsigned char keycode; /*键码变量,一开始存行码*/unsigned char scancode=0x20; /*列扫描码*/unsigned char icolumn=0; /*键的列号*/display(); /*用刷新数码管显示的时间去抖*/XBYTE[KEY_COLUMN]=0x00;keycode=XBYTE[KEY_LINE]&0x0f; /*从行端口读入四位行码*/while((scancode&0x3f)!=0) /*取scancode的低六位,只要没变为全0,则执行循环*/{XBYTE[KEY_COLUMN]=(~scancode)&0x3f; /*给列赋扫描码,第一次为011111*/ if((XBYTE[KEY_LINE]&0x0f)==keycode) break; /*检测按键所在的列跳出循环*/ scancode=scancode>>1; /*列扫描码右移一位*/icolumn++; /*列号加1*/}keycode=keycode<<4; /*把行码移到高四位*/keycode=keycode|icolumn; /*由行码和列码组成键码*///等待按键放开XBYTE[KEY_COLUMN]=0x00;while((XBYTE[KEY_LINE]&0x0f)!=0x0f) display();return keycode;}void keyprocess(unsigned char keycode) /*键值处理函数*/{switch (keycode){case 0x73: Akey();break;case 0xB3: Bkey();break;case 0xD3: Ckey();break;case 0xE3: Dkey();break;default: break;}update_dispbuf(CurrentMode);}unsigned char getmonthdays(unsigned int year,unsigned char month)/*得到某月的天数*/{unsigned char days;switch (month){case 4:case 6:case 9:case 11:days=30;break;case 2: if(year%4==0) days=29;else days=28;break;default:days=31;break;}return days;}/*功能键子函数部分*/void Akey(void) /*对当前设置位进行加一操作,如果设置秒位,则给秒位清零*/ {if(CurrentMode==TIMER) /*秒表模式下启闭走时*/{ TR1=!TR1;return;}if(!IsSet) return; /*如果不是设置模式退出*/switch (SetSelect){case SECOND:if(CurrentMode==CLOCK){clock.s=0; /*如果当前被设置位是秒位,则清零秒位*/update_clockstr();}if(CurrentMode==ALART){Alart_EN=!Alart_EN;update_alartstr();}if(CurrentMode==DATE){if(++date.day==(getmonthdays(date.year,date.month)+1)) date.day=1;update_datestr();}if(CurrentMode==TIMER){TR1=!TR1;}break;case MINUTE:if(CurrentMode==CLOCK){if(++clock.m==60) clock.m=0; /*如果当前被设置分位,则分位加1*/ update_clockstr();}if(CurrentMode==ALART){if(++alart.m==60) alart.m=0;update_alartstr();}if(CurrentMode==DATE){if(++date.month==13) date.month=1;update_datestr();}break;case HOUR: if(CurrentMode==CLOCK){if(++clock.h==24) clock.h=0; /*如果当前被设置时位,则时位加1*/ update_clockstr();}if(CurrentMode==ALART){if(++alart.h==24) alart.h=0;update_alartstr();}if(CurrentMode==DATE){if(++date.year==100) date.year=0;update_datestr();}break;default: break;}update_dispbuf(CurrentMode);}void Bkey(void) /*对当前设置位进行减一操作,如果设置秒分,则给秒位清零,类比Akey()函数*/{if(!IsSet) return;switch (SetSelect){case SECOND:if(CurrentMode==CLOCK){clock.s=0;update_clockstr();}if(CurrentMode==ALART){Alart_EN=!Alart_EN;update_alartstr();}if(CurrentMode==DATE){if(--date.day==0x00)date.day=getmonthdays(date.year,date.month);update_datestr();}break;case MINUTE:if(CurrentMode==CLOCK){if(--clock.m==0xff) clock.m=59;update_clockstr();}if(CurrentMode==ALART){if(--alart.m==0xff) alart.m=59;update_alartstr();}if(CurrentMode==DATE){if(--date.month==0x00) date.month=12;update_datestr();}break;case HOUR: if(CurrentMode==CLOCK){if(--clock.h==0xff) clock.h=23;update_clockstr();}if(CurrentMode==ALART){if(--alart.h==0xff) alart.h=23;update_alartstr();}if(CurrentMode==DATE){if(--date.year==0xffff) date.year=99;update_datestr();}break;default: break;}update_dispbuf(CurrentMode);}void Ckey(void) /*正常走时模式和设置模式的切换*/{if(CurrentMode==TIMER){TR1=0; /*初始化定时器1设置,停止秒表记时*/TH1=6;TL1=6;timer.ms=0; /*初始化秒表数组*/timer.s=0;timer.m=0;update_timerstr();}else{if(IsSet==0) /*在非秒表模式下,第一次按下C键进入设置模式,设置时位*/ {IsSet=1; /*置位标志位,进入设置模式*/SetSelect=HOUR;return;} /*第二次按C键设置分位,第三次按键设置秒位,第四次按键完成退出设置*/if(SetSelect==0) /*按到第四次,即设置完秒位后,将标志位IsSet置0,完成设置*/{IsSet=0; /*复位标志位,进入正常走时模式*/return;}if(SetSelect>0) SetSelect--; /*设置位的标志变量SetSelect=0:时位1:分位2:秒位*/}}void Dkey(void) /*工作状态切换:时钟、闹钟、日期、秒表*/{if(CurrentMode==CLOCK) /*切换至闹钟,同时开关闹钟*/{ CurrentMode=ALART;Alart_EN=!Alart_EN;update_alartstr();return;}if(CurrentMode==ALART) /*切换至日期*/{ CurrentMode=DATE;return;}if(CurrentMode==DATE) /*切换至秒表,同时关闭设置模式*/{CurrentMode=TIMER;IsSet=0;return;}if(CurrentMode==TIMER) /*切换至时钟*/{CurrentMode=CLOCK;return;}}。