原创 【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第三十八章 PS2鼠标实验

2013-3-27 22:58 1803 20 20 分类: MCU/ 嵌入式 文集: STM32学习

  

第三十八章  PS2鼠标实验     

PS/2作为电脑的标准输入接口,用于鼠标键盘等设备。PS/2只需要一个简单的接口(2个IO口),就可以外扩鼠标、键盘等,是单片机理想的输入外扩方式。

ALIENTEK战舰STM32开发板也自带了一个PS/2接口,可以用来驱动标准的鼠标、键盘等外设,也可以用来驱动一些PS/2接口的小键盘,条码扫描*等。在本章中,我们将向大家介绍,如何在ALIENTEK战舰STM32开发板上,通过PS/2接口来驱动电脑鼠标。本章分为如下几个部分:

38.1 PS/2简介

38.2 硬件设计

38.3 软件设计

38.4 下载验证


 

38.1 PS/2简介

PS/2是电脑上常见的接口之一,用于鼠标、键盘等设备。一般情况下,PS/2接口的鼠标为绿色,键盘为紫色。

PS/2接口是输入装置接口,而不是传输接口。所以PS2口根本没有传输速率的概念,只有扫描速率。在Windows环境下,ps/2鼠标的采样率默认为60次/秒,USB鼠标的采样率为120次/秒。较高的采样率理论上可以提高鼠标的移动精度。

物理上的PS/2端口可有2种,一种是5脚的,一种是六脚的。下面给出这两种PS/2接口的引脚定义图,如图38.1.1所示:

 


图38.1.1  PS/2引脚定义图

从图38.1.1可以看出,不管是5脚还是6脚的PS/2接头,都是有4根有用的线连接:时钟线、数据线、电源线、地线。PS/2设备的电源是5V的,而数据线和时钟线均是集电极开路的,这两根信号线都需要接一个上拉电阻(开发板上使用的是10K)。

PS/2 鼠标和键盘遵循一种双向同步串行协议,换句话说每次数据线上发送一位数据并且每在时钟线上发一个脉冲就被读入。键盘/鼠标可以发送数据到主机,而主机也可以发送数据到设备,但主机总是在总线上有优先权,它可以在任何时候抑制来自于键盘/鼠标的通讯,只要把时钟拉低即可。

从设备到主机的数据在时钟信号的下降沿被主机读取,而从主机到设备的数据在时钟信号的上升沿被设备读取。不论通信方向如何,时钟总是由设备产生的,最大的时钟频率为33Khz,大多数设备工作在10~20Khz。

鼠标键盘,采用的是一种每帧包含11/12位的串行协议,这些位的含义如表38.1.1所示:

 


表38.1.2 鼠标/键盘帧数据格式

表38.1.2中校验位的含义是:如果数据位中包含偶数个1,则校验位为1;如果数据位中包含奇数个1,则校验位为0。数据位中的1的个数加上校验位总为奇数(奇校验),用于数据侦错。当主机发送数据给键盘/鼠标的时候,设备会发送一个握手信号来应答数据已经被收到了,该位不会出现在设备到主机的通信中。

设备到主机的通信过程:

正常情况下数据线和时钟线都是高电平,当键盘/鼠标有数据要发送时,它先检测时钟线,确认时钟线是高电平。如果不是,则是主机抑制了通信,设备必须缓冲任何要发送的数据,直到重新获得总线的控制权(键盘有16 字节的缓冲区而鼠标的缓冲区仅存储最后一个要发送的数据包)。如果时钟线是高电平,设备就可以开始传送数据了。

    设备到主机的数据在时钟线的下降沿被主机读入,如图38.1.2所示:

 


图38.1.2 设备到主机通信时序图

主机可以在设备发送数据的时候拉低时钟线来来放弃当前数据的传送。

主机到设备的通信过程:

主机到设备的通信与设备到主机的通信有点不同,因为PS/2的时钟总是由设备产生的,如果主机要发送数据,则它必须首先把时钟线和数据线设置为请求发送状态。请求发送状态通过如下过程实现:

1.拉低时钟线至少100us以抑制通信。

2.拉低数据线,以应用“请求发送”,然后释放时钟线。

设备在不超过10ms的时间内就会检测这个状态,当设备检测到这个状态后,它将开始产生时钟信号,并且在设备提供的时钟脉冲驱动下输入八个数据位和一个停止位。主机仅当时钟线为低的时候改变数据线,而数据在时钟脉冲的上升沿被锁存,这与发生在设备到主机通讯的过程中正好相反。

主机到设备的通信时序图如图38.1.3所示:

 


图38.1.3 主机到设备通信时序图

以上简单介绍了PS/2协议的通信过程,更多的介绍请参考《PS/2技术参考》一文。本章我们要驱动一个PS/2鼠标,所以接下来简单介绍一下PS/2鼠标的相关信息。

标准的PS/2鼠标支持下面的输入:X(左右)位移、Y(上下)位移、左键、中键和右键。但是我们目前用到鼠标大都还有滚轮,有的还有更多的按键,这就是所谓的Intellimouse。它支持5个鼠标按键和三个位移轴(左右、上下和滚轮)。

标准的鼠标有两个计数器保持位移的跟踪:X 位移计数器和Y 位移计数器。可存放9 位的2 进制补码,并且每个计数器都有相关的溢出标志。它们的内容连同三个鼠标按钮的状态一起以三字节移动数据包的形式发送给主机,位移计数器表示从最后一次位移数据包被送往主机后所发生的位移量。

标准PS/2鼠标发送唯一和按键信息以3字节的数据包格式发给主机,三个数据包的意义如图38.1.4所示:

 


图38.1.4 标准鼠标位移数据包格式

位移计数器是一个9位2的补码整数,其最高位作为符号位出现在位移数据包的第一个字节里。这些计数器在鼠标读取输入发现有位移时被更新。这些值是自从最后一次发送位移数据包给主机后位移的累计量(即最后一次包发给主机后位移计数器被复位位移计数器可表示的值的范围是-255 到+255)。如果超过了范围,相应的溢出位就会被置位,并在复位之前,计数器不会再增减。

而所谓的Intellimouse,因为多了2个按键和一个滚轮,所以Intellimouse的一个位移数据包由4个字节组成,如图38.1.5所示:

 


图38.1.5 Intellimouse鼠标位移数据包格式

Z0-Z3是2的补码,用于表示从上次数据报告以来滚轮的位移量。有效范围从-8 到+7,第四键如果按下,则4th Btn位被置位,如果没有按下,则4th Btn位为0。第五键也与此类似。

鼠标的介绍我们就简单的介绍到这里,详细的说明请参考光盘《PS/2技术参考》第三章 PS/2鼠标接口(第36页)。

38.2 硬件设计

本章实验功能简介:开机的时候先检测是否有鼠标接入,如果没有/检测错误,则提示错误代码。只有在检测到PS/2鼠标之后才开始后续操作,当检测到鼠标之后,就在LCD上显示鼠标位移数据包的内容,并转换为坐标值,在LCD上显示,如果有按键按下,则会提示按下的是哪个按键。同样我们也是用LED0来指示程序正在运行。

所要用到的硬件资源如下:

1)  指示灯DS0  

2) TFTLCD模块

3)  PS/2鼠标

       本章需要用到一个PS/2接口的鼠标,大家得自备一个。下面我来看一看开发板上的PS/2接口与STM32的连接电路,如图38.2.1所示:

 


图38.2.1 PS/2接口与STM32的连接电路图

可以看到,PS/2接口与STM32的连接仅仅2个IO口,其中PS_CLK连接在PC11上面,而PS_DAT则连接在PC10上面,这两个口和SDIO_D2和SDIO_D3复用了,所以在SDIO使用的时候,就不能使用PS/2设备了,这个在使用的时候大家要注意一下。

38.3 软件设计

打开上一章的工程,首先在HARDWARE文件夹下新建PS2和MOUSE两个文件夹。在PS2文件夹里面新建ps2.c和ps2.h两个文件。然后在MOUSE文件夹下新建mouse.c和mouse.h两个文件。并将这个两个文件夹加入头文件包含路径。

打开ps2.c,输入如下代码:

#include "ps2.h"

#include "usart.h"

//PS2产生的时钟频率在10~20Khz(最大33K)

//高/低电平的持续时间为25~50us之间.                         

//PS2_Status当前状态标志

//[7]:接收到一次数据;[6]:校验错误;[5:4]:当前工作的模式;[3:0]:收到的数据长度;               

u8 PS2_Status=CMDMODE; //默认为命令模式

u8 PS2_DATA_BUF[16];   //ps2数据缓存区

//位计数器

u8 BIT_Count=0;

//中断15~10处理函数

//每11个bit,为接收1个字节

//每接收完一个包(11位)后,设备至少会等待50ms再发送下一个包

//只做了鼠标部分,键盘部分暂时未加入

void EXTI15_10_IRQHandler(void)

{            

       static u8 tempdata=0;

       static u8 parity=0;           

       if(EXTI->PR&(1<<11))//中断11产生了相应的中断

       {

              EXTI->PR=1<<11;  //清除LINE11上的中断标志位

              if(BIT_Count==0) {parity=0; tempdata=0;}

              BIT_Count++;   

              if(BIT_Count>1&&BIT_Count<10)//这里获得数据

              {      

                     tempdata>>=1;

                     if(PS2_SDA)

                     {

                            tempdata|=0x80;

                            parity++;//记录1的个数

                     }  

              }else if(BIT_Count==10)//得到校验位

              {

                     if(PS2_SDA)parity|=0x80;//校验位为1

              }       

              if(BIT_Count==11)//接收到1个字节的数据了

              {

                    BIT_Count=parity&0x7f;//取得1的个数     

              if(((BIT_Count%2==0)&&(parity&0x80))||((BIT_Count%2==1)&&(parity&0x80)

==0)) //奇偶校验OK

                     {                                    

                            //PS2_Status|=1<<7;//标记得到数据      

                            BIT_Count=PS2_Status&0x0f;           

                            PS2_DATA_BUF[BIT_Count]=tempdata;//保存数据

                            if(BIT_Count<15)PS2_Status++;    //数据长度加1

                            BIT_Count=PS2_Status&0x30;          //得到模式    

                            switch(BIT_Count)

                            {

                                   case CMDMODE://命令模式下,每收到一个字节都会产生接收完成

                                          PS2_Dis_Data_Report();//禁止数据传输

                                          PS2_Status|=1<<7; //标记得到数据

                                          break;

                                   case KEYBOARD: break;

                                   case MOUSE:

                                          if(MOUSE_ID==0)//标准鼠标,3个字节

                                          {

                                                 if((PS2_Status&0x0f)==3)

                                                 {

                                                        PS2_Status|=1<<7;//标记得到数据

                                                        PS2_Dis_Data_Report();//禁止数据传输

                                                 }

                                          }else if(MOUSE_ID==3)//扩展鼠标,4个字节

                                          {

                                                 if((PS2_Status&0x0f)==4)

                                                 {

                                                        PS2_Status|=1<<7;//标记得到数据

                                                        PS2_Dis_Data_Report();//禁止数据传输

                                                 }

                                          }    

                                          break;

                            }                         

                     }else

                     {

                            PS2_Status|=1<<6;//标记校验错误

                            PS2_Status&=0xf0;//清除接收数据计数器

                     }

                     BIT_Count=0;

              }             

       }                    

}

//禁止数据传输

//把时钟线拉低,禁止数据传输      

void PS2_Dis_Data_Report(void)

{

       PS2_Set_Int(0);   //关闭中断

       PS2_SET_SCL_OUT();//设置SCL为输出

       PS2_SCL_OUT=0;    //抑制传输

}

//使能数据传输

//释放时钟线            

void PS2_En_Data_Report(void)

{

       PS2_SET_SCL_IN(); //设置SCL为输入

       PS2_SET_SDA_IN(); //SDA IN

       PS2_SCL_OUT=1;    //上拉  

       PS2_SDA_OUT=1;

       PS2_Set_Int(1);   //开启中断

}

//非常抱歉,由于编辑器篇幅所限,剩下内容,请看附件。

文章评论0条评论)

登录后参与讨论
我要评论
0
20
关闭 站长推荐上一条 /2 下一条