Jeff Epler's blog

23 January 2021, 16:42 UTC

Wrap text according to font width in CircuitPython

I wrote a small function called wrap_text which re-wraps a block of text so that, when rendered in a given font, it is no more than a given number of pixels wide. (There are other functions available for wrapping text according to the number of letters but when the letter "l" is much narrower than the letter "w", this can give unsatisfactory results on specific texts)

The program splits the input string on any whitespace, then uses a simple "greedy" algorithm to add each word to the current line if it fits, otherwise break and continue with the next line.

The indentation of the first and subsequent lines can be specified; the default indents the 2nd line because my use case was to wrap text of a poem.

A new string is returned, with newlines inserted in the required spots, suitable for use with a standard circuitpython label.

The sample program is for the Adafruit MagTag. It uses this function to display a poem (placed in CIRCUITPY/poem.txt) 5 lines at a time, updating once per hour. The first line in poem.txt is always displayed at the bottom, and is intended to hold the poem's name and author. By using deep sleep mode, I expect that I'll only need to charge the device every few days.

I wrote this because I was very moved by Amanda Gorman's poem "The Hill We Climb". I could tell this was a poem that would really "stick with me" and wanted the opportunity to experience the poem over a longer period of time. I can look at the MagTag several times a day and reflect on whatever lines happen to be visible at the moment.

Out of respect for Ms. Gorman's copyright I don't include the poem's full text here. However, many news outlets have quoted it in its entirety. And, of course, you can put whatever you like in poem.txt—Why not something by Maya Angelou or Lin-Manuel Miranda, apparently two people who Ms. Gorman has found inspiring.

"Festive" 3D printed stand on thingiverse

Files currently attached to this page:



6 January 2021, 1:06 UTC

Seven Years of Prius (2013) Fuel Efficiency

Year Miles MPG Gal (est.)
2014 7546.8 44.4 170
2015 7333.6 43.9 167
2016 9368.8 44.8 209
2017 9936.2 45.1 220
2018 8853.6 42.0 210
2019 7188.6 40.1 179
2020 2290.9 42.7 54

At the tail end of 2019 I changed to a work from home job, and of course COVID ended most activities outside the house for the bulk of 2020, leading to such low mileage. Fuel economy went up a bit.


2 January 2021, 15:30 UTC

Where should CircuitPython go in 2021?

Looking back at 2020

In my list for last year, I focused on timekeeping and time measurement. We did do two out of the four goals I mentioned: the built-in time module now works from a real-time clock peripheral, and I implemented minimal time-zone handling for CircuitPython, though it's not sufficiently polished to add to the community bundle.

Together, these enabled one of my 2020 projects, a CircuitPython clock that is accurate to within better than 1 second per month, handles my local time zone rule, and has a battery back up so it doesn't need to be set after power is lost. I never blogged the full project, but there are bits and pieces scattered about: 1 2 3 4

On the other hand, the things I'm proudest of helping with in 2020 didn't even figure on my list—they were unanticipated. The two items I'm thinking of are bringing Zoltán Vörös's ulab into CircuitPython, and making it much easier for users to contribute to CircuitPython translations by setting up Hosted Weblate.

Looking forward to 2021

This year, I don't want to be so closely focused on features that just a few people may be interested in. Also, many of the 2020 submissions I was most struck by were those that focused more on the human side than on the technical. With that in mind, here are some of the broader goals I think we should set:

  • Support at least one new microcontroller family
  • Continue to help people grow into the roles of reviewer and contributor
  • Spotlight or create more "how to" video content around CircuitPython, especially for the more complex libraries

There are also some pain points that we need to deal with sooner or later, but in many cases the right idea hasn't come up yet. We should keep an eye out for ideas on solving these problems:

  • Increasing time & complexity to build CircuitPython core
  • Risk of being spread too thin by multiple in-progress ports
  • No automatic way to gather items from the Bundle for a particular application
  • Wireless-enabled devices deserve a pure Wi-Fi or Bluetooth development workflow
  • There's an increasing need for a path manipulation library
  • We should standardize the location of common files like fonts, icons, and sounds
  • Read/write storage that doesn't interfere with access to CIRCUITPY from a host computer

Finally, some personal goals I have related to CircuitPython and projects in general:

  • Add an antialiased font format to adafruit_bitmap_font
  • Design and fabricate PCBs for my projects
  • Improve my skills at 3D printed enclosure design, including learning FreeCAD
  • Share the PCB and 3D printable designs

Spilling the beans on one particular project I'd love to do in 2021, why isn't there a MagTag-like podcast player appliance running CircuitPython? There are a few missing pieces before this becomes possible, but I think we could—and should—do it.

I fully anticipate that once again the best things in 2021 will be the ones I didn't even imagine. I can't wait to see what this community builds together in 2021.


1 December 2020, 2:37 UTC

ForkAwesome font converted for CircuitPython

ForkAwesome is a fork of the last permissively (OSL) licensed version of FontAwesome. The project doesn't seem to be too active, but I thought it would be interesting and possibly useful to convert the font into the "pcf" format usable by CircuitPython! So here they are, in a range of bitmap sizes from 12 to 72. Unfortunately, some changes are needed to Adafruit_CircuitPython_DisplayLabel before it will work, because these fonts lack the letter "M", which is assumed to be in any font. See this pull request for the status of that change. (it also requires a recently updated Adafruit_CircuitPython_Bitmap_Font, because support for binary "pcf" fonts is pretty new)

The ttf file is also included in case you need to generate other sizes.

The font uses character codes starting at 0xf000, so using regular characters like "012" or "abc" won't show anything. Instead, to find the code of a character, visit the forkawesome website, click the icon you want, and look for the Unicode (f322 for the fa-python icon). Then, in your CircuitPython code, create a label with the text "\uf322" or chr(0xf322) and the FontAwesome font to show it:

from adafruit_bitmap_font.bitmap_font import load_font
from adafruit_display_text.label import Label
from displayio import Group

font = load_font("/forkawesome-36.pcf")

text = chr(0xf322)

group = Group()
label = Label(font=font, text=text, background_color=0x111111)
label.anchor_point = (0, 0)
label.anchored_position = (0,0)

while True:

You can also use the attachment as a reference for the character codes. The python identifiers are the same as the font's "id", except that "-" is replaced with "_","500px" is renamed to "fivehundredpx", and several names that are Python built in functions or reserved words have an underscore appended (file_, filter_, list_, map_, print_, try_).

Files currently attached to this page:



6 November 2020, 1:54 UTC

Head Scratching C(++) Bug

What's wrong with the following loop? I stared at it for quite some time before finding the bug in my code, all too ready to exclaim "it's a compiler bug":

/* n is a parameter of type size_t */
/* table is a pointer to int such that table[0] .. table[n-1] are all valid indices */
for(size_t i = 0; i < n; i++) {
    if(table[i] == needle) return i;

Instead of terminating after at most n iterations, the loop continued indefinitely, eventually returning some nonsense value where another memory location matched the needle value.

Give up? The problem's nothing to do with the loop, but with what came (didn't come) after it:

size_t find_in_table(int *table, size_t n, int needle) {
    for(size_t i = 0; i < n; i++) {
        if(table[i] == needle) return i;

There's no return statement after the loop. If the loop terminates, it reaches the end of a non-void function without returning a value. This is undefined behavior. Therefore, the compiler-author logic goes, it must be the case that the loop can never terminate due to the loop condition; it must only terminate by the return statement inside the loop. (Stated in another way, the "undefined behavior" is to continue to run the loop.)

Without the final return (e.g., return (size_t)-1;), the comparison i < n is completely eliminated at typical optimization levels. Even weirder, you can get the program to print that (say) i==5, n==4, and that i<n is true!

I think gcc has an enabled-by-default warning for this, but either it's disabled by my project's CFLAGS or I overlooked it in a torrent of other compiler output.

Without looking beyond the loop or if you're thinking with the mindset that a missing return statement's undefined behavior is "returns an arbitrary value", you don't think about things like this. This is a very easy mistake to make, since it is also a pretty good model of what a modern compiler will do at "for better debugging" optimization levels, as well as what the "C is portable assembler" moto asks us to falsely believe. No, in reality, C(++) is a slippery language. While it's not a bet you could settle in the negative, I bet there's at least one security bug out there due to this undefined behavior and the way that gcc transforms a finite loop into an infinite one.

My recommendation?

Pay attention to compiler diagnostics. Enable more of them. Understand what the diagnostic is saying, then make an appropriate response. Get your editor's help to parse error lists and make sure you don't miss any.


1 November 2020, 16:18 UTC

wwvbgen updates

I occasionally maintain a Python module called wwvbpy, which can generate WWVB timecodes for any desired time.

The change to winter (standard) time in the US is the perfect moment to review what's up with wwvpy and correct any discovered problems. (in fact, I was prompted by Chris's remark that his WWVB receiver had too eagerly applied the end of DST—as soon as GMT midnight rolled around, I think. Oops!)

This time around, here are the minor tweaks I made:

  • Update license to GPL3+ (was GPL2+)
  • Revamp how DUT1 ("iersdata") is gathered and harmonized
  • Convert tests to run as unittests; fix timezone test problems
  • Add github actions for CI
  • Add github "cron" to update iersdata
  • Add "expected output" tests for phase data, though this is just self-generated, not independently-generated data

WWVB timecode: year=20 days=306 hour=06 min=50 dst=1 ut1=-200 ly=1 ls=0 --style=duration
'20+306 06:50  852522222822222255282255222228255222252822522225282222252258


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:


27 August 2020, 21:10 UTC

GPS and relativity

This is info I've dug up a few times, but it's getting harder to find. Let's put it here, maybe I'll remember its on my own website. The ever-helpful has a copy of an article from stating:
At the time of launch of the first NTS-2 satellite (June 1977), which contained the first Cesium clock to be placed in orbit, there were some who doubted that relativistic effects were real. A frequency synthesizer was built into the satellite clock system so that after launch, if in fact the rate of the clock in its final orbit was that predicted by GR, then the synthesizer could be turned on bringing the clock to the coordinate rate necessary for operation. The atomic clock was first operated for about 20 days to measure its clock rate before turning on the synthesizer. The frequency measured during that interval was +442.5 parts in 1012 faster than clocks on the ground; if left uncorrected this would have resulted in timing errors of about 38,000 nanoseconds per day. The difference between predicted and measured values of the frequency shift was only 3.97 parts in 1012, well within the accuracy capabilities of the orbiting clock. This then gave about a 1% validation of the combined motional and gravitational shifts for a clock at 4.2 earth radii.
It also contains supporting scans of NASA Technical Memorandum 78104, 1977 9th annual PTTI, NTS-2 report showing these statements, although the Technical Memorandum does not state whether the initial pre-tuning value was really due to "doubts" that relativistic effects were real, as compared to other operational reasons.

An earlier document, NTS-2 Cesium Bean Frequency Standard for GPS states (errors mine)

The NTS-2 program office at NRL was tasked by GPS NAVSTAR program office to generate a 10.23 MHz frequency for use with the Pseudo Random Noise System (PRNSA) onboard the NTS-2 Satellite. Frequency requrements for the NTS-2 Orbit Determination and Tracking Systerm (ODATS) was 5MHz with a tunable ΔF offset of approximately +1 × 10-9 with a resolution of approximately 3 × 10-12. The ΔF offset was to compensate for relativistic effects and could not be accomplished by offsetting the cesium standard which has a tuning range of ± 1 × 10-11. The relativistic offset was later added to the 10.23MHz requirement.
We might try to detect a hint of emotion in "The offset was …later added to the requirement".

Citing "Alley, C., “Proper time experiments in gravitational fields with atomic clocks, aircraft, and laser light pulses”, in Meystre, P., and Scully, M.O., eds., Quantum Optics, Experimental Gravitation, and Measurement Theory", we have

There is an interesting story about this frequency offset. At the time of launch of the NTS-2 satellite (23 June 1977), which contained the first Cesium atomic clock to be placed in orbit, it was recognized that orbiting clocks would require a relativistic correction, but there was uncertainty as to its magnitude as well as its sign. Indeed, there were some who doubted that relativistic effects were truths that would need to be incorporated!

Alley: (again, errors mine)

There was considerable uncertainty among the Air Force and contractor personnel designing and building the system whether these effects were being correctly handled, and even, on the part of some, whether the effects were real. The last group was not satisfied until a gravitational frequency shift was measured with a GPS test satellite called NTS-2 by a group at the Naval Research Laboratory in 1977.
Alley cites T. McCaskill, J. White, S. stebbins, and J. Buisson, NTS-2 frequency stability results, "Proceedings, 32nd Annual Symposium, on Frequency Control," U. S. Army Electronics Research and Development Command, Fort Monmouth, N.J. (1978), a different paper with many of the same names as the first Technical Memorandum. However, I don't see that the citation supports the "considerable uncertainty" nor that any doubted "whether the effects were real". It just states that "the Einstein relativistic clock effect was verified to less than one-half percent".

So, left without a citation to follow, I am still in doubt as to whether there were doubters of general relativity on the project. Rather, except for the resigned voice that the offset was "later added" everything seems consistent with: GR (at least once the issue was raised) was assumed correct, but for other reasons (such as synchronizing it with the ground based cesium clocks before launch) the system was built with two modes, and that a story about doubters grew in the retelling.

Files currently attached to this page:



18 August 2020, 1:55 UTC

Some notes on the Si5351a

16 August 2020, 18:05 UTC

Si5351 Frequency Planner in Python

18 July 2020, 13:21 UTC

Quad CharliePlex FeatherWing hack

9 July 2020, 17:55 UTC

Raspberry Pi Gross Hacks

All older entries
Website Copyright © 2004-2021 Jeff Epler