Pied Beauty, ya pie

I’m of an age that I had to learn to recite Gerard Manley Hopkins’ Pied Beauty at school, on several different occasions. I did not excel at learning poems by heart; at least, not the ones I was told to learn. I have a difficult relationship with the poem, you could say.

So when my mother-in-law asked for me to recite it for her daily poetry readings (and knowing full well what she would get), I said yes. Here’s something like what she got:

Pied Beauty, read by Stewart Russell — much against his better judgment

It’s a great poem, but one that should never be inflicted on a teenage boy. Yer man GMH was quite the one for making up words: brinded isn’t a thing, and I dunno what happened with the accents on ‘áll trádes’, but they’ve gone well into the twee zone. And as for ‘trout that swim’: is there any other kind, Gerry? Mibbee there’s ones that fly where you’re from, but they’re all strictly aquatic here.

Raspberry Pi Pico with TTP223 Touch Sensor

This is almost too trivial to write up, as the TTP223 does exactly what you’d expect it to do with no other components.

breadboard with Raspberry Pi Pico and small blue capacitive touch sensor
TTP223 sensor board connected to GP22 / physical pin 29

Breakout boards for the TTP223 capacitive touch sensor come in a whole variety of sizes. The ones I got from Simcoe DIY are much smaller, have a different connection order, and don’t have an indicator LED. What they all give you, though, is a single touch/proximity switch for about $1.50

Trivial code to light the Raspberry Pi Pico’s LED when a touch event is detected looks like this:

import machine
touch = machine.Pin(22, machine.Pin.IN)
led = machine.Pin(25, machine.Pin.OUT)

while True:
    led.value(touch.value())

For the default configuration, the sensor’s output goes high while a touch is detected, then goes low. This might not be the ideal configuration for you, so these sensor boards have a couple of solder links you can modify:

  1. Active Low — sometimes you want a switch to indicate a touch with a low / 0 V signal. On the boards I have, the A link controls that: put a blob of solder across it to reverse the switch’s sense.
  2. Toggle — if you want the output to stay latched at one level until you touch it again, a blob of solder across the T link will do that. Unlike a mechanical switch, this won’t stay latched after a power cycle, though.

And that’s all it does. Sometimes it’s nice to have a sensor that does exactly one thing perfectly well.

Directing, 2003-08-14 Toronto

A woman in business attire stands in the middle of a downtown street. It is late afternoon on 14th August 2003, the first day of the blackout. She is facing west along a street with streetcar lines: her shadow from a beam of sunlight from between two buildings is prominent behind her. Her dark jacket is folded over her left arm, and her right arm is extended in a Stop gesture. No traffic is visible, but there are many pedestrians visible behind her
Directing, 2003-08-14 Toronto

Not a great scan, but it’s a photo I thought I’d lost forever.

Taken in Toronto on the afternoon of the 2003 blackout, I was struck by the way she directed traffic in the middle of the intersection.

(as previously shared on mltshp and subsequently to twitter.)

CardKB mini keyboard with MicroPython

small computer screen with text
*** APPALLING TYPEWRITER ***
** Type stuff, Esc to end **

then further down: "hello I am smol keeb"
it really is the size of a credit card
(running with a SeeedStudio Wio Terminal)

I got one of these CardKB Mini Keyboards to see if I could use it for small interactives with MicroPython devices. It’s very small, and objectively not great as a mass data entry tool. “Better than a Pocket C.H.I.P. keyboard” is how I’d describe the feel. It’s also pretty reliable.

It’s got an I²C Grove connector, and its brains are an ATMega chip just like an Arduino. It’s strictly an ASCII keyboard: that is, it sends the 8-bit ASCII code of the key combination you pressed. It doesn’t send scan codes like a PC keyboard. The driver source is in the CardKB m5-docs, so if you really felt ambitious you could write a scan code-like firmware for yourself.

The device appears at I²C peripheral address 95, and returns a single byte when polled. That byte’s either 0 if no key was pressed, or the character code of what was pressed. The Esc key returns chr(27), and Enter returns CR. If you poll the keyboard too fast it seems to lose the plot a little, so a tiny delay seems to help

Here’s a small demo for MicroPython that acts as the world’s worst typewriter:

# M5Stack CardKB tiny keyboard - scruss, 2021-06
# MicroPython - Raspberry Pi Pico

from machine import Pin, I2C
from time import sleep_ms

i2c = I2C(1, scl=Pin(7), sda=Pin(6))
cardkb = i2c.scan()[0]  # should return 95
if cardkb != 95:
    print("!!! Check I2C config: " + str(i2c))
    print("!!! CardKB not found. I2C device", cardkb,
          "found instead.")
    exit(1)

ESC = chr(27)
NUL = '\x00'
CR = "\r"
LF = "\n"
c = ''

print("*** APPALLING TYPEWRITER ***")
print("** Type stuff, Esc to end **")

while (c != ESC):
    # returns NUL char if no character read
    c = i2c.readfrom(cardkb, 1).decode()
    if c == CR:
        # convert CR return key to LF
        c = LF
    if c != NUL or c != ESC:
        print(c, end='')
    sleep_ms(5)

And here’s the CircuitPython version. It has annoying tiny differences. It won’t let me use the I²C Grove connector on the Wio Terminal for some reason, but it does work much the same:

# M5Stack CardKB tiny keyboard - scruss, 2021-06
# CircuitPython - SeeedStudio Wio Terminal
# NB: can't use Grove connector under CPY because CPY

import time
import board
import busio

i2c = busio.I2C(board.SCL, board.SDA)

while not i2c.try_lock():
    pass

cardkb = i2c.scan()[0]  # should return 95
if cardkb != 95:
    print("!!! Check I2C config: " + str(i2c))
    print("!!! CardKB not found. I2C device", cardkb,
          "found instead.")
    exit(1)

ESC = chr(27)
NUL = '\x00'
CR = "\r"
LF = "\n"
c = ''
b = bytearray(1)

# can't really clear screen, so this will do
for i in range(12):
    print()
print("*** APPALLING TYPEWRITER ***")
print("** Type stuff, Esc to end **")
for i in range(8):
    print()

while (c != ESC):
    # returns NUL char if no character read
    i2c.readfrom_into(cardkb, b)
    c = b.decode()
    if c == CR:
        # convert CR return key to LF
        c = LF
    if c != NUL or c != ESC:
        print(c, end='')
    time.sleep(0.005)

# be nice, clean up
i2c.unlock()

An hour of Pink Noise

cover made by netpbm, of course
an hour of soothing 2-channel noise

Direct download: 01-pink_noise.mp3

There are a million variations on the simple “use sox to play masking pink noise“, such as:

play -n synth pinknoise gain -3

This will play synthesized pink noise until you hit Ctrl-C.

But it you want two independent noise channels rather than mono, that’s a little more complex. It’s probably easier to download/play the MP3 file above than show you the command line.

Note that MP3s really aren’t designed to encode such random data, and it’s likely that your player will cause the audio to clip in a couple of places. I’m not quite sure why it does this, but it does it repeatably.

If you want to create this for yourself (and create a bonus lossless FLAC, which was far too large to upload here), here’s what I did to make this:

#!/bin/bash

duration='60:00'
fade='1'
outfile='pinknoise.wav'

# make the track
sox --combine merge "|sox --norm=-3 -c 1 -b 16 -r 44100 -n -p synth $duration pinknoise" "|sox --norm=-3 -c 1 -b 16 -r 44100 -n -p synth $duration pinknoise" -c 2 -b 16 -r 44100 $outfile fade $fade fade 0 $duration $fade gain -n -3

# make the cover
# 1 - text - 500 x 500 px
pnmcat -white -tb <(pbmmake -white 500 114) <(pbmtextps -font HelveticaBold -fontsize 64 -resolution 180 "PINK" | pnmcrop) <(pbmmake -white 32 32) <(pbmtextps -font HelveticaBold -fontsize 64 -resolution 180 "NOISE" | pnmcrop) <(pbmmake -white 500 114) > cover-text.pbm
# 2 - make the noise bg
pgmnoise 500 500 > cover-noise.pgm
# 3 - make the magenta text
ppmchange black magenta cover-text.pbm > cover-text-magenta.ppm
# 4 - overlay with transparency
pnmcomp -alpha=<(pnminvert cover-text.pbm | pbmtopgm 35 35 ) cover-text-magenta.ppm cover-noise.pgm | cjpeg -qual 50 -opt -baseline -dct float > cover.jpg
# delete the temporary image files, leaving cover.jpg
rm -f cover-text.pbm cover-noise.pgm cover-text-magenta.ppm

# make the mp3
lame -V 2 --noreplaygain -m s --tt 'Pink Noise' --ta 'Pink Noise' --tl 'Pink Noise' --ty $(date +%Y) --tc "scruss, 2021-05" --tn 1/1 --tg Ambient --ti cover.jpg "$outfile" 01-pink_noise.mp3

# make the flac (and delete wav file)
flac --best --output-name=01-pink_noise.flac --delete-input-file --picture=cover.jpg --tag="TITLE=Pink Noise" --tag="ARTIST=Pink Noise" --tag="ALBUM=Pink Noise" --tag="DATE=$(date +%Y)" --tag="COMMENT=scruss, 2021-05" --tag="GENRE=Ambient" --tag="TRACKNUMBER=1" --tag="TRACKTOTAL=1" "$outfile"

You’ll likely need these packages installed:

sudo apt install sox libsox-fmt-all ghostscript gsfonts-x11 netpbm lame flac libjpeg-progs

The cheapest Micro SD card interface in the world

a micro-sd adapter with 7 0.1"-pitch header pins  soldered onto its contacts
micro-SD adapter + pins + solder = working SD interface

It’s only a serial SPI interface, but you can’t beat the price. It should only be used with 3.3 V micro-controllers like the Raspberry Pi Pico, since micro-SD cards don’t like 5 V directly at all.

You might want to pre-tin the pins and apply some extra flux on both surfaces, because these pads are thin and you don’t want to melt them. I used my standard SnAgCu lead-free solder without trouble, though.

label sticker image for 7 pins, from left to right DO, GND, CLK, 3V3, GND, DI, CS
got a label maker? This label’s the same length as an SD card is wide, as shown above.
Made entirely with netpbm

You only need to use one of the Ground connections for the card to work.

cp2up.sh — fits the important part of Canada Post print labels two per sheet

blurred (for privacy) 2-up landscape page of Canada Post Tracked Package (to USA) shipping labels made by this script
no you will not read my 2-up shipping labels

If you need to ship things, you’re probably not too keen on queuing at the post office right now. Canada Post’s Ship Online service is pretty handy if you have a printer. The PDFs it produces are okay to print on plain paper, but if you’re using full-sheet labels like Avery 5165 you’re going to waste half a sheet of expensive labels.

If you’ve got two parcels to mail, this shell script will extract the right side of each page and create a single 2-up PDF with both your labels on the same page. You will need:

On my Ubuntu system, you can get good-enough¹ versions by doing this:

sudo apt install poppler-utils netpbm img2pdf

The code:

#!/bin/bash
# cp2up.sh - fits the important part of Canada Post print labels 2 per sheet
# scruss, 2021-05 - CC-BY-SA
# hard-coded input name (document.pdf)
# hard-coded output name (labels-2up.pdf)
# accepts exactly two labels (sorry)

dpi=600
width_in=11
height_in=8.5
# png intermediate format uses pixels per metre
dpm=$(echo "scale=3; $dpi * 1000 / 25.4" | bc)
# calculated pixel sizes, truncated to integer
half_width_px=$(echo "$width_in * $dpi / 2" | bc | sed 's/\..*$//')
height_px=$(echo "$height_in * $dpi" | bc | sed 's/\..*$//')

pdftoppm -mono -r "$dpi" -x "$half_width_px" -y 0 \
	 -W  "$half_width_px" -H "$height_px" document.pdf labels
pnmcat -lr labels-1.pbm labels-2.pbm |\
    pnmtopng -compression 9 -phys "$dpm" "$dpm" 1 > labels.png \
    && rm labels-1.pbm labels-2.pbm
# fix PDF time stamps
now=$(date --utc --iso-8601=seconds)
img2pdf -o labels-2up.pdf --creationdate "$now" --moddate "$now" \
	--pagesize "Letter^T" labels.png \
    && rm labels.png 

# saved from:
# history | tail | awk '{$1=""; print}' |\ 
#           perl -pwle 'chomp;s/^\s+//;' > cp2up.sh

It’s got a few hard-coded assumptions:

  • input name (document.pdf);
  • output name (labels-2up.pdf);
  • accepts exactly two labels (sorry).

Clever people could write code to work around these. Really clever people could modify this to feed a dedicated label printer.

Yes, I could probably have done all this with one ImageMagick command. When ImageMagick’s command line syntax begins to make sense, however, it’s probably time to retire to that remote mountain cabin and write that million-word thesis on a manual typewriter. Also, ImageMagick’s PDF generation is best described as pish.

One of the issues that this script avoids is aliasing in the bar-codes. For reasons known only to the anonymous PDF rendering library used by Canada Post, their shipping bar-codes are stored as smallish (780 × 54 px) bitmaps that are scaled up to a 59 × 19 mm print size. Most PDF viewers (and Adobe Viewer is one of these) will anti-alias scaled images, making them slightly soft. If you’re really unlucky, your printer driver will output these as fuzzy lines that no bar-code scanner could ever read. Rendering them to high resolution mono images may still render the edges a little roughly, but they’ll be crisply rough, and scanners don’t seem to mind that.

split image of simulated printed barcode: top image is five indistinct black-grey bars merging into a white background, bottom image is the same vertical lines, rendered crisply but showing some slightly rough edges
fuzzy vs crisply rough: scaled image (top) vs direct-rendered (bottom), at simulated 600 dpi laser print resolution

¹: Debian/Ubuntu’s netpbm package is roughly 20 years out of date for reasons that only a very few nerds care about, and the much better package is blocked by Debian’s baroque and gatekeepery packaging protocol. I usually build it from source for those times I need the new features.

Endless screaming in URL

A friend introduced me to A(x56) – URL Lengthener — perhaps better known as https://aaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com/. Consequently, this website should in future be referred to as https://aaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com/a?áaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂáaaÂåAæãæãæaæââÁáÆáÆæâåâæáæäæâæâáÅåâåÆåÄáÆåáåÃåÆåæáÆ.

It sounds even better than it reads:

that URL, as read by Flite

Happy Birthday Alvin Lucier

Happy Birthday Alvin Lucier - spectrogram of ten generations of re-recordings
Happy Birthday Alvin Lucier – spectrogram of ten generations of re-recordings

Just over 50 years ago, Alvin Lucier turned on his tape recorder and started to recite. This wasn’t an entirely unusual thing to do, but what he did next was a little different: he played that tape back, while recording it on another device. He kept doing this until all there was left was the ringing resonant frequency of the room, his voice smeared out of any recognizable sound. He called this piece I am sitting in a room, and it’s still a stunning work of sound art.

Alvin Lucier just turned 90 years old, so in recognition, I made this:

Many Happy Returns: for Alvin Lucier at 90

I don’t have a quiet room with two tape decks, but I do have a large plastic tote in which I can fit a whole bunch of battery-powered recording gubbins:

computer-based recording and playback equipment in plastic tote
my studio in a box. From L to R: Raspberry Pi 3A+, backup audio recorder, microphone stand, battery pack, microphone, USB audio interface, kalimba (for added resonance) and bluetooth speaker

After setting everything up and running, I put the box on my bed and covered it with several layers of blankets to keep our noisy neighbourhood sounds out. The initial audio was made in PicoTTS, and then playback and recording were controlled over wifi and VNC. I (or more accurately, the bash script I wrote) made 90 generations of recordings. I’ve only included the first 10 due to time constraints.

What I did

I guess I’ve been lucky with whatever audio system Raspberry Pi OS is using, because it recognized and used both my ancient Griffin iMic USB microphone adapter and my Sony Bluetooth speaker as default input/output devices.

One annoyance was having to build PicoTTS from source. Raspberry Pi OS doesn’t provide packages for it, but does have the source package. Building it goes something like this: Compile Pico TTS on Raspbian. You might prefer trying flite or espeak-ng, both of which have binary packages available.

I used this script, which may be rather too specific to my particular goal:

#!/bin/bash

parent=/home/pi/Desktop/sitting_in_a_box
gen0="$parent/box0.wav"
dest=${1:-$(date +%y%m%d%H%M)}
outdir="$parent/$dest"
mkdir -p "$outdir" && echo Created "$outdir"
n=0
outfile=$(printf "%s/%s_%03d.wav" "$outdir" "$dest" "$n")
tmpfile=$(printf "%s/%s_TMP.wav" "$outdir" "$dest")

# copy source file
sox -q --norm=-3 "$gen0" "$outfile"
while
    [ ! -f "$parent/STOP" ]
do
    infile="$outfile"
    n=$((n + 1))
    outfile=$(printf "%s/%s_%03d.wav" "$outdir" "$dest" "$n")
    echo Recording "$outfile" . . .
    arecord -f cd -q "$tmpfile" &
    rec_pid=$!
    echo Playing "$infile" . . .
    aplay -q "$infile"
    sleep 0.5
    kill -SIGINT "$rec_pid"
    echo Normalizing "$tmpfile" to "$outfile" . . .
    sox --norm=-3 "$tmpfile" -c 2 "$outfile" && rm "$tmpfile"
    echo ""
done
echo '***' Process stopped after "$n" iterations.

Some notes on the code:

  1. the script creates a folder for the output files, either as specified on the command line, or if not, from the current date/time
  2. I didn’t really know how many iterations I’d want, so I monitored occasional files by pulling them down using scp and listening. When I was satisfied, I’d create a file called STOP in the project folder that would make the loop exit
  3. $gen0 is the name of the source recording
  4. sox -q –norm=-3 infile outfile is a way of normalizing the audio so that it won’t clip
  5. We start the recording in the background (arecord … &) and immediately grab its process ID using $!. This allows us to stop the recording using kill after the audio file has played. arecord doesn’t update the file header when a process is stopped like this, so sox will complain quietly every time
  6. The sleep 0.5 after aplay is to prevent the recording cutting off. Both aplay and sox/play seem to exit as soon as the last block of audio data has been sent to the audio device, and not when the sound has stopped playing. This means you have to edit out increasingly longer gaps from your recordings, but at least you get everything.

What I’d do differently next time

  • Probably not used a Bluetooth speaker. Sure, they’re self-powered and you can’t complain about the number of wires, but they’re noisy and low quality
  • Done more work on the microphone interface. The pair of binaural mics I used to use with the Griffin interface (it’s stereo, unlike most USB interfaces) had a dead channel, so I had to fish around for other microphones. Somewhere I have a nice microphone from a Sony Pro Walkman, and I should have used that
  • Maybe considered a band-pass filter to cut the very high frequency ringing. Yes, I know the whole point of I am sitting in a room is to capture the room dynamics, but when you’re recording in a tiny space with very hard walls, the dynamics take over almost too quickly to be interesting
  • Spent more time on playback and record levels beyond the “hey, it works!” settings I used here
  • Used a higher bitrate recording. This might have resulted in more wolf-tones and ringing, though.

Applied Futility: Re-creating RAND’s ‘A Million Random Digits’

Sometimes, one must obey the inscrutable exhortations of one’s soul and travel deep into the inexplicable. The planet Why? has been left far behind, the chatter of its querulous denizens nothing more than a faint wisp of static. Where I’m going, pure patternlessness is all there is.

Page 53 from “A Million Random Digits …”, showing the five-digit sequential line number at left, followed by ten columns of five random digits each
A page from “A Million Random Digits …”, showing the five-digit sequential line number at left, followed by ten columns of five random digits each

In 1955, military-industrial complex stalwarts RAND Corporation published a huge book of just pages and pages of … digits. The digits were deliberately as random as possible, and were intended to help the fledgling practice of data science carry out truly random simulations. The book was called “A Million Random Digits with 100,000 Normal Deviates”. It was also made available on punched cards, with 50 digits to a card adding up to ten boxes of 2000 cards. A single box of punched cards was about 370 × 200 × 95 mm and weighed roughly 5 kg, so these digits had heft.

I thought it might be fun (or perhaps fun?: pronounced with a rising, questioning tone to stress the might-ness of any enjoyment arising) to dig into how RAND carried out this work, create some electronics to produce a similar random stream, and see how my random digits compare for randomness with RAND’s. For a final trick, I might even typeset the whole giant table into a book that no-one wants.

As I research and progress with this project, I’ll add in links here

I must stress: there is no reason for me to do this. A $5 micro-controller board can generate tens to hundreds of thousands of truly random digits per second. Any results I produce will have no use beyond my own amusement.


A Million Random Digits with 100,000 Normal Deviates is © Copyright 2001 RAND. Apart from a couple of page images and quotations from supporting material, none of that work is reproduced here. RAND does not support or endorse my futile efforts in any way.

Delicate tracings from a terrible PRNG

The two orbits of 16-bit RANDU, plotted as X-Y coordinates

I’d previously mentioned RANDU — IBM’s standard scientific PRNG in the 1970s that was somewhat lacking, to say the least —some time ago, but I found a new wrinkle.

For their 1130 small computer system, IBM published the Scientific Subroutine Package [PDF], which included a cut-down version of RANDU for this 16-bit machine. The code, on page 64 of the manual, doesn’t inspire confidence:

c     RANDU - from IBM Scientific Subroutine 
c     Package Programmer's Manual
c     1130-CM-02X - 5th ed, June 1970
c     http://media.ibm1130.org/1130-106-ocr.pdf

      SUBROUTINE RANDU(IX, IY, YFL)
      IY = IX * 899
      IF (IY) 5, 6, 6
 5    IY = IY + 32767 + 1
 6    YFL = IY
      YFL = YFL / 32727.
      RETURN
      END

(If you’re not hip to ye olde Fortran lingo, that bizarre-looking IF statement means IF (IY < 0) GO TO 5; IF (IY == 0) GO TO 6; IF (IY > 0) GO TO 6)

It accepts an odd number < 32768 as a seed, and always outputs an odd number. Yes, it basically multiplies by 899, and changes the sign if it rolls over. Umm

There are two possible orbits for this PRNG, each 8192 entries long:

  1. Starting seed 1 (899, 21769, 7835, …, 18745, 9003, 1, 899)
  2. Starting seed 5 (4495, 10541, 6407, …, 28189, 12247, 5, 4495).

The plot above is the first series as X and the second as Y. I used INTEGER*2 types to simulate the 1130’s narrow integers.

Niche Knowledge: Z80 parallel port SD card on Zeta2

green circuit board with secure digital card slot on left and 40-pin parallel interface connectors on the right
Mini PPISD board: a slow SD card mass-storage system for 8-bit computers

Almost no-one will need this knowledge, but I might need to remember it. In order to add Mini PPISD support to a RomWBW 3.01-supported system, you need to create a file called something like Source/HBIOS/Config/ZETA2_ppisd.asm (for yes, I’m using a Zeta SBC V2) containing:

#include "cfg_zeta2.asm"
UARTCFG		.SET	UARTCFG | SER_RTS
CRTACT		.SET	TRUE		
PPIDEENABLE	.SET	FALSE		
SDENABLE	.SET	TRUE		
PPPENABLE	.SET	FALSE		
PPISD		.EQU	TRUE

Running make from the top-level directory should create a ROM image called Binary/ZETA2_ppisd.rom for you to write to flash. Since my floppy drive isn’t feeling too happy, I had to resort to buying a TL866II Plus programmer to write the chip.

And it worked!

 RomWBW HBIOS v3.0.1, 2021-03-12

 ZETA V2 Z80 @ 8.000MHz
 0 MEM W/S, 1 I/O W/S, INT MODE 2
 512KB ROM, 512KB RAM

 CTC: MODE=Z2 IO=0x20
 UART0: IO=0x68 16550A MODE=38400,8,N,1
 DSRTC: MODE=STD IO=0x70 Sun 2021-03-14 17:47:13 CHARGE=OFF
 MD: UNITS=2 ROMDISK=384KB RAMDISK=384KB
 FD: IO=0x30 UNITS=2
 SD: MODE=PPI IO=0x60 DEVICES=1
 SD0: SDSC NAME=SD    BLOCKS=0x003C7800 SIZE=1935MB

 Unit        Device      Type              Capacity/Mode
 ----------  ----------  ----------------  --------------------
 Char 0      UART0:      RS-232            38400,8,N,1
 Disk 0      MD1:        RAM Disk          384KB,LBA
 Disk 1      MD0:        ROM Disk          384KB,LBA
 Disk 2      FD0:        Floppy Disk       3.5",DS/HD,CHS
 Disk 3      FD1:        Floppy Disk       3.5",DS/HD,CHS
 Disk 4      SD0:        SD Card           1935MB,LBA

 ZETA V2 Boot Loader

 ROM: (M)onitor (C)P/M (Z)-System (F)orth (B)ASIC (T)-BASIC (P)LAY (U)SER ROM  
 Disk: (0)MD1 (1)MD0 (2)FD0 (3)FD1 (4)SD0 

 Boot Selection? 

I was pleasantly surprised how easy it is to use a TL866 programmer under Linux. minipro does all the work, though. To write and verify the whole 512K Flash ROM, it’s:

minipro -p SST39SF040 -w ZETA2_ppisd.rom

The programmer supports over 16000 devices, of which around 10000 are variants (form factor, programming voltage, speed, OTP, etc). It’ll also verify over 100 different 74-series logic chips. It’s not a super cheap device (mine was a little over $80, from Simcoe Diy) but it does a lot for that price.

Next stop: try rebuilding BBC BASIC with RomWBW’s timer support included ..

ZX81 40th Anniversary

So the Z80-powered doorstop that got so many people started with computers was launched 40 years ago.

My story is that we never had one: we had three, but only for a week each. It’s not that they failed, either. My dad, who ran the computer bureau at King George V Docks for the Clyde Port Authority, was Glasgow’s representative on the European Association for Data Processing in Ports (EVHA). The group was looking at ways for automating ship identification, efficient berthing and documentation handling.

All the ports had data centres, but most of the mainframe time was for predefined tasks such as dock-worker payroll. There wasn’t the budget for computer time to try some of the experimental projects that may have helped with port automation.

Several EVHA members were trying home computers unofficially, but many of them were too expensive to come in under expense account rules. These “big” micros required a business case and purchase order to buy. The ZX81, limited as it was, did fit in the expense budget and – equally importantly – fitted into my dad’s suitcase as he made his monthly trips to Europe.

The week before my dad was scheduled to leave, he’d buy a ZX81. Of course, it needed “testing”, something me and my brother were only too happy to do. At the end of the week, it would get packed up and on its way to Europe.

I’m not sure if the clandestine micros were ever actually used for controlling ship traffic (you get considerably fewer than three lives manoeuvring an LNG tanker), but more likely in simulation. I understand that the ZX81 was able to simulate the traffic management for the entire Port of Rotterdam for a while, at least until its RAM pack wobbled.


reposted from ZX81 40th Anniversary – Histories – Retro Computing

Seeeduino XIAO simple USB volume control with CircuitPython

round computer device with USB cable exiting at left. Small microcontroller at centreshowing wiring to LED ring and rotary encoder
Slightly blurry image of the underside of the device, showing the Seeeduino XIAO and the glow from the NeoPixel ring. And yes, the XIAO is really that small

Tod Kurt’s QTPy-knob: Simple USB knob w/ CircuitPython is a fairly simple USB input project that relies on the pin spacing of an Adafruit QT Py development board being the same as that on a Bourns Rotary Encoder. If you want to get fancy (and who wouldn’t?) you can add a NeoPixel Ring to get an RGB glow.

The QT Py is based on the Seeeduino XIAO, which is a slightly simpler device than the Adafruit derivative. It still runs CircuitPython, though, and is about the least expensive way of doing so. The XIAO is drop-in replacement for the Qt Py in this project, and it works really well! Everything you need for the project is described here: todbot/qtpy-knob: QT Py Media Knob using rotary encoder & neopixel ring

I found a couple of tiny glitches in the 3d printed parts, though:

  1. The diffuser ring for the LED ring is too thick for the encoder lock nut to fasten. It’s 2 mm thick, and there’s exactly 2 mm of thread left on the encoder.
  2. The D-shaft cutout in the top is too deep to allow the encoder shaft switch to trigger.

I bodged these by putting an indent in the middle of the diffuser, and filling the top D-shaft cutout with just enough Blu Tack.

Tod’s got a bunch of other projects for the Qt Py that I’m sure would work well with the XIAO: QT Py Tricks. And yes, there’s an “Output Farty Noises to DAC” one that, regrettably, does just that.

Maybe I’ll add some mass to the dial to make it scroll more smoothly like those buttery shuttle dials from old video editing consoles. The base could use a bit more weight to stop it skiting about the desk, so maybe I’ll use Vik’s trick of embedding BB gun shot into hot glue. For now, I’ve put some rubber feet on it, and it mostly stays put.


Hey! Unlike my last Seeed Studio device post, I paid for all the bits mentioned here.

Nyan Cat, except it gets faster — RTTTL on the Raspberry Pi Pico

Raspberry Pi Pico with small piezo speaker connected to pins 23 and 26
piezo between pins 26 and 23. Sounds a little like this: https://twitter.com/scruss/status/1364646879758278657

It was inevitable:

  1. A Raspberry Pi Pico; plus
  2. a tiny piezo PC beeper; plus
  3. MicroPython; plus
  4. dhylands / upy-rtttl; plus
  5. nyancat.rtttl; plus
  6. my unfailing sense of knowing when to stop, then ignoring it

brings you this wonderful creation, which plays the Nyan Cat theme forever, except it gets 20% faster each time. This is weapons-grade annoying (thank’ee kindly), so I’m not going to include a recording here. If you must hear an approximation, paste the RTTTL into Play RTTTL Online and enjoy.

# Raspberry Pi Pico RTTTL example
# nyan cat, but it gets faster
# scruss - 2021-02: sorry, not sorry ...

# Uses rtttl.py from
#  github.com/dhylands/upy-rtttl
# Nyan Cat RTTTL from
#  github.com/KohaSuomi/emb-rtttl/blob/master/rtttl/nyancat.rtttl

from rtttl import RTTTL
from time import sleep_ms
from machine import Pin, PWM

b = 90    # bpm variable

# pin 26 - GP20; just the right distance from GND at pin 23
#  to use one of those PC beepers with the 4-pin headers
pwm = PWM(Pin(20))
led = Pin(25, Pin.OUT)


def play_tone(freq, msec):
    # play RTTL notes, also flash onboard LED
    print('freq = {:6.1f} msec = {:6.1f}'.format(freq, msec))
    if freq > 0:
        pwm.freq(int(freq))       # Set frequency
        pwm.duty_u16(32767)       # 50% duty cycle
    led.on()
    sleep_ms(int(0.9 * msec))     # Play for a number of msec
    pwm.duty_u16(0)               # Stop playing for gap between notes
    led.off()
    sleep_ms(int(0.1 * msec))     # Pause for a number of msec


while True:
    nyan = 'nyancat:d=4,o=5,b=' + str(b) + ':16d#6,16e6,8f#6,8b6,16d#6,16e6,16f#6,16b6,16c#7,16d#7,16c#7,16a#6,8b6,8f#6,16d#6,16e6,8f#6,8b6,16c#7,16a#6,16b6,16c#7,16e7,16d#7,16e7,16c#7,8f#6,8g#6,16d#6,16d#6,16p,16b,16d6,16c#6,16b,16p,8b,8c#6,8d6,16d6,16c#6,16b,16c#6,16d#6,16f#6,16g#6,16d#6,16f#6,16c#6,16d#6,16b,16c#6,16b,8d#6,8f#6,16g#6,16d#6,16f#6,16c#6,16d#6,16b,16d6,16d#6,16d6,16c#6,16b,16c#6,8d6,16b,16c#6,16d#6,16f#6,16c#6,16d#6,16c#6,16b,8c#6,8b,8c#6,8f#6,8g#6,16d#6,16d#6,16p,16b,16d6,16c#6,16b,16p,8b,8c#6,8d6,16d6,16c#6,16b,16c#6,16d#6,16f#6,16g#6,16d#6,16f#6,16c#6,16d#6,16b,16c#6,16b,8d#6,8f#6,16g#6,16d#6,16f#6,16c#6,16d#6,16b,16d6,16d#6,16d6,16c#6,16b,16c#6,8d6,16b,16c#6,16d#6,16f#6,16c#6,16d#6,16c#6,16b,8c#6,8b,8c#6,8b,16f#,16g#,8b,16f#,16g#,16b,16c#6,16d#6,16b,16e6,16d#6,16e6,16f#6,8b,8b,16f#,16g#,16b,16f#,16e6,16d#6,16c#6,16b,16f#,16d#,16e,16f#,8b,16f#,16g#,8b,16f#,16g#,16b,16b,16c#6,16d#6,16b,16f#,16g#,16f#,8b,16b,16a#,16b,16f#,16g#,16b,16e6,16d#6,16e6,16f#6,8b,8a#,8b,16f#,16g#,8b,16f#,16g#,16b,16c#6,16d#6,16b,16e6,16d#6,16e6,16f#6,8b,8b,16f#,16g#,16b,16f#,16e6,16d#6,16c#6,16b,16f#,16d#,16e,16f#,8b,16f#,16g#,8b,16f#,16g#,16b,16b,16c#6,16d#6,16b,16f#,16g#,16f#,8b,16b,16a#,16b,16f#,16g#,16b,16e6,16d#6,16e6,16f#6,8b,8c#6'
    tune = RTTTL(nyan)
    print('bpm: ', b)
    for freq, msec in tune.notes():
        play_tone(freq, msec)
    b = int(b * 1.2)

Visualizing PWM

my desk is not usually this tidy

I built a DS0150 — successfully, on my second try — and wanted to measure something. My demo MicroPython program from MicroPython on the terrible old ESP8266-12 Development Board has been running since May 2020, and the RGB LED’s red channel is conveniently broken out to a header. Since the DSO150 has precisely one channel, it let me see the how the PWM duty cycle affects the voltage (and brightness) of the LED.

I can’t think of oscilloscopes without being reminded of a scene from one of my favourite sci-fi books, The Reproductive System by John Sladek. Cal, the hapless new hire in the Wompler toy factory-turned-military research lab, is showing the boss some equipment while making up more and more extravagant names for them for the clueless owner:

At each exhibit, Grandison [Wompler] would pause while Cal named the piece of equipment. Then he would repeat the name softly, with a kind of wonder, nod sagely, and move on. Cal was strongly reminded of the way some people look at modern art exhibitions, where the labels become more important to them than the objects. He found himself making up elaborate names.
“And this, you’ll note, is the Mondriaan Modular Mnemonicon.”
“—onicon, yes.”
“And the Empyrean diffractosphere.”
“—sphere. Mn. I see.”
Nothing surprised Grandison, for he was looking at nothing. Cal became wilder. Pointing to Hita’s desk, he said, “The chiarascuro thermocouple.”
“Couple? Looks like only one, to me. Interesting, though.”
A briar pipe became a “zygotic pipette,” the glass ashtray a “Piltdown retort,” and the lamp a “phase-conditioned Aeolian.” Paperclips became “nuances.”
“Nuances, I see. Very fine. What’s that thing, now?”
He pointed to an oscilloscope. Cal took a deep breath.
“Its full name,” he said, “is the Praetorian eschatalogical morphomorphic tangram, Endymion-type, but we usually just call it a ramification.”
The old man fixed him with a stern black eye. “Are you trying to be funny or something? I mean, I may not be a smart-aleck scientist, but I sure as hell know a television when I see one.”
Cal assured him it was not a television, and proved it by switching it on. “See,” he said, pointing to a pattern of square waves, “there are the little anapests.”

— The Reproductive system, by John Sladek (text copypasta from Grey Goo in the 1960s)

So we were displaying roughly 500 anapests/s there. Not bad, not bad at all …

Raspberry Pi Pico: DS18x20 in MicroPython

Hidden away in the Pico MicroPython guide is a hint that there may be more modules installed in the system than they let on. In the section about picotool, the guide has a seemingly innocuous couple of lines:

frozen modules: _boot, rp2, ds18x20, onewire, uasyncio, uasyncio/core, uasyncio/event, uasyncio/funcs, uasyncio/lock, uasyncio/stream

The third and fourth ‘frozen modules’ are a giveaway: it shows that support for the popular Dallas/Maxim DS18x20 1-Wire temperature sensors is built in. Nowhere else in the guide are they mentioned. I guess someone needs to write them up …

DS18x20 digital temperature sensors — usually sold as DS18B20 by Maxim and the many clone/knock-off suppliers — are handy. They can report temperatures from -55 to 125 °C, although not every sensor will withstand that range. They come in a variety of packages, including immersible sealed units. They give a reliable result, free from ADC noise. They’re fairly cheap, the wiring’s absurdly simple, and you can chain long strings of them together from the same input pin and they’ll all work. What they aren’t, though, is fast: 1-Wire is a slow serial protocol that takes a while to query all of its attached devices and ferry the results back to the controller. But when we’re talking about environmental temperature, querying more often than a few times a minute is unnecessary.

So this is the most complex way you can wire up a DS18x20 sensor:

breadboard with raspberry Pi Pico, DS18x20 sensor with 47 kΩ pull-up resistor between 3V3 power and sensor data line
Raspberry Pi Pico connected to a single DS18x20 sensor

and this is how it’s wired:

   DS18X20    Pico
   =========  =========
   VDD      → 3V3
              /
     --47 kΩ--
    /
   DQ       → GP22
   GND      → GND

 (47 kΩ resistor between DQ and 3V3 as pull-up)

Adding another sensor is no more complicated: connect it exactly as the first, chaining the sensors together —

breadboard with raspberry Pi Pico, two DS18x20 sensors with 47 kΩ pull-up resistor between 3V3 power and sensor data line
Two DS18x20 sensors, though quite why you’d want two temperature sensors less than 8 mm apart, I’ll never know. Imagine one is a fancy immersible one on a long cable …

The code is not complex, either:

# Raspberry Pi Pico - MicroPython DS18X20 Sensor demo
# scruss - 2021-02
# -*- coding: utf-8 -*-

from machine import Pin
from onewire import OneWire
from ds18x20 import DS18X20
from time import sleep_ms
from ubinascii import hexlify    # for sensor ID nice display

ds = DS18X20(OneWire(Pin(22)))
sensors = ds.scan()

while True:
    ds.convert_temp()
    sleep_ms(750)     # mandatory pause to collect results
    for s in sensors:
        print(hexlify(s).decode(), ":", "%6.1f" % (ds.read_temp(s)))
        print()
    sleep_ms(2000)

This generic code will read any number of attached sensors and return their readings along with the sensor ID. The sensor ID is a big ugly hex string (the one I’m using right now has an ID of 284c907997070344, but its friends call it ThreeFourFour) that’s unique across all of the sensors that are out there.

If you’re reading a single sensor, the code can be much simpler:

# Raspberry Pi Pico - MicroPython 1x DS18X20 Sensor demo
# scruss - 2021-02
# -*- coding: utf-8 -*-

from machine import Pin
from onewire import OneWire
from ds18x20 import DS18X20
from time import sleep_ms

ds = DS18X20(OneWire(Pin(22)))
sensor_id = ds.scan()[0]  # the one and only sensor

while True:
    ds.convert_temp()
    sleep_ms(750)         # wait for results
    print(ds.read_temp(sensor_id), " °C")
    sleep_ms(2000)

The important bits of the program:

  1. Tell your Pico you have a DS18x20 on pin GP22:
    ds = DS18X20(OneWire(Pin(22)))
  2. Get the first (and only) sensor ID:
    sensor_id = ds.scan()[0]
  3. Every time you need a reading:
    1. Request a temperature reading:
      ds.convert_temp()
    2. Wait for results to come back:
      sleep_ms(750)
    3. Get the reading back as a floating-point value in °C:
      ds.read_temp(sensor_id)

That’s it. No faffing about with analogue conversion factors and mystery multipliers. No “will it feel like returning a result this time?” like the DHT sensors. While the 1-Wire protocol is immensely complicated (Trevor Woerner has a really clear summary: Device Enumeration on a 1-Wire Bus) it’s not something you need to understand to make them work.