新聞中心

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

        如何用51單片機接收鼠標的“三軸位移”與按鍵信息

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

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

        我們先要知道現(xiàn)存的總共有兩類鼠標,一類就是所謂的2D(二維)鼠標,它就是我們平常用的那種沒有滾輪的鼠標,由于這種鼠標在位移上只有X與Y兩個方向,所以稱之為2D(二維)鼠標;還有一類就是現(xiàn)在比較常見的3D(三維)鼠標,它們中間存在有一個滾輪,而這個滾輪會產生一個額外的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位移量


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


        第4個數(shù)據(jù)包Z位移量
        三維鼠標數(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。
        看到這里,你或許有疑問了,系統(tǒng)是怎么來知道到我到底應當接收3個數(shù)據(jù)包還是接收4個數(shù)據(jù)包的呢?三維鼠標的標準是由微軟制定的,最初,這種三維的鼠標只工作在標準的PS/2模式下,如果你想讓它工作在三維模式下,你需要用0xF3這個設置鼠標采樣率的命令,按如下的順序進行操作:
        1.設置鼠標采樣率為200
        2.設置鼠標采樣率為100
        3.設置鼠標采樣率為80
        這之后,如果你的鼠標是個三維鼠標,那么,它將轉到三維模式下進行工作,這個時候,主機向它發(fā)送0xF2(獲得鼠標類型ID)命令,你的工作在三維模式下的鼠標將向主機返回它的類型ID,但如果你的鼠標不支持三維模式,即如果你的鼠標只是一個二維鼠標,它返回給主機的類型ID將是0,這樣,主機就能夠知道現(xiàn)在你用的鼠標是什么類型的鼠標,并由此知道應當接受3個還是4個數(shù)據(jù)包了。本實驗將只操作標準的二維鼠標,如果你有興趣,你可以對程序進行改動,以讓它支持三維鼠標。
        下圖是PS2鼠標位移數(shù)據(jù)包格式:


        雖然不能實現(xiàn)定位,但最少我又學多了一種通信協(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;//這個廷時相當重要,否則可能在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ù),用以作調試
        write_command(0x80);
        write_bytes(deal_1);//顯示初始化鼠標時返回給主機的部分數(shù)據(jù),用以作調試
        write_command(0x80+0x40);
        write_bytes(deal_2);//顯示初始化鼠標時返回給主機的部分數(shù)據(jù),用以作調試
        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();//將應答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);//設置工作方式
        delay(5);
        write_command(0x0f);//設置顯示、光標和閃爍開、關
        delay(5);
        write_command(0x06);//設置光標、畫面移動方式
        delay(5);
        write_command(0x80);//設置光標位置
        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; //間接尋址片內數(shù)據(jù)存儲區(qū),可訪問片內全部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;//接收應答位
        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讀設備類型
        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); //復位命令,鼠標連續(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ù),用以作調試
        {//處理成十六進制和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;
        }
        }


        評論


        技術專區(qū)

        關閉
        主站蜘蛛池模板: 岳池县| 大方县| 磐安县| 玉树县| 衡阳市| 缙云县| 拉萨市| 德惠市| 新和县| 长白| 柘荣县| 克东县| 新昌县| 阿拉善左旗| 吉隆县| 达州市| 江门市| 来凤县| 乐清市| 南通市| 陇南市| 台江县| 江门市| 宁夏| 鲁甸县| 瑞昌市| 卓尼县| 双桥区| 苏尼特右旗| 永善县| 长乐市| 大埔县| 民丰县| 九台市| 汕头市| 莎车县| 宽甸| 台南市| 建昌县| 阿城市| 北京市|