❶ 如何用单片机模拟键盘控制电脑
接口协议原理
PS/2键盘接口采用一种双向同步串行协议。即每在时钟线上发一个脉冲,就在数据线上发送一位数据。在相互传输中,主机拥有总线控制权,即它可以在任何时候抑制键盘的发送。方法是把时钟线一直拉低,键盘就不能产生时钟信号和发送数据。在两个方向的传输中,时钟信号都是由键盘产生,即主机不产生通信时钟信号。
如果主机要发送数据,它必须控制键盘产生时钟信号。方法如下:主机首先下拉时钟线至少100μs抑制通信,然后再下拉数据线,最后释放时钟线。通过这一时序控制键盘产生时钟信号。当键盘检测到这个时序状态,会在10ms内产生时钟信号。如图3中 A 时序段。主机和键盘之间,传输数据帧的时序如图2、图3所示。2.2 数据包结构在主机程序中,利用每个数据位的时钟脉冲触发中断,在中断例程中实现数据位的判断和接收。在实验过程中,通过合适的编程,能够正确控制并接收键盘数据。但该方案有一点不足,由于每个CLOCK都要产生一次中断,中断频繁,需要耗用大量的主机资源。
/*-----------------------------------------------
ps2鼠标的基本原理应用
说明:此程序使用标准PS2键盘输入。此样例仅作测试使用
晶振使用12M或者11.0592M,本键盘使用部分字母和数字测试,其他按键不能使用,用
户可以自行扩展。由于开发板和程序的各种参数,程序中没有使用奇偶校验,不保证没有
误码,校验程序请自行添加。
-------------------------------------------------*/
#include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
sbit Key_Data = P3^3 ; //定义Keyboard引脚
sbit Key_CLK = P3^2; //使用中断
bit BF=0;
bit Shift; //定义上档键标志
bit Key_UP; //定义通码断码标志
unsigned char KeyV;
unsigned char IntNum;
unsigned char DisNum;
/*-----------------------------------------------
外部中断读入信息
-----------------------------------------------*/
void Keyboard_out(void) interrupt 0
{
if ((IntNum > 0) && (IntNum < 9))
{
KeyV = KeyV >> 1; //因键盘数据是低>>高,结合上一句所以右移一位
if (Key_Data)
KeyV = KeyV | 0x80; //当键盘数据线为1时到最高位
}
IntNum++;
while (!Key_CLK); //等待PS/2CLK拉高
if (IntNum > 10)
{
IntNum = 0; //当中断11次后表示一帧数据收完,清变量准备下一次接收
BF = 1; //标识有字符输入完了
EA = 0; //关中断等显示完后再开中断
}
}
/*-----------------------------------------------
解码信息
注意:如SHIFT+G为12H 34H F0H 34H F0H 12H
也就是说shift的通码+G的通码+shift的断码+G的断码
-----------------------------------------------*/
void Decode(unsigned char ScanCode) //
{
unsigned char TempCyc,Val;
if (!Key_UP) //当键盘按下时
{
switch (ScanCode)
{
case 0xF0 : // 当收到0xF0,Key_UP置1表示断码开始
Key_UP = 1;
break;
case 0x12 : // 左 SHIFT
Shift = 1;
break;
case 0x59 : // 右 SHIFT
Shift = 1;
break;
default:
if(!Shift) //如果SHIFT没按下
{
for (TempCyc = 0;(UnShifted[TempCyc][0]!=ScanCode)&&(TempCyc<59); TempCyc++); //查表显示
if (UnShifted[TempCyc][0] == ScanCode)
{
Val= UnShifted[TempCyc][1];
LCD_Write_Char(DisNum%16,DisNum/16,Val);
DisNum++;
if(DisNum==33)
{
LCD_Clear(); //清屏
DisNum=0; //重头写数据
}
}
}
else //按下SHIFT
{
for(TempCyc = 0; (Shifted[TempCyc][0]!=ScanCode)&&(TempCyc<59); TempCyc++); //查表显示
if (Shifted[TempCyc][0] == ScanCode)
{
Val= Shifted[TempCyc][1];
LCD_Write_Char(DisNum%16,DisNum/16,Val);
DisNum++;
if(DisNum==33)
{
LCD_Clear(); //清屏
DisNum=0; //重头写数据
}
}
}
break;
}
}
else
{
Key_UP = 0;
switch (ScanCode) //当键松开时不处理判码,如G 34H F0H 34H 那么第二个34H不会被处理
{
case 0x12 : // 左 SHIFT
Shift = 0;
break;
case 0x59 : // 右 SHIFT
Shift = 0;
break;
}
}
BF = 0; //标识字符处理完了
}
/*-----------------------------------------------
ps2初始化(实际初始化外部中断)
-----------------------------------------------*/
void PS2_Init(void)
{
IT1 = 0; //设外部中断1为低电平触发
EA = 1; //外部中断开
EX0 = 1; //开中断
}
/*-----------------------------------------------
读取键盘值
-----------------------------------------------*/
void Read_KeyBoard(void)
{
if (BF)
Decode(KeyV);
else
EA = 1; //开中断
}
❷ 鍒╃敤STC89S51鍗旷墖链哄仛阌鐩樸
#include
"KBCODE.H"
#define
LCM_RS
P2_0
#define
LCM_RW
P2_1
//瀹氢箟LCD寮曡剼
#define
LCM_E
P2_2
#define
LCM_Data
P0
#define
Busy
0x80
//鐢ㄤ簬妫娴婰CM鐘舵佸瓧涓镄凚usy镙囱瘑
#define
Key_Data
P3_2
//瀹氢箟Keyboard寮曡剼
#define
Key_CLK
P3_3
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
Delay5Ms(void);
void
Delay400Ms(void);
void
Decode(unsigned
char
ScanCode);
void
WriteDataLCM(unsigned
char
WDLCM);
void
WriteCommandLCM(unsigned
char
WCLCM,BuysC);
unsigned
char
ReadStatusLCM(void);
unsigned
char
code
cdle_net[]
=
{"RICHMCU
PS2
TEST"};
unsigned
char
code
email[]
=
{"www.RICHMCU.COM"};
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鏄阌𨱒惧紑镙囱瘑锛孲hift鏄疭hift阌鎸変笅镙囱瘑
static
unsigned
char
BF
=
0;
//镙囱瘑鏄钖︽湁瀛楃﹁鏀跺埌
void
main(void)
{
unsigned
char
TempCyc;
Delay400Ms();
//钖锷ㄧ瓑寰咃纴绛茔CM璁插叆宸ヤ綔鐘舵
LCMInit();
//LCM鍒濆嫔寲
DisplayListChar(0,
0,
cdle_net);
DisplayListChar(0,
1,
email);
for(TempCyc=0;
TempCyc<10;
TempCyc++)
{
Delay400Ms();
//寤舵椂
}
DisplayListChar(0,
1,
Cls);
IT1
=
0;
//璁惧栭儴涓鏂1涓轰绠鐢靛钩瑙﹀彂
EX1
=
1;
//寮涓鏂
EA
=
1;
while(1)
{
if(BF)
Decode(KeyV);
else
{
EA
=
1;
//寮涓鏂
}
}
}
//鍐欐暟鎹
void
WriteDataLCM(unsigned
char
WDLCM)
{
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
ReadStatusLCM(void)
{
LCM_Data
=
0xFF;
LCM_RS
=
0;
LCM_RW
=
1;
LCM_E
=
0;
LCM_E
=
1;
while(LCM_Data
&
Busy);
//妫娴嫔繖淇″彿
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锛孻涓嶈兘澶т簬1
if(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锛孻涓嶈兘澶т簬1
while
(DData[ListLength]>0x19)
{//鑻ュ埌杈惧瓧涓插熬鍒欓鍑
if(X
<=
0xF)
{//X鍧愭爣搴斿皬浜0xF
DisplayOneChar(X,
Y,
DData[ListLength]);
//鏄剧ず鍗曚釜瀛楃
ListLength++;
X++;
}
}
}
//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--)
;
}
}
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;
//鍏充腑鏂绛夋樉绀哄畬钖庡啀寮涓鏂
}
}
void
Decode(unsigned
char
ScanCode)
//娉ㄦ剰:濡係HIFT+G涓12H
34H
F0H
34H
F0H
12H锛屼篃灏辨槸璇磗hift镄勯氱爜+G镄勯氱爜+shift镄勬柇镰+G镄勬柇镰
{
unsigned
char
TempCyc;
if(!Key_UP)
{
//褰挞敭鐩樻涧寮镞
switch(ScanCode)
{
case
0xF0
:
//褰撴敹鍒0xF0锛孠ey_UP缃1琛ㄧず鏂镰佸紑濮
Key_UP
=
1;
break;
case
0x12:
//
宸
SHIFT
Shift
=
1;
break;
case
0x59:
//
鍙
SHIFT
Shift
=
1;
break;
default:
if(DisNum
>
15)
{
DisplayListChar(0,1,Cls);
//娓匧CD绗浜岃
DisNum
=
0;
}
if(Shift
==
1)
{
//濡傛灉鎸変笅SHIFT
for(TempCyc
=
0;(Shifted[TempCyc][0]!=ScanCode)&&(TempCyc<59);
TempCyc++);
//镆ヨ〃鏄剧ず
if(Shifted[TempCyc][0]
==
ScanCode)
{
DisplayOneChar(DisNum,1,Shifted[TempCyc][1]);
}
DisNum++;
}
else
{
//娌℃湁鎸変笅SHIFT
for(TempCyc
=
0;
(UnShifted[TempCyc][0]!=ScanCode)&&(TempCyc<59);TempCyc++);
//镆ヨ〃鏄剧ず
if(UnShifted[TempCyc][0]
==
ScanCode)
{
DisplayOneChar(DisNum,1,UnShifted[TempCyc][1]);
}
DisNum++;
}
break;
}
}
else
{
Key_UP
=
0;
switch(ScanCode)
{
//褰挞敭𨱒惧紑镞朵笉澶勭悊鍒ょ爜锛屽侴
34H
F0H
34H
闾d箞绗浜屼釜34H涓崭细琚澶勭悊
case
0x12:
//
宸
SHIFT
Shift
=
0;
break;
case
0x59:
//
鍙
SHIFT
Shift
=
0;
break;
default:
break;
}
}
BF
=
0;
//镙囱瘑瀛楃﹀勭悊瀹屼简
}
❸ 51单片机普通i/o可以模拟USB键盘吗
用2个普通的I/O口来模拟USB键盘当然是可以的。用USB控制芯片当然好了,写程序更方便简单。但增加了成本。
用I/O口来模拟,必须要知道USB的协议,更要知道USB键盘的协议,要把这些协议都写进程序中去才行啊。
这样,写程序比较麻烦,但可以节省硬件成本了。
❹ 外设键盘的工作原理
有人知道外设键盘的工作原理吗?没有的话就跟我来看看咯!以下就是我做的整理,希望大家能喜!
外设键盘的简介
键盘是一组按键的组合,它是最常用的单片机输入设备,操作人员可以通过键盘输入数据或命令,实现简单的人机对话。单片机使用的键盘是一种常开型的开关,通常键的两个触点处于断开状态,按下键时它们才闭合。键盘分编码和非编码键盘,键盘的识别可用软件识别也可用专用芯片识别。
MCS-51单片机扩展键盘接口的 方法 用很多,从硬件结构上,可通过单片机I/0接口扩展键盘,也可通过扩展I/O接口设计键盘,还有些用的是专用键盘芯片。
键盘的工作原理
键盘从结构上分为独立式键盘与矩阵式键盘。一般按键较少时采用独立式键盘,按键较多时采用矩阵式键盘。
(1) 独立式键盘。
在由单片机组成的测控系统及智能化仪器中,用的最多的是独立式键盘。这种键盘具有硬件与软件相对简单的特点,其缺点是按键数量较多时,要占用大量口线。图1是一个利用MCS-51单片机的P1口设计的非编码键盘。
图1 独立式键盘
当按键没按下时,CPU对应的I/O接口由于内部有上拉电阻,其输入为高电平;当某键被按下后,对应的I/O接口变为低电平。只要在程序中判断I/O接口的状态,即可知道哪个键处于闭合状态。以下是非编码键盘键处理子程序。
JNB P1.0, KEY00 ;转按键1处理程序
JNB P1.1, KEY01 ;转按键1处理程序
JNB P1.2, KEY02 ;转按键1处理程序
JNB P1.3, KEY03 ;转按键1处理程序
JNB P1.4, KEY04 ;转按键1处理程序
JNB P1.5, KEY05 ;转按键1处理程序
JNB P1.6, KEY06 ;转按键1处理程序
JNB P1.7, KEY07 ;转按键1处理程序
RET ;无键按下,返回
KEY00: …
RET
KEY01: …
RET
…
(2) 矩阵式键盘。
矩阵式键盘使用于按键数量较多的场合,它由行线与列线组成,按键位于行、列的交叉点上。一个3*3的行列结构可以构成一个有9个按键的键盘。同理,一个4*4的行列可以构成一个16按键的键盘。很明显,在按键数量较多的场合,与独立式键盘相比,矩阵式键盘要节省很多I/0接口。
2.键盘按键识别方法
(1)扫描法。
下面以图2的K2键按下为例,说明此键是如何识别出来的。
图2 8031与键盘连接
扫描法有行扫描和列扫描两种,无论采用哪种,无论采用哪种,其效果是一样的,只是在程序中的处理方法有所区别。下面以列扫描法为例来介绍扫描法识别按键的方法。首先在键处理程序中将P1.4-P1.7依次按位变低,P1.4-P1.7在某一时刻只有一个为低。在某一位为低时读行线,根据行线的状态即可判断出哪一个按键被按下。如2号键按下,当列线P1.5为低时,读回的行线状态中P1.0被拉低,由此可知K2键被按下。一般在扫描法中分两步处理按键,首先是判断有无键按下,如行线有一个为低,则有键按下。当判断有键按下时,使列线依次变低,读行线,进而判断出具体哪个键被按下。
(2)线反转法。
扫描法是逐行或逐列扫描查询,当被按下的键处于最后一列时,要经过多次扫描才能最后获得此按键所处的行列值。而线反转法则显的简练,无论被按的键处于哪列,均可经过两步即能获得此按键所在的行列值,仍以图4.38为例来介绍线反转法。
首先将行线P1.0-P1.3作为输入线,列线P1.4-P1.7作为输出线,并且输出线输出全为低电平,读行线状态,则行线中电平为低的是按键所在的行。然后将列线作为输入线,行线作为输出线,并将输出线输出为低电平,读列线状态,则列线是电平为低的是按键所在的列。综合上述两步结果,确定按键所在的行和列,从而识别出所按下的键。
假设10号键被按下,在第一步P1.3-P1.0全为低电平时,读P1.4-P1.7的值,则P1.5为低电平;在第二步P1.4-P1.7输出全为低电平时,读P1.3-P1.0时,P1.2为低电平。由此可判断第3行第2列有键被按下,此键就是K10键。
3. 键盘的接口电路
设计MCS-51单片机键盘时可根据单片机系统的实际情况来灵活处理。在使用内部有程序存储器的单片机时,如单片机的I/O接口够用,可直接利用单片机的I/O接口连接键盘。如果I/O接口不够用,可利用扩展I/O接口连接键盘,有时也可使用专用的键盘接口芯片。
(1) 利用单片机的I/O接口连接键盘。
利用MCS-51单片机的I/O接口连接键盘时分两种情况,一是当P0、P1、P2、P3均为普通输入/输出时,可使用任意I/0接口连接键盘;二是当单片机系统扩展程序存储器、数据存储器、I/O时,由于P0、P2作为地址数据总线的使用,所以扩展键盘时只能使用P1口、P3口。如图2所示为利用MCS-51单片机的P1口设计的4*4矩阵键盘。
注意如果用P0口设计键盘,要给P0口各口线提供上拉电阻,其大小一般为2-10kn。
(2) 利用扩展I/O接口设计键盘。
MCS-51单片机在总线扩展凡是时由于P0口、P2口分别作为数据总线及地址总线,而P1口、P3口又有其他用途时,扩展键盘可利用扩展的I/O接口。利用8255的PC口设计的4*4矩阵键盘如图3所示,利用8255的PC口设计的编码键盘,PC0-PC3为行输入,PC4-PC7为列输出。
图3 8255与键盘连接图
(3) 按键去抖。
由于通常的按键所用的开关是机械开关,当开关闭合、断开时并不是马上稳定地接通和断开,而是在闭合与断开瞬间均伴随有一连串的抖动。
为了确保CPU对键的一次闭合仅做一次处理,必须要在程序或硬件上进行防抖处理。为节省硬件,通常在单片机系统中,一般不采用硬件方法消除键的抖动,而是用软件消抖方法。即检测键闭合后延时5-10ms,让前延抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认真正有键按下。当检测到按键释放后,也要给5-10ms的延时,待后延抖动消失后才转入该键处理程序。以下是具有消抖功能的键程序,只有按键按下再放开后才做一次键处理。
KEY_00:
JB P1.0, KEY_01 ;无键按下,查下一个键
LCALL DELAY ;延时10ms
JNB P1.0, $ ;键一直按下,等待
LCALL DELAY ;键松开,延时10ms
JB P1.0, KEY_00 ;一次按键完成,转键盘处理程序
KEY_01: …
RET
(4) 键盘的编码。
对于独立式按键键盘,由于按键数目较少,可根据实际情况灵活编码。对于矩阵式键盘,按键的位置由行号和列号唯一确定,所以分别对行号与列号进行二进制编码,然后将两值合成一个字节,高4位是行号,低4位是列号。如10号键被按下时,列号读回的值为1011,行号读回的值为1101,此两值合成为11011011=0DBH,据此值可转到10号键处理程序。这种方式虽然简单,但其离散性很大,在读程序时必须要结合硬件电路。也可将读回的键值按一定的方式运算后,算出对应的键值进行散转,但这样会增加程序的工作量,因而大多数单片机系统在键盘处理程序中只根据读回的键值进行散转。
(5) 常用的专用键盘芯片。
无论是利用CPU的I/O接口扩展键盘,还是利用扩展I/O芯片扩展键盘,由于均是用普通I/O接口扩展,如果要在单片机的程序中设计专用的键盘程序,特别是矩阵式键盘,其程序相对复杂一些。因而在较复杂一些的单片机系统中可选用专用的键盘芯片设计键盘。现常用的键盘扩展芯片有Intel8279、CH451、ICM7218、PCF8574等。
(6) 单片机对键盘的控制方式。
在单片机应用系统设计中,为了节省硬件,无论是采用独立式键盘还是采用矩阵式键盘,单片机对键盘的控制有以下3种方式。
i 程序控制扫描方式。
这种方式只有单片机空闲时,才可调用键盘扫描子程序,查询键盘的输 入状态是否改变。
ii 定时扫描方式。
单片机对键盘的扫描也可采用定时扫描方式,即单片机每隔一定的时间对键盘扫描一次。在这种方式中,通常采用单片机内部的定时器,产生10ms的定时中断,CPU响应定时中断请求后对键盘进行扫描,以查询键盘是否有键按下。
iii 中断扫描方式。
虽然采用程序查询与定时对键盘的扫描方式时的程序编制简单,但一个单片机系统在运行时的大多数时间里键盘基本是不工作的。为了进一步提高CPU的工作效率,可采用中断方式。当键盘有键动作时产生中断,CPU响应键盘中断后,执行键盘中断程序,判别键盘按下键的键号,并做相应处理。
❺ 51单片机键盘电路的两种方式,各自的优缺点
独立按键优点:可以直接读取,检测占用时间较少,不受其他因素影响
缺点:占用IO口资源较多,每一个按键都独占一个IO口。
矩阵键盘优点:占用IO口资源较少。
缺点:必须扫描检测按键情况,程序复杂,占用时间较多。