PS2键盘C51例程
基于51单片机的PS2键盘的单片机编程
PS2键盘的单片机编程在单片机系统中,经常使用的键盘都是专用键盘.此类键盘为单独设计制作的,成本高、使用硬件连接线多,且可靠性不高,这一状况在那些要求键盘按键较多的应用系统中更为突出.与此相比,在PC系统中广泛使用PS/2键盘具有价格低、通用可靠,且使用连接线少(仅使用2根信号线)的特点,并可满足多种系统的要求.因此在单片机系统中应用PS/2键盘是一种很好的选择.文中在介绍PS/2协议和PS/2键盘工作原理与特点的基础上,给出了一个在单片机上实现对PS/2键盘支持的硬件连接与驱动程序设计实现.该设计实现了在单片机系统中对PS/2标准104键盘按键输入的支持.使用Keil C51开发的驱动程序接口和库函数可以方便地移植到其他单片机或嵌入式系统中.所有程序在Keil uVision2上编译通过,在单片机AT89C51上测试通过.1 PS/2协议目前,PC机广泛采用的PS/2接口为mini-DIN 6pin的连接器,如图1所示.PS/2设备有主从之分,主设备采用Female插座,从设备采用Male插头.现在广泛使用的PS/2键盘鼠标均在从设备方式下工作.PS/2接口的时钟与数据线都是集电极开路结构,必须外接上拉电阻(一般上拉电阻设置在主设备中).主从设备之间数据通信采用双向同步串行方式传输,时钟信号由从设备产生.1.1 从设备到主设备的通信当从设备向主设备发送数据时,首先检查时钟线,以确认时钟线是否为高电平.如果是高电平,从设备就可以开始传输数据;反之,从设备要等待获得总线的控制权,才能开始传输数据.传输的每一帧由11位组成,发送时序及每一位的含义如图2所示.每一帧数据中开始位总是为0,数据校验采用奇校验方式,停止位始终为1.从设备到主设备通信时,从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读人数据线状态.1.2 主设备到从设备的通信主设备与从设备进行通信时,主设备首先将时钟线和数据线设置为“请求发送”状态,具体方式为:首先下拉时钟线至少100us抑制通信,然后下拉数据线“请求发送”,最后释放时钟线.在此过程中,从设备在不超过10us的间隔内必须检查这个状态,当设备检测到这个状态时,它将开始产生时钟信号.此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示.与从设备到主设备通信相比,其每帧数据多了一个ACK位.这是从设备应答接收到字节的应答位,由从设备通过拉低数据线产生,应答位ACK总是为0.主设备到从设备通信过程中,主设备总是在时钟线为低电平时改变数据线的状态,从设备在时钟上升沿读人数据线状态.2 PS/2键盘的编码与命令集2.1 PS/2键盘的编码目前,PC机使用的PS/2键盘都默认采用第2套扫描码集.扫描码有两种不同的类型:“通码(make code)”和“断码(break code)”.当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机.根据键盘按键扫描码的不同,可将按键分为3类:第1类按键通码为一个字节,断码为0xF0+通码形式.如A键,其通码为0x1C;断码为0xF0 0x1C.第2类按键通码为两字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式.如Right Ctrl键,其通码为0xE0 0x14;断码为0xE0 0xF0 0x14.第3类特殊按键有两个,Print Screen键,其通码为0xE0 0x12 0xE0 0x7C;断码为0xE0 0xF0 0x7C 0xE0 0xF0 0x12.Pause键,其通码为0xE1 0x14 0x77 0xE1 0xF0 0xl4 0xF0 0x77;断码为空.组合按键扫描码的发送是按照按键发生的次序,如按下面顺序按左Shift十A 键:① 按下左Shift键;② 按下A键;③ 释放A键;④ 释放左Shift键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12.在文中的驱动程序设计中,就是根据按键的分类对其分别进行处理.2.2 PS/2键盘的命令集主机可通过向PS/2键盘发送命令对键盘进行设置或者获得键盘的状态等操作.每发送一个字节,主机都会从键盘获得一个应答0xFA(“重发resend”和“回应echo”命令例外).驱动程序在键盘初始化过程中所用的指令:0xED,主机在该命令后跟随发送一个参数字节,用于指示键盘上Num Lock,Caps Lock,Scroll Lock Led的状态;0xF3,主机在这条命令后跟随发送一个字节参数定义键盘机打的速率和延时;0xF4,用于当主机发送0xF5禁止键盘后,重新使能键盘.3 PS/2键盘与单片机的连接电路PS/2键盘与AT89C51单片机的连接方式如图4所示.P1.0接PS/2数据线;P3.2(INT0)接PS/2时钟线.因为单片机的P1,P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1,P3相连接.4 驱动程序设计驱动程序的开发使用Keil C51语言以及KeiluVision2编程环境.PS/2 104键盘驱动程序主要任务是实现单片机与键盘间PS/2通信,同时将接收到的按键扫描码转换为该按键的键值KeyVal,提供给系统上层软件使用.4.1 单片机与键盘间PS/2通信的程序设计在PS/2通信过程中,主设备(文中是单片机)是在时钟信号为低时发送和接收数据信号.因为单片机向键盘发送的是指令,需要键盘回应,所以这部分程序采用查询方式;而单片机接收键盘数据时,数据线上的信号在时钟为低时已经稳定,所以这部分程序采用中断方式,且不需要在程序中加入延时程序.单片机向PS/2键盘发送数据程序代码为:void ps2_sentchar(unsigned char sentchar){//ps2主设备向从设备发送数据unsigned char sentbit_cnt= 0x00;unsigned char sentchar_chk = 0x00;EX0=0; //关外部中断0//发起一个传送,发起始位PS2_SGN_CLOCK = 0; //将时钟线拉低并保持100 usdelay100us();PS2_SGN_DATA= 0; //起始位PS2_SGN_CLOCK = 1;//发送DATA0-7for(sentbit_cnt=0;sentbit_cnt< 8;sentbit_cnt++){while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变为低PS2_SGN_DATA = sentchar& 0x01;//发送数据if(PS2_SGN_DATA) sentchar_chk++; //计算校验while(!PS2_SGN_CL0CK) _nop_(); //等待时钟线变高sentchar>>=1; //待发送数据右移一位}//发送校验位while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低switch(sentchar_chk){case 0:case 2:case 4:case 6:PS2_SGN_DATA =1;break;//奇校验case 1:case 3:case 5:case 7:PS2_SGN_DATA = 0;break;//奇校验default;break;)while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低PS2_SGN_DATA =1;//发送停止位,停止位总为1while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低//接收ACK//if(PS2_SGN_DATA) error();//ACK信号由键盘发出,总为低电平while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高EX0= 1; //开外部中断0}单片机由PS/2键盘接收数据程序:外部中断0设置为下降沿触发void int0() interrupt 0 using 0 {//EX0=0; //关外部中断0switch(ps2_revchar_cnt){case 1:……case 8:mcu_revchar<<=1;if(PS2_SGN_DATA) mcu_revchar |= 0x01;ps2_revchar_cnt++;break;case 0:ps2_revchar_cnt++;break; //开始位,case 9:ps2_revchar_cnt++;break; //校验位,可添加校验程序case 10: _nop_();//停止位ps2_revchar_cnt= 0;revchar_flag=1;//置接收到数据标识位break;default:break;}EX0=1;//开外部中断0}4.2 键盘扫描码转换程序设计由于键盘扫描码无规律可循,因此由键盘扫描码获得相应按键的键值(字符键为其ASCII值,控制键如F1,Ctrl等为自定义值),只能通过查表的方式获得.由于按键的3种类型及部分按键对应着两个键值(如A键的键值根据Caps和Shift键状态有0x41(A)和0x61(a)两种),因此综合考虑查表转换速度和资源消耗,设计中使用4个键盘表:键盘扫描码转换基本集和切换集(kb_plain_map[NR_KEYS]与kb_shift_map[NR_KEYS]);包含E0前缀的键盘扫描码转换基本集和切换集(kbeO_plain_map[NR_KEYS]与kbe0_shiftmap[NR_KEYS]).PS/2 104键盘按键扫描码最大值为0x83,所以设置NR_KEYS为132.所有4个键盘表的定义均为如下形式:KB_MAP[MAKE CODE]=KEYVAL,如果扫描码对应的按键为空(如KB_MAP[0x00]),则定义相应键值为NULL_KEY(0x00).以下是键盘扫描码基本集的部分代码实例: kb_plain_map[NR_KEYS]={……NULL_KEY;0x2C;0x6B;0x69;0x6F;0x30;0x39;NULL_KEY; //扫描码0x40~0x47//对应按键空,逗号,K,I,O,0,9,空//对应键值0x00,',','k','i','o','O','9',0x00…… };如此设计键盘转换表的另一个好处在于,以后如需扩展支持有ACPI、Windows多媒体按键键盘时,只需要将键表中相应处修改即可,如ACPIPower按键通码为0xE0 0x37,修改kbe0_plain_map[0x37]=KB_ACPI_PWR即可. 特殊按键Pause使用单独程序处理,如果接收到0xE1就转入这段程序.而Print Screen键则将其看作是两个通码分别为0xE0 0x12和0xE0 0x7C的“虚键”的组合键处理.在驱动程序中设定如下全局变量:led_status记录Scroll Lock Led,Num Lock Led和Caps Lock Led的状态(关为0,开为1);agcs_status记录左右Shift Ctrl Gui Alt状态,相应键按下则对应位为1,释放为0.E0_FLAG接到0xE0置1;E1_FLAG接收到0xE1置1;F0_FLAG接收到0xF0置1.按键键值通过KeyVal提供上层程序使用.PS/2键盘扫描码键值转换程序ps2_codetrans()流程框架如图5所示.第1类按键的扫描码键值转换程序代码。
键盘程序
我用C51做的键盘程序,望大家批砖该程序没有被仿真过,可能有一些错误.不过编译是通过的.这是我一时兴起编的,望高手指点.#include<intrins.h>#include<reg51.h>#define u CHAR unsigned CHAR#define uint unsigned intu CHAR bdata flags ;sbit flag0=flags^0 ;sbit KEY_A=P0^2; //列1sbit KEY_B=P0^1; //列2sbit KEY_C=P0^0 ; //列3void delayms(void);/*================================================*/ /* KEY程序 *//*================================================*/ void main(void){u CHAR m;int i;int h=0 ;int n=4;flag0=0;m=0xef;P0=0x0f ;if((P0&0x0f)!=0x0f) //有键按下往下执行{delayms();if((P0&0x0f)!=0x0f) //确认有键按下{for(i=0;i<4;i++) //扫描4行{P0=m; //从P0.4=0(第一行)开始扫描if(KEY_A==0) //列1有效{h=h+1; //算出列1键盘值flag0=1;while(KEY_A==0) //等待按键松开;}else if(KEY_B==0) //列2有效{h=h+2 ; //算出列2键盘值flag0=1;while(KEY_B==0);}else if(KEY_C==0)//列3有效{h=h+3; //算出列3键盘值flag0=1;while(KEY_C==0);}if(flag0==1)break ;m<<=1; //调整扫描行m=m+1; //修正列h=h+3; //修正键盘值}}}}/*============================================================== =======*//* 延时10毫秒*//*============================================================== =======*/void delayms(void){u CHAR i;for(i=300;i>0;i--);}/*============================================================== ====*下面是键盘布局:P02 P01 P001 2 3 P044 5 6 P057 8 9 P06M 0 C P07看看我的;一直在用的键盘子程序;uchar getkey(){uchar i,state,key;p2=0x0F;while((p2&0x0F)==0x0F) //等待键盘按下{while((p2&0x0F)==0x0F);delay(100); //延时1MS}p2=0xFF;for(i=4;i>0;i--){p2=0xFF&(~(0x01<<(0x8-i))); //要扫描的列置0 state=p2&0x0F; //屏蔽高4位 if(state!=0x0F) //Óмü°´ÏÂ{SWITCH(state){case 0x0E:key=(0x4-i)*0x4+0x1;break;case 0x0D:key=(0x4-i)*0x4+0x2;break;case 0x0B:key=(0x4-i)*0x4+0x3;break;case 0x07:key=(0x4-i)*0x4+0x4;break;default:break;}}}return key;}// 键盘说明// P24 P25 P26 P27// 1 5 9 D P20// 2 6 A E P21// 3 7 B F P22// 4 8 C 10 P23基本上都是一个模式:有键?--YES——和上次相同?--No--Keyold=0;退出| || YES--Keyold++;| || Keyold==首键延时KEYDELAY?--No--退出| || YES--Keyok=1; //按下不放一定时间发首键,后面发加速键| 保存键值到Keydata;(必要时译码)| Keyold=KEYDELAY-KEYLOOP(按住不放的延时);--退出|No--Keyold==0?--YES--退出|N0--Keyok=1; //不为0说明按了键再放开,当然作按键有效处理保存键值到Keydata;(必要时译码)Keyold=0;--退出所有键盘程序对外接口:unsigned CHAR KeyGetCode(void) //返回按键码{key_ok=0;return(key_data);}bit KeyTest(void) //检查有无按键{KeyScan(); //键扫描return(key_ok);}RE/*===================================NOTE:键盘处理主程序模块此程序在主程序中调用===================================*/void key_scan(void){unsigned int i;SWITCH(key_state)。
C51程序PS2
}
}
void getkey() interrupt 1 //内部中断0 用来处理缓冲区里的数据
{
unsigned char i=0;
tr0=0;
th0=0;
tl0=0;
count=0; //中断记数则0
if((temp[0]==18 || temp[0]==89) && temp[1]!=0xf0 ) //shift被按下
{
key=noshift[i][1];
ie=0x83;
return;
}
}
}
for(i=0;i<5;i++)
{
temp[i]=0;
}
}
unsigned char count=0,num=9,temp[5],shu=0; //中数次数 中断控制变量 缓冲区数组 缓冲区指针
unsigned char key=0; //按键最终值
void zhongduan() interrupt 0 //外部中断0 用来接受键盘发来的数据
{
dat>>=1; //接受数据 低->高
tr0=1;
}
else
{
temp[shu]=dat1;temp[shu+1]=dat2; shu+=2; //如果shift键被按下则记录与它同时按下的那个键
count=0;
}
if((temp[0]==18 || temp[0]==89) && (temp[2]==18 || temp[2]==89) ) tr0=1; //如果缓冲区中有两个间隔的shift键则证明需要的铵键结束
C51实验程序(流水灯、矩阵键盘、动态显示、串行口、1602液晶)
switch(SBUF)//根据收到的数据决定模式
{ case 'A':LED1=~LED1,LED2=1;break;
case 'B':LED2=~LED2,LED1=1;break;
case 'C': LED1=~LED1,LED2=~LED2;break;
}
}
else
LED1=LED2=1;
TMOD=0x20;
PCON=0x00;
TH1=0xfd;
TL1=0xfd;
TI=0;
TR1=1;
while(1)
{ if(K1==0)
{ while(K1==0);
Operation_NO=(Operation_NO+1)%4; //计按键次数决定模式
}
switch(Operation_NO)
{ case 0:LED1=LED2=1;break;
{uchar t;
while(ms--)
{
for(t=0;t<120;t++);
}
}
void main()//主函数
{SCON=0x50;//以下为串行口初始化
TMOD=0x20;
PCON=0x00;
TH1=0xfd;
TL1=0xfd;
RI=0;
TR1=1;
LED1=LED2=1;
while(1)
{ if(RI)
DelayMS(10);
}}
#include<reg52.h>(LCD1602)
#define uint unsigned int
#define uchar unsigned char
C51典型例程(按键驱动)
主要(zhǔyào)内容:
1. 键盘(jiànpán) 2. LED数码管 3. 字符型LCD
精品资料
键盘(jiànpán)
1. 独立式键盘(jiànpán)
(1) 独立式键盘的结构 与特点
精品资料
键盘(jiànpán)
1. 独立式键盘(jiànpán)
(2) 编程模式 在确定了键盘的编程结构后,就可以编制键盘接口程序。键盘接口程序 的功能实际上就是驱动键盘工作,完成按键的识别,根据所识别按键的键值, 完成按键子程序的正确散转,从而完成单片机应用系统对用户按键动作的预 定义响应。
形。 (2)16×2字符(zìfú)型LCD引脚说明 16×2字符(zìfú)型LCD共有14个引脚,其功能见表6-3所示。
精品资料
字符(zìfú)型LCD
2. HD44780 驱动器(16×2字符型LCD)
(3)LCD内部的内存 LCD内部存储器共分为3种:固定字形ROM,称为CG(Character
(2) 定时扫描方式
定时扫描方式就是每隔一段时间对键 盘扫描一次,它利用单片机内部的定时 器产生一定时间(例如10ms)的定时, 当定时时间到就产生定时器溢出中断, CPU响应中断后对键盘进行扫描,并在 有键按下时识别出该键,再执行该键的 功能程序。
精品资料
键盘(jiànpán)
4. 键盘(jiànpán)的工作方式
2)忙碌标志(Busy Flag,BF)。当LCD模块内部处于忙碌状态,不能接 收或处理外部信号时,这个标志就会被设定为1,因此在对LCD模块控制时,一 定要先检查这个忙碌标志,当BF=0时,才可以对LCD模块进行控制。
精品资料
字符(zìfú)型LCD
2. HD44780 驱动器(16×2字符型LCD)
C51的4X4键盘程序设计
二、中断请求标志
1、TCON的中断标志
IT0(TCON.0),外部中断0触发方式控制位。 当IT0=0时,为电平触发方式。 当IT0=1时,为边沿触发方式(下降沿有效)。 IE0(TCON.1),外部中断0中断请求标志位。 IT1(TCON.2),外部中断1触发方式控制位。 IE1(TCON.3),外部中断1中断请求标志位。 TF0(TCON.5),定时/计数器T0溢出中断请求标志位。 TF1(TCON.7),定时/计数器T1溢出中断请求标志位。
图 3-3-4 利用延时函数消除键盘抖动实现键盘输入电路
二、程序设计
#include<reg51.h> #define uchar unsigned char code uchar seven_seg[10] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; sbit key1 = P3.0; sbit key2 = P3.1; uchar key1_state,key2_state; //按键按下的状态变量 void delay(uchar i) { while (i) i--; } void key_scan (void) //键盘扫描函数 { if(key1 == 0) //如果按键按下 { delay(200); //延时一段时间,消除键抖 while(key1 == 0) key1_state = 1; //再对按键检测,如果确实按 下,按键状态变量为1 } if(key2 == 0) { delay(200); while(key2 == 0) key2_state = 1; } }
3.3.4 4×4键盘程序设计
4×4键盘16键盘阵列,本案例采用P1口完成, 见图3-3-5所示,把P18条I/O口分成4条列线4条行 线交叉但不接触,16个按键放置交叉位置,在单 片机复杂系统需要较多按键时,这种接法可以节 省单片机的硬件资源。键盘和P1口之间采用总线 连接布局,在电路原理设计过程中,总线只是图 示引脚之间的相连接关系,但不具有电气连接特 性,相连引脚之间需要标注相同的网络标号采用 电气连接特性,如图3-3-5中,单片机的P1.0与键 盘阵列上的P1.0相连。在设计单片机系统或其它 电子系统时,由于器件引脚较多,器件与器件之 间会经常采用总线连接,并且把电路模块化设计。
---C51例程
初学者必备第01 篇基础程序设计01 闪烁的LED/* 名称:闪烁的LED说明:LED按设定的时间间隔闪烁*/#include<reg51.h>#define uchar unsigned char#define uint unsigned intsbit LED=P1^0;//延时void DelayMS(uint x){uchar i;while(x--){for(i=0;i<120;i++);}}//主程序void main(){while(1){LED=~LED;DelayMS(150);}}02 从左到右的流水灯/* 名称:从左到右的流水灯说明:接在P0口的8个LED从左到右循环依次点亮,产生走马灯效果*/#include<reg51.h>#include<intrins.h>#define uchar unsigned char#define uint unsigned int//延时void DelayMS(uint x){uchar i;while(x--){for(i=0;i<120;i++);}}//主程序void main(){P0=0xfe;while(1){P0=_crol_(P0,1); //P0的值向左循环移动DelayMS(150);}}03 8只LED左右来回点亮/* 名称:8只LED左右来回点亮说明:程序利用循环移位函数_crol_和_cror_形成来回滚动的效果*/#include<reg51.h>#include<intrins.h>#define uchar unsigned char#define uint unsigned int//延时void DelayMS(uint x){uchar i;while(x--){for(i=0;i<120;i++);}}//主程序void main(){uchar i;P2=0x01;while(1){for(i=0;i<7;i++){P2=_crol_(P2,1); //P2的值向左循环移动DelayMS(150);}for(i=0;i<7;i++){P2=_cror_(P2,1); //P2的值向右循环移动DelayMS(150);}}}04 花样流水灯/* 名称:花样流水灯说明:16只LED分两组按预设的多种花样变换显示*/#include<reg51.h>#define uchar unsigned char#define uint unsigned intuchar code Pattern_P0[]={0xfc,0xf9,0xf3,0xe7,0xcf,0x9f,0x3f,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xe7,0xdb,0xbd,0x7e,0xbd,0xdb,0xe7,0xff,0xe7,0xc3,0x81,0x00,0x81,0xc3,0xe7,0xff, 0xaa,0x55,0x18,0xff,0xf0,0x0f,0x00,0xff,0xf8,0xf1,0xe3,0xc7,0x8f,0x1f,0x3f,0x7f,0x7f,0x3f,0x1f,0x8f,0xc7,0xe3,0xf1,0xf8,0xff,0x00,0x00,0xff,0xff,0x0f,0xf0,0xff,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe, 0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff};uchar code Pattern_P2[]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfc,0xf9,0xf3,0xe7,0xcf,0x9f,0x3f,0xff,0xe7,0xdb,0xbd,0x7e,0xbd,0xdb,0xe7,0xff,0xe7,0xc3,0x81,0x00,0x81,0xc3,0xe7,0xff, 0xaa,0x55,0x18,0xff,0xf0,0x0f,0x00,0xff,0xf8,0xf1,0xe3,0xc7,0x8f,0x1f,0x3f,0x7f,0x7f,0x3f,0x1f,0x8f,0xc7,0xe3,0xf1,0xf8,0xff,0x00,0x00,0xff,0xff,0x0f,0xf0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f,0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff};//延时void DelayMS(uint x){uchar i;while(x--){for(i=0;i<120;i++);}}//主程序void main(){uchar i;while(1){ //从数组中读取数据送至P0和P2口显示for(i=0;i<136;i++){P0=Pattern_P0[i];P2=Pattern_P2[i];DelayMS(100);}}}05 LED模拟交通灯/* 名称:LED模拟交通灯说明:东西向绿灯亮若干秒,黄灯闪烁5次后红灯亮,红灯亮后,南北向由红灯变为绿灯,若干秒后南北向黄灯闪烁5此后变红灯,东西向变绿灯,如此重复。
PS2键盘的设计---C51程序
PS2键盘的设计---C51程序+详细注释(转)PS2键盘的设计---C51程序#include <reg51.H>#define Frequence 11 //晶振频率单位是MHZ#define DELAY 10*Frequence/6 //发送程序延时#define SLEEP 8*Frequence/6 //发送程序延时sbit KBCLK="P3"^0; //键盘时钟线sbit KBDATA="P3"^1; //键盘数据线bit bat(void); //基本保证测试无错误返回0,有错返回1unsigned char buf_length(); //返回缓冲区数据个数bit command_s(); //键盘命令检查,有命令要接受返回1void clr_buf(void); //清键盘缓冲区void del_head(); //删除缓冲区头unsigned char exist(unsigned char);//检查键盘缓冲区中是否有与参数相等数,有则返回位置,无则返回255//bit emputy(); //检查键盘缓冲区是否空,是返回1unsigned char get_head(); //取键盘缓冲区头,头指针不变unsigned char get_head_f();//取缓冲区头对应标记,标记为0表示对应键已经松下bit insert(unsigned char,unsigned char);//插入缓冲区,并设置对应标记,成功则返回1void ini_timer01(); //定时器初始化void receive_process(); //接收键盘命令并处理void reset(); //软件复位unsigned char scankb(unsigned char); //扫描第N行,返回列直void send_buf(); //发送缓冲区扫描码bit send_code(unsigned char _KeyNo,bit flag);//发送按键扫描码,flag=0发送断开码,flag=1发送接通码bit send(unsigned char); //发送数据void set_default(); //设置缺省值void set_timer1(); //复位定时器1void set_scan_v(unsigned char); //设置扫描速度(拍发速率、延迟时间)void set_flag(unsigned char); //设置缓冲区对应标记void set_led(unsigned char); //设置LEDvoid secret(unsigned char);void scan(void);unsigned char get_end();bit emputy(void);//-----------------------函数声明,变量定义--------------------------------------------------------#include <reg51.h>#define KEY P1unsigned char key_code; //键值unsigned char key_buf[8]; //按键缓冲区unsigned char key_COUNT; //按键计数器unsigned char COUNT_TI; //定时中断计数//-----------------------变量声明---------------------------------------------------------------------void system_init(void ); //初始化,设置定时器0的工作方式,供主程序调用void TIMER0_SCANkey(); //定时器0中断处理函数bit judge_hitkey(); //判断是否有键按下,有返回1,没有返回0unsigned char scan_key(); //扫描键盘,返回键值(高四位代表行,低四位代表列) void key_manage(unsigned char keycode); //按键处理//...........每个按键对应一个处理程序//--------------------------------------------------------------------------------------------------// 函数名称:scan_key// 函数功能:扫描键盘,返回键值(高四位代表行,低四位代表列)// 无键按下返回0//--------------------------------------------------------------------------------------------------unsigned char scan_key() //扫描键盘,返回键值(高四位代表行,低四位代表列){unsigned char scancode,keycode,keycode_line,keycode_row;scancode="0xF0"; //列置低,行置高KEY="scancode"; //输入扫描码,扫描行keycode_line=KEY;scancode="0xF0"; //列置高,行置低KEY="scancode"; //输入扫描码,扫描列keycode_row=KEY;keycode=(((keycode_line<<4)&0xF0)|(keycode_row&0x0F));return(keycode);}//--------------------------------------------------------------------------------------------------// 函数名称:Timer0_init()// 函数功能:初始化设置// 设定INT0的工作方式//--------------------------------------------------------------------------------------------------void Timer0_init(void ){TMOD="0x20"; //定时器0工作在方式2的定时模式ET0=1; //定时器0中断允许TH0=0;TL0=0;TR0=1; //定时器0开始计数EA="1"; //系统中断允许}//--------------------------------------------------------------------------------------------------// 函数名称:TIMER0_intrupt// 函数功能:定时器0中断处理程序按键定时查询//--------------------------------------------------------------------------------------------------void TIMER0_SCANkey() interrupt 1 using 1{EA="0"; //系统中断禁止if((++COUNT_TI)%30==0){switch(COUNT_TI/30){case 1:if(scan_key()==0)COUNT_TI=0; //无键按下,计数值归零break;case 2:break;case 3:if(scan_key()==0)COUNT_TI=0; //无键按下,计数值归零,上次按键未扰动elsekey_code=scan_key(); //又有效建,获取键值break;default:if(scan_key()==0) //等待按键释放key_manage(key_code); //有一个有效按键,调用按键处理程序}}EA=1;}//--------------------------------------------------------------------------------------------------// 函数名称:key_manage// 函数功能:有效按键处理// 按键计数器加1,缓存区数据后移1位//--------------------------------------------------------------------------------------------------void key_manage(unsigned char keycode){unsigned char i;for(i=7;i>=0;i--){key_buf[i]=key_buf[i-1]; //缓冲区内数据后移1位}key_buf[0]= keycode; //将键值送入缓冲区key_COUNT++; //按键计数器加一}//-----------------------函数声明,变量定义-------------------------------------------------------- #include <reg51.h>#define KEY P1sbit DATA="P3"^1; //数据线sbit CLK="P3"^2; //时钟线unsigned char key_buf[8]; //按键缓冲区unsigned char key_COUNT; //按键计数器//--------------------------------------------------------------------------------------------------// 函数名称:delay// 入口参数:N// 函数功能:延时子程序,实现(16*N+24)us的延时// 系统采用11.0592MHz的时钟时,延时满足要求,其它情况需要改动//--------------------------------------------------------------------------------------------------void delay(unsigned int N){int i;for(i=0;i<N;i++);}//--------------------------------------------------------------------------------------------------// 函数名称:CAL_jiaoyan// 函数功能:计算校验位//--------------------------------------------------------------------------------------------------bit CAL_jiaoyan(unsigned char byte_data){//}//--------------------------------------------------------------------------------------------------// 函数名称:SEND_byte// 函数功能:发送一子节数据//--------------------------------------------------------------------------------------------------void SEND_byte(unsigned char byte_data){unsigned char i,temp;if(CLK==0) //时钟线为低temp="byte"_data;CLK="1";DA TA="0";delay(0);CLK="0"; //发送起始位for(i=0;i<8;i++){delay(0);CLK="1";DA TA=(temp&0x01); //发送数据byte_data=byte_data>>1;delay(0);CLK=0;}delay(0);CLK="1";DA TA=CAL_jiaoyan(byte_data); //发送校验位delay(0);CLK=0;delay(0);CLK="1";DA TA=1; //发送结束位delay(0);CLK=0;}//-------------------------------------------------------------------------------------------------- // 函数名称:RECEIVE_byte// 函数功能:接收一子节数据//-------------------------------------------------------------------------------------------------- unsigned char RECEIVE_byte(){unsigned char byte_data,i;CLK="0";delay(0);CLK="1"; //接收起始位,丢弃for(i=0;i<8;i++){delay(0);CLK="0";delay(0);CLK=1;byte_data=byte_data>>1;if(DATA=1)byte_data=byte_data|0x80;elsebyte_data=byte_data&0x7F; //接收8位数据}for(i=0;i<2;i++){delay(0); //接收校验位和结束位CLK="0";delay(0);CLK=1;}return(byte_data);}//--------------------------------------------------------------------------------------------------// 函数名称:manage// 函数功能:主机命令处理函数//-------------------------------------------------------------------------------------------------- void manage(unsigned char rec_data){}//--------------------------------------------------------------------------------------------------// 函数名称:SEND_keydata// 函数功能:发送按键值到主机//-------------------------------------------------------------------------------------------------- void SEND_keydata(){unsigned char ASCII_code; //// ASCII_code=judge_key(key_buf[key_COUNT]); //判断键值,按键编码成ASCII码SEND_byte(ASCII_code);key_COUNT--;}//--------------------------------------------------------------------------------------------------// 函数名称:主程序// 函数功能:循环查询主机状态//-------------------------------------------------------------------------------------------------- void main(){unsigned char rec_data;while(1){if(CLK==0&&DATA==0){rec_data=RECEIVE_byte(); //接收主机键盘manage(rec_data); //指令处理函数}if(key_COUNT!=0&&CLK==1) //有按键等待处理//线路空闲SEND_keydata();}。
实用C51编程实例20例
主程序从 030H 开始
MAIN: MOV P0,#11111110B; 调取立即数到 P0 口,P0.0 口灯点亮
ACALL DEL;
调用延时子程序,维持点亮
MOV P0,#11111101B;
调取立即数 0FD 到 P0 口,P0.1 灯点亮
ACALL DEL;
调用延时子程序,维持熄灭
MOV P0,#11111011B;
ORG 000H;
程序开始,ORG 伪指令
LJMP MAIN;
跳转到主程序,LJMP 长跳转
ORG 030H;
主程序从 030H 开始
MAIN: MOV P0,#11111110B; 调取立即数到 P0 口,P0.0 口灯点亮
ACALL DEL;
调用延时子程序,维持点亮
MOV P0,#11111101B;
ACALL DEL; MOV P0,#07fH;
调用延时子程序,维持熄灭 调取立即数 07F 到 P0 口,P0。7 灯点亮
ACALL DEL;
调用延时子程序,维持熄灭
AJMP MAIN;
跳转到主程序开始
DEL: MOV R5,#08H;
延时子程序
DEL1: MOV R6,#0FFH
DEL2: MOV R7,#0FFH DEL3: DJNZ R7,DEL3;
调取立即数 07F 到 P0 口,P0。7 灯点亮
ACALL DEL;
调用延时子程序,维持熄灭
MOV P0,#0FFH;
封闭 p0 口
MOV P1,#0FEH; 调取立即数到 P1 口,P1.0 口灯点亮
ACALL DEL;
调用延时子程序,维持点亮
MOV P1,#0FDH;
单片机驱动标准PC机键盘的C51程序
单片机驱动标准PC机键盘的C51程序单片机驱动标准PC 机键盘的C51 程序//#i nclude”reg51.h”#i nclude “intrins.h”#i nclude “ku.h”//按键通码与ascii对照表sbit sda= p1; //键盘数据线unsigned char dat=0,dat1=0,dat2=0; //接收键盘数据变量? 存储通码变量接受连续通码变量unsigned char count=0,num=9,temp[5],shu=0; //中数次数中断控制变量缓冲区数组缓冲区指针unsigned char key=0; //按键最终值void zhongduan() interrupt 0 //外部中断0 用来接受键盘发来的数据{dat>>=1; //接受数据低->高if(sda) dat|=0x80;count++;if(count==num){if(count==9){dat1=dat; //中断9 次后为键盘所按按键的通码(开始位始终为0 在第一次中断时右移中忽略)num=20; //使中断可以继续中断11 次}if(count==20){dat2=dat; //取回第二个通码if(dat1==0xe0 || dat2==0xf0) //第一个通码是0xe0 则证明所按按键为功能键,第二个通码是0xf0证明按键结束{temp[shu]=dat1;temp[shu+1]=dat2; shu+=2; //将所按按键存到缓冲区中ie=0x82; //关闭外部中断并打开内部中断来处理所按按键tr0=1;} else{temp[shu]=dat1;temp[shu+1]=dat2; shu+=2; //如果shift 键被按下则记录与它同时按下的那个键count=0;}if((temp[0]==18 || temp[0]==89) && (temp[2]==18 || temp[2]==89) ) tr0=1; //如果缓冲区中有两个间隔的shift 键则证明需要的铵键结束}}}void getkey() interrupt 1 //内部中断0 用来处理缓冲区里的数据{unsigned char i=0;tr0=0;th0=0;tl0=0;count=0; //中断记数则0if((temp[0]==18 || temp[0]==89) && temp[1]!=0xf0 ) //shift 被按下{for(i=0;itips:感谢大家的阅读,本文由我司收集整编。
课程设计,键盘扫描显示程序(c51)
课程设计,键盘扫描显示程序(c51)
//课程设计,键盘扫描显示程序//由于开发板中使用了锁存器,在proteus 中仿真的话,加上锁存器的话,会出现乱码,并且//不成功,所以,就不贴图了,不过以下代码,在事物板上测试成功
//实验代码为实现按键扫描,并显示在数码管上
//实验班上共有4 个按键,分别接在p3.4,p3,5,p3.6.p3.7 上,因此并没有用到矩阵键盘扫描,//控制起来比较简单,6 数码管观通过锁存器接在P0 口上。
#include
#define uchar unsigned char
#define uint unsigned int
sbit dula=P2;//数码管段选控制位
sbit wela=P2;//数码管位选控制位
uchar key=8;
//共阴数码管段码(不叫特殊的编码)
uchar code table[]=
{
0xed, 0x48, 0xf4, 0xb5, 0x99,
0x3d, 0x7d, 0x85, 0xfd, 0xbd,
0xdd, 0x79, 0x6c, 0xf1, 0x7c,
0x5c, 0x10, 0x00,
};
//位选编码
uchar code table1[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};。
用51单片机制作ps2接口的工业小键盘
用51单片机制作ps2接口的工业小键盘经过我的验证,是完全可行的。
#include <reg52.h>#include <intrins.h>#define uchar unsigned char#define SEM_FULL 1#define SEM_EMPTY 0uchar bdata ps2char;uchar bdata keystatus;uchar bdata parity_buf;uchar bdata s_buf,r_buf;uchar data last_s;sbit date=P3^0;sbit clock=P3^2;sbit key0=P2^0;sbit key1=P2^1;sbit key2=P2^2;sbit key3=P2^3;sbit key4=P2^4;sbit key5=P2^5;sbit key6=P2^6;sbit key7=P2^7;sbit s_buf0=s_buf^0;sbit r_buf7=r_buf^7;sbit parity_buf0=parity_buf^0;bit ps2_tx_sem=SEM_EMPTY;bit ps2_sendbyte(uchar c);void delayus(uchar us){while(--us);}void delayms(uchar ms){uchar i;while(ms--){for(i=0;i<120;i++);}}bit parity(){bit PARITY=1;uchar i;for(i=0;i<8;i++){PARITY^=parity_buf0;parity_buf=parity_buf>>1;}return PARITY;}void ps2_tx_sem_take(){ps2_tx_sem0:EA=0;if(ps2_tx_sem==SEM_FULL){EA=1;delayus(30);delayus(30);goto ps2_tx_sem0;}ps2_tx_sem=SEM_FULL;EA=1;return;}void ps2_tx_sem_give(){EA=0;ps2_tx_sem=SEM_EMPTY;EA=1;return;}void ps2_clk_check(){ps_tx_sem:EA=0;if(clock==0) // if PS2_CLK low, wait for 50us {EA=1;delayus(30);goto ps_tx_sem;}EA=1;return;}void ps2_send(uchar dat){ps2_tx_sem_take();ps2_clk_check();if(date==1)ps2_sendbyte(dat);ps2_tx_sem_give();}uchar ps2_recbyte() // Return receice data,Error return 0{uchar i;bit PARITY;r_buf=0;while(!clock); // Wait KBCLK Highdelayus(10);if(date) // KBDA TA shoud be low,mean received start bit{ps2_send(0xfe); // if KBDATA is High, return and send error status date(0xfe)return 0;}delayus(30);clock=0; // Device control the KBCLK signalfor(i=0;i<8;i++) // Rising edge of KBCLK, write data into r_buf7{delayus(30);clock=1;delayus(10);if(!clock) return 0; // if KBCLK pull down, mean Host cancelled this sendingr_buf7=date;if(i!=7)r_buf=r_buf>>1;delayus(10);clock=0;}delayus(30);clock=1;delayus(10);if(!clock) return 0; // if KBCLK pull down, mean Host cancelled this sendingPARITY=date; // Receive odd parity bitdelayus(10);clock=0;delayus(30);clock=1;delayus(10); // Receive Stop bit, should be highif(!date) // Otherwise,send error status date(0xfe){ps2_send(0xfe);return 0;}date=0; // Send ACK bit, mean have receive data done delayus(10);clock=0;delayus(30);clock=1; // Set KBCLK and KBDATA highdelayus(10);date=1;delayus(10);parity_buf=r_buf;if(PARITY==parity()) // Check Odd parity{return r_buf;}else{ps2_send(0xfe);return 0;}delayus(30);}bit ps2_sendbyte(uchar c) // Success return 1,Fail return 0{uchar i;bit PARITY;clock=1;if(!clock) // Host prevent Keyboard sending datareturn 0;parity_buf=c;PARITY=parity(); // Calculate value about sending data odd paritylast_s=c; // Save last sending datas_buf=c;if(!clock) // Host prevent Keyboard sending datareturn 0;if(!date) // Host prepared sending command data to keyboard return 0;date=0; // Falling edge of KBCLK send data, start bit 0 delayus(10);clock=0;for(i=0;i<8;i++) // First send LSB{delayus(10);clock=1;delayus(10);if(!clock) // if KBCLK is low,mean Hos prevent Keyboard sending datareturn 0;date=s_buf0;s_buf=s_buf>>1;delayus(10);clock=0;}delayus(10);clock=1;delayus(10);if(!clock) // if KBCLK is low,mean Hos prevent Keyboard sending datareturn 0;date=PARITY; // Sending odd parity bitdelayus(10);clock=0;delayus(10);clock=1;delayus(10);date=1; // Sending stop bitdelayus(10);clock=0;delayus(30);clock=1;delayus(30);delayus(10);return 1;}bit bat(){if(P2!=0xff)return 1;elsereturn 0;}void re_send ( ){ps2_send(last_s);}void reset(){date=1;while(!clock||!date); // Wait KBCLK and KBDATA highif(bat())ps2_send(0xfc); // Self-Check failedelseps2_send(0xaa); // Self-Check successed}bit Check_command ( ) // Keyboard receive host command,return 1 { clock=1;date=1;if(!clock&!date)return 1;elsereturn 0;}void receive_process(){uchar command;if(!Check_command()) return;command=ps2_recbyte();if(!command) return;switch(command){case 0xff: // reset commandps2_send(0xfa);reset();break;case 0xfe: // re-send commandps2_send(0xfa);re_send();break;case 0xf3: // Set Typematic Rate/Delayps2_send (0xfa);break;case 0xf2: // Read IDps2_send(0xfa);ps2_send(0xAB);ps2_send(0x83);break;case 0xee: // respond commandps2_send(0xee);case 0x00:ps2_send(0xfe);default:ps2_send(0xfa); // other command, just send a response of 0xFA break;}}void main(){P2=0xff;if(bat())ps2_send(0xfc);elseps2_send(0xaa);while(1){ if(Check_command())receive_process();key4=0;while(!key4){if(!key0){delayms(5);if(!key0){ps2_sendbyte(0x1C);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X1C);while(!key0);}}else if(!key1){delayms(5);if(!key1){ps2_sendbyte(0x32);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X32);while(!key1);}}else if(!key2){delayms(5);if(!key2){ps2_sendbyte(0x21);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X21);while(!key2);}}else if(!key3){delayms(5);if(!key3){ps2_sendbyte(0x23);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X23);while(!key3);}}else key4=1;}key5=0;while(!key5){if(!key0){delayms(5);if(!key0){ps2_sendbyte(0x24);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X24);while(!key0);}}else if(!key1){delayms(5);if(!key1){ps2_sendbyte(0x2B);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X2B);while(!key1);}}else if(!key2){delayms(5);if(!key2){ps2_sendbyte(0x34);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X34);while(!key2);}}else if(!key3){delayms(5);if(!key3){ps2_sendbyte(0x33);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X33);while(!key3);}}else key5=1;}key6=0;while(!key6){if(!key0){delayms(5);if(!key0){ps2_sendbyte(0x43);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X43);while(!key0);}}else if(!key1){delayms(5);if(!key1){ps2_sendbyte(0x3B);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X3B);while(!key1);}}else if(!key2){delayms(5);if(!key2){ps2_sendbyte(0x42);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X42);while(!key2);}}else if(!key3){delayms(5);if(!key3){ps2_sendbyte(0x4B);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X4B);while(!key3);}}else key6=1;}key7=0;while(!key7){if(!key0){delayms(5);if(!key0){ps2_sendbyte(0x3A);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X3A);while(!key0);}}else if(!key1){delayms(5);if(!key1){ps2_sendbyte(0x31);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X31);while(!key1);}}else if(!key2){delayms(5);if(!key2){ps2_sendbyte(0x44);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X44);while(!key2);}}else if(!key3){delayms(5);if(!key3){ps2_sendbyte(0x4D);delayms(5);ps2_sendbyte(0xF0);delayms(1);ps2_sendbyte(0X4D);while(!key3);}}else key7=1;}}}。
实例制作一个51单片机连接PS2键盘
实例制作的是用一个AT89C51单片机连接PS/2键盘接口和一个16x2的液晶显示屏,当敲击键盘时,字母可以显示在液晶显示屏上。
这个实例能启发你如何利用单片机来实现对PS/2接口的控制。
实例中提供的源代码修改后可以用到其他PS/2键盘制作项目中。
实例中提供的16x2字符型的液晶显示屏的驱动函数也可以其他项目。
电路原理主电路板中的AT89C51单片机(可以用AT89C52/S51/S52直接替换,如用AT89C2051/4051则需要改程序)组成了51最小化系统。
液晶显示屏于嗯了SMC1602A. 键盘通过PS/2六孔插座和主电路板。
PS/2设备的连接器使用mini-DIN连接器,正有6个引线,其中2个保留为用。
DATA和CLK是可双向通信的I/O线,也就是说通过这两根线,既可以把主机的数据发送到PS/2设备,有可以把设备的数据发向主机。
在无键按下是,DATA 和CLK一直处于高电平状态。
但有键按下时,键盘先检查CLK,看它是否处于高电平,如果是处在低电平,说明主机无空闲接收数据,这是键盘将会把数据放在自己的缓冲区(16Bytes).直到CLK重新被拉高。
键盘获得总线权,这是键盘产生始终信号在CLK上输出。
同时每一个时钟周期在DATA 线上输出一位数据。
第1位是起始位为0,第2-9位为一个八位二进制数据由地位到高位依次输出,第10位为奇偶校验位下面是电路原理图PS/2设备接口用于许多现代的鼠标和键盘,PS/2连接器上有四个管脚:电源地、+5V、数据和时钟。
Host(计算机)提供+5V并且键盘/鼠标的地连接到host的电源地上,数据和时钟都是集电极开路的这就意味着它们通常保持高电平而且很容易下拉到地(逻辑0)。
任何你连接到PS/2鼠标、键盘或host 的设备,在时钟和数据线上要有一个大的上拉电阻。
置“0”就把线拉低,置“1”就让线上浮成高。
从键盘/鼠标发送到主机的数据在时钟信号的下降沿(当时钟从高变到低的时候)被读取;从主机发送到键盘/鼠标的数据在上升沿(当时钟从低变到高的时候)被读取。
C51单片机键盘检测原理以及实现
C51单片机键盘检测原理以及实现首先,在做软件之前确定硬件。
明确键盘类型:弹性按键:按下时闭合,松手后自动断开。
如电脑键盘自锁式按键:按下时闭合,且自动锁住。
一边用于开关在I/O 口检测触电电压时应该考虑按键抖动问题,一般按键抖动为5~10ms左右,具体与其机械特性有关,所以要加检测抖动环节,可以用软件或者去抖动芯片硬件处理,当然通常用延时方法处理。
注意正确连接引脚。
下面是一个简单的4 按键独立键盘程序,在51hei 开发板的数码管上操作。
完整的源代码下载51hei/f/jpdd.rar 数值为0~59 变化,开始显示00 按key1 数值加1,按key2 数值减1,按key3 数值归0,按key4 数值每秒加1。
#includereg52.h#define uchar unsigned char#define unit unsigned intsbit key1=P3 ;sbit key1=P3;sb it key1=P3;sb it key1=P3;sb it dula=P2;sb it wela=P2;uch a r code table[]={0x3f,0x06.0x5b,0x4f,0x66,0x6d,0x7d,0x070x7f,0x6f,0x77,0x7c0x39,0x5e,0 x79,0x71};void delayms(unit);uchar numt0,unm;void display(uchar numdis) //定义一个显示的函数,分个位十位,用来显示{uchar shi,ge; //然后用轮流显示发,利用人眼图像残留分别显示shi=numdis/10; //上面的numdis 表示num 这个显示的数ge=numdis%10; //numdis 由下面主函数num 赋值dula=1;P0=table[shi]; dula=0;P0=0xff;wela=1;P0=0xfe;wela=0;delayms(5);dula=1;P0=table[ge];dula=0;P0 =0xff;wela=1;P0=0xfe;wela=0;delayms(5);}void delayms(unit xms) //自定义延迟环节{unit i,j;for(i=xms,i0,i--);for(j=110,j0,j--);}void init() //初始化函数,保证程序正常运行{TMOD=0x01;TH0=(65536-45872)/256;TL0=(65536-458720%256;EA=1;ET=0;}tips:感谢大家的阅读,本文由我司收集整编。
MiPS2键盘编程详细资料
PS2键盘编程详细资料在单片机系统中,经常使用的键盘都是专用键盘.此类键盘为单独设计制作的,成本高、使用硬件连接线多,且可靠性不高,这一状况在那些要求键盘按键较多的应用系统中更为突出.与此相比,在PC系统中广泛使用PS/2键盘具有价格低、通用可靠,且使用连接线少(仅使用2根信号线)的特点,并可满足多种系统的要求.因此在单片机系统中应用PS/2键盘是一种很好的选择.文中在介绍PS/2协议和PS/2键盘工作原理与特点的基础上,给出了一个在单片机上实现对PS/2键盘支持的硬件连接与驱动程序设计实现.该设计实现了在单片机系统中对PS/2标准104键盘按键输入的支持.使用Keil C51开发的驱动程序接口和库函数可以方便地移植到其他单片机或嵌入式系统中.所有程序在Keil uVision2上编译通过,在单片机AT89C51上测试通过.1 PS/2协议目前,PC机广泛采用的PS/2接口为mini-DIN 6pin的连接器,如图1所示.PS/2设备有主从之分,主设备采用Female插座,从设备采用Mal e插头.现在广泛使用的PS/2键盘鼠标均在从设备方式下工作.PS/2接口的时钟与数据线都是集电极开路结构,必须外接上拉电阻(一般上拉电阻设置在主设备中).主从设备之间数据通信采用双向同步串行方式传输,时钟信号由从设备产生.1.1 从设备到主设备的通信当从设备向主设备发送数据时,首先检查时钟线,以确认时钟线是否为高电平.如果是高电平,从设备就可以开始传输数据;反之,从设备要等待获得总线的控制权,才能开始传输数据.传输的每一帧由1 1位组成,发送时序及每一位的含义如图2所示.每一帧数据中开始位总是为0,数据校验采用奇校验方式,停止位始终为1.从设备到主设备通信时,从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读人数据线状态.1.2 主设备到从设备的通信主设备与从设备进行通信时,主设备首先将时钟线和数据线设置为“请求发送”状态,具体方式为:首先下拉时钟线至少100us抑制通信,然后下拉数据线“请求发送”,最后释放时钟线.在此过程中,从设备在不超过10us的间隔内必须检查这个状态,当设备检测到这个状态时,它将开始产生时钟信号.此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示.与从设备到主设备通信相比,其每帧数据多了一个ACK位.这是从设备应答接收到字节的应答位,由从设备通过拉低数据线产生,应答位ACK总是为0.主设备到从设备通信过程中,主设备总是在时钟线为低电平时改变数据线的状态,从设备在时钟上升沿读人数据线状态.2 PS/2键盘的编码与命令集2.1 PS/2键盘的编码目前,PC机使用的PS/2键盘都默认采用第2套扫描码集.扫描码有两种不同的类型:“通码(ma ke code)”和“断码(break code)”.当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机.根据键盘按键扫描码的不同,可将按键分为3类:第1类按键通码为一个字节,断码为0xF0+通码形式.如A 键,其通码为0x1C;断码为0xF0 0x1C.第2类按键通码为两字节0xE0+0xXX形式,断码为0xE0+0 xF0+0xXX形式.如Right Ctrl键,其通码为0xE0 0x14;断码为0xE0 0xF0 0x14.第3类特殊按键有两个,Print Screen键,其通码为0xE0 0x12 0xE0 0x7C;断码为0xE0 0xF0 0x7C 0xE0 0xF0 0x12.Pause键,其通码为0xE1 0x14 0x77 0xE1 0xF0 0xl4 0xF0 0x77;断码为空.组合按键扫描码的发送是按照按键发生的次序,如按下面顺序按左Shift十A键:① 按下左Shift键;② 按下A键;③ 释放A键;④ 释放左Shift键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12.在文中的驱动程序设计中,就是根据按键的分类对其分别进行处理.2.2 PS/2键盘的命令集主机可通过向PS/2键盘发送命令对键盘进行设置或者获得键盘的状态等操作.每发送一个字节,主机都会从键盘获得一个应答0xFA (“重发resend”和“回应echo”命令例外).驱动程序在键盘初始化过程中所用的指令:0xED,主机在该命令后跟随发送一个参数字节,用于指示键盘上Num Lock,Caps Lock,Scroll Lock Led的状态;0xF3,主机在这条命令后跟随发送一个字节参数定义键盘机打的速率和延时;0xF4,用于当主机发送0xF5禁止键盘后,重新使能键盘.3 PS/2键盘与单片机的连接电路PS/2键盘与AT89C51单片机的连接方式如图4所示.P1.0接PS/ 2数据线;P3.2(INT0)接PS/2时钟线.因为单片机的P1,P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1,P3相连接.4 驱动程序设计驱动程序的开发使用Keil C51语言以及KeiluVision2编程环境. PS/2 104键盘驱动程序主要任务是实现单片机与键盘间PS/2通信,同时将接收到的按键扫描码转换为该按键的键值KeyVal,提供给系统上层软件使用.4.1 单片机与键盘间PS/2通信的程序设计在PS/2通信过程中,主设备(文中是单片机)是在时钟信号为低时发送和接收数据信号.因为单片机向键盘发送的是指令,需要键盘回应,所以这部分程序采用查询方式;而单片机接收键盘数据时,数据线上的信号在时钟为低时已经稳定,所以这部分程序采用中断方式,且不需要在程序中加入延时程序.单片机向PS/2键盘发送数据程序代码为:void ps2_sentchar(unsigned char sentchar){//ps2主设备向从设备发送数据unsigned char sentbit_cnt= 0x00;unsigned char sentchar_chk = 0x00;EX0=0; //关外部中断0//发起一个传送,发起始位PS2_SGN_CLOCK = 0; //将时钟线拉低并保持100 us delay100us();PS2_SGN_DATA= 0; //起始位PS2_SGN_CLOCK = 1;//发送DATA0-7for(sentbit_cnt=0;sentbit_cnt< 8;sentbit_cnt++){ while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变为低PS2_SGN_DATA = sentchar& 0x01;//发送数据if(PS2_SGN_DATA) sentchar_chk++; //计算校验while(!PS2_SGN_CL0CK) _nop_(); //等待时钟线变高sentchar>>=1; //待发送数据右移一位}//发送校验位while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低switch(sentchar_chk){case 0:case 2:case 4:case 6:PS2_SGN_DATA =1;break;//奇校验case 3:case 5:case 7:PS2_SGN_DATA = 0;break;//奇校验default;break;)while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低PS2_SGN_DATA =1;//发送停止位,停止位总为1while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低//接收ACK//if(PS2_SGN_DATA) error();//ACK信号由键盘发出,总为低电平while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高EX0= 1; //开外部中断0}单片机由PS/2键盘接收数据程序:外部中断0设置为下降沿触发void int0() interrupt 0 using 0 {//EX0=0; //关外部中断0switch(ps2_revchar_cnt){……case 8:mcu_revchar<<=1;if(PS2_SGN_DATA) mcu_revchar |= 0x01;ps2_revchar_cnt++;break;case 0:ps2_revchar_cnt++;break; //开始位,case 9:ps2_revchar_cnt++;break; //校验位,可添加校验程序case 10: _nop_();//停止位ps2_revchar_cnt= 0;revchar_flag=1;//置接收到数据标识位break;default:break;}EX0=1;//开外部中断0}4.2 键盘扫描码转换程序设计由于键盘扫描码无规律可循,因此由键盘扫描码获得相应按键的键值(字符键为其ASCII值,控制键如F1,Ctrl等为自定义值),只能通过查表的方式获得.由于按键的3种类型及部分按键对应着两个键值(如A键的键值根据Caps和Shift键状态有0x41(A)和0x61(a)两种),因此综合考虑查表转换速度和资源消耗,设计中使用4个键盘表:键盘扫描码转换基本集和切换集(kb_plain_map[NR_KEYS]与kb_shift_ map[NR_KEYS]);包含E0前缀的键盘扫描码转换基本集和切换集(kbe O_plain_map[NR_KEYS]与kbe0_shiftmap[NR_KEYS]).PS/2 104键盘按键扫描码最大值为0x83,所以设置NR_KEYS为132.所有4个键盘表的定义均为如下形式:KB_MAP[MAKE CODE]=KEYVAL,如果扫描码对应的按键为空(如KB_MAP[0x00]),则定义相应键值为NULL_KEY(0x00).以下是键盘扫描码基本集的部分代码实例:kb_plain_map[NR_KEYS]={……NULL_KEY;0x2C;0x6B;0x69;0x6F;0x30;0x39;NULL_KEY; //扫描码0x40~0x47//对应按键空,逗号,K,I,O,0,9,空//对应键值0x00,',','k','i','o','O','9',0x00…… };如此设计键盘转换表的另一个好处在于,以后如需扩展支持有AC PI、Windows多媒体按键键盘时,只需要将键表中相应处修改即可,如ACPIPower按键通码为0xE0 0x37,修改kbe0_plain_map[0x37]=KB_ACPI_ PWR即可.特殊按键Pause使用单独程序处理,如果接收到0xE1就转入这段程序.而Print Screen键则将其看作是两个通码分别为0xE0 0x12和0xE0 0x7C的“虚键”的组合键处理.在驱动程序中设定如下全局变量:led_status记录Scroll Lock Led,Num Lock Led和Caps Lock Led的状态(关为0,开为1);agcs_status记录左右Shift Ctrl Gui Alt状态,相应键按下则对应位为1,释放为0.E0_FLAG接到0xE0置1;E1_FLAG 接收到0xE1置1;F0_FLAG接收到0xF0置1.按键键值通过KeyVal提供上层程序使用.PS/2键盘扫描码键值转换程序ps2_codetrans()流程框架如图5所示.第1类按键的扫描码键值转换程序代码。
51单片机PS2键盘程序
51单片机PS2键盘程序/*中断程序,帮助了解中断事件*/#include#includesbit key_data=P3^0;sbit key_clk=P3^2; //定义键盘接口的时钟脚sbit RELAY=P1^0; //继电器bit BF=0;//code unsigned char tmpdate[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//定义常量做为输出unsigned char keyv=0; //变量为常量的索引unsigned int intNum=0; //计数接收个数unsigned char key_up=1; //检测按键按下否void ldedata(unsigned char scandata);//数据处理void main(void) //入口函数{EA=1; //首先开启总中断EX0=1; //开启外部中断0IT0=1; // 设置成下降沿触发方式P2=0;do{ //循环if(BF)ldedata(keyv);elseEA=1;}while(1);}void key_scan() interrupt 0{ //外部中断0if((intNum>0)&&(intNum<9)){keyv=keyv>>1;if(key_data)keyv=keyv|0x80;}intNum++;while(!key_clk)if(intNum>10){intNum=0;BF=1;EA=0; //等待处理完键值再开启}}void ldedata(unsigned char scandata) {// unsigned char Tempdata;if(!key_up) //键盘松开时{switch(scandata){case 0xf0:key_up=1;break;case 0x12: //左SHIFT,可以下面写相应处理RELAY=0;//shift=1;break;case 0x59: //右SHIFT,可以下面写相应处理RELAY=1;//shitf=1;break;default:P0=scandata;if(scandata==0x76) //当按下键盘上的J键时,继电器响RELAY=0;if(scandata==0x52){ //当按下键盘上的空格键时,断开继电器RELAY=1;}break;}}else{key_up=0;switch(scandata){case 0x12://shift=0;break;case 0x59://shift=0;break;}}BF=0; //标识字符处理完了}。
键盘显示的C51编程
第6章MCS-51系列单片机键盘/显示的C51编程 (2)6.1 七段数码显示器的C51编程 (2)6.1.1 7段LED显示器基础知识 (2)6.1.2 七段数码静态显示的C51编程 (3)6.1.3 七段数码动态显示的C51编程 (6)6.1.4 串行口控制的LED显示的C51编程 (10)6.2 键盘接口的C51编程 (11)6.2.1 键盘的基础知识 (11)6.2.2 独立式键盘接口的C51编程 (12)6.2.3 矩阵键盘的C51编程 (14)6.3 点阵LED显示器接口的C51编程 (20)6.3.1 点阵LED显示器基础知识 (20)6.3.2 一个5×7点阵字符显示的C51编程 (21)6.3.3 一个8×8点阵字符串显示的C51编程 (22)6.3.4 两个8×8点阵字符串显示的C51编程 (24)6.4 液晶显示器的C51编程 (26)6.4.1 LCD基础知识 (26)6.4.2 字符型LCD1602应用的C51编程 (27)本章小结 (36)习题 (37)第6章MCS-51系列单片机键盘/显示的C51编程在单片机应用系统中,除了完成基本的功能外,另一个最重要的任务是设计优秀的人机交互接口。
人机交互接口用来实现控制命令及数据的输入,并且将系统运行信息反映给操作者。
系统运行信息的反映是通过各种显示器来完成的,主要包括八段数码显示、液晶显示、点阵显示等;控制命令及数据的输入时通过键盘来实现的。
本章主要介绍键盘及显示的C51编程技术。
6.1 七段数码显示器的C51编程在单片机应用系统中,七段LED显示器的应用非常广泛。
6.1.1 七段LED显示器基础知识1.结构与原理通常的七段LED显示器中有8个发光二极管,管脚如图6-1(a)所示。
a、b、c、d、e、f、g、dp称为LED的段,公共端com称为LED的位。
从管脚a~dp输入不同的8位二进制数,可显示不同的数字或字符。
C51编程实例
一. 数据类型的长度
signed char 1个字节 1个字节 2个字节 2个字节 1个字节 1位 1位 -128~127 0~255 -32768~32767 0~65535 0~255 0~1 0~1 特殊功能寄存器
void time0_int(void) interrupt 1 using 2
编程
在C语言里面提供了函数指针,我认为它比较重要的功能就是用来提供接口,使得C语 言可以模拟面向对象的语言为某些功能提供接口,实现功能代码的隔离。 这不,前些日子写了个小程序,用C51写的,其中有个功能就是操作液晶屏,在上面显 示菜单、输出结果什么的。在我看来,这部分功能使用函数指针最好不过了。譬如, 不管是什么菜单,总得要显示出来吧,定义一个show()接口就好。这样上层代码很简 单,反正对每个菜单都调用其show()接口,它们自己完成显示。 嗯,可是想法是好的,结果是不妙的。首先这么做在C51的语法层面上没有任何问题, 编译后没有任何错误。可是实际运行时就发现一些奇怪的现象,譬如调用某个函数, 明明入口参数的值是100,跑到函数内部就莫名其妙的变成了其它的值了。弄了好久不 得其解。 最后没有办法,切换keil到汇编模式,这才发现出问题,入口参数的变量地址与某个全 局变量的地址重了。在这里说一点题外话,我做的那个小东西用的是8031,片内变量 空间才128个,这里面还包括了堆栈段的空间,因此我轻易不定义片内变量,大部分情 况下都是使用片外变量,反正对于我做的那个东西,片外变量的速度也是足够了。 现在的问题是入口参数变量的地址怎么会跟其它全局变量的地址重复了呢?我仔细分 析了半天,发现原来是函数指针惹的祸。如果没有函数指针,程序里的每个函数都有 直接被调用的代码,譬如初始菜单显示,可能就会调用startMenuShow()函数。但是现 在变成了函数指针,显示初始菜单的代码就变成了menu[START].show()。也就是说编 译器在编译的时候并不知道startMenuShow()函数在哪里被调用,而只有在运行的时候 才知道是哪一个函数。 本来如果没有函数指针,编译器能够构建出一个完整的函数调用树,并且根据这个树 完成变量空间的分配。现在有了函数指针,这个函数调用树就跟实际的情况不一样了, 编译器的优化导致有些变量空间就重复。譬如在C51中有如下代码: unsigned short c_add(unsigned char xdata a, unsigned char xdata b) {
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
{
if ((key == 0xaa) || (key == 0xfa))
PS2Buffers.PS2KeyPushCount = 0;//长压键计数器清零
uchar PS2KeyMessage;//存键值
}PS2Buffers;
//======================================================
//
void main(void)
{
PS2Buffers.PS2KeyCount == 0;
#define unsigned char uchar
#define DEFPS2PUSHCOUNT 100 //长压最大时间
//定义接口
sbit PS2CLOC=P3^3;
sbit PS2DATA=P3^5;
enum PS2B //枚举
{
uchar PS2KeyTemp; //键盘缓冲区
{
PS2Buffers.PS2KeyPushCount = 0;//长压键计数器清零
PS2Buffers.PS2KeyMessage = key;//存入当前键值并执行命令
key >>= 1;
if (PS2DATA) key |= 0x80;
PS2Buffers.PS2KeyTemp = key;
PS2Buffers.PS2KeyCount++;//脉冲计数
}
else if (PS2Buffers.PS2KeyCount == 9) //PS2数据奇校验
while(1)
{
;
}
}
//=======================================================
//PS2键盘外部INT1中断服务程序
void int1proc() interrupt IE1_VECTOR using 1
if (PS2DATA) //高电平是停止位
{
if (key == 0xe0) //本次是扩展键
{
PS2Buffers.PS2KeyExtFlage = 0xe0;//置扩展键标志(小键盘只有回车键)
}
else if (key == 0xf0) //本次是键断码,键释放
{
PS2Buffers.PS2KeyExtFlage = 0xf0;//置键释放标志
}
{
uchar i, key;
code unsigned char PS2TAB[] = {//20键PS2小键盘键码表
0x70,//0
0x69,//1
0x72,//2
0x7a,//3
0x6b,//4
0x73,//5
0x74,//6
0x6c,//7
/*------------------------------------
PS2键盘外部INT1中断服务程序
原创: HotPower@
------------------------------------*/
#include"reg51.h"
if (PS2Buffers.PS2KeyPushCount > DEFPS2PUSHCOUNT) //长压时间到
{
PS2Buffers.PS2KeyMessage = key | 0x40;//存入当前键值并执行命令
PS2CLOCK = 1;//释放PS2时钟总线
}
}
}
else PS2Buffers.PS2KeyPopError = 0xed;//置停止位错误号0xed
PS2Buffers.PS2KeyPushCount = 0;//长压键计数器清零
}
}
0x75,//8
0x7d,//9
0x05,//F1
0x06,//F2
0x04,//F3
0x0c,//F4
0x03,//F5
0x0b,//F6
0x5a,//Enter
0x76,//Esc
0x66,//Bksp
0x71//KP.
};
{
ACC = PS2Buffers.PS2KeyTemp;//取键盘缓冲区移位数据(C51取偶校验位)
if (P != PS2DATA) PS2Buffers.PS2KeyCount ++;//脉冲计数
else
}
}
//如果出错长压键计数
if (PS2Buffers.PS2KeyPopError) PS2Buffers.PS2KeyPushCount = 0;器清零
else
{
PS2CLOCK = 0;//阻止PS2键盘立即回送数据
PS2Buffers.PS2KeyPopError = 0xaa;//出错码
for (i = 0; i < 20; i++)
}
else if (PS2Buffers.PS2KeyCount == 10) //停止位
{
key = PS2Buffers.PS2KeyTemp;//取键盘缓冲区移位数据
PS2Buffers.PS2KeyCount = 0;//脉冲计数归零
if (PS2Buffers.PS2KeyCount == 0) //PS2起始位测试
{
if (!PS2CLOCK && !PS2DATA) //低电平是起始位
{
if (PS2Buffers.PS2KeyExtFlage != 0xf0)
{
PS2Buffers.PS2KeyExtFlage = 0;//扩展键标志
PS2Buffers.PS2KeyPopError = 0;//键释放标志或出错码
break;
PS2Buffers.PS2KeyTemp = 0;
PS2Buffers.PS2KeyExtFlage = 0;
PS2Buffers.PS2KeyPopError = 0;
}
PS2Buffers.PS2KeyCount ++;//脉冲计数
{
PS2Buffers.PS2KeyPopError = 0xec;////置键奇校验位错误号0xec
PS2Buffers.PS2KeyCount = 0;//脉冲计数归零
}
}
else //未换键
{
PS2Buffers.PS2KeyPushCount ++;//长压键计数器计数
{
key |= 0x80;//键释放
PS2Buffers.PS2KeyPushCount = 0;//长压键计数器清零
PS2Buffers.PS2KeyMessage = key;//存入当前键值并执行命令
{
if (key == (PS2TAB[i])) //搜索
{
key = i + 1;
if (PS2Buffers.PS2KeyExtFlage == 0xf0)
}
else PS2Buffers.PS2KeyPopError = 0xeb;//置键起始位错误号0xeb
}
else if (PS2Buffers.PS2KeyCount < 9) //PS2数据位
{
key = PS2Buffers.PS2KeyTemp;//取键盘缓冲区移位数据
}
else PS2Buffers.PS2KeyCount = 0;//PS2键盘出错
}
}
PS2Buffers.PS2KeyVal = key;//存入当前键值值1~20或0x80+(1~20)
PS2Buffers.PS2KeyTemp = 0;//键码移位记录器
}
else
{
if (key != PS2Buffers.PS2KeyVal) //换了一个键
uchar PS2KeyExtFlage; // 键释放/扩展键标志(小键盘只有回车键)
uchar PS2KeyPopError; //错误
uchar PS2KeyCount; //脉冲计数
uchar PS2KeyVal; //键值值1~20或0x80+(1~20)
uchar PS2KeyPushCount;//长压键计数器清零