header source
my icon
esplo.net
ぷるぷるした直方体
Cover Image for QMK Introduction: Running meishi Trackball Module with Custom Firmware

QMK Introduction: Running meishi Trackball Module with Custom Firmware

about20mins to read

The created firmware code is available in this repository. Please check here if you have any questions.

https://github.com/esplo/meishi_trackball_test_fw

By the way, the header image is a keyboard drawn by DALL-E, with many columns and rows, and grass growing on it.

Background

https://shop.yushakobo.jp/products/6956

I bought this namecard-sized trackball, thinking, "Why not use it to move the mouse without leaving the keyboard?" It comes with two switches and a rotary encoder, allowing it to be used like a mouse. The trackball part can be used separately, making it a great product.

After assembling it with a Pro Micro microcontroller and struggling with the USB connection, I finally completed it. Although there is a detailed guide by the creator, I still managed to get the orientation wrong, which was a humbling experience.

Trackball Angle Problem

After successfully assembling the trackball and confirming that it works, I noticed that the trackball angle doesn't change smoothly. The ROT_R15 and ROT_L15 keycodes are supposed to change the angle by 15 degrees each time, but it seems to reset to around 90 degrees.

Fortunately, the firmware code is publicly available. After reading it, I found that the rotation angle is fixed, with a total range of 120 degrees. Although this is intentional, it seems that the code needs to be modified to set it to any angle.

#define COCOT_ROTATION_ANGLE { -60, -45, -30, -15, 0, 15, 30, 45, 60 }

https://github.com/aki27kbd/qmk_firmware/blob/067112151a0634b7a1d01604041e5347179431e0/keyboards/aki27/trackball_module/trackball_module.c#L54

QMK Compilation Error

The publicly available firmware can be modified to solve this issue. This firmware uses QMK.

QMK is a widely used firmware library for custom keyboards. It's not only easy to implement but also provides convenient features like browser-based keymap editing.

However, since QMK is actively developed, there are sometimes breaking changes. The publicly available code for the meishi Trackball Module is written for QMK 0.16, which causes compilation errors with QMK 0.22.

……So, what to do? Since I had to rewrite it anyway, I decided to implement it from scratch to support the new version.

Writing the Firmware

QMK has excellent documentation, so most issues can be resolved by referring to the official documentation.

https://docs.qmk.fm/

From here, I'll only describe the points where I stumbled.

Scope of Implementation

For now, I'll focus on implementing the minimum required features: making the keys work, enabling scrolling with the rotary encoder, and making the trackball move.

Convenient Tools

  • QMK Toolbox
    • This tool burns the firmware. Although it can be done from the browser, it's reassuring to have console output.
  • REMAP
    • This is a firmware repository site. It's very convenient. The official meishi Trackball Module firmware can also be burned or downloaded from here. I'll use it mainly for testing.

Hardware Settings

These are the hardware settings, including the MCU type, pin settings, and more. I extracted them from the original code for reference.

  • MCU: atmega32u4
  • bootloader: atmel-dfu
  • Trackball sensor: adns5050
  • diode_direction: COL2ROW
  • Pins
    • B6: Key 1
    • B3: Key 2 (rotary encoder click?)
    • B2: Key 3
    • D2: Rotary encoder rotation 1
    • D3: Rotary encoder rotation 2

Setup

Since I'm using an M1 Mac, I installed QMK using brew install qmk/qmk/qmk. However, it takes a ridiculously long time. It's essential to do this before starting to write code, or you'll lose motivation.

Moreover, since C projects are modularized for each keyboard, setup can be challenging. Even following the official guide, there are parts where IDE completion doesn't work. VS Code gets frustrated, but I'll persevere.

qmk setup -H ./qmk_firmware clones the QMK repository. It's better to use the v0.22.0 tag, which is the latest version at the time of writing (2024/02).

https://www.eisbahn.jp/yoichiro/2023/12/remap_in_2023.html#gsc.tab=0

info.json Settings

This is an important file, but it's unclear what settings are required. Although similar settings can be written in config.h using macros, it's better to set them here for clarity.

Many custom keyboards use a matrix (row-column combination) for switch detection, but in this case, matrix_pins.direct can be set. The layouts specification is almost the same as for matrix keyboards.

Moreover, USB vendor IDs and other settings should not be taken from the original firmware. If you do, REMAP might try to match the existing keyboard definition, leading to a day of debugging.

{
    "manufacturer": "aaaa",
    "keyboard_name": "test_trackball",
    "maintainer": "aaaa",
    "bootloader": "atmel-dfu",
    "diode_direction": "COL2ROW",
    "features": {
        "bootmagic": true,
        "command": false,
        "console": false,
        "extrakey": true,
        "mousekey": true,
        "nkro": true
    },
    "processor": "atmega32u4",
    "url": "",

    "matrix_pins": {
        "direct": [["B6", "B3", "B2"]]
    },

    "usb": {
        "device_version": "1.0.0",
        "pid": "0x000A",
        "vid": "0x1720"
    },

    "layouts": {
        "LAYOUT": {
            "layout": [
                { "matrix": [0, 0], "label": "0,0", "x": 0, "y": 0 },
                { "matrix": [0, 1], "label": "0,1", "x": 1, "y": 0 },
                { "matrix": [0, 2], "label": "0,2", "x": 2, "y": 0 }
            ]
        }
    }
}

Receiving Key Input

In keymaps/default/keymap.c, I wrote the processing for when the keys set in matrix_pins and layouts are pressed. The order is crucial. Using the names specified in info.json under layouts allows for compile-time checking.

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {[0] = LAYOUT(KC_MS_BTN1, KC_MS_BTN2, KC_MS_BTN3)};

Enabling the Rotary Encoder

This is described in the QMK documentation.

https://docs.qmk.fm/features/encoders#encoders

By specifying ENCODER_ENABLE = yes in rules.mk, the rotary encoder is enabled. The pin settings are specified in config.h.

#define ENCODERS_PAD_A \
    { D2 }
#define ENCODERS_PAD_B \
    { D3 }
#define ENCODER_RESOLUTION 4

The keymap is specified in keymaps/default/rules.mk and keymaps/default/keymap.c.

#if defined(ENCODER_MAP_ENABLE)
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = {
    [0] = {ENCODER_CCW_CW(KC_MS_WH_UP, KC_MS_WH_DOWN)},
};
#endif

Trackball Support

Finally, I reached the part where I want to change the trackball behavior. This is also described in the QMK documentation.

First, I enabled it in rules.mk. Specifying the sensor driver is crucial.

POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = adns5050

In config.h, I specified the pins. The original firmware code can be used as is.

#define ADNS5050_SCLK_PIN F7
#define ADNS5050_SDIO_PIN F6
#define ADNS5050_CS_PIN B1

Here, I implemented the processing in <keyboard>.c (where <keyboard> is the specified keyboard name). The callback function for processing the coordinate values is prepared, so I rewrote it. The angle value is incremented or decremented by 1 each time a button is pressed.

#define ANGLE_UNIT 15
#define ANGLE_MAX (360 / ANGLE_UNIT)
uint8_t angle = 0;

report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
    double rad     = ANGLE_UNIT * angle * (M_PI / 180);
    int8_t x_rev   = +mouse_report.x * cos(rad) - mouse_report.y * sin(rad);
    int8_t y_rev   = +mouse_report.x * sin(rad) + mouse_report.y * cos(rad);
    mouse_report.x = x_rev;
    mouse_report.y = y_rev;
    return pointing_device_task_user(mouse_report);
}

Custom Key Settings

Here, I implemented the processing to change the angle value according to the custom keys ROT_R15 and ROT_L15. Using printf makes debugging easier.

enum my_keycodes { ROT_R15 = QK_KB_0, ROT_L15 };

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
#    ifdef CONSOLE_ENABLE
    uprintf("KL: kc: 0x%04X, col: %2u, row: %2u, pressed: %u, time: %5u, int: %u, count: %u\n", keycode, record->event.key.col, record->event.key.row, record->event.pressed, record->event.time, record->tap.interrupted, record->tap.count);
    uprintf("%04X\n", ROT_R15);
#    endif

    switch (keycode) {
        case ROT_R15:
            angle = (angle + 1) % ANGLE_MAX;
            return false;
        case ROT_L15:
            angle = (angle + ANGLE_MAX - 1) % ANGLE_MAX;
            return false;
        default:
            return true;
    }
}

VIA Support

To set keys in the browser, VIA support is necessary. REMAP also requires this.

The definition file is specified as follows. The vendorId and productId should be the same. The custom keycodes specified in customKeycodes correspond to QK_KB_0, QK_KB_1, and so on.

{
    "name": "test_trackball",
    "vendorId": "0x1720",
    "productId": "0x000A",
    "lighting": "none",
    "menus": [],
    "keycodes": [],
    "matrix": { "cols": 5, "rows": 1 },
    "customKeycodes": [
        {
            "name": "ROT_R15",
            "title": "Rotate sensor Y-axis by 15 degrees clockwise",
            "shortName": "ROT_R15"
        },
        {
            "name": "ROT_L15",
            "title": "Rotate sensor Y-axis by 15 degrees counterclockwise",
            "shortName": "ROT_L15"
        }
    ],
    "layouts": {
        "keymap": [["0,0", "0,2\n\n\n\n\n\n\n\n\ne0", "0,1"]]
    }
}

Completion!

If you can burn the firmware using a suitable method and assign custom keys using REMAP, you're done!

Please refer to the sample code for detailed explanations. It's also a good idea to debug using printf.

https://github.com/esplo/meishi_trackball_test_fw

Afterword

QMK is very well-organized and convenient. It's also great for custom keyboard development.

The meishi Trackball Module is an excellent product, with publicly available documentation and code. It's perfect for custom keyboard beginners.

Now that I've completed the QMK-based firmware, I'd like to explore keyboard separation and PCB design next.

Share