If you had different hardware, you could specify the driver with a WS2812_DRIVER option.
QMK can also control single-color LEDs with PWM (a.k.a. backlighting), and per-key RGB LEDs (a.k.a. RGB Matrix). These functions, their configuration / controls / data, and their documentation overlap and intermingle to the extent that I spent most of my time figuring out what not to include.
The first two lines describe a single WS2812 RGB LED wired to pin B2 (a.k.a. MOSI) of the Atmel 32U4 microcontroller. The default Reset duration and Byte Order values work for the LED I used
Protip: swapping the order from GRB to RGB is a quick way to discover if the firmware actually writes to the LED, even before you get anything else working: it’ll be red with the proper setting and green with the wrong one.
Dialing the maximum intensity down works well with a bright LED shining directly at your face from a foot away.
Turning on RGBLIGHT_LAYERS is what makes this whole thing happen. The RGBLIGHT_EFFECT_RGB_TEST option enables a simple test animation at the cost of a few hundred bytes of code space; remove that line after everything works.
The last two lines remove the debugging facilities; as always with microcontroller projects, there’s enough room for either your code or the debugger required to get it running, but not both.
With those files set up, the keymap.c file does the heavy lifting:
Having helped grossly over-fund the Atreus Kickstarter earlier this year, a small box arrived pretty much on-time:
I did get the blank keycap set, but have yet to screw up sufficient courage to install them. The caps sit atop the stock Kailh (pronounced, I think, kale) BOX Brown soft tactile switches; they’re clicky, yet not offensively loud.
Removing a dozen screws lets you take it apart, revealing all the electronics on the underside of the PCB:
The central section holds most of the active ingredients:
There’s not much to it, because the RPi can enable pullup resistors on its digital inputs, whereupon the encoder switches its code bits to common. The third oscilloscope probe to the rear syncs on a trigger output from my knob driver.
I started with the Encoder library from PyPi, but the setup code doesn’t enable the pullup resistors and the interrupt (well, it’s a callback) handler discards the previous encoder state before using it, so the thing can’t work. I kept the overall structure, gutted the code, and rebuilt it around a state table. The code appears at the bottom, but you won’t need it.
Here’s the problem, all in one image:
The top two traces are the A and B encoder bits. The bottom trace is the trigger output from the interrupt handler, which goes high at the start of the handler and low at the end, with a negative blip in the middle when it detects a “no motion” situation: the encoder output hasn’t changed from the last time it was invoked.
Over on the left, where the knob is turning relatively slowly, the first two edges have an interrupt apiece. A detailed view shows them in action (the bottom half enlarge the non-shaded part of the top half):
Notice that each interrupt occurs about 5 ms after the edge causing it!
When the edges occur less than 5 ms apart, the driver can’t keep up. The next four edges produce only three interrupts:
A closer look at the three interrupts shows all of them produced the “no motion” pulse, because they all sampled the same (incorrect) input bits:
In fact, no matter how many edges occur, you only get three interrupts:
The groups of interrupts never occur less than 5 ms apart, no matter how many edges they’ve missed. Casual searching suggests the Linux Completely Fair Scheduler has a minimum timeslice / thread runtime around 5 ms, so the encoder may be running at the fastest possible response for a non-real-time Raspberry Pi kernel, at least with a Python handler.
If. I. Turn. The. Knob. Slowly. Then. It. Works. Fine. But. That. Is. Not. Practical. For. My. Purposes.
Nor anybody else’s purposes, really, which leads me to think very few people have ever tried lashing a rotary encoder to a Raspberry Pi.
So, OK, I’ll go with Nearer and Farther focusing buttons.
The same casual searching suggested tweaking the Python thread’s priority / niceness could lock it to a different CPU core and, obviously, writing the knob handler in C / C++ / any other language would improve the situation, but IMO the result doesn’t justify the effort.
My attempt at a Python encoder driver + simple test program as a GitHub Gist:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
I made a cursory attempt to crack the case open, but gave up before doing any permanent damage. Hey, that UL listing (and, presumably, the interior details) means they’re three times the price of those Anonymous chargers!
Start by grabbing opposite corners in a small vise and gently cracking the solvent-bonded joint between the sections:
Pull the base past the molded latches:
On both sides of both PCBs!
The top half of both boards, above the isolation cut, handles the line voltage and the lower half handles the 5 V USB output. You’ll note the absence of extra-cost parts like voltage feedback or ahem safety fuses.
Treating the whole regulator as a black box simplifies the schematic:
The cap bridging the two sides should be a Y capacitor, but it’s an ordinary 1 nF ceramic cap with a generous 1 kV rating. As far as I can tell, having it inject AC line noise directly into the +5 V side of the USB supply is just a bonus.