[en] Reverse engineering Skidata trackball

Recently, I have a chance to examine Skidata keyboard during my trip to Germany. During my trip to 35C3, I stayed at my friend(peremen)’s house and I asked something to hack to him. He kindly provided Skidata trackball to me and I did some reverse engineering task.

At first, I examined its PCB. It has four buttons and two optical quadrature encoders with a single-sided board. It does not have any processing module on it and exposed its encoders and buttons directly to the keyboard.

Then, I fired up KiCAD and reconstructed its schematic while traced trace on its PCB. Since they used single sided PCB for their trackball, I can reconstruct its schematic fairly easily.

Here is my reconstructed schematic:

Now, using this schematic, I know how to plug it into a modern microcontroller development board. I quickly wrote basic trackball driver code for Atmel AVR.

for(;;) {
    uint8_t buf = PINA;
    enc_cur[0] = buf & 0x03;
    enc_cur[1] = (buf & 0x0C) >> 2;

    for (int i = 0; i < 2; i++) {
        if (enc_prev[i] != enc_cur[i]) {
            char dir = i == 1 ? 'x' : 'y';
            //printf("coord = %c, enc_prev = %d, enc_cur = %d, pos_prev = %d, pos_cur = %d\r\n", dir, enc_prev[i], enc_cur[i], postbl[enc_prev[i]], postbl[enc_cur[i]]);

            //printf("dir = %d\r\n", postbl[enc_cur[i]] - postbl[enc_prev[i]]);
            switch (postbl[enc_cur[i]] - postbl[enc_prev[i]]) {
            case 1:
            case -3:
                if (dir == 'x') {
                    xpos ++;
                } else {
                    ypos ++;
                }
                break;
            case -1:
            case 3:
                if (dir == 'x') {
                    xpos --;
                } else {
                    ypos --;
                }
                break;
            }

            enc_prev[i] = enc_cur[i];
            cnt++;

            if (cnt > 100) {
                printf("x = %ld, y = %ld\r\n", xpos, ypos);
                cnt = 0;
            }
        }
    }
}

I loaded it on ATmega2560 and do some test with the trackball. It successfully tracked the movement of the trackball.

So, I ported my simple driver code into ATmega32U4 with LUFA framework. At first try, it was not able to track the trackball well. It missed lots of samples. To resolve this, I take another approach: Use the interrupts.

ISR(PCINT_vect) {
    uint8_t buf = PINB;
    enc_cur[0] = (buf >> 1) & 0x03;
    enc_cur[1] = (buf & 0x18) >> 3;

    for (int i = 0; i < 2; i++) {
        if (enc_prev[i] != enc_cur[i]) {
            char dir = i == 1 ? 'x' : 'y';
            switch (postbl[enc_cur[i]] - postbl[enc_prev[i]]) {
            case 1:
            case -3: 
                if (dir == 'x') {
                    xpos ++; 
                } else {
                    ypos ++; 
                }   
                break;
            case -1: 
            case 3:
                if (dir == 'x') {
                    xpos --; 
                } else {
                    ypos --; 
                }   
                break;
            }   

            enc_prev[i] = enc_cur[i];
            cnt++;

            if (cnt > 100) {
                printf("x = %ld, y = %ld\r\n", xpos, ypos);
                cnt = 0;
            }   
        }   
    }   
}

I re-implemented trackball tracking code using PCINT and INT. Well.. it did not work well either. Much better than polling the trackball, but still misses lots of samples.

Then, I made another tweak: Use a lookup table.

void handle_xenc_int() {
    enc_cur[0] = (PIND & 0x0C) >> 2;
    xpos += dirtbl[enc_prev[0] << 2 | enc_cur[0]];
    enc_prev[0] = enc_cur[0];
}

void handle_yenc_int() {
    enc_cur[1] = (PIND & 0x03) >> 0;
    ypos += dirtbl[enc_prev[1] << 2 | enc_cur[1]];
    enc_prev[1] = enc_cur[1];
}

ISR(INT0_vect) {
    handle_yenc_int();
}

ISR(INT1_vect) {
    handle_yenc_int();
}

ISR(INT2_vect) {
    handle_xenc_int();
}

ISR(INT3_vect) {
    handle_xenc_int();
}

It helped a lot but, well. not quite cut enough. It still misses some samples at high speed. At this point, I think there is no way to reduce its latency dramatically without writing ISR in heavily optimized assembly code (which will harm code readability and maintainability a lot).

At this point, maybe faster processor like STM32 will help to plug this trackball to PC but unfortunately, I’m not able to do another experiment with this trackball (My trip to Germany ended).

Note that I put all of my reverse engineering effort on Github and I wish this information can help someone who wants to implement its trackball driver.

Here is a link to my repository: https://github.com/perillamint/skidata-trackball