新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 如何用51單片機(jī)接收鼠標(biāo)的“三軸位移”與按鍵信息

如何用51單片機(jī)接收鼠標(biāo)的“三軸位移”與按鍵信息

作者: 時(shí)間:2016-11-17 來源:網(wǎng)絡(luò) 收藏
這里所用的鼠標(biāo)是PS/2協(xié)議的鼠標(biāo),測(cè)試鼠標(biāo)為電腦普通光電鼠標(biāo)(以下簡稱從機(jī)),有一個(gè)滾輪,三個(gè)按鍵等。所用編程語言為單片機(jī)C語言。用AT89S52作為接收方(以下簡稱主機(jī)),主要負(fù)責(zé):接收從機(jī)送給主機(jī)的信息包并處理、用LCD1602作為顯示屏并實(shí)時(shí)顯示位移計(jì)數(shù)和按鍵信息,最初無論如何也無法驅(qū)動(dòng)滾輪,經(jīng)過努力終于完成了這一任務(wù)。如下圖所示:

相對(duì)來說,主機(jī)的程序比較易寫,但是,主機(jī)(AT89S52)處理這些信息還是相當(dāng)吃力,這時(shí)代碼的執(zhí)行效率就非常值得注意,如果設(shè)置鼠標(biāo)工作在stream模式,即使AT89S52用24Mhz的晶振也會(huì)經(jīng)常出現(xiàn)數(shù)據(jù)處理失常。所以最好還是讓鼠標(biāo)工作在remote模式,祥細(xì)請(qǐng)參考《ps2技術(shù)參考》。
我的初衷是將鼠標(biāo)的數(shù)據(jù)作為實(shí)現(xiàn)2D定位的依據(jù),也就是說,將鼠標(biāo)當(dāng)作一智能小車
通過無線讀取鼠標(biāo)的位移計(jì)數(shù)來實(shí)現(xiàn)定位。可惜所得的計(jì)數(shù)偏差太大,比如,將鼠標(biāo)從A點(diǎn)移到B點(diǎn),再回到A點(diǎn),此時(shí)的計(jì)數(shù)值并不是當(dāng)初在A點(diǎn)時(shí)的計(jì)數(shù)值。后來在論壇里發(fā)現(xiàn)有人曾經(jīng)也有過我這種想法,而他所用的是激光鼠標(biāo),同樣也是計(jì)數(shù)偏差過大而無法實(shí)現(xiàn)定位。

我們先要知道現(xiàn)存的總共有兩類鼠標(biāo),一類就是所謂的2D(二維)鼠標(biāo),它就是我們平常用的那種沒有滾輪的鼠標(biāo),由于這種鼠標(biāo)在位移上只有X與Y兩個(gè)方向,所以稱之為2D(二維)鼠標(biāo);還有一類就是現(xiàn)在比較常見的3D(三維)鼠標(biāo),它們中間存在有一個(gè)滾輪,而這個(gè)滾輪會(huì)產(chǎn)生一個(gè)額外的Z位移量,因此,它在位移上有X、Y、Z三個(gè)方向,所以又稱之為3D(三維)鼠標(biāo)。下面,我們就來看看這兩類鼠標(biāo)發(fā)給主機(jī)的數(shù)據(jù)包有什么不同。下面,我們先來看看二維鼠標(biāo)。
第1個(gè)數(shù)據(jù)包


位0:左鍵按下標(biāo)志位,為1表示左鍵被按下。
位1:右鍵按下標(biāo)志位,為1表示右鍵被按下。
位2:中鍵按下標(biāo)志位,為1表示中鍵被按下。
位3:保留位,總是為1。
位4:X符號(hào)標(biāo)志位,為1表示X位移量為負(fù)。
位5:Y符號(hào)標(biāo)志位,為1表示Y位移量為負(fù)。
位6:X溢出標(biāo)志位,為1表示X位移量溢出了。
位7:Y溢出標(biāo)志位,為1表示Y位移量溢出了。
第2個(gè)數(shù)據(jù)包X位移量


第3個(gè)數(shù)據(jù)包Y位移量


第4個(gè)數(shù)據(jù)包Z位移量
三維鼠標(biāo)數(shù)據(jù)包中第一個(gè)數(shù)據(jù)包每位的含義與二維鼠標(biāo)數(shù)據(jù)包中第一個(gè)數(shù)據(jù)包中每位含義完全相同,唯一不同的就在于它每次會(huì)多發(fā)送一個(gè)數(shù)據(jù)包,即第4個(gè)數(shù)據(jù)包,這個(gè)數(shù)據(jù)包包含了Z的位移量,同X、Y位移量相同的是,它們都是以補(bǔ)碼表示的。不過與X及Y位移量不同的是,Z位移量是4位的,其中最高位(第四位)是符號(hào)位,因此,Z位移量的有效的范圍為:-8~7。而X與Y的位移量是9位的,最高一位(第9位)是符號(hào)位,這個(gè)符號(hào)位在第一個(gè)數(shù)據(jù)包中表示,故,X與Y的位移量的有效范圍為:-256~255。
看到這里,你或許有疑問了,系統(tǒng)是怎么來知道到我到底應(yīng)當(dāng)接收3個(gè)數(shù)據(jù)包還是接收4個(gè)數(shù)據(jù)包的呢?三維鼠標(biāo)的標(biāo)準(zhǔn)是由微軟制定的,最初,這種三維的鼠標(biāo)只工作在標(biāo)準(zhǔn)的PS/2模式下,如果你想讓它工作在三維模式下,你需要用0xF3這個(gè)設(shè)置鼠標(biāo)采樣率的命令,按如下的順序進(jìn)行操作:
1.設(shè)置鼠標(biāo)采樣率為200
2.設(shè)置鼠標(biāo)采樣率為100
3.設(shè)置鼠標(biāo)采樣率為80
這之后,如果你的鼠標(biāo)是個(gè)三維鼠標(biāo),那么,它將轉(zhuǎn)到三維模式下進(jìn)行工作,這個(gè)時(shí)候,主機(jī)向它發(fā)送0xF2(獲得鼠標(biāo)類型ID)命令,你的工作在三維模式下的鼠標(biāo)將向主機(jī)返回它的類型ID,但如果你的鼠標(biāo)不支持三維模式,即如果你的鼠標(biāo)只是一個(gè)二維鼠標(biāo),它返回給主機(jī)的類型ID將是0,這樣,主機(jī)就能夠知道現(xiàn)在你用的鼠標(biāo)是什么類型的鼠標(biāo),并由此知道應(yīng)當(dāng)接受3個(gè)還是4個(gè)數(shù)據(jù)包了。本實(shí)驗(yàn)將只操作標(biāo)準(zhǔn)的二維鼠標(biāo),如果你有興趣,你可以對(duì)程序進(jìn)行改動(dòng),以讓它支持三維鼠標(biāo)。
下圖是PS2鼠標(biāo)位移數(shù)據(jù)包格式:


雖然不能實(shí)現(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"鼠標(biāo)測(cè)試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符號(hào)標(biāo)志位,為1表示x位移量為負(fù)
move_x-=(256-a1);//x坐標(biāo)減
else
move_x+=a1;//x坐標(biāo)加
if(fy) //位6:y符號(hào)標(biāo)志位,為1表示y位移量為負(fù)
move_y-=(256-a2);//y坐標(biāo)減
else
move_y+=a2;//y坐標(biāo)加
if(fz)
move_z-=(16-(a3&0x0f));
else
move_z+=(a3&0x07);
if(fr)//如果點(diǎn)下右鍵
{lmr[4]=R;return;}
else if(fm)//如果點(diǎn)下中鍵
{lmr[4]=M;return;}
else if(fl)//如果點(diǎn)下左鍵
{lmr[4]=L;return;}
else
{lmr[4]=N;return;}
}
void main()
{
SDA=1;CLK=1;
delay(500);//鼠標(biāo)上電后在500ms左右就會(huì)發(fā)給主機(jī)0xaa和0x00
mouse_to_host();//如果沒有接收這兩個(gè)字節(jié),可能鼠標(biāo)一次上電后,
mouse_to_host();//不能正常初始化成功或者可以用加長廷時(shí)來代替接收
init_lcd();//初始化1602
delay100;//這個(gè)廷時(shí)相當(dāng)重要,否則可能在1602中有亂碼出現(xiàn)
write_command(0x80);//定位光標(biāo)在第一行
write_bytes("Initializing....");
write_command(0x80+0x40);//定位光標(biāo)在第二行
write_bytes(" Please wait! ");
while(init_mouse());//初始化鼠標(biāo)
deal_recive_data();//處理初始化鼠標(biāo)時(shí)返回給主機(jī)的部分?jǐn)?shù)據(jù),用以作調(diào)試
write_command(0x80);
write_bytes(deal_1);//顯示初始化鼠標(biāo)時(shí)返回給主機(jī)的部分?jǐn)?shù)據(jù),用以作調(diào)試
write_command(0x80+0x40);
write_bytes(deal_2);//顯示初始化鼠標(biāo)時(shí)返回給主機(jī)的部分?jǐn)?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模式中,主機(jī)每發(fā)送一個(gè)0xeb命令,從機(jī)
mouse_to_host();//將應(yīng)答0xfa,之后就是數(shù)據(jù)包
a0=mouse_to_host();//第一個(gè)數(shù)據(jù)包
fr=a0&0x02;//右鍵
fm=a0&0x04;//中鍵
fl=a0&0x01;//左鍵
fx=a0&0x10;//x的符號(hào)位
fy=a0&0x20;//y的符號(hào)位

a1=mouse_to_host();//第二個(gè)數(shù)據(jù)包 x位移量
a2=mouse_to_host();//第三個(gè)數(shù)據(jù)包 y位移量
a3=mouse_to_host();//第四個(gè)數(shù)據(jù)包 z位移量
fz=a3&0x08;//z的符號(hào)位
/*fxf=a0&0x40+0x30;
fyf=a0&0x80+0x30;
lmr[6]=fxf;
lmr[7]=fyf;*/
deal_data(); //將x,y,z,fl,fr,fm加入字符串中
display();//加入之后再一次性刷新顯示
}
}
/*
第1個(gè)數(shù)據(jù)包
位0:左鍵按下標(biāo)志位,為1表示左鍵被按下。
位1:右鍵按下標(biāo)志位,為1表示右鍵被按下。
位2:中鍵按下標(biāo)志位,為1表示中鍵被按下。
位3:保留位,總是為1。
位4:X符號(hào)標(biāo)志位,為1表示X位移量為負(fù)。
位5:Y符號(hào)標(biāo)志位,為1表示Y位移量為負(fù)。
位6:X溢出標(biāo)志位,為1表示X位移量溢出了。
位7:Y溢出標(biāo)志位,為1表示Y位移量溢出了。
三維鼠標(biāo)數(shù)據(jù)包中第一個(gè)數(shù)據(jù)包每位的含義與
二維鼠標(biāo)數(shù)據(jù)包中第一個(gè)數(shù)據(jù)包中每位含義完全相同,
唯一不同的就在于它每次會(huì)多發(fā)送一個(gè)數(shù)據(jù)包,
即第4個(gè)數(shù)據(jù)包,這個(gè)數(shù)據(jù)包包含了Z的位移量,
同X、Y位移量相同的是,它們都是以補(bǔ)碼表示的。
不過與X及Y位移量不同的是,Z位移量是4位的,
其中最高位(第四位)是符號(hào)位,因此,Z位移量的有效的范圍為:-8~7。
而X與Y的位移量是9位的,最高一位(第9位)是符號(hào)位,
這個(gè)符號(hào)位在第一個(gè)數(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; //使能信號(hào)位,將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ā)送單個(gè)字節(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è)置顯示、光標(biāo)和閃爍開、關(guān)
delay(5);
write_command(0x06);//設(shè)置光標(biāo)、畫面移動(dòng)方式
delay(5);
write_command(0x80);//設(shè)置光標(biāo)位置
delay(5);
}
void write_bytes(char *ch)//發(fā)送字符串
{
while(*ch)
write_dat(*ch++);
}
在"鼠標(biāo)測(cè)試2.h"文件中:
#include
#define delay10 {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();}//延時(shí)10us
#define delay100 {delay10 delay10 delay10 delay10 delay10 delay10 delay10 delay10 delay10 delay10;}
sbit SDA=P3^2; //P3^3 //int0號(hào)中斷(本程序不用中斷接收方式)
sbit CLK=P3^3;
bit pp=0,ACK=0;
uchar recv=0;
signed int move_x=00000;//存放橫坐標(biāo)
signed int move_y=00000;//存放縱坐標(biāo)
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]=" "; //用來存放初始化鼠標(biāo)時(shí)鼠標(biāo)返回的信息
unsigned char idata deal_2[20]=" ";
uchar idata ret_ini_dat[18]=0; //間接尋址片內(nèi)數(shù)據(jù)存儲(chǔ)區(qū),可訪問片內(nèi)全部RAM空間(256bytes)

void host_to_mouse(uchar cmd)
{
uchar i;
CLK=0;
delay100;
delay100;
ACC=cmd;
pp=~P;//獲得奇偶校驗(yàn)位
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ā)送奇偶校驗(yàn)位
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;//接收奇偶校驗(yàn)位
while(CLK!=1);
while(CLK!=0);
while(CLK!=1);
ACC=temp;
if(~P==pp)//如果檢驗(yàn)成功則返回接收到的數(shù)據(jù),否則返回0
{
recv=temp;
return temp;
}
return 0;
}
//用0xf0代替相鄰的0xc8,0x03可使鼠標(biāo)進(jìn)入remote模式,默認(rèn)為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ū)動(dòng)
/*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ù)位命令,鼠標(biāo)連續(xù)返回三個(gè)字節(jié)
ret_ini_dat[0]=mouse_to_host();//鼠標(biāo)返回0xfa
ret_ini_dat[1]=mouse_to_host();//鼠標(biāo)返回0xaa
ret_ini_dat[2]=mouse_to_host();//鼠標(biāo)返回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()//處理初始化鼠標(biāo)時(shí)返回給主機(jī)的部分?jǐn)?shù)據(jù),用以作調(diào)試
{//處理成十六進(jìn)制和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;
}
}


評(píng)論


技術(shù)專區(qū)

關(guān)閉