
QMK Introduction: Running meishi Trackball Module with Custom Firmware
Table of Contents
- Top
- Background
- Trackball Angle Issue
- QMK and Compilation Failure Issue
- Writing Firmware
- Scope of Implementation This Time
- Useful Tools
- Various HW Configuration Values
- Setup
- What to Write in info.json
- Receiving Key Input
- Moving the Rotary Encoder
- Trackball Support
- Setting Custom Keys
- VIA Support
- Completed!
- Afterword
Announcement: A book related to the content of this article has been published. If you want comprehensive information, please check it out.
The firmware I created is uploaded in this repository. If you have any questions, please check here.
By the way, the header image is a keyboard drawn by DALL-E, which has too many columns and is wobbly, making me laugh.
Background
This is a business card-sized trackball that I bought thinking it might be useful for moving the mouse without taking my hands off the keyboard. It comes with two switches and a rotary encoder, which can be used like a mouse. As the name suggests, it's an excellent device that allows you to cut out and use just the trackball part.
After various experiences like crying while desoldering the microcontroller (Pro Micro) that I soldered on backwards, and having to buy it again because the USB connection part came off, I finally completed the assembly. There's a careful guide by the creator, but I still made mistakes with the orientation, allowing me to fully experience my own foolishness.
Trackball Angle Issue
Well, I successfully assembled it and confirmed that the trackball and various switches were working, but for some reason, the angle of the trackball doesn't change subtly. It's supposed to change by 15° each time the ROT_R15
or ROT_L15
keycodes are issued, but it seems to reset at about 90°.
Thankfully, the firmware code is publicly available. Reading it, it appears that the rotatable angle is hardcoded, with a total of 120°. It was intentional, but it seems that the code needs to be modified to set it to all directions.
#define COCOT_ROTATION_ANGLE { -60, -45, -30, -15, 0, 15, 30, 45, 60 }
QMK and Compilation Failure Issue
It seems that the problem could be solved by tweaking the publicly available firmware a bit. This firmware uses QMK.
QMK is a firmware library commonly used in custom keyboards. Not only does it make implementation easy, but it also allows for convenient features like remapping keys in a browser. It's really amazing.
And because it's being actively developed, there are occasional breaking changes. The publicly available code for meishi Trackball Module was created with QMK 0.16, a slightly older version, so it results in compilation errors with QMK 0.22.
...So what to do? If there were 100 people, 1000 would think, "Well, let's re-implement it to be compatible with the new version." Not to be outdone, I was one of those 10000 people, so I decided to reinvent the wheel for the 100000th time.
Writing Firmware
QMK has very comprehensive documentation, so most issues can be resolved by looking at the official docs.
Therefore, from here on, I'll only mention the points where I stumbled.
Scope of Implementation This Time
We'll consider it OK if keys can be pressed, the rotary encoder can scroll, and the trackball can move. The original firmware is more feature-rich, but here we'll focus on the minimum functionality.
It's fun to feel the progress by flashing and checking the operation each time a feature is implemented.
Useful Tools
- QMK Toolbox
- This flashes the firmware for you. You can also flash from the browser, but it's reassuring to have this installed to see the console output.
- REMAP
- This is the firmware repository site. It's very convenient. You can also flash or download the official meishi Trackball Module firmware from here. It's safe to open in Chrome. This time, we'll mainly use it for operation confirmation.
Various HW Configuration Values
Hardware model numbers, pin settings, etc. I pulled these from the original code. They're for reference when you get stuck.
- 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
For me on an M1 Mac, brew install qmk/qmk/qmk
completed it if left alone. However, it takes an extremely long time. It takes several hours, so if you don't do it before you feel like writing code, your motivation will dissipate.
Also, because it's a C project and settings are modularized for each keyboard, setup is difficult. Even if you follow the official procedure, there are places where IDE completion doesn't work. VS Code's face is also bright red, but let's not worry about it. It's OK as long as compilation passes.
VS Code with a bright red face
Use something like qmk setup -H ./qmk_firmware
to clone the QMK repository. Instead of using this as is, it seems good to checkout and use the v0.22.0 tag. As of February 2024, the latest is 0.23, but looking at the REMAP compatibility status, it seems safer to use 0.22.0.
What to Write in info.json
This is an important file, but it's also an area where it's unclear what to set. Similar settings can be written as macros in config.h
, but it seems clearer to set them here as much as possible.
Many custom keyboards have pin layouts in a matrix (determining which switch is pressed by a combination of rows and columns), but I was stuck because I didn't know that we could use the matrix_pins.direct
setting in this case. Even in this case, the layouts
specification is almost the same as in the matrix case.
Also, for USB vendor IDs and such, let's use arbitrary values that aren't yet registered in REMAP, rather than using the values from the original firmware. Otherwise, REMAP might try to match it with existing keyboard definitions and go silent, leading to a day wasted on cause investigation.
By the way, if you want to output debug information to the console using printf, just set features.console to true.
{
"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
Write the processing for when pins set in matrix_pins
or layouts
are received in keymaps/default/keymap.c
. The order is important. By using the name specified under layouts
in info.json
, you can detect differences in numbers at compile time. It's a macro paradise.
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {[0] = LAYOUT(KC_MS_BTN1, KC_MS_BTN2, KC_MS_BTN3)};
Moving the Rotary Encoder
It's as described here.
Writing ENCODER_ENABLE = yes
in rules.mk
enables it. The pins for this rotation are specified in config.h
. I couldn't find a way to specify this in info.json
.
#define ENCODERS_PAD_A \
{ D2 }
#define ENCODERS_PAD_B \
{ D3 }
#define ENCODER_RESOLUTION 4
For the keymap, specify ENCODER_MAP_ENABLE = yes
in keymaps/default/rules.mk
, and specify the actual keycodes to be issued in 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
We've finally reached the part we originally wanted to change. This is also mostly described in the documentation.
First, enable it in rules.mk
. It's important to specify the sensor driver.
POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = adns5050
Specify the pins in config.h
. What's written in the original firmware can be used as is.
#define ADNS5050_SCLK_PIN F7
#define ADNS5050_SDIO_PIN F6
#define ADNS5050_CS_PIN B1
At this point, the trackball should be working. This time, we want to be able to change the Y-axis, so we'll implement the processing in <keyboard>.c
(<keyboard>
is the same as the specified keyboard name). There's a callback function prepared for processing coordinate values, so we'll overwrite it to process.
pointing_device_task_kb
receives the movement amount in a variable called mouse_report
, so we'll process it referring to the original firmware. Here, angle
is assumed to take values from 0 to 360/15, and it's 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);
}
Setting Custom Keys
Up to this point, we've created a process that changes the movement direction according to the value of angle
. Finally, we'll make angle
change with the custom keys ROT_R15
and ROT_L15
. Also, using printf makes debugging easier.
QK_KB_0 = 0x7E00
seems to be the custom key constant set on the keyboard side in recent QMK. On the user side, there's a constant QK_USER_0 = SAFE_RANGE = 0x7E40
that doesn't conflict with other keys, which can be used. Since QK_KB_MAX = 0x7E3F
is the maximum, about 64 can be set.
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. The same applies when using REMAP.
Give the definition file an appropriate name and specify it as follows. Specify the same vendorId
and productId
. The keycodes of those specified in customKeycodes
correspond to QK_KB_0
, QK_KB_1
in order.
{
"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"]]
}
}
Completed!
If you can flash it by an appropriate method and get it to work by assigning custom keys in REMAP, it's complete!
I've omitted explanations of detailed areas, so please check the sample code. Also, it's easier to debug the callback parts by checking with printf.
Afterword
QMK was very well-organized and convenient. It's understandable why it facilitates DIY projects.
The meishi Trackball Module, with its published docs and code, was an excellent device that allows experimenting with switches, rotary encoders, and trackballs all together. It seems good for DIY beginners.
Now that I've PerfectlyUnderstood firmware using QMK, I'd like to look into keyboard splitting and PCB design next.