header
单片机 > 单片机程序设计 > 详情

如何用51单片机接收鼠标的“三轴位移”与按键信息

发布时间:2020-06-06 发布时间:
|
         这里所用的鼠标是PS/2协议的鼠标,测试鼠标为电脑普通光电鼠标(以下简称从机),有一个滚轮,三个按键等。所用编程语言为单片机C语言。用AT89S52作为接收方(以下简称主机),主要负责:接收从机送给主机的信息包并处理、用LCD1602作为显示屏并实时显示位移计数和按键信息,最初无论如何也无法驱动滚轮,经过努力终于完成了这一任务。如下图所示:

         相对来说,主机的程序比较易写,但是,主机(AT89S52)处理这些信息还是相当吃力,这时代码的执行效率就非常值得注意,如果设置鼠标工作在stream模式,即使AT89S52用24Mhz的晶振也会经常出现数据处理失常。所以最好还是让鼠标工作在remote模式,祥细请参考《ps2技术参考》。
         我的初衷是将鼠标的数据作为实现2D定位的依据,也就是说,将鼠标当作一智能小车,
通过无线读取鼠标的位移计数来实现定位。可惜所得的计数偏差太大,比如,将鼠标从A点移到B点,再回到A点,此时的计数值并不是当初在A点时的计数值。后来在论坛里发现有人曾经也有过我这种想法,而他所用的是激光鼠标,同样也是计数偏差过大而无法实现定位。

    我们先要知道现存的总共有两类鼠标,一类就是所谓的2D(二维)鼠标,它就是我们平常用的那种没有滚轮的鼠标,由于这种鼠标在位移上只有X与Y两个方向,所以称之为2D(二维)鼠标;还有一类就是现在比较常见的3D(三维)鼠标,它们中间存在有一个滚轮,而这个滚轮会产生一个额外的Z位移量,因此,它在位移上有X、Y、Z三个方向,所以又称之为3D(三维)鼠标。下面,我们就来看看这两类鼠标发给主机的数据包有什么不同。下面,我们先来看看二维鼠标。
第1个数据包


位0:左键按下标志位,为1表示左键被按下。
位1:右键按下标志位,为1表示右键被按下。
位2:中键按下标志位,为1表示中键被按下。
位3:保留位,总是为1。
位4:X符号标志位,为1表示X位移量为负。
位5:Y符号标志位,为1表示Y位移量为负。
位6:X溢出标志位,为1表示X位移量溢出了。
位7:Y溢出标志位,为1表示Y位移量溢出了。
第2个数据包  X 位移量


第3个数据包  Y 位移量

 

第4个数据包      Z 位移量
 
三维鼠标数据包中第一个数据包每位的含义与二维鼠标数据包中第一个数据包中每位含义完全相同,唯一不同的就在于它每次会多发送一个数据包,即第4个数据包,这个数据包包含了Z的位移量,同X、Y位移量相同的是,它们都是以补码表示的。不过与X及Y位移量不同的是,Z位移量是4位的,其中最高位(第四位)是符号位,因此,Z位移量的有效的范围为:-8~7。而X与Y的位移量是9位的,最高一位(第9位)是符号位,这个符号位在第一个数据包中表示,故,X与Y的位移量的有效范围为:-256~255。
看到这里,你或许有疑问了,系统是怎么来知道到我到底应当接收3个数据包还是接收4个数据包的呢?三维鼠标的标准是由微软制定的,最初,这种三维的鼠标只工作在标准的PS/2模式下,如果你想让它工作在三维模式下,你需要用0xF3这个设置鼠标采样率的命令,按如下的顺序进行操作:
1. 设置鼠标采样率为200
2. 设置鼠标采样率为100
3. 设置鼠标采样率为80
这之后,如果你的鼠标是个三维鼠标,那么,它将转到三维模式下进行工作,这个时候,主机向它发送0xF2(获得鼠标类型ID)命令,你的工作在三维模式下的鼠标将向主机返回它的类型ID,但如果你的鼠标不支持三维模式,即如果你的鼠标只是一个二维鼠标,它返回给主机的类型ID将是0,这样,主机就能够知道现在你用的鼠标是什么类型的鼠标,并由此知道应当接受3个还是4个数据包了。本实验将只操作标准的二维鼠标,如果你有兴趣,你可以对程序进行改动,以让它支持三维鼠标。
    下图是PS2鼠标位移数据包格式:

 
     虽然不能实现定位,但最少我又学多了一种通信协议。以下是程序的所有源代码:
在 "main.c"文件中:
#include
#include
#include"LCD1602.h"
#include
#define uchar   unsigned char
#define sint    signed int
#define uint    unsigned int
#include"鼠标测试2.h"
void display()
{
 signed int nx=move_x,ny=move_y,nz=move_z;
 uchar length=0;
 if(move_x<0) {nx=-move_x;xy[2]='-';}
 else
  xy[2]=' ';
 for(length=7;length>2;length--)
 {
  xy[length]=nx%10+48;
  nx/=10;
 }
 if(move_y<0) {ny=-move_y;xy[10]='-';}
 else
  xy[10]=' ';
 for(length=15;length>10;length--)
 {
  xy[length]=ny%10+48;
  ny/=10;
 }
 if(move_z<0){nz=-move_z;lmr[10]='-';}
 else
  lmr[10]=' ';
 for(length=15;length>10;length--)
 {
  lmr[length]=nz%10+48;
  nz/=10;
 }
 write_command(0x80);
 write_bytes(xy);
 write_command(0x80+0x40);
 write_bytes(lmr);
}
uchar fx=0,fy=0,fz=0,a0=0,a1=0,a2=0,a3=0,fl=0,fm=0,fr=0;
//uchar fxf=0,fyf=0;
void deal_data()
{
 if(fx)    //位5:x符号标志位,为1表示x位移量为负
  move_x-=(256-a1);//x坐标减
 else
  move_x+=a1;//x坐标加 
 if(fy)    //位6:y符号标志位,为1表示y位移量为负
  move_y-=(256-a2);//y坐标减
 else
  move_y+=a2;//y坐标加
 if(fz)
  move_z-=(16-(a3&0x0f));
 else
  move_z+=(a3&0x07);
 if(fr)     //如果点下右键
  {lmr[4]='R';return;}
  else if(fm)   //如果点下中键
   {lmr[4]='M';return;}   
   else if(fl) //如果点下左键  
    {lmr[4]='L';return;}
   else
    {lmr[4]='N';return;}     
}  
void main() 
{
 SDA=1;CLK=1; 
 delay(500);//鼠标上电后在500ms左右就会发给主机0xaa和0x00
 mouse_to_host();//如果没有接收这两个字节,可能鼠标一次上电后,
 mouse_to_host();//不能正常初始化成功或者可以用加长廷时来代替接收
 init_lcd();  //初始化1602
 delay100;  //这个廷时相当重要,否则可能在1602中有乱码出现
 write_command(0x80);//定位光标在第一行
 write_bytes("Initializing....");
 write_command(0x80+0x40);//定位光标在第二行
 write_bytes("  Please wait!  ");
 while(init_mouse());//初始化鼠标
 deal_recive_data();//处理初始化鼠标时返回给主机的部分数据,用以作调试
 write_command(0x80);
 write_bytes(deal_1);//显示初始化鼠标时返回给主机的部分数据,用以作调试
 write_command(0x80+0x40);
 write_bytes(deal_2);//显示初始化鼠标时返回给主机的部分数据,用以作调试
 write_command(0x80+0x40);
 delay(500);
 write_bytes("  Mouse Normal  ");
 delay(500);  
 
 write_command(0x80);
 write_bytes("Test PS/2 mouse.");
 write_command(0x80+0x40);
 write_bytes("Copyright-11-28-");
 while(1)
 {
  host_to_mouse(0xeb);//在remote模式中,主机每发送一个0xeb命令,从机
  mouse_to_host();  //将应答0xfa,之后就是数据包
  a0=mouse_to_host();//第一个数据包
  fr=a0&0x02;   //右键
  fm=a0&0x04;   //中键
  fl=a0&0x01;   //左键
  fx=a0&0x10;   //x的符号位
  fy=a0&0x20;   //y的符号位
  
  a1=mouse_to_host();//第二个数据包 x位移量
  a2=mouse_to_host();//第三个数据包 y位移量
  a3=mouse_to_host();//第四个数据包 z位移量
  fz=a3&0x08;   //z的符号位
  /*fxf=a0&0x40+0x30;
  fyf=a0&0x80+0x30;
  lmr[6]=fxf;
  lmr[7]=fyf;*/
  deal_data();   //将x,y,z,fl,fr,fm加入字符串中
  display();   //加入之后再一次性刷新显示
 }
}
/*
第1个数据包
位0:左键按下标志位,为1表示左键被按下。
位1:右键按下标志位,为1表示右键被按下。
位2:中键按下标志位,为1表示中键被按下。
位3:保留位,总是为1。
位4:X符号标志位,为1表示X位移量为负。
位5:Y符号标志位,为1表示Y位移量为负。
位6:X溢出标志位,为1表示X位移量溢出了。
位7:Y溢出标志位,为1表示Y位移量溢出了。
三维鼠标数据包中第一个数据包每位的含义与
二维鼠标数据包中第一个数据包中每位含义完全相同,
唯一不同的就在于它每次会多发送一个数据包,
即第4个数据包,这个数据包包含了Z的位移量,
同X、Y位移量相同的是,它们都是以补码表示的。
不过与X及Y位移量不同的是,Z位移量是4位的,
其中最高位(第四位)是符号位,因此,Z位移量的有效的范围为:-8~7。
而X与Y的位移量是9位的,最高一位(第9位)是符号位,
这个符号位在第一个数据包中表示,
故,X与Y的位移量的有效范围为:-256~255。*/
 
在"LCD1602.h"文件中:
#define uint unsigned int 
#define uchar unsigned char
sbit RS=P2^0;    //寄存器选择位,将RS位定义为P2.0引脚
sbit RW=P2^1;    //读写选择位,将RW位定义为P2.1引脚
sbit LCDEN=P2^2;     //使能信号位,将E位定义为P2.2引脚
void delay(uint z)
{
 uint x,y;
 for(x=z;x>0;x--)
  for(y=110;y>0;y--);
}
void write_command(char command)//发送命令
{
 RS=0;
 P0=command;
 LCDEN=1;
 delay(3);
 LCDEN=0;
 RS=1; 
}
void write_dat(char dat)  //发送单个字节
{
 RS=1;
 P0=dat;
 LCDEN=1;
 delay(1);
 LCDEN=0;
}
void init_lcd()   //初始化1602
{
 RW=0;
 delay(5);
 write_command(0x38);//设置工作方式
 delay(5);
 write_command(0x0f);//设置显示、光标和闪烁开、关
 delay(5);
 write_command(0x06);//设置光标、画面移动方式
 delay(5);
 write_command(0x80);//设置光标位置 
 delay(5);
}
void write_bytes(char *ch)//发送字符串
{
 while(*ch)
  write_dat(*ch++);
}
在"鼠标测试2.h"文件中:
#include
#define delay10 {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();}//延时10us     
#define delay100 {delay10 delay10 delay10 delay10 delay10 delay10 delay10 delay10 delay10 delay10;}
sbit SDA=P3^2;  //P3^3 //int0号中断(本程序不用中断接收方式) 
sbit CLK=P3^3;
bit pp=0,ACK=0;
uchar recv=0;
signed int move_x=00000;//存放横坐标
signed int move_y=00000;//存放纵坐标
signed int move_z=00000; //总共接收到的字节总数
unsigned char data xy[16]=    "x:      y:      ";  //2  10
unsigned char data lmr[16]=   "key:N   z:      ";  //5  10
unsigned char idata deal_1[20]="                ";  //用来存放初始化鼠标时鼠标返回的信息
unsigned char idata deal_2[20]="                ";
uchar idata ret_ini_dat[18]=0;   //间接寻址片内数据存储区,可访问片内全部RAM空间(256bytes)
          
void host_to_mouse(uchar cmd)
{
 uchar i;
 CLK=0;
 delay100;
 delay100;
 ACC=cmd; 
 pp=~P;  //获得奇偶校验位
 SDA=0;
 CLK=1;
 for(i=0;i<8;i++)
 {
  while(CLK!=0);
  SDA=cmd&0x01;
  cmd>>=1;
  while(CLK!=1);
 }
 while(CLK!=0);
 SDA=pp;  //发送奇偶校验位
 while(CLK!=1);
 while(CLK!=0);
 SDA=1;
 while(CLK!=1);
 while(CLK!=0);
 ACK=SDA; //接收应答位
 while(CLK!=1);
}
uchar mouse_to_host()
{
 uchar i,temp=0;
 while(CLK!=0);  //等待低电平
 while(SDA!=0);
 while(CLK!=1);  //等待高电平
 for(i=0;i<8;i++)
 {
  temp>>=1;
  while(CLK!=0);
  if(SDA==1)
   temp=0x80|temp;
  while(CLK!=1); 
 }
 while(CLK!=0);
 pp=SDA;    //接收奇偶校验位
 while(CLK!=1);
 while(CLK!=0);
 while(CLK!=1);
 ACC=temp;
 if(~P==pp)   //如果检验成功则返回接收到的数据,否则返回0
 {
  recv=temp;
  return temp;
 }
 return 0;
}
   //用0xf0代替相邻的0xc8,0x03可使鼠标进入remote模式,默认为stream模式
uchar code num[15]={0xf3,0xc8,0xf3,0x64, //0xc8 200/sec,0x64 100/sec
        0x50,0xc8,0xf2, //0x50 80/sec,0xf2读设备类型
        0xf3,0xC8,0xf2,0XF0, //0x0a 10/sec,0xf2读设备类型,0x03滚轮分辨率8count/mm
        0xe6,0xf3,0x28,0xf4};//0XE6 设置缩放比率为1:1,0x28 40/sec
    //(0xe8,0xxx)设置滚轮分辨率,/0xe8,0x03/
/*
uchar code num[13]={0xf3,0xc8,0xf3,0x64,//
        0xf3,0x50,0xf2,0xe8,0x03,,
     0xe6,0xf3,0x28,0xf4};//
*/  //微软支持第4 和第5 键的Intellimouse 的驱动
/*uchar code num[17]={0xf3,0xc8,0xf3,0x64,
        0xf3,0x50,0xf2,0xf3,
        0xc8,0xf3,0xc8,0xf3,
        0xc8,0xf3,0x50,0xf2,0x04};*/
bit init_mouse()
{
 uchar i=0;
 bit good=1;
 for(i=0;i<3;i++)
 {
  host_to_mouse(0xff); //复位命令,鼠标连续返回三个字节
  ret_ini_dat[0]=mouse_to_host();//鼠标返回0xfa
  ret_ini_dat[1]=mouse_to_host();//鼠标返回0xaa
  ret_ini_dat[2]=mouse_to_host();//鼠标返回0x00
 }
 for(i=0;i<15;i++)
 {
     host_to_mouse(num[i]);
  ret_ini_dat[i+3]=mouse_to_host();
 } 
 return good=0;
}
void deal_recive_data()//处理初始化鼠标时返回给主机的部分数据,用以作调试
{      //处理成十六进制和ASCII码
 uchar i=0,j=0,xx=0;
 for(i=0;i<10;i++)
 {
  xx=ret_ini_dat[i];
  if(((xx>>4)&0x0f)>=0x00 && ((xx>>4)&0x0f)<=0x09)
   deal_1[j++]=((xx>>4)&0x0f)+0x30;
  else
   deal_1[j++]=((xx>>4)&0x0f)+55;
  if((xx&0x0f)>=0x00 && (xx&0x0f)<=0x09)
   deal_1[j++]=(xx&0x0f)+0x30;
  else
   deal_1[j++]=(xx&0x0f)+55;
 }
 j=0;
 for(i=10;i<20;i++)
 {
  xx=ret_ini_dat[i];
  if(((xx>>4)&0x0f)>=0x00 && ((xx>>4)&0x0f)<=0x09)
   deal_2[j++]=((xx>>4)&0x0f)+0x30;
  else
   deal_2[j++]=((xx>>4)&0x0f)+55;
  if((xx&0x0f)>=0x00 && (xx&0x0f)<=0x09)
   deal_2[j++]=(xx&0x0f)+0x30;
  else
   deal_2[j++]=(xx&0x0f)+55;
 }
}



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
第一次偿试51单片机做个100分钟倒计时定时器
footer