Jeff Epler's blog

4 September 2022, 2:11 UTC

Recent keyboard deeds

I made several USB HID keyboard conversions recently. They all use CircuitPython. The hardware setup is not documented as none are worthy of being called finished projects at this point, but the source code gives an idea what connections are needed. One or more of these may be the subject of a future Adafruit Learning System Guide.

Click a triangle to see the full code as an embedded github gist. (Note: if you're viewing this in a RSS reader, you may have to click through to get the embeds. Sorry!)

Commodore 16 Keyboard

Used an Adafruit KB2040. Gratuitous use of asyncio. The keymap needs work to be usable, but it's a good start. Direct gist link.

IBM XT "Model F" Keyboard

Used an Adafruit QT PY RP2040. This layout is AWFUL, the "beloved" iteration of the Model F was for AT computers, with a layout a lot more like a modern keyboard (aside from the placement of the Esc and Ctrl keys anyway) Direct gist link.

Tandy 1000 Keyboard

Used an Adafruit Feather RP2040. The trickiest, as caps lock and num lock are handled in the firmware; and there are some real oddities about the layout compared to the keyboards we're used to. This keyboard also needed the most clean-up but mechanically it's 100%. Direct gist link.


5 January 2022, 3:44 UTC

Where should CircuitPython go in 2022?

Looking back at 2021

Some of the goals I proposed for 2021 have been accomplished, yay!

  • Support at least one new microcontroller family (I may have known the RP2040 was coming when I said that)
  • Partially addressed the problem of the time required to build CircuitPython core with changes to GitHub CI
  • circup and bundlefly greatly ease the problem of gathering items from the bundles for a particular application
  • In the Adafruit Learn System, we got much more systematic about how programs and their assets are organized
  • The bluetooth workflow is in the wild and people are using it, such as with the PyLeap app for Apple phones.

At a personal level, I accomplished one of the goals I listed, which was to improve my skills at 3D modeling and in particular I have become reasonably competent at part design using FreeCAD.

Some items we made progress on, but there's always room for further improvement:

  • Continue to help people grow into the roles of reviewer and contributor
  • JP's CircuitPython Parsec and Scott and FoamyGuy's livestreams are great, but I'm sure there's room for more "how to" video content around CircuitPython.

Some big pieces of progress we made that I didn't even anticipate (but wish I had) were

  • Getting up to date with MicroPython, and then continuing to merge in their new releases
  • Releasing asyncio support for CircuitPython
  • Adding typing stubs to the core and types to many libraries in the bundle to work better in advanced IDEs

Looking forward to 2022

I'm not a big picture visionary about where the project should head. Lots of times it's modest or even invisible changes that give me the most satisfaction.

I look forward to implementing new drivers for hardware I wouldn't have dreamed of working on (reading from old DOS floppy drives, perfect example), adapting old algorithms into CircuitPython (let's do more image processing or maybe start doing audio processing), and finding small efficiency improvements and firmware size savings when they're needed.

I want to both learn more about and improve asyncio. A personal project goal would be to have an interactive display that continues to update & respond to input even while doing wifi requests. I don't think this works right now (I could be wrong), and I don't know how much is needed in order to make it work.

I look forward to hearing all about the projects you (yes, you) are planning to do in 2022; let's figure out if CircuitPython is the right coding language for those projects, and if it's not let's figure out if there are sensible additions that will make it work.


13 July 2021, 21:05 UTC

Hybrid Keyboard Descriptor supports Boot & NKRO

Updated 2021-09-25: For compatibility with CircuitPython 7.0.0.

It has been suggested that it's possible to have both N-key rollover (NKRO) and be compatible with computer early boot screens (BIOS/UEFI/etc) by ensuring that the first 8 bytes of the report are compatible with the standard keyboard protocol.

On the heels of my earlier experience with how easy CircuitPython 7 made it to try a custom descriptor for NKRO, I tried implementing this too. And it works! On a sample of one Dell notebook computer, the hybrid descriptor can navigate in the boot menu but also enjoys full NKRO once Linux has booted.

You have to configure the descriptor in and then use the HybridKeyboard class in your code. It is compatible with the adafruit_hid.keyboard.Keyboard class, except that if you ask it to press more than 6 keys in a row, it doesn't throw an exception!

The original NKRO descriptor is also in this, in case you want to try both and compare how they work for you; is intended to auto-select the correct interface class. Remember that you need to fully restart the Feather when modifying!

The first file below is and the second is, and the code is designed for an Adafruit Feather RP2040 with custom 4x5 keypad, detailed in a Learning System guide.


24 June 2021, 1:43 UTC

Quick CircuitPython Driver for ES100 WWVB Receiver

I picked up an ES100 WWVB receiver kit and wrote a quick & dirty library to interact with it in CircuitPython.

I'm not super thrilled with how the chip works; I imagined that the date & time registers would act like an RTC after a successful reception, but instead they just mark the second when reception & decoding completed and are cleared to zero as soon as a new reception attempt is kicked off.

Still, I'll have to figure out a clock to put it inside. I am still thinking of doing an edge-lit display version of the Roman Solar Clock, so maybe that's where it'll go.

The library is and the example is (rename to I ran it on a Feather nRF52840 Expess with CircuitPython 6.3, but it should work on a range of boards.

Because the ES100 just locks up the I2C bus if you "repeated-start" it, I had to use my custom rolled register library instead of adafruit_register. I did build it on top of adafruit_bus_device.

Files currently attached to this page:



7 September 2020, 14:31 UTC

Pi Zero W USB Proxy

I'm not sure exactly what to call it, but here's a little something I set up this weekend.

On my Linux desktop, I have occasional problems where being stopped at the debugger prompt for a plugged-in USB device hoses the whole computer. The problem waxes and wanes but on a particularly frustrating day I decided that maybe a Pi was the answer to the problem.

Using screen I can access the USB-serial devices on the pi, and using sshfs I can access the files. If the whole pi freezes, I can just reboot it with essentially no harm done.

I selected a Pi Zero W with a Zero4U hub and Adafruit MiniPiTFT 1.14" attached. To a base raspbian lite system I added some software, including tio, udiskie, screen, and Adafruit Blinka; enabled ssh access and disk mounting by the pi user, and set up GNU screen and my custom script for the LCD which is (confusingly) also called screen.

The screen shows information about each of the 4 USB connectors. In brackets "S" is shown if there is a serial device; "D" is shown if there's a partitioned disk, "d" if there's an unpartitioned disk; and "M" is shown if it is mounted. After that, the device name is shown.

Automount can be toggled with the B button (silk screen 23) and any non-mounted disks can be mounted with the A button (silk screen 24)

So far I've only used it lightly, but if it prevents a single crash of my desktop, it will be worth it.

This isn't a detailed guide so a lot of the setup is omitted. However, here are the scripts that are the essential parts:


16 August 2020, 18:05 UTC

Si5351 Frequency Planner in Python

The Si5351 and related clock generators are very flexible, but they are a bit cumbersome to "program".

The basic design of the Si5351 is that an incoming frequency is first multiplied to create a high internal frequency (nominally in the range from 600MHz to 900MHz), then divided to create an output frequency. The multipliers and dividers have restrictions on their ranges, there are just 2 PLLs but 3 output clocks, and certain types of configurations (e.g., "divisor is an integer multiple of 2") are said to give lower jitter outputs than others.

The datasheet advising users on how to create their own register values is very complicated and some of the parts resist comprehension even after multiple readings. Thie chip maker Silicon Labs provides a graphical closed source Windows program for generating register maps, though some internet commenters regard it as buggy.

This program represents my own effort to create a frequency planner. It neglects some datasheet rules, mostly related to output frequencies above 50MHz or so. It tries to

  • Favor lower-jitter configurations where possible
  • Favor making the first-listed frequency as accurate as possible
  • Try each combination of ways to allocate output clocks to the PLLs
  • Obey the datasheet restrictions as I've understood them
  • Do all arithmetic as infinite precision arithmetic, not floating point
It does not
  • Implement the divisor rules for extremely fast clocks
  • Implement the divisor rules for higher numbered outputs on 20QFN packages

For exact input clocks such as 25MHz, it is surprisingly hard to find a "plausible" frequency that is inexact, and even harder to find a "plausible" frequency that is less exact than your reference clock. I didn't actually find a case where the error is not much smaller than the frequency stability of any reference I'd plausibly have access to.

(Of course, if you measure your reference clock as 25MHz + 13.37Hz and plug that in as the --input-freq then almost everything is related to that clock inexactly, so the fact that the clocks will still be really, really close to the requested value is very nice.)

It does not directly create a register map, but the values shown are easy enough to convert to the form used in the CircuitPython adafruit_si5151 library. Sadly, as CircuitPython does not support the fractions module, it's not currently feasible to run this code on a CircuitPython board directly controlling the si5351 chip.


Generate 315M/88 (NTSC colorburst), 4.43361875M (PAL colour carrier), 25.8048M (UART clock) all exactly from a 25MHz reference clock or crystal. However, the PAL colour carrier will have more jitter since it uses a fractional divisor:

$ ./ 315M/88 4.43361875M 25.8048M
Input frequency: 25000000 (25000000.0)
Frequency plan score: 10.38

Frequency plan type: Fractional multiplier, double integer divisor (1)
Multiplier = 126/5 (25.2)
Intermediate frequency = 630000000 (630000000.0)

Desired output frequency: 39375000/11 (3579545.4545454546)
Divider = 176 (176.0)
r_divider = 0 (/ 1)

Frequency plan type: Fractional multiplier, fractional divisor (5)
Multiplier = 12059443/500000 (24.118886)
Intermediate frequency = 602972150 (602972150.0)

Desired output frequency: 17734475/4 (4433618.75)
Divider = 136 (136.0)
r_divider = 0 (/ 1)

Desired output frequency: 25804800 (25804800.0)
Divider = 12059443/516096 (23.366666279141864)
r_divider = 0 (/ 1)

Generate pi MHz from a 25MHz reference clock or crystal (error: 27.8 nano Hz)

$ ./ 3.1415926535898M
Input frequency: 25000000 (25000000.0)
Frequency plan score: 1.00

Frequency plan type: Fractional multiplier, fractional divisor (5)
Multiplier = 24 (24.0)
Intermediate frequency = 600000000 (600000000.0)

Desired output frequency: 15707963267949/5000000 (3141592.6535898)
Divider = 40306053/211042 (190.98593171027568)
Actual output frequency: 42208400000000/13435351 (3141592.653589772)
Relative Error: -8.83757e-15
Absolute Error: 2.78e-08Hz
r_divider = 0 (/ 1)

The source is available in a github gist:


18 July 2020, 13:21 UTC

Quad CharliePlex FeatherWing hack

Adafruit makes these neat "CharlieWing" displays that allow you to control a 15x7 LED matrix using the I2C bus. I2C uses two signal wires (called SDA and SCL, for Serial DAta and Serial CLock), and can connect multiple devices as long as they have different addresses.

I noticed that the bigger brother of this device, with a whopping 144 LEDs, could be configured for 4 different I2C addresses, while this one could only be configured for 2.

Or could it?

read more…

17 July 2020, 21:37 UTC

Minimal Time-Zone Handling for CircuitPython

For my clock, I want automatic handling of Daylight Saving Time. However, CircuitPython doesn't build in any distinction between local and UTC time, and fitting in the entire Python3 datetime module or an Olson time zone database is simply not going to happen. What can we do that is simple enough to fit, but can represent the reality of timezones where I live?

read more…

All older entries
Website Copyright © 2004-2021 Jeff Epler