2D positioning: hacking an optical mouse!

For my next project, I will need to know the relative position of a moving (<10cm/s) object above (+/- 10 cm ) a surface (flat, but of any quality). After playing with accelerometers, I realized it won’t work (damn you, gravity) so I took the other option I thought of: an optical mouse.

A regular optical mouse contains a chip, usually from avagotech (Agilent company) with a small 16*16 CCD sensor, taking pictures at high rate of what’s under the mouse. It calculates the “travelled distance” by looking for differences from several pictures and outputs the value via an i2c bus or QAM pins. A microcontroller interfaces the first chip with the USB or PS/2 port and also decodes the buttons/scroll wheel.

I found a cheap second hand USB mouse at my local flea market for 2 Euros:

The “Mini Notebook Mouse”, from the Typhoon brand gave her body to (my) science.

Luckily, it contains the A2051 sensor. The datasheet is freely downloadable on Avagotech’s website (link) [EDIT: Not anymore. But you can get it here]. The electronics are just two PCBs, stacked and connected with two 6-pin headers (easy to separate). The bottom one has the sensor and its red LED, the buttons and the encoder for the scroll wheel. The upper one, the chip with the USB capabilities.

On the left: My USB dev board with a PIC18F87J50. In the middle: the sensor PCB. On the right: the desoldered PCB for interfacing the sensor and buttons with the PC.

Closer look at the sensor:

It has 400/800 cpi resolution , with a two-wire SPI interface. The CCD is on the other side.

The main problem is that the focus distance of this kind of sensor assembly is very narrow, about +/- 3mm. I needed more.

All the optical mouse sensors have a function to get the actual image of the surface below the mouse.

I wrote a small software to get that image. The sensor is connected to the USB dev board, via the SPI interface.

A software to get the image of the CCD (256 pixels with 64 values of gray, up-scaled so each pixel for the sensor is 8 pixels on the screen)

It allowed me to try various combinations of lenses in front of the sensor to get a bigger focus zone and a higher minimal focus distance (I wanted 10cm +/- 5cm)

I used these small lenses for security cameras/babyphones, like this one: Mini 12mm Lens  (but you can find cheaper ones on eBay, I saw some for $7). They’re easily opened unscrewing the front part (need to heat the glue on the thread sometimes) and contain between two and four small optical elements that can be re-arranged to get different magnifications/view angles).

After a lot of try-and-error, I managed to get a good combination of optical elements. The positioning is really precise, but depends of the contrast of surface the sensor “is looking at”.

Here’s some of the code I use (Microchip MCC18):

Two functions to read/write to the A2051 optical sensor.

void WriteSensor(unsigned int address, unsigned int data)
{
    unsigned int bitcounter;

    bitcounter = 0;
    address = address | 0b10000000; //Set the MSB of the adress to 1
    SDIO_LAT  = 1;
    SDIO_TRIS = 0; //Output
    SCLK_TRIS = 0;
    SCLK = 1;
    while(bitcounter <8)
    {
        SCLK = 0; //Lower Clock
        Nop();
        if(address & 0b10000000 )
            SDIO_LAT  = 1;
        else
            SDIO_LAT  = 0;
        Nop();
        Nop();
        SCLK = 1; //High pulse on clock
        Nop();  //Let the sensor read the sent bit.
        address = address <<1;
        bitcounter++;
    }
    bitcounter = 0;
    while(bitcounter <8)
    {
        SCLK = 0; //Lower Clock
        Nop();
        if(data & 0b10000000 )
            SDIO_LAT  = 1;
        else
            SDIO_LAT  = 0;
        Nop();
        SCLK = 1; //High pulse on clock
        Nop();
        Nop();  //Let the sensor read the sent bit.
        data = data <<1;
        bitcounter++;
    }
} //end WriteSensor

unsigned int ReadSensor(unsigned int address)
{
    unsigned int bitcounter;
    unsigned int res;
    res = 0;
    bitcounter = 0;
    address = address & 0b01111111; //Set the MSB of the adress to 0 (read)
    SDIO_LAT  = 1;
    SDIO_TRIS = 0; //Output
    SCLK = 1;
    //Start to write the address of the register we want to read from
    while(bitcounter <8)
    {
        SCLK = 0; //Lower Clock
        if(address & 0b10000000 )
            SDIO_LAT  = 1; //Change the output to match the bit we want to send
        else
            SDIO_LAT  = 0;
        SCLK = 1; //High pulse on clock
        address = address <<1; //Shift the next bit to send
        bitcounter++; //Inc the bit counter
    }
    //We have to wait at least 100us
    SDIO_TRIS = 1;
    for(bitcounter=0;bitcounter<0x10;bitcounter++)
        Nop();
    //Configure the SDIO pin as an input
    SDIO_LAT  = 1;
    SDIO_TRIS = 1; //Input
    SDIO_TRIS = 1; //Input
    bitcounter=0;
    while(bitcounter <8)
    {
        SCLK = 0; //Lower the Clock line
        //Nop(); //Works without the Nop
        SCLK = 1; //Set the Clock line
        res = res <<1; //Shift the received bit in
        if(SDIO_PORT )
            res  = res | 0b00000001; //Read the current bit
        else
            res  = res & 0b11111110;
        bitcounter++;
    }
    SDIO_TRIS = 0;
    SDIO_LAT  = 1;
    SCLK = 1;
    return (res); //Return with the read byte
}//End ReadSensor

With I/O declared as such:

#define SDIO_PORT               PORTCbits.RC4
#define SDIO_LAT                LATCbits.LATC4
#define SDIO_TRIS               TRISCbits.TRISC4
#define SCLK                    LATCbits.LATC3
#define SCLK_TRIS               TRISCbits.TRISC3
#define PD                      LATCbits.LATC5
#define PD_TRIS                 TRISCbits.TRISC5

(PD is the sensor Power Down pin. Used to reset the A2051 at board’s power-up. You can use any pin of you PIC to communicate with the sensor, as long as there’s a pull-up resistor)

To dump the sensor image:

int ptr = 0;
WORD_VAL LowerB,UpperB;
unsigned char Buffer[256];
WriteSensor(0x0A,0b00001001); //Configuration register: Dump the pixels, no power saving
while(ptr <0xFF)
{
   LowerB.Val = ReadSensor(0x0C);//"LowerData" Contains the pixel value, 6bits.
   UpperB.Val = ReadSensor(0x0D);//"UpperData" The MSB indicates if the pixel in LowerData is valid. 
                                 //The seven other bits are the pixel address (form 0 to 0xFF)
   if(!(LowerB.bits.b7)) //Check if data valid
   {
     Buffer[UpperB.Val] = ((LowerB.Val)&0b00111111);
     ptr++;
   }
 }

The Buffer will contain the image from the sensor. I send it via USB to my mac application.

The next steps will be to build a frame around the PCB, integrate it to my project and see how good the positioning gets with different surfaces. I’ll probably still use an accelerometer to measure the tilt angle of the optical sensor and to provide a backup continuity in the displacement measurement in the case the surface contrast gets too bad for the sensor.

Inspiration from this article: Sprites mods.

Advertisements

15 thoughts on “2D positioning: hacking an optical mouse!

  1. Hi !

    Thanks for sharing this ! I’ve been googling quite a lot and finally found your tutorial. I’ve a small experience in PIC programming, but never quite succeeded interfacing my PIC (2550 or 4500) properly… (I should go back to the Blink a Led tutorial, I know…). I’ve cracked open my old logitech mouse, looks like a compatible unit (other part number, but pin outs look the same). Have you already planned/drawn/programmed such a PIC for this project ? or you are still using the DEV board ? 2) Can you post an image of a 10cm-distant object ?

  2. Hi!
    For this project, I used a PIC87J50. But I’m using a 2450 for my reflow oven. You should be able to use any USB pic, as the communication for the mouse sensor is easy and doesn’t require high speeds. Maybe looking at some of Microchip libraries and projects will help you.
    The picture in this blog post (the screen cap of HIP PnP Demo) is taken at a distance of 10cm, I think. I’m using a zoom lens, so the what you see in the small window is actually a surface about 2mm*2mm. I also tried a wide angle lens, where I could picture “objects” (like the keys of my keyboard, for example) but I don’t have screen caps and it’s not working well for positioning (you have to be able to distinguish the texture of the objects below the sensor, not the objects themselves).
    Your mouse sensor can have the same pinout, but you need the datasheet to check the communication protocol (the commands could be different)
    Good luck!

  3. Hi Jean. I was quite pleasantly surprised when I found your write up about the ADNS-2051 sensor. I have an application where I want to navigate a robotic vehicle motion path by means of an “odometer” in this case the optical mouse sensor. I also realize the limitation of the nominal lens to viewing surface distance of 2.4 mm of the ADNS-2051. I then also thought that perhaps one could either use other lenses to create a slightly greater ground clearance of let’s say about 30 mm. That will certainly help my little rover navigate quite well as these sensors seem to be quite accurate. Of course one will get a certain amount of drift error induced over time. I did a test to see what amount of drift error would be accumulated over time/distance traveled simply by opening a paint package. Creating a white BG with a blue ref dot in the page center and then with the base of my hand resting firmly on one position, I made 100 x small circles with my eyes closed. First CW then CCW. Just make sure your mouse driver does not accelerate the mouse pointer motion else you’ll induce huge errors. The results were quite astonishing. After 100 circles the drift error was just less than 1% (0.93%)of the total distance covered by the mouse pointer which is directly proportional to the actual distance traveled in the real world which was calculated to be an estimated 3.141 m thus the total drift error, which incidentally was to the left, was 29 mm in the real world. Do realize that the test I did is very inaccurate in the first place, but it should give you a rough idea. Check out Roborealm… if you haven’t already done so. Thanks for your post : )

  4. Check out the Avago ADNS-9500 LaserStream Gaming sensor. It has a 30 x 30 ccd, 16-bit dX dY motion data each, runs at 11750 fps! Wow! Resolution up to 5000 cpi that translates to a 5 micron resolution. Astonishing! Can detect up to 150 ips (3.81m/s) or acceleration up to 30 g. What next? Curious: Are you still busy with your project involving motion sensors?

  5. We’ll you could try the ADNS 9800 which is even more up spec from the 9500. I did see the ADNS 9500 avail from RS-online PN 716-0590P (RS-online South Africa) for about $10. If you don’t need such a high performance device, why not go for a main stream device? You could also try out their ADNK-2620,2610,2630,2652 kits also avail from RS.

    • Dear sir! i am working on one project 2D mapping with optical laser mouse. can you provide me initial boost or any related material where i can understand the basic steps or process to build 2D mapp with laser mouse. If you have any related data or material then kindly send on my email engr.adnan13@yahoo.com
      and you can also provide me just steps or hints to implement.
      I just want initial boost.
      Your help will be highly appreciated.
      Regards Adnan jafar

  6. Hi,
    I have some questions:
    can you help me?
    How much is the accuracy of this sensor?
    Is it necessary that the surface be flat? ( can I use this in uneven surfaces?)

    • Hi!
      I can try 🙂
      The sensor can be very accurate (I know, “very” doesn’t mean a lot). Let’s say you can detect 0,1mm movements easily.
      All the problem is the distance between the sensor and the surface. If it’s the same as with a regular mouse, you can use the mouse’s optics and your resolution will be very good.
      With an added lens (in the case your surface is further than the 3mm -or so of a mouse support surface), the accuracy will depend of how much focus you get and also of how much detail/contrast it has.
      I’m afraid the best solution would be to try by yourself, with the surface you plan to use your project with..

  7. What is the farthest distance you made so far with a very accurate and high resolution with this project? And what kind of lens did you use? the specs of the lens i mean. I am planning to make this project with pic16f877a and i will make this as my referrence. Great project by the way. 🙂

    • I am also trying to compile this code with my mplab using HI-tech c compiler but i am having an error (the Nop(); and LAT registers). Right now, i want to get the x and y coordinates the mouse sensor gets and not interfacing it with a program just yet. From there maybe i can progress a little bit, need your advice, Thanks 🙂

      • I didn’t test for “repeatability over distance”, mainly because the mouse sensor was hand held and its tilt changes the measured distance a lot . But if your lens and surface are “ok”, I think you could get something like 1cm over 1 metre of accuracy.
        The “Nop();” is just a no-operation command. Your compiler should have one, or you can write an inline assembly. Same for the LATx registers, the documentation of your compiler should tell you the equivalents.

    • What is the farthest distance you made so far with a very accurate and high resolution with this project? And what kind of lens did you use? the specs of the lens i mean. I am planning to make this project with pic16f877a and i will make this as my referrence. Great project by the way.

  8. Hi – Have you ever thought of using two of these sensors side-by-side to get some form of “binocular vision”, so one could calculate small distances by triangulation?? I remember seeing something like this many years ago in a robotics book I had: this experiment used two old (VERY old!) RAM chips that still had a metal cap of sorts on top. The idea was to carefully pop the cap of to expose the chip inside which would then behave like an array of photo sensors. This was mounted on a servo drive that would change the angle between the two sensors – hence the triangulation with the focus/image matching being done by software.

  9. Dear all
    How Could I writing a code so as C++ in visual basic ?
    I will writing the same code for reading data from optical mouse in VB.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s