Optical mouse chip camera- Seeking advice [SOLVED]

Hello: My name is Betsy. I come to you no prior knowledge. So I am really looking for your guidance and expertise as I am flying by the seat of my pants.

I am interested in making this optical mouse camera scanner http://www.bidouille.org/hack/mousecam for an art project I am currently working on.

My question is: Is the Arduino code I am copying (and posted below) written to work with the ADNS-2051 optical sensor chip only? (I am using a ADNS-2610 optical sensor chip I am reading that it might be "interface compliant with the 2051 4--ADNS-2051/2610/2620 datasheet & application notes - Datasheet Archive) if the answer is yes it only works with the ADNS-2051 chip then my next question is: what aspects of the code do I have to modify to make this work for my chip?

Here is the Arduino code

#define SCLK 2
#define SDIO 3
#define PD 4

#define REG_PRODUCT_ID 0x00
#define REG_REVISION_ID 0x01
#define REG_MOTION 0x02
#define REG_DELTA_X 0x03
#define REG_DELTA_Y 0x04
#define REG_SQUAL 0x05
#define REG_AVERAGE_PIXEL 0x06
#define REG_MAXIMUM_PIXEL 0x07
#define REG_CONFIG_BITS 0x0A
#define REG_DATA_OUT_LOWER 0x0C
#define REG_DATA_OUT_UPPER 0x0D
#define REG_SHUTTER_LOWER 0x0E
#define REG_SHUTTER_UPPER 0x0F
#define REG_FRAME_PERIOD_LOWER 0x10
#define REG_FRAME_PERIOD_UPPER 0x11

int dumpWidth = 256; // Number of pixels to read for each frame.
byte frame[256];

void setup() {
  Serial.begin(115200);

  reset();
  byte productId = readRegister(REG_PRODUCT_ID);
  byte revisionId = readRegister(REG_REVISION_ID);
  Serial.print("Found productId ");
  Serial.print(productId, HEX);
  Serial.print(", rev. ");
  Serial.print(revisionId, HEX);
  Serial.println(productId == 0x02 ? " OK." : " Unknown productID. Carry on.");

  byte config = readRegister(REG_CONFIG_BITS);
  config |= B00000001; // Don't sleep (LED always powered on).
  writeRegister(REG_CONFIG_BITS, config);
}

void loop() {
  // Allows to set the dump window by sending the number of lines to read via the serial port.
  if(Serial.available() > 0) {
    dumpWidth = 16 * Serial.read();
    dumpWidth = constrain(dumpWidth, 0, 256);
  }

  readRegister(REG_MOTION); // Freezes DX and DY until they are read or MOTION is read again.
  char dx = readRegister(REG_DELTA_X);
  char dy = readRegister(REG_DELTA_Y);
  Serial.print("DELTA:");
  Serial.print(dx, DEC);
  Serial.print(" ");
  Serial.println(dy, DEC);

  if( dumpWidth > 0 )
    dumpFrame();
}

void dumpFrame() {
  byte config = readRegister(REG_CONFIG_BITS);
  config |= B00001000; // PixDump
  writeRegister(REG_CONFIG_BITS, config);

  int count = 0;
  do {
    byte data = readRegister(REG_DATA_OUT_LOWER);
    if( (data & 0x80) == 0 ) { // Data is valid
      frame[count++] = data;
    }
  } 
  while (count != dumpWidth);

  config = readRegister(REG_CONFIG_BITS);
  config &= B11110111;
  writeRegister(REG_CONFIG_BITS, config);

  Serial.print("FRAME:");
  for(int i = 0; i < dumpWidth; i++) {
    byte pix = frame[i];
    if( pix < 0x10 )
      Serial.print("0");
    Serial.print(pix, HEX);
  }
  Serial.println();
}

void reset() {
  pinMode(SCLK, OUTPUT);
  pinMode(SDIO, INPUT);
  pinMode(PD, OUTPUT);
  digitalWrite(SCLK, LOW);
  digitalWrite(PD, HIGH);
  delayMicroseconds(1);
  digitalWrite(PD, LOW);
}

byte readRegister(byte address) {
  pinMode (SDIO, OUTPUT);

  for (byte i=128; i >0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (address & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  pinMode (SDIO, INPUT);

  delayMicroseconds(100); // tHOLD = 100us min.

  byte res = 0;
  for (byte i=128; i >0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SCLK, HIGH);
    if( digitalRead (SDIO) == HIGH )
      res |= i;
  }

  return res;
}

void writeRegister(byte address, byte data) {
  address |= 0x80; // MSB indicates write mode.
  pinMode (SDIO, OUTPUT);

  for (byte i = 128; i > 0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (address & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  for (byte i = 128; i > 0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (data & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  delayMicroseconds(100); // tSWW, tSWR = 100us min.
}

I am hoping that if these chips are interface compliant with each other that it means that I do not actually have to modify anything. However, I wanted to pose this question to you all.

Thank you for taking the time to read this and give me any help you might have.

betsy

Briefly comparing this http://www.pobot.org/IMG/pdf/datasheet_ADNS2610.pdf datasheet (is it the correct one?) with the page you linked to suggests that they are probably similar but it would take a lot of reading to know if they are interchangeable without reprogramming. Also the '2610 has an 18x18 sensor.

This seems to be the ADNS2051 datasheet.

It sounds like an interesting idea. I will bookmark this Thread for follow-up when I have time. I must buy a cheap mouse and see what is inside.

Edit your original post to change the title to something useful such as "Optical mouse chip camera"

...R

Thank you so much, Robin2!

Sadly the ADNS2610 is not register compatible with the ADNS2051. The address of the configuration register was moved from 0x0A to 0x00. The clock and data pins are probably compatible so the register read and write functions can probably be re-used. There is a "Pixel Data" register at address 0x08 so you probably use that to get to the raw image data.

You write to the Pixel Data register to start a frame and can then read 324 (18x18) pixels from that register. Pixels order is reading up the columns from bottom to top and across from left column to right column.

byte frame[324];
const int REG_PIXEL_DATA = 0x08;
void dumpFrame() {
  int count = 0;
  byte data;
  // Start a frame
  writeRegister(REG_PIXEL_DATA, 0);
  do {
    data = readRegister(REG_PIXEL_DATA);
    if (data & 0x40) { // Data is valid
      frame[count++] = data & 0x3F;
    }
  }
  while (count < 324);

  Serial.print("FRAME:");
  for (int i = 0; i < 324; i++) {
    byte pix = frame[i];
    if ( pix < 0x10 )
      Serial.print("0");
    Serial.print(pix, HEX);
    if ((i % 18) == 17)  // New line at the end of each column.
      Serial.println();
  }
}

Hello Johnwasser! Thank you for the code. It helped me a lot to begin "fixing" (I put in quotes because I am not quite sure if I am fixing or just making worse)

I changed the beginning bit:

#define REG_PRODUCT_ID 0x00
#define REG_REVISION_ID 0x01
#define REG_MOTION 0x02
#define REG_DELTA_X 0x03
#define REG_DELTA_Y 0x04
#define REG_SQUAL 0x05
#define REG_AVERAGE_PIXEL 0x06
#define REG_MAXIMUM_PIXEL 0x07
#define REG_CONFIG_BITS 0x0a
#define REG_DATA_OUT_LOWER 0x0C
#define REG_DATA_OUT_UPPER 0x0D
#define REG_SHUTTER_LOWER 0x0E
#define REG_SHUTTER_UPPER 0x0F
#define REG_FRAME_PERIOD_LOWER 0x10
#define REG_FRAME_PERIOD_UPPER 0x11

to be the same listed on page 38 of the datasheet and I also took the code you wrote and placed it in place of the old code. However, I am getting the below error messages when I hit 'verify.' I have changed all values that used to read 256 to 324

mouse-cam.ino:60:15: error: redefinition of 'byte frame [324]'
mouse-cam.ino:22:6: error: 'byte frame [324]' previously declared here
Error compiling.

I know that this might get tiresome and frustrating helping me, who has no knowledge of this at all, but I really appreciate all help.

Looks like you have frame declared on line 22 and line 60. You can only have one declaration per variable. The one near the top is probably a good place to have it.

#define SCLK 2
#define SDIO 3
#define PD 4

#define REG_PRODUCT_ID 0x00
#define REG_REVISION_ID 0x01
#define REG_MOTION 0x02
#define REG_DELTA_X 0x03
#define REG_DELTA_Y 0x04
#define REG_SQUAL 0x05
#define REG_AVERAGE_PIXEL 0x06
#define REG_MAXIMUM_PIXEL 0x07
#define REG_CONFIG_BITS 0x0a
#define REG_DATA_OUT_LOWER 0x0C
#define REG_DATA_OUT_UPPER 0x0D
#define REG_SHUTTER_LOWER 0x0E
#define REG_SHUTTER_UPPER 0x0F
#define REG_FRAME_PERIOD_LOWER 0x10
#define REG_FRAME_PERIOD_UPPER 0x11

int dumpWidth = 324; // Number of pixels to read for each frame.
byte frame[324];

void setup() {
  Serial.begin(115200);

  reset();
  byte productId = readRegister(REG_PRODUCT_ID);
  byte revisionId = readRegister(REG_REVISION_ID);
  Serial.print("Found productId ");
  Serial.print(productId, HEX);
  Serial.print(", rev. ");
  Serial.print(revisionId, HEX);
  Serial.println(productId == 0x02 ? " OK." : " Unknown productID. Carry on.");

  byte config = readRegister(REG_CONFIG_BITS);
  config |= B00000001; // Don't sleep (LED always powered on).
  writeRegister(REG_CONFIG_BITS, config);
}

void loop() {
  // Allows to set the dump window by sending the number of lines to read via the serial port.
  if(Serial.available() > 0) {
    dumpWidth = 16 * Serial.read();
    dumpWidth = constrain(dumpWidth, 0, 324);
  }

  readRegister(REG_MOTION); // Freezes DX and DY until they are read or MOTION is read again.
  char dx = readRegister(REG_DELTA_X);
  char dy = readRegister(REG_DELTA_Y);
  Serial.print("DELTA:");
  Serial.print(dx, DEC);
  Serial.print(" ");
  Serial.println(dy, DEC);

  if( dumpWidth > 0 )
    dumpFrame();
}

const int REG_PIXEL_DATA = 0x08;
void dumpFrame() {
  int count = 0;
  byte data;
  // Start a frame
  writeRegister(REG_PIXEL_DATA, 0);
  do {
    data = readRegister(REG_PIXEL_DATA);
    if (data & 0x40) { // Data is valid
      frame[count++] = data & 0x3F;
    }
  }
  while (count < 324);

  Serial.print("FRAME:");
  for (int i = 0; i < 324; i++) {
    byte pix = frame[i];
    if ( pix < 0x10 )
      Serial.print("0");
    Serial.print(pix, HEX);
    if ((i % 18) == 17)  // New line at the end of each column.
      Serial.println();
  }
}

void reset() {
  pinMode(SCLK, OUTPUT);
  pinMode(SDIO, INPUT);
  pinMode(PD, OUTPUT);
  digitalWrite(SCLK, LOW);
  digitalWrite(PD, HIGH);
  delayMicroseconds(1);
  digitalWrite(PD, LOW);
}

byte readRegister(byte address) {
  pinMode (SDIO, OUTPUT);

  for (byte i=128; i >0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (address & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  pinMode (SDIO, INPUT);

  delayMicroseconds(100); // tHOLD = 100us min.

  byte res = 0;
  for (byte i=128; i >0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SCLK, HIGH);
    if( digitalRead (SDIO) == HIGH )
      res |= i;
  }

  return res;
}

void writeRegister(byte address, byte data) {
  address |= 0x80; // MSB indicates write mode.
  pinMode (SDIO, OUTPUT);

  for (byte i = 128; i > 0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (address & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  for (byte i = 128; i > 0 ; i >>= 1) {
    digitalWrite (SCLK, LOW);
    digitalWrite (SDIO, (data & i) != 0 ? HIGH : LOW);
    digitalWrite (SCLK, HIGH);
  }

  delayMicroseconds(100); // tSWW, tSWR = 100us min.
}
[code/]

I shall let you know how it works out!

This is how I would write the sketch. If you have any questions about why I made the changes I made, just ask.

const byte ADNS2610_SCLK_PIN = 2;
const byte ADNS2610_SDIO_PIN = 3;
const byte ADNS2610_PD_PIN = 4;

const byte REG_CONFIGURATION        = 0x00;
const byte REG_STATUS               = 0x01;
const byte REG_DELTA_X              = 0x02;
const byte REG_DELTA_Y              = 0x03;
const byte REG_SQUAL                = 0x04;
const byte REG_MAXIMUM_PIXEL        = 0x05;
const byte REG_MINIMUM_PIXEL        = 0x06;
const byte REG_PIXEL_SUM            = 0x07;
const byte REG_PIXEL_DATA           = 0x08;
const byte REG_SHUTTER_UPPER        = 0x09;
const byte REG_SHUTTER_LOWER        = 0x0A;
const byte REG_INVERSE_PORODUCT_ID  = 0x11;

const int FRAME_WIDTH = 18;
const int FRAME_HEIGHT = 18;
const int FRAME_SIZE = FRAME_WIDTH * FRAME_HEIGHT;

byte FrameBuffer[FRAME_SIZE];

void setup() {
  Serial.begin(115200);

  ADNS2610_reset();

  byte productId = (~readRegister(REG_INVERSE_PORODUCT_ID)) & 0x0F;
  Serial.print("Found productId ");
  Serial.print(productId, HEX);
  Serial.println(productId == 0x00 ? " OK." : " Unknown productID. Carry on.");

  byte config = readRegister(REG_CONFIGURATION);
  config |= B00000001; // Forced Awake Mode (LED always powered on).
  writeRegister(REG_CONFIGURATION, config);
}

void loop() {
  char dx = readRegister(REG_DELTA_X);
  char dy = readRegister(REG_DELTA_Y);
  Serial.print("DELTA:");
  Serial.print(dx, DEC);
  Serial.print(" ");
  Serial.println(dy, DEC);

  dumpFrame();
}

void dumpFrame() {
  int count = 0;

  // Start a frame
  writeRegister(REG_PIXEL_DATA, 0);
  do {
    byte data = readRegister(REG_PIXEL_DATA);
    if (data & 0x40) // Data is valid
      FrameBuffer[count++] = data & 0x3F;
    if ((count != 1) && (data & 0x80)) { // Unexpected Start-of-Frame
      Serial.print(F("Unexpected Start-of-Frame at index "));
      Serial.println(count - 1);
    }
  } while (count < FRAME_SIZE);

  // Dump the frame buffer.
  // Pixels read bottom to top and columns read left to right.
  // Display the pixels left to right and rows top to bottom
  Serial.print("FRAME:");
  for (int row = 0; row < FRAME_HEIGHT; row++) {
    for (int column = 0; column < FRAME_WIDTH; column++) {
      // Top row is FRAME_HEIGHT-1
      // Bottom row is 0;
      int index = column * FRAME_HEIGHT + ((FRAME_HEIGHT - 1) - row);
      byte pix = FrameBuffer[index];
      if ( pix < 0x10 )
        Serial.print("0");
      Serial.print(pix, HEX);
    }  // end for(column)
    // New line after each row
    Serial.println();
  } // end for(row)
}

void ADNS2610_reset() {
  pinMode(ADNS2610_SCLK_PIN, OUTPUT);
  pinMode(ADNS2610_SDIO_PIN, INPUT);
  pinMode(ADNS2610_PD_PIN, OUTPUT);

  digitalWrite(ADNS2610_SCLK_PIN, LOW);
  digitalWrite(ADNS2610_PD_PIN, HIGH);
  delayMicroseconds(1);
  digitalWrite(ADNS2610_PD_PIN, LOW);
}

byte readRegister(byte address) {
  pinMode (ADNS2610_SDIO_PIN, OUTPUT);

  for (byte i = 0x80; i > 0 ; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SDIO_PIN, address & i);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
  }

  pinMode (ADNS2610_SDIO_PIN, INPUT);

  delayMicroseconds(100); // tHOLD = 100us min.

  byte res = 0;
  for (byte i = 0x80; i; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
    if ( digitalRead (ADNS2610_SDIO_PIN))
      res |= i;
  }

  return res;
}

void writeRegister(byte address, byte data) {
  address |= 0x80; // MSB indicates write mode.
  pinMode (ADNS2610_SDIO_PIN, OUTPUT);

  for (byte i = 0x80; i; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SDIO_PIN, address & i);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
  }

  for (byte i = 0x80; i; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SDIO_PIN, (data & i) != 0 ? HIGH : LOW);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
  }

  delayMicroseconds(100); // tSWW, tSWR = 100us min.
}

...You are my hero.

Programming used to be my job. It's still my hobby.

So, I finally got the mice I ordered in the mail... Of course the chip in the mice are not the ADNS-2610 I had hoped. I have rather the MCS-12085 http://www.rmrsystems.co.uk/download/MCS12085.pdf

I connected the 4 SCK (Serial Port Clock) into the digital 2 Arduino port. The 3 SDIO (Serial Port Data) into the digital 3 port. The 6 GND to the GND (on the left power side of the Arduino) port and finally the VDD into the 5v (left power side of the Arduino).

And I can't get even the power on.

I am not sure what I am doing wrong here. But, I compared the datasheets on the MCS-12085 and the ADNS-2610 and they are the same (at least to me)

Does anyone have any clues as to what I might be doing wrong?

elewin:
I am not sure what I am doing wrong here. But, I compared the datasheets on the MCS-12085 and the ADNS-2610 and they are the same (at least to me)

Does anyone have any clues as to what I might be doing wrong?

It looks like the MCS-12085 needs a 24 MHz ceramic resonator connected to the OSC-OUT and OSC-IN pins.
The SPI clock is limited to 2 MHz so you have to use SPI.setClockDivider(SPI_CLOCK_DIV8);

Johnwasser you are so fantastic! With all the soldering I've been doing (getting those connector wires to solder on well is difficult) I hope that the little chip can take the heat...

(I realized after I posted that I had turned around two of the pins and I can now get a tiny tiny LED light to go on, but certainly not at full power)

Just so I make sure I am reading this correctly, I am to get a two prong ceramic resonator and solder it onto the OSC-OUT and IN pins, and then take the 4 SCK and connect it to the digital 8 on the Ardunio?

Thank you thank you thank you thank you

Hello! Just wanted to post an update here: I got the mouse to work. I had switched around a few of the pins and the wires, but after I got them straightened out I started to get readings from the serial port!
I connected the sck to digital port 2, SDIO to digital port 3, and the VDD and GND to the 5v and GND ports respectively. I have also attached a picture of the connections for those interested...

I couldn't have done it without the help of a couple people for sure.
I used the code that was provided by Johnwasser and the technical advice from an artist.

Now all this being said I am not getting very good visual readings from the jar file I am using. located here http://www.bidouille.org/hack/mousecam I have attached an image and as you can see I am only getting data from the left.

Does anyone know what might be causing this? Mouse, arduino code, or jar file?

Here is part of your problem:

public class PixelImagePanel extends JPanel {
    private int width = 16;
    private int height = 16;

Looks like the Java app expects a 16x16 image and your mouse chip is producing 18x18?

This function also seems to rely on the 16x16 assumption. It is using >>4 to divide by 16 and <<4 to multiply by 16 as well as using &0x0F and &0xF0 to do modulo 16.

 public void setImageData( int x, int y, byte[] newPixels, int newPixelsWidth ) {
        if ( newPixels != null ) {
            for ( int i = 0; i < newPixels.length; i++ ) {
                int trans = 0xFF-((i&0x0F)<<4)-((i&0xF0)>>4); // Do a little transposition to go from chip pixel order to standard pixel order.
                int destIndex = x + trans % newPixelsWidth + (y + trans / newPixelsWidth) * width;
                if ( destIndex < getPixels().length  )
                getPixels()[destIndex] = newPixels[i];
            }
        }

Okay. I am glad to hear that you think the problem lies in the java app and not in the chip or the Arduino code. I shall update this thread with any results. Thanks again for your help!

I am curious, how can you tell if the chip is producing a 18x18 image? I can't find any information on it in the datasheet.

elewin:
I am curious, how can you tell if the chip is producing a 18x18 image? I can't find any information on it in the datasheet.

The text in the MCS-12085 datasheet is almost identical to the text in the ADNS-2610 datasheet, including this part:

"To dump a complete image, set the LED to forced awake mode, write anything to this register, then read 324 times where the DataValid bit is set. On the 325th read, the StartOfFrame bit will be set indicating that we have completed one frame of pixels and are starting back at pixel 1.
It takes at least 324 frames to complete an image as we can only read 1 pixel per frame."

324 is 18*18 so that implies that the image layout is the same as for the ADNS-2610.

What they left out was all the charts and diagrams, including the 18x18 pixel map. It appears that the MCS-12085 is intended to be a drop-in replacement for the ADNS-2610 so you can probably rely on the ADNS-2610 datasheet.

Interesting! Thanks for the information, now at least I am getting idea of what I should be looking for... And wishing I were better in the math department.

Thanks for clarifying!

Hello- I wanted to close out this post with the conclusion of this project. This is in the event that someone else would like to copy this project.

I could not have done ANY of this without the help of Johnwasser and I really thank him for helping me!

Below is the Arduino code and attached is the java program.

A few notes:

  1. have realized is that the java program runs MUCH better on Macs than PCs. I am not sure why this is, but for my PC the program seizes up after several seconds of use... If anyone has a solution to this I'd be interested in hearing it.

  2. The line on the far left is not correct... It seems like the left line is supposed to be on the right. The person who was troubleshooting this problem for me couldn't figure out the solution.

Anyways, I just wanted to close this out! Thanks for all the help!

const byte ADNS2610_SCLK_PIN = 2;
const byte ADNS2610_SDIO_PIN = 3;
const byte ADNS2610_PD_PIN = 4;

const byte REG_CONFIGURATION        = 0x00;
const byte REG_STATUS               = 0x01;
const byte REG_DELTA_X              = 0x02;
const byte REG_DELTA_Y              = 0x03;
const byte REG_SQUAL                = 0x04;
const byte REG_MAXIMUM_PIXEL        = 0x05;
const byte REG_MINIMUM_PIXEL        = 0x06;
const byte REG_PIXEL_SUM            = 0x07;
const byte REG_PIXEL_DATA           = 0x08;
const byte REG_SHUTTER_UPPER        = 0x09;
const byte REG_SHUTTER_LOWER        = 0x0A;
const byte REG_INVERSE_PORODUCT_ID  = 0x11;

const int FRAME_WIDTH = 18;
const int FRAME_HEIGHT = 18;
const int FRAME_SIZE = FRAME_WIDTH * FRAME_HEIGHT;

byte FrameBuffer[FRAME_SIZE];

void setup() {
  Serial.begin(115200);

  ADNS2610_reset();

  byte productId = (~readRegister(REG_INVERSE_PORODUCT_ID)) & 0x0F;
  Serial.print("Found productId ");
  Serial.print(productId, HEX);
  Serial.println(productId == 0x00 ? " OK." : " Unknown productID. Carry on.");

  byte config = readRegister(REG_CONFIGURATION);
  config |= B00000001; // Forced Awake Mode (LED always powered on).
  writeRegister(REG_CONFIGURATION, config);
}

void loop() {
  char dx = readRegister(REG_DELTA_X);
  char dy = readRegister(REG_DELTA_Y);
  
  // these seemed to be coming in backwards, so i flipped the printing of 'dy' and 'dx' to compensate
  Serial.print("DELTA:");
  Serial.print(dy, DEC);
  Serial.print(" ");
  Serial.println(dx, DEC);

  dumpFrame();
}

void dumpFrame() {
  int count = 0;

  // Start a frame
  writeRegister(REG_PIXEL_DATA, 0);
  do {
    byte data = readRegister(REG_PIXEL_DATA);
    if (data & 0x40) // Data is valid
      FrameBuffer[count++] = data & 0x3F;
    if ((count != 1) && (data & 0x80)) { // Unexpected Start-of-Frame
      Serial.print(F("Unexpected Start-of-Frame at index "));
      Serial.println(count - 1);
    }
  } while (count < FRAME_SIZE);

  // Dump the frame buffer.
  // Pixels read bottom to top and columns read left to right.
  // Display the pixels left to right and rows top to bottom
  Serial.print("FRAME:");
  
  // the buffer seems to be written from right to left, bottom to top, rather than the other way around
  // so instead of starting the loop which reads the frame buffer from 0,0 i started from FRAME_WIDTH-1, FRAMEHEIGH-1
  // which is essentially 17,17. BUT for whatever reason, column 0 is actually giving us the data from what should be
  // column 17.

  for (int column = FRAME_WIDTH - 1; column >= 0; column--) {
    for (int row = FRAME_HEIGHT - 1; row >= 0; row--) {
      // Top row is FRAME_HEIGHT-1
      // Bottom row is 0;
      int index = column * FRAME_HEIGHT + ((FRAME_HEIGHT - 1) - row);
      byte pix = FrameBuffer[index];

      if ( pix < 0x10 )
      Serial.print("0");

      Serial.print(pix, HEX);

    }  // end for(row)
  } // end for(column)
  
  // IF you manage to get it to 17x17 in the Java file...
    /*
    for (int column = FRAME_WIDTH; column > 1; column--) {
      for (int row = FRAME_HEIGHT; row > 1; row--) {
        // Top row is FRAME_HEIGHT-1
        // Bottom row is 0;
        int index = column * FRAME_HEIGHT + ((FRAME_HEIGHT - 1) - row);
        byte pix = FrameBuffer[index];
  
        if ( pix < 0x10 )
        Serial.print("0");
  
        Serial.print(pix, HEX);
  
      }  // end for(row)
    } // end for(column)
    */
  // Newline after the last row
  Serial.println();
}

void ADNS2610_reset() {
  pinMode(ADNS2610_SCLK_PIN, OUTPUT);
  pinMode(ADNS2610_SDIO_PIN, INPUT);
  pinMode(ADNS2610_PD_PIN, OUTPUT);

  digitalWrite(ADNS2610_SCLK_PIN, LOW);
  digitalWrite(ADNS2610_PD_PIN, HIGH);
  delayMicroseconds(1);
  digitalWrite(ADNS2610_PD_PIN, LOW);
}

byte readRegister(byte address) {
  pinMode (ADNS2610_SDIO_PIN, OUTPUT);

  for (byte i = 0x80; i > 0 ; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SDIO_PIN, address & i);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
  }

  pinMode (ADNS2610_SDIO_PIN, INPUT);

  delayMicroseconds(100); // tHOLD = 100us min.

  byte res = 0;
  for (byte i = 0x80; i; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
    if ( digitalRead (ADNS2610_SDIO_PIN))
      res |= i;
  }

  return res;
}

void writeRegister(byte address, byte data) {
  address |= 0x80; // MSB indicates write mode.
  pinMode (ADNS2610_SDIO_PIN, OUTPUT);

  for (byte i = 0x80; i; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SDIO_PIN, address & i);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
  }

  for (byte i = 0x80; i; i >>= 1) {
    digitalWrite (ADNS2610_SCLK_PIN, LOW);
    digitalWrite (ADNS2610_SDIO_PIN, (data & i) != 0 ? HIGH : LOW);
    digitalWrite (ADNS2610_SCLK_PIN, HIGH);
  }

  delayMicroseconds(100); // tSWW, tSWR = 100us min.
}