实验25
PS2键盘实验
在单片机系统中,经常使用的键盘都是专用键盘.此类键盘为单独设计制作的,成本高、使用硬件连接线多,且可靠性不高,这一状况在那些要求键盘按键较多的应用系统中更为突出.与此相比,在PC系统中广泛使用PS/2键盘具有价格低、通用可靠,且使用连接线少(仅使用2根信号线)的特点,并可满足多种系统的要求.因此在单片机系统中应用PS/2键盘是一种很好的选择.
下面给出了一个在单片机上实现对PS/2键盘支持的硬件连接与驱动程序设计实现.该设计实现了在单片机系统中对PS/2标准104键盘按键输入的支持.使用Keil
C51开发的驱动程序接口和库函数可以方便地移植到其他单片机或嵌入式系统中.所有程序在Keil
uVision2上编译通过,在单片机AT89s52上测试通过.
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.
PS2键盘的硬件原理图,3脚,5脚已经连接到
单片机的P3.4,P3.3

|
PS2实验照片,把程序烧写后,
把计算机的键盘插入XL2000的
JP81,液晶屏的
JP41所有跳线帽全部插入,按电脑键盘
则有相应的显示。(注意试验完不用液晶要去掉JP41所有跳线
帽) |
汇编语言参考程序:
;安装1602液晶, 烧写程序后,把电脑的键盘
;插入键盘接口。按键液晶则有显示.
PS2_CLK BIT P3.3
PS2_DATA BIT P3.4
LCD_RS BIT P2.0 ;LCD控制端口
LCD_RW BIT P2.1
LCD_EN BIT P2.2
LCD_X EQU 27H ;LCD 地址变量
KEY_DATA EQU 30H
EXT1_NUM EQU 31H
BEEP BIT P3.3
RESET BIT 20H.0
H_SCAN BIT 20H.1 ;行扫描标志
SHIFT BIT 20H.3 ;SHIFT标志
BREAK_C BIT 20H.4 ;断码标志
;---------------------------------------------------
ORG 0000H
JMP MAIN
ORG 0013H
JMP EXT1
ORG 0043H
;---------------------------------------------------
MAIN:
MOV SP,#60H
MOV P0,#0FFH
MOV P2,#0FFH
MOV P3,#0FFH
ACALL INIT_LCD
ACALL SET_LCD1
ACALL SET_LCD2
MOV R5,#100 ;延时5S
ACALL DELAY
MOV A,#01H
ACALL WCOM ;清屏
MOV 20H,#00H
MOV LCD_X,#00H
MOV R1,#00H
MOV R2,#00H
MOV KEY_DATA,#00H
CLR IT1 ;外部中断1为低电平触发
SETB EA ;开总中断
SETB EX1 ;开外部中断1
DISP:
JB RESET,MAIN ;程序热复位
JMP DISP
;----------------------------------------------------------
;根据PS2的键值来查找其代码,并取得顺序码。
;然后再根据顺序码来查找ASCII码。
;入口:
;A PS2的键值
;出口:
;A 键值的ASCII码
;R3存放顺序码
;----------------------------------------------------------
PS2KEY_D:
MOV B,A
MOV DPTR,#TABLE_D
MOV R3,#0FFH
KEY_IN1:
INC R3
MOV A,R3
MOVC A,@A+DPTR
CJNE A,B,KEY_IN2
MOV A,R3 ;找到,取顺序码
MOV DPTR,#TABLE_D_ASC ;根据顺序码来查找ASCII码
MOVC A,@A+DPTR
RET
KEY_IN2: CJNE A,#0FFH,KEY_IN1 ;末完,继续查
RET ;0FFH为结束码
;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
PS2KEY_U:
MOV B,A
MOV DPTR,#TABLE_D
MOV R3,#0FFH
KEY_IN3:
INC R3
MOV A,R3
MOVC A,@A+DPTR
CJNE A,B,KEY_IN4
MOV A,R3 ;找到,取顺序码
MOV DPTR,#TABLE_U_ASC ;根据顺序码来查找ASCII码
MOVC A,@A+DPTR
RET
KEY_IN4: CJNE A,#0FFH,KEY_IN3 ;末完,继续查
RET ;0FFH为结束码
;------------------------------------------------------
;外部中断子程序
;R1 中断次数计数
;------------------------------------------------------
EXT1:
CJNE R1,#00H,IN_LOOP ;跳过第一位启动位
JMP IN_LOOP3
IN_LOOP:
CJNE R1,#09H,IN_LOOP1 ;2-9位为数据
IN_LOOP1:
JNC IN_LOOP3 ;大于或等于9,转。
RR A ;
JB PS2_DATA,IN_LOOP2 ;判数据是“1”,还是“0”
ANL A,#7FH ;是“0”最高置位为0
JMP IN_LOOP3
IN_LOOP2:
ORL A,#80H ;是“1”最高置位为1
IN_LOOP3:
INC R1 ;中断计数
JNB PS2_CLK,$ ;等待PS2_CLK变高
IN_LOOP4:
CJNE R1,#0BH,IN_LOOP5 ;一桢数据是否读完?
IN_LOOP5: JNC IN_LOOP6 ;大于或等于11,转。
JMP EXT1_END
IN_LOOP6:
CJNE A,#0F0H,IN_LOOP6E ;断码是否开始
SETB BREAK_C ;置断码标志
MOV R1,#00H
JMP EXT1_END
IN_LOOP6E:
CJNE A,#66H,IN_LOOP6A ;Back Space键功能
JB BREAK_C,IN_LOOP6F
MOV R1,#00H
MOV R4,LCD_X
CJNE R4,#00H,IN_LOOP6G ;判是否到显示起始位
JMP EXT1_END
IN_LOOP6G:
DEC LCD_X ;删除前一位数
MOV A,#20H
ACALL CONV1 ;有INC LCD_X指令
DEC LCD_X ;所以要再减一次
MOV A,#10H ;光标左移一格
ACALL WCOM
JMP EXT1_END
IN_LOOP6F:
CLR BREAK_C
MOV R1,#00H
JMP EXT1_END
IN_LOOP6A:
CJNE A,#12H,IN_LOOP6C ;左SHIFT
JB BREAK_C,IN_LOOP6B
MOV R1,#00H
SETB SHIFT ;置SHIFT标志
JMP EXT1_END
IN_LOOP6B:
CLR SHIFT
CLR BREAK_C
MOV R1,#00H
JMP EXT1_END
IN_LOOP6C:
CJNE A,#59H,IN_LOOP7 ;右SHIFT
JB BREAK_C,IN_LOOP6D
MOV R1,#00H
SETB SHIFT
JMP EXT1_END
IN_LOOP6D:
CLR SHIFT
CLR BREAK_C
MOV R1,#00H
JMP EXT1_END
IN_LOOP7:
CJNE A,#71H,IN_LOOP8 ;DEL键功能,清屏。
JB BREAK_C,IN_LOOP7A
MOV R1,#00H
MOV A,#01H
ACALL WCOM
MOV 20H,#00H ;清所有的标志
MOV LCD_X,#00H ;
JMP EXT1_END
IN_LOOP7A:
CLR BREAK_C
MOV R1,#00H
JMP EXT1_END
IN_LOOP8:
CJNE A,#5AH,IN_LOOP9 ;ENTER键功能,换行。
JB BREAK_C,IN_LOOP8C
MOV R1,#00H
CPL H_SCAN ;换行取反
JNB H_SCAN,IN_LOOP8A
MOV A,#0C0H ;设置 LCD 的第二行地址
ACALL WCOM
ACALL CLR_LINE ;清第二行的内容
MOV A,#0C0H ;设置 LCD 的第二行地址
ACALL WCOM
MOV LCD_X,#00H
JMP IN_LOOP8B
IN_LOOP8A:
MOV A,#80H ;设置 LCD 的第一行地址
ACALL WCOM
ACALL CLR_LINE ;清第一行的内容
MOV A,#80H ;设置 LCD 的第一行地址
ACALL WCOM
MOV LCD_X,#00H
IN_LOOP8B:
JMP EXT1_END
IN_LOOP8C:
CLR BREAK_C
MOV R1,#00H
JMP EXT1_END
IN_LOOP9:
CJNE A,#05H,IN_LOOPA ;F1 帮助键功能
JB BREAK_C,IN_LOOP9A
MOV R1,#00H
ACALL SET_LCD3
ACALL SET_LCD4
JMP EXT1_END
IN_LOOP9A:
CLR BREAK_C
MOV R1,#00H
JMP EXT1_END
IN_LOOPA:
CJNE A,#76H,IN_LOOPB ;ESC键功能
SETB RESET ;置复位标志
JMP EXT1_END
IN_LOOPB:
CLR EX1 ;关中断,准备显示
MOV R1,#00H
JB SHIFT,IN_LOOPBA
ACALL PS2KEY_D
CJNE A,#0FFH,IN_LOOPC ;没有定义的键不显示
JMP IN_LOOPD
IN_LOOPBA:
ACALL PS2KEY_U
CJNE A,#0FFH,IN_LOOPC ;没有定义的键不显示
JMP IN_LOOPD
IN_LOOPC:
ACALL CONV1
ACALL BEEP_BL
IN_LOOPD:
SETB EX1 ;显示完毕,开中断
EXT1_END:
RETI
;----------------------------------------------------------
;PS2键值表(下行键)
;----------------------------------------------------------
TABLE_D:
DB
1CH,32H,21H,23H,24H,2BH,34H,33H,43H,3BH,42H
DB
4BH,3AH,31H,44H,4DH,15H,2DH,1BH,2CH,3CH,2AH
DB 1DH,22H,35H,1AH ;A-Z
DB 45H,16H,1EH,26H,25H,2EH
DB 36H,3DH,3EH,46H ;0-9
DB 0EH,4EH,55H,5DH,29H,54H
DB 5BH,4CH,52H,41H,49H,4AH ;,71H
DB 70H,69H,72H,7AH,6BH ;右边数字键
DB 73H,74H,6CH,75H,7DH ;0-9
DB 0FFH
;----------------------------------------------------------
;键值的ASCII码 (下行键)
;----------------------------------------------------------
TABLE_D_ASC:
DB
61H,62H,63H,64H,65H,66H,67H,68H,69H,6AH,6BH
DB
6CH,6DH,6EH,6FH,70H,71H,72H,73H,74H,75H,76H
DB 77H,78H,79H,7AH ;A-Z
DB 30H,31H,32H,33H,34H,35H
DB 36H,37H,38H,39H ;0-9
DB 60H,2DH,3DH,0A4H,20H,5BH
DB 5DH,3BH,27H,2CH,2EH,2FH ;,2EH
DB 30H,31H,32H,33H,34H,35H ;右边数字键
DB 36H,37H,38H,39H ;0-9
DB 0FFH
;**********************************************************
;上行键ASCII码表
;**********************************************************
TABLE_U_ASC:
DB
41H,42H,43H,44H,45H,46H,47H,48H,49H,4AH,4BH
DB
4CH,4DH,4EH,4FH,50H,51H,52H,53H,54H,55H,56H
DB 57H,58H,59H,5AH ;A-Z
DB
29H,21H,40H,23H,24H,25H,5EH,26H,2AH,28H
;0-9上行键
DB
5CH,5FH,2BH,7CH,20H,7BH,7DH,3AH,22H,3CH,3EH,3FH
;,2EH
DB 30H,31H,32H,33H,34H,35H ;右边数字键
DB 36H,37H,38H,39H ;0-9
;--------------------------------------------------------
;LCD初始化显示子程序
;--------------------------------------------------------
SET_LCD1:
MOV DPTR,#LMESS1 ;指针指到显示信息1
MOV A,#1 ;显示在第一行
CALL LCD_PRINT
RET
SET_LCD2:
MOV DPTR,#LMESS2 ;指针指到显示信息2
MOV A,#2 ;显示在第二行
CALL LCD_PRINT
RET
LMESS1:
DB " PS2 KEYBOARD ",0 ;LCD 第一行显示
LMESS2:
DB " DEMO PROGRAM ",0 ;LCD 第二行显示
SET_LCD3:
MOV DPTR,#LMESS3 ;指针指到显示信息1
MOV A,#1 ;显示在第一行
CALL LCD_PRINT
RET
SET_LCD4:
MOV DPTR,#LMESS4 ;指针指到显示信息2
MOV A,#2 ;显示在第二行
CALL LCD_PRINT
RET
LMESS3:
DB " WELCOME TO ",0 ;LCD 第一行显示
LMESS4:
DB " WWW.WILLAR.COM ",0 ;LCD 第二行显示
;--------------------------------------------------------
;LCD 初始化子程序
;8位数据传送方式,双行显示,字形5*7点阵。
;开显示,显示光标并闪动。
;--------------------------------------------------------
INIT_LCD:
CALL DELAY5MS ;延时15MS
CALL DELAY5MS
CALL DELAY5MS ;等待LCD电源稳定
MOV A,#38H ;双行显示,字形5*7点阵,8位数据。
CALL WCOM_NC ;不检测忙信号
CALL DELAY5MS
MOV A,#38H ;双行显示,字形5*7点阵
CALL WCOM_NC ;不检测忙信号
CALL DELAY5MS
MOV A,#38H ;双行显示,字形5*7点阵
CALL WCOM_NC ;不检测忙信号
CALL DELAY5MS
MOV A,#38H ;双行显示,字形5*7点阵
CALL WCOM ;检测忙信号
CALL DELAY5MS
MOV A,#0FH ;开显示,显示光标,光标闪烁。
CALL WCOM
CALL DELAY5MS
MOV A,#01H ;清除 LCD 显示屏
CALL WCOM
CALL DELAY5MS
RET
;
---------------------------------------------------------
;LCD 显示子程序
;
;H_SCAN 行扫描标志
;----------------------------------------------------------
CONV1:
JB H_SCAN,CONV3 ;
CONV2:
MOV B,LCD_X
ACALL LCDP1
INC LCD_X
MOV R0,LCD_X
CJNE R0,#10H,CONV_END ;一行显示完否?
SETB H_SCAN
MOV LCD_X,#00H ;设置显示起始位置
JMP CONV_END
CONV3:
MOV B,LCD_X
ACALL LCDP2
INC LCD_X
MOV R0,LCD_X
CJNE R0,#11H,CONV_END ;一行显示完否
CLR H_SCAN
MOV A,#01H ;清屏
ACALL WCOM
MOV LCD_X,#00H ;设置显示起始位置
CONV_END:
RET
;--------------------------------------------------------
;清屏子程序
;--------------------------------------------------------
;CLR_DISP:
MOV A,#80H ;设置 LCD 的第一行地址
CALL WCOM ;写入命令
CALL CLR_LINE ;清除该行字符数据
MOV A,#0C0H ;设置 LCD 的第二行地址
CALL WCOM ;写入命令
CALL CLR_LINE ;清除该行字符数据
RET
;--------------------------------------------------------
;在LCD的第一行显示字符
;--------------------------------------------------------
LCDP1:
PUSH ACC ;入栈保护
MOV A,B ;设置显示地址
ADD A,#80H ;设置LCD的第二行地址
CALL WCOM ;写入命令
POP ACC ;由堆栈取出A
CALL WDATA ;写入数据
RET
;--------------------------------------------------------
;在LCD的第二行显示字符
;--------------------------------------------------------
LCDP2:
PUSH ACC ;入栈保护
MOV A,B ;设置显示地址
ADD A,#0C0H ;设置LCD的第二行地址
CALL WCOM ;写入命令
POP ACC ;由堆栈取出A
CALL WDATA ;写入数据
RET
;--------------------------------------------------------
;在LCD的第一行或第二行显示字符
;--------------------------------------------------------
LCD_PRINT:
CJNE A,#1,LINE2 ;判断是否为第一行
LINE1: MOV A,#80H ;设置 LCD 的第一行地址
CALL WCOM ;写入命令
CALL CLR_LINE ;清除该行字符数据
MOV A,#80H ;设置 LCD 的第一行地址
CALL WCOM ;写入命令
JMP FILL
LINE2: MOV A,#0C0H ;设置 LCD 的第二行地址
CALL WCOM ;写入命令
CALL CLR_LINE ;清除该行字符数据
MOV A,#0C0H ;设置 LCD 的第二行地址
CALL WCOM
FILL: CLR A ;填入字符
MOVC A,@A+DPTR ;由信息区取出字符
CJNE A,#00H,LC1 ;判断是否为结束码
RET
LC1: CALL WDATA ;写入数据
INC DPTR ;指针加1
JMP FILL ;继续填入字符
RET
;--------------------------------------------------------
;清除 LCD 指定行的字符
;“空白”的ASCII代码为20H。
;--------------------------------------------------------
CLR_LINE:
MOV R0,#24
CL1: MOV A,#20H ;' '
CALL WDATA
DJNZ R0,CL1
RET
;--------------------------------------------------------
;写控制指令子程序
;写控制指令到LCD
;传入参数: ACC(要写入的指令)
;RS=L,RW=L,E=高脉冲,D0-D7=指令码 。
;--------------------------------------------------------
WCOM:
ACALL CHECKBUSY
WCOM_NC:
MOV P0,A ;写入指令
CLR LCD_EN
NOP
NOP
CLR LCD_RS
CLR LCD_RW
SETB LCD_EN
ACALL DEL_250
CLR LCD_EN
RET
;--------------------------------------------------------
;写数据子程序
;写显示数据到LCD
;传入参数: ACC(要写入的数据)
;RS=H,RW=L,E=高脉冲,D0-D7=数据码 。
;--------------------------------------------------------
WDATA:
ACALL CHECKBUSY
MOV P0,A ;写入数据
CLR LCD_EN
NOP
NOP
SETB LCD_RS
CLR LCD_RW
SETB LCD_EN
CALL DEL_250
CLR LCD_EN
RET
DEL_250:
MOV R7,#125 ;延时250微秒
DJNZ R7,$
RET
;--------------------------------------------------------
;检测LCD控制器忙碌状态
;正常读写操作之前必须检测LCD控制器状态
;BUSY FLAG(P0.7)=1时,忙,不能接收MCU送来的指令和数据。
;BUSY FLAG(P0.7)=0时,闲,能够接收MCU送来的指令和数据。
;读数据时
;RS=L,RW=H,E=H,输出:D0-D7=数据
;--------------------------------------------------------
CHECKBUSY:
PUSH ACC
MOV P0,#0FFH ;置P0口为输入状态
CLR LCD_EN
NOP
NOP
SETB LCD_RW
CLR LCD_RS
SETB LCD_EN
BUSYLOOP:
NOP
JB P0.7,BUSYLOOP
CLR LCD_EN
POP ACC
RET
;---------------------------------------------------------
;延时50MS子程序
;---------------------------------------------------------
DELAY50MS:
MOV R6,#100
DEL2: MOV R7,#250
DJNZ R7,$
DJNZ R6,DEL2
RET
;--------------------------------------------------------
;延时5MS子程序
;--------------------------------------------------------
DELAY5MS:
MOV R6,#25
DEL3: MOV R7,#100
DJNZ R7,$
DJNZ R6,DEL3
RET
;===================================================
;
;蜂鸣器响一声子程序
;
;===================================================
BEEP_BL:
MOV R6,#100
BL1: CALL DEX1
CPL BEEP ;P3.7取反
DJNZ R6,BL1
MOV R5,#8 ;修改此参数可以调整键盘响应速度
CALL DELAY
RET
DEX1: MOV R7,#180
DEX2: NOP
DJNZ R7,DEX2
RET
DELAY: ;延时R5*20MS
MOV R6,#50
D1: MOV R7,#200
DJNZ R7,$
DJNZ R6,D1
DJNZ R5,DELAY
RET
;----------------------------------------------------------
END
|