If you want to skip the yapping and just want to see the final product, click here!
Back then, I did not know that the OLED display of Odin75 is programmable. I recently learned about the existence of QMK firmware and was happy that my keyboard uses that firmware. You can check the Github repository of QMK here. Basically, I can customize my keyboard firmware, including what is shown on the OLED display.

Above, that is the default display of Odin75. Bongo Cat animation with Words Per Minute (WPM) and Caps Lock status. I wanted to change it to something more personal. These are the things that I wanted to show on the OLED display:
- Instead of Bongo Cat, I want to see an animation if I pressed a key.
- Show the last key pressed.
- Show the total number of keys pressed since the last reset.
- Show session time.
- A way to reset the data above, like starting a new session.
Change the Bongo Cat!
As much as everyone love cats, I am getting tired of seeing the same animation, a change would be nice. After grueling hours of creating the perfect frames, below is what I manage to create:


These images are only 128x32, OLED display has 128x64. The other remaining pixels can be used for texts (which will come later this post).
Now that we have the images, it is time to build the code. Upon checking the Github repository of QMK, under the Odin75 directory, I found a file named bongocat.c. Obviously, this is the file responsible in displaying the Bongo Cat animation. The gist of the render_bongocat is it renders the appropriate frame depending on the WPM (provided by a QMK function get_current_wpm()).
As for me, I don't need the get_current_wpm(). I need a function that executes when a key is pressed. While searching the QMK codebase, I found process_record_user(). Click here to know more about the function. Below is a not-so-complete code of displaying our custom animation based on key press state.
1static const char PROGMEM new_frames_to_display[2][512] = {{...}, {...}};
2// new_frames_to_display[0] is the frame for the key pressed
3// new_frames_to_display[1] is the frame for the key released
4bool process_record_user(uint16_t keycode, keyrecord_t *record) {
5 if (record->event.pressed) {
6 oled_write_raw_P(new_frames_to_display[0], 512);
7 } else {
8 oled_write_raw_P(new_frames_to_display[1], 512);
9 }
10 return true;
11}As of writing, the guide to convert an image to OLED display in bongocat.c seems to be broken. A quick google search led me to javl.github.io/image2cpp. The website converts an image to a C array that can be used in QMK. As soon as I got the array, I just plugged that to Listing 1 line 1. You can add Listing 1 to odin75.c and it should work!
Show the last key pressed and total number of keys pressed
We can use the argument keycode in process_record_user(). It is equal to the key being pressed or released.
1if (record->event.pressed) {
2 key_pressed++;
3 oled_write_raw_P(new_frames_to_display[0], 525);
4 oled_set_cursor(16, 4);
5 const char *padding = " "; // padding for key name display
6 oled_write(padding, false); // clear previous key name
7 size_t key_name_length = strlen(keycode_to_name(keycode));
8 oled_set_cursor(16 + (5 - key_name_length), 4);
9 oled_write(keycode_to_name(keycode), false); // display key name
10
11 // Code to display the total number of key presses
12 sprintf(buffer, "%d", key_pressed);
13 oled_set_cursor(0, 5);
14 oled_write(buffer, false); // display number of key presses
15 oled_set_cursor(numPlaces(key_pressed), 5);
16 oled_write_P(PSTR(" Thocks! "), false);
17}Listing 2 is just added to the if-statement in Listing 1. On line 2, key_pressed is a global variable that I initialized somewhere in the code. This is my counter for the total number of keys pressed. Line 4 and 8 tells the firmware on what part of the display to write the text. oled_write() is a QMK function that writes text to the OLED display. When using oled_write(), you can fit 21 characters per line. The text that I will display has a total of 5 characters. Line 6 is for clearing the previous displayed text (5 empty spaces). Line 9 is for displaying the actual last key pressed. Line 7 and 8 is my magic for displaying the 5 characters in the most right part of the container (Kinda like text-align: right; in CSS). Line 12 to 16 is the code for displaying the total number of THOCKS or key pressed.
Show session time
To show the session time, I implemented the code inside oled_task_kb() function. Based from the code inside of that function, I believe this is called in an infinite loop somewhere. Since we want to show the updated session time all the time, I believe this is the best place to put it. Below is the code that I added.
1bool oled_task_kb(void) {
2 if (!oled_task_user()) {
3 return false;
4 }
5 if (uptime_timer == 0) {
6 uptime_timer = timer_read32();
7 }
8 char uptime_str[12];
9 uint32_t uptime = timer_elapsed32(uptime_timer) / 1000;
10 uint32_t minutes = uptime / 60;
11 uint32_t hours = minutes / 60;
12 sprintf(uptime_str, " %lu:%02lu:%02lu", hours, minutes % 60, uptime % 60);
13 led_t led_state = host_keyboard_led_state();
14 // render_bongocat();
15 // oled_set_cursor(14, 0); // sets cursor to (column, row) using charactar
16 // spacing (4 rows on 128x32 screen, anything more will overflow back to the
17 // top) oled_write_P(PSTR("WPC:"), false);
18 // oled_write(get_u8_str(get_current_wpm(), '0'), false); // writes wpm on top
19 // right corner of string
20 if (led_state.caps_lock) {
21 oled_set_cursor(13, 0);
22 oled_write_P(PSTR(" CAPS ON"), false);
23 } else {
24 oled_set_cursor(13, 0);
25 oled_write_P(PSTR("CAPS OFF"), false);
26 }
27 // Yes, you can improve this by just getting the number of digits then
28 // subtract it to 14 But I like to keep it simple since I wont be seeing this
29 // code for the next 10 years lol
30 if (hours > 99) {
31 oled_set_cursor(11, 2);
32 } else if (hours > 9) {
33 oled_set_cursor(12, 2);
34 } else {
35 oled_set_cursor(13, 2);
36 }
37 oled_write_P(PSTR(uptime_str), false);
38 return false;
39}Regarding how we display the text, it is the same logic. Line 30 to Line 36 is to display the session time on the right most part of the OLED.
Reset the data
Since we are dealing with user input, then we need to modify process_record_user again. I configured that if I press the KC_PAUSE + r key codes. Below is the code that I added to reset the session time and total number of keys.
1// Listing 5
2//...
3bool is_pause_pressed = false;
4bool is_r_pressed = false;
5//...
6
7bool process_record_user(uint16_t keycode, keyrecord_t *record) {
8 //...
9 if (record->event.pressed) {
10 //...
11 if (keycode == KC_PAUSE) {
12 is_pause_pressed = true;
13 } else if (keycode == KC_R) {
14 is_r_pressed = true;
15 }
16 //...
17 } else {
18 if (keycode == KC_PAUSE) {
19 is_pause_pressed = false;
20 } else if (keycode == KC_R) {
21 is_r_pressed = false;
22 }
23 }
24 //...
25 if (is_pause_pressed && is_r_pressed) {
26 key_pressed = 0; // reset key pressed count
27 uptime_timer = 0; // reset uptime
28 }
29}How it looks!
Kinda slick if you ask me. I was thinking to add some notification feature but that would be too much, maybe in the future. If you want to see the full code, you can check my QMK fork here!