如何用51單片機接收鼠標的“三軸位移”與按鍵信息
相對來說,主機的程序比較易寫,但是,主機(AT89S52)處理這些信息還是相當(dāng)吃力,這時代碼的執(zhí)行效率就非常值得注意,如果設(shè)置鼠標工作在stream模式,即使AT89S52用24Mhz的晶振也會經(jīng)常出現(xiàn)數(shù)據(jù)處理失常。所以最好還是讓鼠標工作在remote模式,祥細請參考《ps2技術(shù)參考》。
我的初衷是將鼠標的數(shù)據(jù)作為實現(xiàn)2D定位的依據(jù),也就是說,將鼠標當(dāng)作一智能小車,
通過無線讀取鼠標的位移計數(shù)來實現(xiàn)定位??上玫挠嫈?shù)偏差太大,比如,將鼠標從A點移到B點,再回到A點,此時的計數(shù)值并不是當(dāng)初在A點時的計數(shù)值。后來在論壇里發(fā)現(xiàn)有人曾經(jīng)也有過我這種想法,而他所用的是激光鼠標,同樣也是計數(shù)偏差過大而無法實現(xiàn)定位。
我們先要知道現(xiàn)存的總共有兩類鼠標,一類就是所謂的2D(二維)鼠標,它就是我們平常用的那種沒有滾輪的鼠標,由于這種鼠標在位移上只有X與Y兩個方向,所以稱之為2D(二維)鼠標;還有一類就是現(xiàn)在比較常見的3D(三維)鼠標,它們中間存在有一個滾輪,而這個滾輪會產(chǎn)生一個額外的Z位移量,因此,它在位移上有X、Y、Z三個方向,所以又稱之為3D(三維)鼠標。下面,我們就來看看這兩類鼠標發(fā)給主機的數(shù)據(jù)包有什么不同。下面,我們先來看看二維鼠標。
第1個數(shù)據(jù)包 |
位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個數(shù)據(jù)包X位移量 | |
|
第4個數(shù)據(jù)包Z位移量
看到這里,你或許有疑問了,系統(tǒng)是怎么來知道到我到底應(yīng)當(dāng)接收3個數(shù)據(jù)包還是接收4個數(shù)據(jù)包的呢?三維鼠標的標準是由微軟制定的,最初,這種三維的鼠標只工作在標準的PS/2模式下,如果你想讓它工作在三維模式下,你需要用0xF3這個設(shè)置鼠標采樣率的命令,按如下的順序進行操作:
1.設(shè)置鼠標采樣率為200
2.設(shè)置鼠標采樣率為100
3.設(shè)置鼠標采樣率為80
這之后,如果你的鼠標是個三維鼠標,那么,它將轉(zhuǎn)到三維模式下進行工作,這個時候,主機向它發(fā)送0xF2(獲得鼠標類型ID)命令,你的工作在三維模式下的鼠標將向主機返回它的類型ID,但如果你的鼠標不支持三維模式,即如果你的鼠標只是一個二維鼠標,它返回給主機的類型ID將是0,這樣,主機就能夠知道現(xiàn)在你用的鼠標是什么類型的鼠標,并由此知道應(yīng)當(dāng)接受3個還是4個數(shù)據(jù)包了。本實驗將只操作標準的二維鼠標,如果你有興趣,你可以對程序進行改動,以讓它支持三維鼠標。
下圖是PS2鼠標位移數(shù)據(jù)包格式:
雖然不能實現(xiàn)定位,但最少我又學(xué)多了一種通信協(xié)議。以下是程序的所有源代碼:
在"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左右就會發(fā)給主機0xaa和0x00
mouse_to_host();//如果沒有接收這兩個字節(jié),可能鼠標一次上電后,
mouse_to_host();//不能正常初始化成功或者可以用加長廷時來代替接收
init_lcd();//初始化1602
delay100;//這個廷時相當(dāng)重要,否則可能在1602中有亂碼出現(xiàn)
write_command(0x80);//定位光標在第一行
write_bytes("Initializing....");
write_command(0x80+0x40);//定位光標在第二行
write_bytes(" Please wait! ");
while(init_mouse());//初始化鼠標
deal_recive_data();//處理初始化鼠標時返回給主機的部分數(shù)據(jù),用以作調(diào)試
write_command(0x80);
write_bytes(deal_1);//顯示初始化鼠標時返回給主機的部分數(shù)據(jù),用以作調(diào)試
write_command(0x80+0x40);
write_bytes(deal_2);//顯示初始化鼠標時返回給主機的部分數(shù)據(jù),用以作調(diào)試
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模式中,主機每發(fā)送一個0xeb命令,從機
mouse_to_host();//將應(yīng)答0xfa,之后就是數(shù)據(jù)包
a0=mouse_to_host();//第一個數(shù)據(jù)包
fr=a0&0x02;//右鍵
fm=a0&0x04;//中鍵
fl=a0&0x01;//左鍵
fx=a0&0x10;//x的符號位
fy=a0&0x20;//y的符號位
a1=mouse_to_host();//第二個數(shù)據(jù)包 x位移量
a2=mouse_to_host();//第三個數(shù)據(jù)包 y位移量
a3=mouse_to_host();//第四個數(shù)據(jù)包 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個數(shù)據(jù)包
位0:左鍵按下標志位,為1表示左鍵被按下。
位1:右鍵按下標志位,為1表示右鍵被按下。
位2:中鍵按下標志位,為1表示中鍵被按下。
位3:保留位,總是為1。
位4:X符號標志位,為1表示X位移量為負。
位5:Y符號標志位,為1表示Y位移量為負。
位6:X溢出標志位,為1表示X位移量溢出了。
位7:Y溢出標志位,為1表示Y位移量溢出了。
三維鼠標數(shù)據(jù)包中第一個數(shù)據(jù)包每位的含義與
二維鼠標數(shù)據(jù)包中第一個數(shù)據(jù)包中每位含義完全相同,
唯一不同的就在于它每次會多發(fā)送一個數(shù)據(jù)包,
即第4個數(shù)據(jù)包,這個數(shù)據(jù)包包含了Z的位移量,
同X、Y位移量相同的是,它們都是以補碼表示的。
不過與X及Y位移量不同的是,Z位移量是4位的,
其中最高位(第四位)是符號位,因此,Z位移量的有效的范圍為:-8~7。
而X與Y的位移量是9位的,最高一位(第9位)是符號位,
這個符號位在第一個數(shù)據(jù)包中表示,
故,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)//發(fā)送命令
{
RS=0;
P0=command;
LCDEN=1;
delay(3);
LCDEN=0;
RS=1;
}
void write_dat(char dat)//發(fā)送單個字節(jié)
{
RS=1;
P0=dat;
LCDEN=1;
delay(1);
LCDEN=0;
}
void init_lcd()//初始化1602
{
RW=0;
delay(5);
write_command(0x38);//設(shè)置工作方式
delay(5);
write_command(0x0f);//設(shè)置顯示、光標和閃爍開、關(guān)
delay(5);
write_command(0x06);//設(shè)置光標、畫面移動方式
delay(5);
write_command(0x80);//設(shè)置光標位置
delay(5);
}
void write_bytes(char *ch)//發(fā)送字符串
{
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; //總共接收到的字節(jié)總數(shù)
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; //間接尋址片內(nèi)數(shù)據(jù)存儲區(qū),可訪問片內(nèi)全部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;//發(fā)送奇偶校驗位
while(CLK!=1);
while(CLK!=0);
SDA=1;
while(CLK!=1);
while(CLK!=0);
ACK=SDA;//接收應(yīng)答位
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)//如果檢驗成功則返回接收到的數(shù)據(jù),否則返回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讀設(shè)備類型
0xf3,0xC8,0xf2,0XF0, //0x0a 10/sec,0xf2讀設(shè)備類型,0x03滾輪分辨率8count/mm
0xe6,0xf3,0x28,0xf4};//0XE6 設(shè)置縮放比率為1:1,0x28 40/sec
//(0xe8,0xxx)設(shè)置滾輪分辨率,/0xe8,0x03/
/*
uchar code num[13]={0xf3,0xc8,0xf3,0x64,//
0xf3,0x50,0xf2,0xe8,0x03,,
0xe6,0xf3,0x28,0xf4};//
*///微軟支持第4 和第5 鍵的Intellimouse 的驅(qū)動
/*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); //復(fù)位命令,鼠標連續(xù)返回三個字節(jié)
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()//處理初始化鼠標時返回給主機的部分數(shù)據(jù),用以作調(diào)試
{//處理成十六進制和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;
}
}
評論