ZMK Support for MNT Reform Standalone Keyboard V4

Hey everyone!

I’ve been working on supporting the standalone MNT Reform Keyboard V4 on the ZMK firmware for the past few weeks and have reached a point where I’m looking for others to help test!

The basic keyboard features are all functional and should support all of the
expected features from ZMK. This first release also has support for ZMK Studio allowing for dynamic remapping of the keymap via a graphical interface without recompiling and re-flashing the firmware.

RGB is disabled by default. While RGB is functional, in my testing it appears
to cause the USB driver stack to crash, and the keyboard will no longer respond to the host. If the driver stack crashes the keyboard can be reset by pressing Circle + Hyper + R. This is the primary area I’m looking for additional testing! Please let me know if you see issues with the USB driver stack crashing and whether or not RGB was on or off when it crashed.

Download for a test release is available here:
https://github.com/joguSD/zmk-mntre-module/actions/runs/16653592939/artifacts/3659745372

To flash the ZMK firmware, put the keyboard into update mode and copy over the reform_zmk_studio.uf2 firmware file.

Under ZMK, you can put the keyboard into update mode by pressing the key combo Circle + Hyper + X (all at the same time) to flash updates or the original firmware.

For instructions on downloading / flashing the original firmware back, follow
the guidance in the handbook here:

2 Likes

Key Combinations for reference:

Combos (press all keys at the same time):

  • Circle + Hyper + R - Resets the firmware
  • Circle + Hyper + U - Unlocks the keyboard for editing by ZMK Studio
  • Circle + Hyper + X - Reboots the keyboard into update mode

Hyper Functions:

  • Hyper + ESC - Enable/Disble RGB
  • Hyper + F1 - Decrease RGB Brightness
  • Hyper + F2 - Increase RGB Brightness
  • Hyper + F3 - Decrease RGB Hue
  • Hyper + F4 - Increase RGB Hue
  • Hyper + F5 - Decrease RGB Saturation
  • Hyper + F6 - Increase RGB Saturation
  • Hyper + F7 - Cycle RGB Effect. Solid, Breathe, Spectrum, Swirl (default)
2 Likes

Hi, this is a super awesome contribution, thank you very much! I just tested it on my own keyboard and typing works fine. I’m having the same issues with RGB backlight though. Surprisingly even in solid mode.

I encountered similar issues during development of the RGB backlight in the custom firmware when the RGB “bitmap” updating was starving the USB thread. In general what works best if the USB thread is on an interrupt that preempts everything else (like OLED updating, too). I don’t have Zephyr experience yet but I imagine that RGB updating and USB servicing are different threads/processes? How are their priorities? Can you explain or point me to how this is implemented in ZMK / your code? Also, is the RGB code implemented as PIO code?

Furthermore, while typing this, I also had some USB dropouts without the RGB backlight.

1 Like

One more thing: Circle+Hyper+X doesn’t seem to do anything for me (i.e. it doesn’t enter flashing mode). Edit: actually none of the Circle+Hyper+combos seem to do anything.

Another update: I figured it out, these 3 special key combos only work if you press all 3 keys together in a timeframe of 100ms, you can’t just press and hold hyper and/or circle and then U/R/X. With this in mind, I was able to successfully try ZMK studio in Chromium (an arm64 build is sorely missing), and also restore the MNT firmware. Nice.

BTW, I see that the OLED is defined in the .dts, but it doesn’t do anything yet (at least for me). Is that normal/intentional?

Hey Minute!

Thanks so much for taking the time to help test the firmware, I really appreciate it. Also good (?) to hear that you can reproduce the problem and it’s not just an issue with my board or environment haha.

I don’t have Zephyr experience yet but I imagine that RGB updating and USB servicing are different threads/processes? How are their priorities? Can you explain or point me to how this is implemented in ZMK / your code?

There’s no logic in the module I’ve written at the moment. It’s just the board device tree definition and configuration of features in ZMK / drivers in Zephyr.

I’m also brand new to Zephyr (and hardware development in general). From what I’ve grokked the USB appears to be interrupt driven, I’ll see if I can get some links to share. Also, there definitely are different threads / worker priorities. RGB updates are on the low priority work queue:


Also, is the RGB code implemented as PIO code?

The WS2812 PIO driver is a relatively recent addition to Zephyr:

Afaik, no other keyboard with ZMK support leverages this driver yet. The current release of ZMK actually doesn’t even support this driver. I’ll touch more on this later.


I figured it out, these 3 special key combos only work if you press all 3 keys together in a timeframe of 100ms

Right, this is the “Key Combo” feature in ZMK. It’s less than ideal, so these are temporary in my mind until I can get the OLED / a menu system functioning.


BTW, I see that the OLED is defined in the .dts, but it doesn’t do anything yet (at least for me). Is that normal/intentional?

Yes, the OLED is disabled at the moment. I’m basing my work on top of an unstable WIP branch of ZMK that is moving to a more recent version of Zephyr. I’ve confirmed with the lead dev of ZMK that display support is broken on this branch at the moment so I haven’t put too much effort on that front yet. Trying to enable it currently causes a segfault that hard locks the board.

A bit of background: ZMK has been stuck on Zephyr 3.5 for nearly 2 years as Zephyr introduced some breaking changes in 3.6+. ZMK also did not have versioned releases, just a main branch. Tagging releases, and moving towards a more recent version of Zephyr (4.1) is something the ZMK devs are actively working on, and what I’m working on top of. RP2040 support within Zephyr has matured quite a bit since 3.5, including things like the WS2812 PIO driver, so it’s the only way to get the hardware functional.

1 Like

Notes from my testing, ZMK/Zephyr debug logs show a lot of errors like this:

[00:00:04.757,000] <err> udc_rpi: crc error
[00:00:05.108,000] <wrn> udc_rpi: rx overflow
[00:00:15.657,000] <wrn> udc_rpi: bit stuff error

However, these don’t indicate when the device driver stack dead locks and stops responding. USB can cut out with or without seeing these messages, and seeing these messages does not mean it has cutout.

Using some additional hardware (Cynthion) to analyze the USB traffic the only thing I’ve been able to really see when the device cuts out is a malformed packet:

Packet #12907 with 1 bytes
Timestamp: 9,023,533,916 ns from capture start
Malformed packet (invalid PID) of 1 byte
Hex bytes: ','

Some side quests / things I’ve tested along the way to get here:

  • This firmware on a bare RPI Pico does not exhibit unstable USB behavior and has no error logs like I pasted above. Manually bridging pins to press keys does work and is stable for multiple days.
  • I ported ZMK to the Adafruit MACROPAD RP2040, which has a similar hardware configuration. This also does not exhibit unstable USB behavior and has been perfectly stable.
  • Before trying ZMK, I actually ported to QMK first. QMK exhibits unstable USB device behavior.
  • I’ve also written a KMK definition that exhibited unstable USB behavior. I was running this on top of a standard RPI Pico CircuitPython u2f.
1 Like

This week I took a break from investigating the USB issue and instead decided to focus on getting the display functional under ZMK. Display support is currently broken on the unstable branch of ZMK I’m working on top of so I wanted to see if I could get the display working using Zephyr directly. I first tested using Zephyr’s sample for the basic character framebuffer library which worked straight away with no issues. Then trying the LVGL samples I was initially getting crashes, but a few additional configuration definitions and I got that sample working as well.

Looking more into the LVGL library, which is what ZMK builds it’s display support on top of, and what is crashing currently it seems pretty feature rich. On the flip side, I feel it’s rather heavy / overkill for a monochrome 128x32 screen. Putting segfaulting because a few config definitions were missing aside…

To replicate the functionality of the MNT firmware, we’ll need custom logic to drive the display. My hunch currently is that it will likely be more effort to do the initial development and maintenance of this if we build it on top of the ZMK display logic. For now, I decided to take the first step to adding custom logic into ZMK and have the basic framework setup to drive the display. For now, it just displays a static image and turns off when the keyboard is idle to avoid burn-in. With this, all of the hardware is functional at a basic level!

While testing the display, I found that having an active timer to update the display every 100ms also increased instability in a similar fashion to enabling RGB. Logging over UART also seems to hurt stability as well. This is leading me to believe that the issue is not specific to RGB, but some sort of concurrency/priority/interrupt issue as the firmware runs more logic. As for why other RP2040 boards don’t show this issue I’m not sure.

Next I’ll try tackling the USB instability in a similar fashion by trying to get a minimal reproduction on top of Zephyr directly to rule out something in ZMK being the issue.