Enhanced keyboard/OLED firmware

I spent the last few nights hacking on the keyboard firmware, and posted my branch here:

The changes are pretty extensive, because I split the single “Keyboard” source file into a half dozen modules so I could understand them better. (I know this will be annoying to the authors. I’m sorry.) Then I added a bunch of features to the display and the hidraw interface (/dev/hidraw2) which added about 2KB to the binary image and 400 bytes to the RAM usage (now 53% and 52%, respectively).

Here’s a list of the enhancements I made:

  • switched out the 6x8 font for an Apple-II-inspired font (128 ASCII characters only)
    • you can switch back to the original with a #define at the top of display.c
  • added a larger 8x16 font (bizcat) that can be selected by the hidraw interface
  • compressed the MNT reform logo, and pulled it out of the font, but kept it animated (because that looks cool)
  • new hidraw commands:
    • UXPW: display the battery status as if the user pressed circle-B
    • UXST: display the keyboard status as if the user pressed circle-S
    • WCLR: clear the OLED display
    • FONT0: select the default 6x8 font (21x4 characters)
    • FONT1: select the bizcat 8x16 font (16x2 characters)
    • WRIT (text): write to the OLED display as if it were a scrolling terminal, with extra escape codes listed below
    • WBIT (u16 LE offset) (bytes): write a bitmap directly into the 512-byte display memory, starting at offset
    • WRLE (u16 LE offset) (bytes): write RLE-encoded data into the 512-byte display memory, starting at offset
  • special escape codes in WRIT to make it easier to write a daemon that displays battery status, using byte 255 (0xff) as an escape:
    • C: clear the display
    • I: turn on inverted text mode
    • N: turn off inverted text mode
    • +: switch to bizcat 8x16 font
    • -: switch to default 6x8 font
    • B: 2-character-wide battery icon showing overall battery level
    • A: 1-character-wide lightning bolt if the battery is charging, or a space otherwise
    • P: 3-character-wide battery percentage (" 0" to “100”)

To draw a tiny square in the upper right corner (offset 120):

  • echo -ne "xWBIT\x78\x00\xff\x81\x81\x81\x81\x81\x81\xff" > /dev/hidraw2

Or via RLE mode:

  • echo -ne "xWRLE\x78\x00\x01\xff\x86\x81\x01\xff" > /dev/hidraw2

I doubt the RLE mode is interesting outside of displaying the boot logo, but the very simple encoding is described at the bottom of display.c.

And here’s the shell command I ran to get the current date/time and battery status displayed in the large font, using escape codes:

  • echo -ne "xWRIT\xff+\xffC"$(date '+%d %b')" "$(date +%H:%M)" \xffB \xffA \xffP%" > /dev/hidraw2

I would love if some or all of these enhancements were pulled into the primary MNT driver, but I understand that I may have just moved the code around too much. Hopefully others will find this useful though. :slight_smile:

10 Likes

@robey Great work! I was able to build and flash the firmware by following these steps:

  1. Clone your forked repo
  2. Install the dependencies you outlined in your previous post, ie:
    apt install gcc-avr avr-libc dfu-programmer
  3. In the repository’s reform2-keyboard-fw directory, run make
  4. Remove top keyboard cover plate
  5. Switch program switch to ON (as specified in Reform manual here
  6. Push the reset button (keyboard turns off)
  7. Plug in external keybord and execute ./flash.sh as root

All of that seemed to do the trick. I am able to see the “new” animation, as well as run some of the example commands.

One issue I’m having – and this comes down to my lack of knowledge in these matters – is that I do not have permission to write to /dev/hidraw2 as my regular user. I have to run the example commands as root in order to get them to work. Is there some way to change the permissions for this?

1 Like

Excellent that you got that far. Flashing the keyboard firmware was a bit scary for me. :slight_smile:

I just noticed that the name of the “hidraw” file seems to depend on whether you booted with a USB keyboard plugged in. Now that I booted without it (just using the builtin keyboard), it’s back to /dev/hidraw0.

My dev files have strict permissions by default, too. I found this SO question about opening up the permissions, tried it, and rebooted, and it worked for me: devices - /dev/hidraw: read permissions - Unix & Linux Stack Exchange

1 Like

The simple solution proposed in that post did the trick. I’ll summarize it here for everyone else.

You need to add a simple udev rule file that sets permissions for the interface. To do so, follow some version of these steps:

  1. Create a new file in /etc/udev/rules.d/ and name it whatever you want, as long as it ends with .rules
  2. Add a line similar to this:
    KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"
  3. Run the following to reset udev with the new rules:
    sudo udevadm control --reload-rules && udevadm trigger

You should not have permission to write to /dev/hidraw*

1 Like

Opening all read access for all hidraw devices seems like it might be a security risk. For example, does this allow any process on the system to snoop on all keys typed (including login passwords), including on external USB keyboards?

I’d suggest:

  • adding ATTR{idVendor}=="xxxx" ATTR{idProduct}=="yyyy" clauses to limit it to just the Reform’s built-in keyboard (sorry I don’t know the vendor/product codes, still waiting for my Reform),
  • changing to MODE="0660", and
  • creating and using a group specifically for this purpose (in case plugdev is used by other things).
1 Like

@devkev Good thinking. I tried several variations of this at first but couldn’t get any of them to work. I assume it’s because I was pulling out the wrong information.

Here is what it reported from lsusb on my system:

Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 0451:8140 Texas Instruments, Inc. TUSB8041 4-Port Hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 005: ID 03eb:2041 Atmel Corp. LUFA Mouse Demo Application
Bus 001 Device 003: ID 03eb:2042 Atmel Corp. LUFA Keyboard Demo Application
Bus 001 Device 002: ID 0451:8142 Texas Instruments, Inc. TUSB8041 4-Port Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

I assumed it was the third to last, but got conflicting information onlne about what counts as the vendor and what counts as the id.

Based on when I’ve done similar rules in the past for USB devices, the first four hex bytes (ie. 03eb) are definitely the vendor, and the latter ones (ie. 2042) are the product.

But udev rules can be tricky to get right, and I’ve never done any for hidraw devices. The main thing is that the listed attributes have to match the actual hidraw device node itself, or a single ancestor node in the udev tree. I would expect the KERNEL and SUBSYSTEM clauses to match the hidraw device, and the ATTR{idVendor/idProduct} to match the node of the USB device itself. But maybe that doesn’t work for some reason (eg. some separation in the kernel between the hidraw device, and the usb device). That said, I can see rules in /lib/udev/rules.d/* on my current system which use all 4 of these clauses.

The usual way to debug these things is to run something like sudo udevadm info -a -n hidraw0, and study the output carefully to try to figure out why it isn’t matching.

You might also want to add something like SYMLINK+="hidraw-internal-keyboard", so that scripts have a stable name to use (regardless of whether the actual device is hidraw0, hidraw, …).

I made a few more changes/“enhancements” which I’ve pushed to my mirror. The main thing is I hooked up the keyboard backlight PWM to an interrupt and use that to count 32KHz “ticks”. This let me fix the low-battery indicator to flash at a lower frequency that’s independent of how fast the keyboard scan is.

With some help from Patrick Georgi on the fediverse, I got the keyboard scan itself down to about 350 usec, which means we can scan for keypresses once every millisecond, and allow the CPU to idle between scans, which may save an extremely tiny amount of power. (However, the ATmega32U4 is using so little power, I doubt it matters. It just made me feel better and less wasteful.)

I also added a README that documents all the stuff I described at the top of this thread.

Assuming this has very little chance of being merged into the primary git repo, would it make sense for me to re-fork this into just the keyboard firmware? The reform repo contains a lot of other code that I’m not modifying.

I’m also unsure on how to merge into the stock firmware as I’ve got my version to enter power-down state when the computer is shut off (by the menu) or a selection in the menu. Helping to greatly reduce the draw when the system is off.

Currently waiting on mntmn to have some free time as well for the corresponding LPC half as I admit that’s a bit over my head. But combined these should greatly reduce the battery drain in the off state so overdischarge shouldn’t be as much of a problem

I did try creating an account on source.mnt.re though that’s still not approved so not sure the “official way” to submit pull requests and such. I’ll likely throw mine on github. Would be nice to combine all these updates together. And maybe make some customized ones, or at least get some of the core features into the stock firmware.

Another simple patch I did that you may want to consider to yours as well is adding a bool flag for when the battery status is being displayed on the oled and every second or so recalling that routine to update and draw it again.

1 Like

Hi, the official way is totally to get an account on source.mnt.re and notify me about it, and if your intentions are pure I will let you in!

Also, I am excited about both of your mods/hacks but will need some time to review everything and see what I want in officially and what not.

4 Likes

Oh, awesome! I signed up for an account just now, too, though I’ll understand if my changes are a bit too intense to be taken wholesale.

I recommend splitting the keyboard firmware out into its own repo, so it’s not mixed in with a lot of unrelated stuff. Apparently it’s as simple as:

> git filter-repo --subdirectory-filter reform2-keyboard-fw/

I just tried it on a copy of the reform2 repo and it seemed to work.

Sorry for the delay since then. I just rebased my branch with the current master and pushed it: refactoring and enhanced support for the OLED (!17) · Merge requests · Reform / reform · GitLab

I tried out the power-off feature (after merging) and it works great – very cool idea. :slight_smile: (I think it might be using a 2 second WDT instead of 1 second, though, unless I misread the spec.)

Oops you’re right, not sure why I set WDT0. That’d explain why it was feeling longer than 1s. I was thinking it was just since the WDT timer is very imprecise. Yeah it’s set to 2s right now. I’ll fixed that. Though even 2s doesn’t seem too long to have to hold it.

Yeah, it totally worked for me before, tho it seems a little snappier now. I merged that into my PR also, in case I have a chance of getting merged. :slight_smile: