Category: goatee-stroking musing, or something

  • Official SCRABBLE®Brand ANAGRAMS

    game box with red top saying Official Scrabble® Brand ANAGRAMS with the last word picked out in pictures of rectangular wooden game tiles
    that red is much more orange in daylight. Note 1964 copyright year …

    Found in a thrift store, the 1960s Selchow & Righter SCRABBLE® variant that nobody loved. It has no board, but 180 tiles, slightly different from the SCRABBLE® ones (dang, I love that I can type ®, can’t you tell?)

    26 wooden letter tiles from SCRABBLE® Anagrams, arranged in alphabetical order
    that Q, tho …

    The stencilling/printing isn’t perfectly even in position, but I do have to remember these are at least 60 years old:

    six wooden letter E tiles from SCRABBLE® Anagrams
    some variation in same letter placement

    The instructions from inside the box (which were murder to scan btw: appreciate me!) are dated 1962, unlike the box. There’s a PDF linked under this image for those who enjoy legibility:

    Scrabble® Anagrams instructions - the full text is later in the article

    Included in the box, possibly original, is an instruction sheet typed in the naffest font known to man:

    Scrabble® Anagrams instructions, typed in two columns in a weird cursive-like typewriter font.
    say you were typed on an early 1960s Smith-Corona Galaxie without saying, etc. …

    Should you have no taste at all and want this excuse for type in your own documents, go here: zai Smith-Corona Galaxie Typewriter Font. May your documents smell of stale cigarette smoke forever. At least no-one will be able to OCR them.

    The typed rules seem to disagree with the box rules a little. Pick the one you dislike less.

    The 180 tiles have a slightly odd distribution for English: A×14, B×4, C×4, D×8, E×22, F×4, G×6, H×4, I×18, J×2, K×2, L×8, M×4, N×10, O×14, P×4, Q×2, R×12, S×8, T×10, U×8, V×2, W×2, X×2, Y×4, Z×2. You’re not going to make this up with any number of SCRABBLE® tile sets.

    You can’t even make up a full 144 tile Bananagrams set with these. Even if you remove the excess tiles (A, B, C, D×2, E×4, F, G×2, H, I×6, L×3, M, N×2, O×3, P, R×3, S×2, T, U×2, Y), you’d still be short by a V and a W. A bug, perhaps?

    Here are the rules from the box lid in full:

    OFFICIAL SCRABBLE® BRAND ANAGRAMS

    HOW TO PLAY ANAGRAMS

    Of the many varieties of word games, Anagrams is one of the oldest and best known. The word Anagram means a word formed by rearranging the letters of a different word so as to completely change it. For example, the three letters of A, P, T can be combined to spell APT, TAP and PAT. These words are anagrams of one another. The game of Anagrams is a contest in forming words by combining letters drawn at random or by rearranging letters of words already formed.

    This anagram set consists of 180 letters assorted in proportion to their frequency of use in forming words in the English language as computed by experts in analysis of word formations.

    To Start:

    Place all letters face down on a table. Shuffle them and arrange in approximately equal groups at the corners or sides of the table. (Or place them in a box or bowl from which they can be drawn conveniently one-by-one without being seen in advance.) Leave room in the center of the table for a dozen or more letters and clear a space in front of each player for the words that he will form.

    Each player (2 to 6 make the best game) then draws one letter and places it face up in the center of the table. The player drawing the letter nearest the beginning of the alphabet wins first turn. Others follow him clockwise around the table. (In case of a tie for first turn, the tied players draw again until the tie is broken.)

    If fewer than 10 letters have been turned up in the center of the table the player who has won first turn draws enough additional letters to make a total of 10 exposed letters on the table. For the remainder of the game the stock of exposed letters in the center of the table is replenished only by discards.

    Word Formation:

    The person who is taking his turn begins by drawing one concealed letter from any place on the table. This is his key letter. He tries to play it in one of the following three ways:

    1. He may combine his key letter with 3 or more of the exposed letters from the center of the table to form a word of 4 or more letters. (For beginners use 3 or more letters.) If he can do so he places the word on the table in front of him and facing the center so that it is legible to all other players. (Note: He may not use letters from the center of the table to form a word which does not include his key letter.)
    2. He may add his key letter (and one or more letters from the center of the table if possible) to enlarge or change one of his own words. For example, if his key letter is S and one of his own words is CARE, he may play the key letter to form CARES, RACES or SCARE; or he might play it and a T from the center of the table to form CARETS, CASTER, CRATES or TRACES.
    3. He may steal a word previously formed by another player by adding his key letter (with one or more letters from the center of the table if possible) to form a new word in which the letters of the stolen word are rearranged. In the example used above he might steal the word CARE from another player by changing it to RACES, CASTER, CRATES or TRACES; he could not steal it to form CARES, CARETS or SCARE because the letters of the original word have not been rearranged.

    Each player continues to draw and play key letters as long as he can use them in one of these three ways. If he draws a key letter that can not be played he discards it face up in the center of the table and his turn ends. The next player to the left then draws and begins his turn.

    Challenging:

    Before the game begins the players must agree on the types of words that will be used. Common practice is to use only words found in the alphabetical section of a standard dictionary excluding abbreviations, prefixes, suffixes, capitalized words and those requiring hyphens or apostrophes. These or other classes of words (such as geographical names) may be included by agreement. The important thing is to have a clear understanding before the game begins.

    When a word is formed, changed or stolen during the game any other player or players may challenge it for spelling or other requirements that have been agreed upon. The word then must be found in the dictionary and verified. If it is correct the player retains the word and continues his turn; any player who has challenged a correct word loses his next turn. If the word can not be verified the letters are returned to the center of the board, to the original word or to the player from whom they were stolen, as the case may be; the person who attempted to form the word ends his turn at once and discards his key letter even though there might be other ways in which he could have used it.

    A word may be challenged only at the time that it is played.

    Penalty:

    If a player, when drawing his key letter or at any other time after the play begins, exposes any concealed letter other than the key letter to which he is entitled, all letters so exposed are placed face up in the center of the table and the player loses the remainder of his turn or his next turn, as the case may be.

    Scoring:

    The game ends when one player has accumulated ten words or when the last concealed letter has been drawn, whichever occurs first. Scores then are counted by crediting each player with one point for each four-letter word and one additional point for each letter over four in any word.

    COPYRIGHT 1962 BY SELCHOW & RIGHTER COMPANY, BAY SHORE, NEW YORK

    Makers of PARCHEESI®, A Backgammon Game of India

    Made in U.S.A.

    The game is listed as Scrabble Scoring Anagrams on Board Game Geek and given a much later date (1972) than this one.

  • Crosstown is finally open and I am 😐

    It’s years late and many millions over budget, but — at last — the TTC Line 5 Eglinton Crosstown is open today! I am slightly happy for them, as finally they’ll have to stop making excuses about why it’s closed.

    I rode a little bit of it today (it was free) and this little bit of dust graffiti sums up how I feel

    dust inscription on Kennedy station's window says: it's finally open! ?
    dust inscription on Kennedy station’s window says: it’s finally open! 😐

    I rode the surface section from Kennedy to Aga Khan Park this afternoon, and my overall impression was: wow, this is really slow.

    Map of Eglinton Avenue East, showing Crosstown stations from Aga Khan Park & Museum to Ionview
    Map of Eglinton Avenue East, showing Crosstown stations from Aga Khan Park & Museum to Ionview

    I was only able to track the train from Ionview, as my phone GPS is useless underground.

    Westbound

    I got on a westbound train a little after 15:30

    Station Distance / km Arrive Depart Time Speed / km/h
    Ionview 15:37:20
    Birchmount 0.552 15:39:05 15:39:40 1′ 45″ 18.9
    Golden Mile 1.244 15:42:37 15:42:39 2′ 57″ 25.3
    Hakimi Lebovic 0.455 15:44:29 15:44:30 1′ 50″ 14.9
    Pharmacy 0.592 15:47:55 15:48:30 3′ 25″ 10.4
    O’Connor 0.584 15:50:00 15:51:55 1′ 30″ 23.3
    Sloane 1.225 15:53:25 15:54:03 1′ 30″ 49.0
    Wynford 1.600 15:56:45 15:57:17 2′ 42″ 35.5
    Aga Khan Park & Museum 0.718 15:58:25 1′ 08″ 38.0
    Total 6.969 21′ 05″ 19.8

    Despite getting up to almost 50 km/h between O’Connor and Sloane, we still didn’t exceed an average of 20 km/h over the whole 7 km trip. So many stops for lights. Traffic on Eglinton was moving faster than us.

    Eastbound

    Two Alstom TTC/Metrolinx Crosstown light rail trains sit at the rather snowy Aga Khan Park station
    Two Alstom TTC/Metrolinx Crosstown light rail trains sit at the rather snowy Aga Khan Park station

    I had to wait for 10 minutes at Aga Khan Park station for the return train. It was just a little brisk out. When it finally arrived, it was so busy that I ended up smushed against a door for most of the ride.

    Station Distance / km Arrive Depart Time Speed / km/h
    Aga Khan Park & Museum 16:08:55
    Wynford 0.718 16:10:10 16:11:00 1′ 15″ 34.4
    Sloane 1.600 16:14:00 16:14:45 3′ 00″ 32.0
    O’Connor 1.225 16:17:35 16:19:15 2′ 50″ 25.9
    Pharmacy 0.584 16:20:35 16:21:15 1′ 20″ 26.3
    Hakimi Lebovic 0.592 16:23:10 16:23:30 1′ 55″ 18.5
    Golden Mile 0.455 16:25:52 16:25:54 2′ 22″ 11.5
    Birchmount 1.244 16:29:50 16:30:25 3′ 56″ 19.0
    Ionview 0.552 16:31:35 1′ 10″ 28.4
    Total 6.969 22′ 40″ 18.4

    Even slower coming back.

    It’s okay, TTC/Metrolinx: we’ve got used to waiting.

  • The Epic of Mitorzp (fragment)

    A lined index card with lines of cut up teletype paper tape stuck to it. The tapes read: HE F=RIDZT GOT T BAZU N LH EENGER COULDDARA= LSEEWTARM. = LISON =ASWHJDOO ZAS ZETH MI=TI ZEAC OS B=PN LLHERE T-R=RS =WE=Z=PNS=E M HIZI VALD R G M M ANT =ART=H MITORZP PBTHAT L EEUEB XTAZ=ECL EELING F OMB= ICKSAWN=LTO HIS ENZ= The words MITORZP and ICKSAWN are highlighted, the first in pink and the second in yellow.
    teleprinter tape glued to index card, 15 × 10 cm, paper/card/highlighter pencils (2025)

    This is the only surviving fragment of The Epic of Mitorzp. It was transmitted by an unknown intelligence, but discarded by human operators as mere line noise.

    HE F=RIDZT GOT
    T BAZU N LH EENGER COULDDARA=
    LSEEWTARM. = LISON =ASWHJDOO ZAS ZETH
    MI=TI ZEAC OS B=PN LLHERE T-R=RS
    =WE=Z=PNS=E M HIZI VALD R G M
    M ANT =ART=H MITORZP PBTHAT L
    EEUEB XTAZ=ECL EELING F OMB=
    ICKSAWN=LTO HIS ENZ=

    Who was Mitorzp? A hero? An outcast? We will never know. This tiny remnant can only hint at the colossal magnitude of the lost epic.

  • img2blocks

    this image is supposed to be made almost entirely of sextant blocks, the Unicode characters around U+1FB00 – U+1FB1E made out of two columns of three blocks. They’re originally from broadcast teletext, and were made to build low-resolution images on a text screen

    a blocky image of a large flightless bird with the text "Cassowary Detected"
    redrawn from an image in Artificial intelligence used to reduce cassowary road deaths in Queensland

    And here’s the original tiny image:

    a small image of a large flightless bird with the text "Cassowary Detected"
    “Cassowary Detected”, at actual size

    Making the pixel to character map is quite tricky. The Sextant character block isn’t contiguous, and it’s not in the order we need. It’s also missing four characters: empty block, full block, left half block and right half block. These have to be pulled in from other Unicode blocks.

    This is the map I came up with, from 0–63 with LSB at bottom right and MSB at top left:

     🬞🬏🬭🬇🬦🬖🬵🬃🬢🬓🬱🬋🬩🬚🬹🬁🬠🬑🬯🬉▐🬘🬷🬅🬤🬔🬳🬍🬫🬜🬻🬀🬟🬐🬮🬈🬧🬗🬶🬄🬣▌🬲🬌🬪🬛🬺🬂🬡🬒🬰🬊🬨🬙🬸🬆🬥🬕🬴🬎🬬🬝█

    After that, it’s a small matter of bashing something together in Python with PIL and Numpy. Here’s the source:

    or if you want to take a look first: img2blocks – bitmap to Unicode sextants

    blocky criss-crossed bars make a pseudo woven pattern
    X11’s wide_weave, scaled up

    (yes, there are clever things like Chafa that can do more, but it can’t do exactly what this does)

  • For 4 MB ESP32-S3 users

    If you have a Wemos/LOLIN S3 MINI PRO board, you might find that firmware images don’t flash so well. That’s because the ESP32-S3FH4R2 has 4 MB of flash storage, and most ESP32-S3 boards have 8 MB.

    glenn20/mp-image-tool-esp32 might be your new friend:

    mp-image-tool-esp32 -f 4M --resize vfs=2M ESP32_GENERIC_S3-20250415-v1.25.0.bin

    This trims down a standard MicroPython ESP32-S3 firmware from a 4 MB filesystem partition down to 2 MB, and sets the overall flash size to 4 MB. Upload that to your board, and all will be well.

    Alternatively, v1.26 supports “4MiB and larger” flash chips. I have confirmed that ESP32_GENERIC_S3-20250724-v1.26.0-preview.bin works as expected:

    $ mpremote a1 run boardstats.py 
    Board : Generic ESP32S3 module with ESP32S3
    Frequency : 160 MHz
    Free Memory : 2061232
    File storage: 2036 / 2048 K
  • Snow-loving Solar Marble Machine

    Martin Raynsford / Solarbotics Solar Marble Machine loving glare off deep snow

    Still going strong after more than a decade in the front window, the Solar Marble Machine has been running flat out all day because of the glare from the deep snow outside. It might normally do one click a day, if any at all.

  • Cheap NeoPixels at the Dollar Store

    Exhibit A:

    box of "Monster BASICS" Sound reactive RGB+IC Color Flow LED strip

    also known as “Monster BASICS Sound reactive RGB+IC Color Flow LED strip”. It’s $5 or so at Dollarama, and includes a USB cable for power and a remote control. It’s two metres long and includes 60 RGB LEDs. Are these really super-cheap NeoPixel clones?

    I’m going to keep the USB power so I can power it from a power bank, but otherwise convert it to a string of smart LEDs. We lose the remote control capability.

    Pull back the heatshrink at the USB end:

    led strip with shrink tubing pulled back to show the +5 V, Din and GND solder tterminals

    … and there are our connectors. We want to disconnect the blue Din (Data In) line from the built in controller, and solder new wires to Din and GND to run from a microcontroller board.

    led strip with additional wires soldered to Din and GND contacts

    Maybe not the best solder job, but there are new wires feeding through the heatshrink and soldered onto the strip.

    led strip with two additional wires soldered in and heatshrink pushed back, all held in place by a cable tie

    Here’s the heatshrink pushed back, and everything secured with a cable tie.

    Now to feed it from standard MicroPython NeoPixel code, suitably jazzed up for 60 pixels.

    a glowing multicolour reel of of LEDs

    A pretty decent result for $5!

  • Thousand Days: Concept

    reference copy: Thousand Days: Concept on github.

    Stewart Russell – scruss.com — 2024-03-26, at age 19999 days …

    Summary

    One’s thousand day(s) celebration occurs every thousand days of a person’s life. They are meant to be a recognition of getting this far, and are celebrated at the person’s own discretion.

    Who is this for?

    • Maybe your birthday’s on a day associated with an unpleasant event. Your thousand day will never coincide with your birthday.
    • Maybe your birthday’s in the middle of winter, or in another part of the year that you’re not keen on. Your thousand day is every 2 years and 3 seasons, so it shifts back by a season every time it happens.

    Quantities and scale

    1000 days is approximately:

    • 2.738 years
    • 2 years 269 days
    • 2 years 8.85 months
    • 2 years, 3 seasons.

    4000 days is just shy of 11 years.

    Disadvantages

    Compared to regular birthdays, thousand days:

    • must be calculated; they’re not intuitive when they’re going to happen. But we have computers and calendar reminders for that …
    • can be used to work out your actual date of birth, if someone knows that you’re going to be x000 days old on a particular day. It’s possible to know someone’s birthday, but not know their age.

    Implementations

    Web

    My ancient Your 1000 Day Birthday Calculator, first published in 2002 and untouched since 2010.

    Shell

    So it turns out that GNU date can handle arbitrary date maths quite well. For example:

    date --iso-8601=date --date="1996-11-09 + 10000 days"

    returns 2024-03-27.

    Other Ways

    Excel or any other spreadsheet will do, too. Although not for too many years back

    People with the same thousand day as you

    This is an idea for finding people who have a thousand day on the same day as you. I suggest using 1851-10-01 as a datum, because:

    • nothing particularly interesting happened that day;
    • it’s conveniently 43000 days before my birthday.

    then calculate

    ( (birth_date - 1851-10-01) mod 1000 ) + 1
    

    This results in a number 1 – 1000. Everyone with the same number shares a 1000 day birthday with you.

    Why not 0 – 999?

    1. No-one deserves to be a zero;
    2. Wouldn’t be much of a thousand day if it only went up to 999, would it?

    Incomplete list of people with day = 1

    There are more, but these were found from Wikipedia’s year pages

    Licence

    🅭 2024, Stewart Russell, scruss.com

    This work is licensed under CC BY-SA 4.0.

    There are no trademarks, patents, official websites, social media or official anythings attached to this concept. Please take the idea and do good with it.

    So why aren’t you implementing this further?

    I’ve had this idea kicking around my head for at least the last 20 years. For $REASONS, it turns out I’m not very good at implementing stuff. I’d far rather someone else took this idea and ran with it than let it sit undeveloped.

  • The Potato

    … is a thing to help soldering DIN connectors. I had some made at JLCPCB, and will have them for sale at World of Commodore tomorrow.

    a rectangular green circuit board with markings for various DIN connectors, and holes for the connector pins to fin through. One of the sets of holes is filled by a 7-pin 270° DIN connector, ready for soldering.

There are various pinouts for retrocomputers on the board. There is also the slogan THE POTATO, with a smiling cartoon potato next to it
    Sven Petersen’s “The Potato” – front. DIN7 connector not included
    a rectangular green circuit board with markings for various DIN connectors, and holes for the connector pins to fin through. One of the sets of holes is filled by a 7-pin 270° DIN connector, ready for soldering.

There are various pinouts for retrocomputers on the board. There is also the slogan DE KANTÜFFEL, with a smiling cartoon potato next to it
    Sven Petersen’s “The Potato” – back

    You can get the source from svenpetersen1965/DIN-connector_soldering-aid-The-Potato. I had the file Rev. 0/Gerber /gerber_The_Potato_noFrame_v0a.zip made, and it seems to fit connector pins well.

    Each Potato is made up of two PCBs, spaced apart by a nylon washer and held together by M3 nylon screws.

  • can we…?

    This is a mini celebratory post to say that I’ve fixed the database encoding problems on this blog. It looks like I will have to go through the posts manually to correct the errors still, but at least I can enter, store and display UTF-8 characters as expected.

    “? µ ° × — – ½ ¾ £ é?êè”, he said with some relief.

    Postmortem: For reasons I cannot explain or remember, the database on this blog flipped to an archaic character set: latin1, aka ISO/IEC 8859-1. A partial fix was effected by downloading the entire site’s database backup, and changing all the following references in the SQL:

    • CHARSET=latin1 → CHARSET=utf8mb4
    • COLLATE=latin1_german2_ci → COLLATE=utf8mb4_general_ci
    • COLLATE utf8mb4_general_ci → COLLATE utf8mb4_general_ci
    • latin1_general_ci → utf8mb4_general_ci
    • COLLATE latin1_german2_ci → COLLATE utf8mb4_general_ci
    • CHARACTER SET latin1 → CHARACTER SET utf8mb4

    For additional annoyance, the entire SQL dump was too big to load back into phpmyadmin, so I had to split it by table. Thank goodness for awk!

    #!/usr/bin/awk -f
    
    BEGIN {
        outfile = "nothing.sql";
    }
    
    /^# Table: / {
        # very special comment in WP backup that introduces a new table
        # last field is table_name,
        # which we use to create table_name.sql
        t = $NF
        gsub(/`/, "", t);
        outfile = t ".sql";
    }
    
    {
        print > outfile;
    }
    

    The data still appears to be confused. For example, in the post Compose yourself, Raspberry Pi!, what should appear as “That little key marked “Compose”” appears as “That little key marked “Compose””. This isn’t a straight conversion of one character set to another. It appears to have been double-encoded, and wrongly too.

    Still, at least I can now write again and have whatever new things I make turn up the way I like. Editing 20 years of blog posts awaits … zzz

  • SYN6288 TTS board from AliExpress

    After remarkable success with the SYN-6988 TTS module, then somewhat less success with the SYN-6658 and other modules, I didn’t hold out much hope for the YuTone SYN-6288, which – while boasting a load of background tunes that could play over speech – can only convert Chinese text to speech

    small blue circuit board with 6 MHz crystal oscillator, main chip, input headers at bottom and headphone jack/speaker output at top
    as bought from quason official store: SYN6288 speech synthesis module

    The wiring is similar to the SYN-6988: a serial UART connection at 9600 baud, plus a Busy (BY) line to signal when the chip is busy. The serial protocol is slightly more complicated, as the SYN-6288 requires a checksum byte at the end.

    As I’m not interested in the text-to-speech output itself, here’s a MicroPython script to play all of the sounds:

    # very crude MicroPython demo of SYN6288 TTS chip
    # scruss, 2023-07
    import machine
    import time
    
    ### setup device
    ser = machine.UART(
        0, baudrate=9600, bits=8, parity=None, stop=1
    )  # tx=Pin(0), rx=Pin(1)
    
    busyPin = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
    
    
    def sendspeak(u2, data, busy):
        # modified from https://github.com/TPYBoard/TPYBoard_lib/
        # u2 = UART(uart, baud)
        eec = 0
        buf = [0xFD, 0x00, 0, 0x01, 0x01]
        # buf = [0xFD, 0x00, 0, 0x01, 0x79]  # plays with bg music 15
        buf[2] = len(data) + 3
        buf += list(bytearray(data, "utf-8"))
        for i in range(len(buf)):
            eec ^= int(buf[i])
        buf.append(eec)
        u2.write(bytearray(buf))
        while busy.value() != True:
            # wait for busy line to go high
            time.sleep_ms(5)
        while busy.value() == True:
            # wait for it to finish
            time.sleep_ms(5)
    
    
    for s in "abcdefghijklmnopqrstuvwxy":
        playstr = "[v10][x1]sound" + s
        print(playstr)
        sendspeak(ser, playstr, busyPin)
        time.sleep(2)
    
    for s in "abcdefgh":
        playstr = "[v10][x1]msg" + s
        print(playstr)
        sendspeak(ser, playstr, busyPin)
        time.sleep(2)
    
    for s in "abcdefghijklmno":
        playstr = "[v10][x1]ring" + s
        print(playstr)
        sendspeak(ser, playstr, busyPin)
        time.sleep(2)
    

    Each sound starts and stops with a very loud click, and the sound quality is not great. I couldn’t get a good recording of the sounds (some of which of which are over a minute long) as the only way I could get reliable audio output was through tiny headphones. Any recording came out hopelessly distorted:

    I’m not too disappointed that this didn’t work well. I now know that the SYN-6988 is the good one to get. It also looks like I may never get to try the XFS5152CE speech synthesizer board: AliExpress has cancelled my shipment for no reason. It’s supposed to have some English TTS function, even if quite limited.

    Here’s the auto-translated SYN-6288 manual, if you do end up finding a use for the thing

  • SYN-6988 Speech with MicroPython

    Full repo, with module and instructions, here: scruss/micropython-SYN6988: MicroPython library for the VoiceTX SYN6988 text to speech module

    (and for those that CircuitPython is the sort of thing they like, there’s this: scruss/circuitpython-SYN6988: CircuitPython library for the YuTone VoiceTX SYN6988 text to speech module.)

    I have a bunch of other boards on order to see if the other chips (SYN6288, SYN6658, XF5152) work in the same way. I really wonder which I’ll end up receiving!

    Update (2023-07-09): Got the SYN6658. It does not support English TTS and thus is not recommended. It does have some cool sounds, though.

    Embedded Text Command Sound Table

    The github repo references Embedded text commands, but all of the sound references were too difficult to paste into a table there. So here are all of the ones that the SYN-6988 knows about:

    • Name is the string you use to play the sound, eg: [x1]sound101
    • Alias is an alternative name by which you can call some of the sounds. This is for better compatibility with the SYN6288 apparently. So [x1]sound101 is exactly the same as specifying [x1]sounda
    • Type is the sound description from the manual. Many of these are blank
    • Link is a playable link for a recording of the sound.
    NameAliasTypeLink
    sound101sounda
    sound102soundb
    sound103soundc
    sound104soundd
    sound105sounde
    sound106soundf
    sound107soundg
    sound108soundh
    sound109soundi
    sound110soundj
    sound111soundk
    sound112soundl
    sound113soundm
    sound114soundn
    sound115soundo
    sound116soundp
    sound117soundq
    sound118soundr
    sound119soundt
    sound120soundu
    sound121soundv
    sound122soundw
    sound123soundx
    sound124soundy
    sound201phone ringtone
    sound202phone ringtone
    sound203phone ringtone
    sound204phone rings
    sound205phone ringtone
    sound206doorbell
    sound207doorbell
    sound208doorbell
    sound209doorbell
    sound301alarm
    sound302alarm
    sound303alarm
    sound304alarm
    sound305alarm
    sound306alarm
    sound307alarm
    sound308alarm
    sound309alarm
    sound310alarm
    sound311alarm
    sound312alarm
    sound313alarm
    sound314alarm
    sound315alert/emergency
    sound316alert/emergency
    sound317alert/emergency
    sound318alert/emergency
    sound401credit card successful
    sound402credit card successful
    sound403credit card successful
    sound404credit card successful
    sound405credit card successful
    sound406credit card successful
    sound407credit card successful
    sound408successfully swiped the card
    SYN-6988 Sound Reference

  • MicroPython on the Seeed Studio Wio Terminal: it works!

    A while back, Seeed Studio sent me one of their Wio Terminal devices to review. It was pretty neat, but being limited to using Arduino to access all of it features was a little limiting. I still liked it, though, and wrote about it here: SeeedStudio Wio Terminal

    Small screen device showing geometric pattern
    Wio Terminal, doing a thing

    There wasn’t any proper MicroPython support for the device as it used a MicroChip/Atmel SAMD51 ARM® Cortex®-M4 micro-controller. But since I wrote the review, one developer (robert-hh) has worked almost entirely solo to make SAMD51 and SAMD21 support useful in mainline MicroPython.

    Hey! Development is still somewhere between “not quite ready for prime time” and “beware of the leopard”. MicroPython on the SAMD51 works remarkably well for supported boards, but don’t expect this to be beginner-friendly yet.

    I thought I’d revisit the Wio Terminal and see what I could do using a nightly build (downloaded from Downloads – Wio Terminal D51R – MicroPython). Turns out, most of the board works really well!

    What doesn’t work yet

    • Networking/Bluetooth – this is never going to be easy, especially with Seeed Studio using a separate RTL8720 SoC. It may not be entirely impossible, as previously thought, but so far, wifi support seems quite far away
    • QSPI flash for program storagethis is not impossible, just not implemented yet this works now too, but it’s quite slow since it relies on a software SPI driver. More details: samd51: MicroPython on the Seeed Wio Terminal · Discussion #9838 · micropython
    • RTCthis is a compile-time option, but isn’t available on the stock images. Not all SAMD51 boards have a separate RTC oscillator, and deriving the RTC from the system oscillator would be quite wobbly. RTC works now! It may even be possible to provide backup battery power and have it keep time when powered off. VBAT / PB03 / SPI_SCK is broken out to the 40-pin connector.

    What does work

    • Display – ILI9341 320×240 px, RGB565 via SPI
    • Accelerometer – LIS3DHTR via I²C
    • Microphone – analogue
    • Speaker – more like a buzzer, but this little PWM speaker element does allow you to play sounds
    • Light Sensor – via analogue photo diode
    • IR emitter – PWM, not tied to any hardware protocol
    • Internal LED – a rather faint blue thing, but useful for low-key signalling
    • Micro SD Card – vi SPI. Works well with MicroPython’s built-in virtual file systems
    • Switches and buttons – three buttons on the top, and a five-way mini-joystick
    • I²C via Grove Connector – a second, separate I²C channel.

    I’ll go through each of these here, complete with a small working example.

    Wio Terminal main board
    Inside the remarkably hard-to-open Wio Terminal

    LED

    Let’s start with the simplest feature: the tiny blue LED hidden inside the case. You can barely see this, but it glows out around the USB C connector on the bottom of the case.

    • MicroPython interfaces: machine.Pin, machine.PWM
    • Control pin: Pin(“LED_BLUE”) or Pin(15), or Pin(“PA15”): any one of these would work.

    Example: Wio-Terminal-LED.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-LED.py - blink the internal blue LED
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    from machine import Pin
    from time import sleep_ms
    
    led = Pin("LED_BLUE", Pin.OUT)  # or Pin(15) or Pin("PA15")
    
    try:
        while True:
            led.value(not led.value())
            sleep_ms(1200)
    except:
        led.value(0)  # turn it off if user quits
        exit()
    

    IR LED

    I don’t have any useful applications of the IR LED for device control, so check out Awesome MicroPython’s IR section for a library that would work for you.

    • MicroPython interfaces: machine.PWM
    • Control pin: Pin(“PB31”)

    Example: Wio-Terminal-IR_LED.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-IR_LED.py - blink the internal IR LED
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    # Hey! This is a completely futile exercise, unless you're able
    # to see into the IR spectrum. But we're here to show you the pin
    # specification to use. For actual useful libraries to do stuff with
    # IR, take a look on https://awesome-micropython.com/#ir
    
    # So this is a boring blink, 'cos we're keeping it short here.
    # You might be able to see the LED (faintly) with your phone camera
    
    from machine import Pin, PWM
    from time import sleep_ms
    
    ir = PWM(Pin("PB31"))  # "IR_CTL" not currently defined
    
    try:
        while True:
            ir.duty_u16(32767)  # 50% duty
            ir.freq(38000)  # fast flicker
            sleep_ms(1200)
            ir.duty_u16(0)  # off
            sleep_ms(1200)
    except:
        ir.duty_u16(0)  # turn it off if user quits
        exit()
    

    Buttons

    There are three buttons on top, plus a 5-way joystick on the front. Their logic is inverted, so they read 0 when pressed, 1 when not. It’s probably best to use machine.Signal with these to make operation more, well, logical.

    • MicroPython interface: machine.Signal (or machine.Pin)
    • Control pins: Pin(“BUTTON_3”) or Pin(92) or Pin(PC28) – top left; Pin(“BUTTON_2”) or Pin(91) or Pin(PC27) – top middle; Pin(“BUTTON_1”) or Pin(90) or Pin(PC26) – top right; Pin(“SWITCH_B”) or Pin(108) or Pin(PD12) – joystick left; Pin(“SWITCH_Y”) or Pin(105) or Pin(PD09) – joystick right; Pin(“SWITCH_U”) or Pin(116) or Pin(PD20) – joystick up; Pin(“SWITCH_X”) or Pin(104) or Pin(PD08) – joystick down; Pin(“SWITCH_Z”) or Pin(106) or Pin(PD10) – joystick button

    Example: Wio-Terminal-Buttons.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-Buttons.py - test the buttons
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    # using Signal because button sense is inverted: 1 = off, 0 = on
    from machine import Pin, Signal
    from time import sleep_ms
    
    pin_names = [
        "BUTTON_3",  # Pin(92)  or Pin(PC28) - top left
        "BUTTON_2",  # Pin(91)  or Pin(PC27) - top middle
        "BUTTON_1",  # Pin(90)  or Pin(PC26) - top right
        "SWITCH_B",  # Pin(108) or Pin(PD12) - joystick left
        "SWITCH_Y",  # Pin(105) or Pin(PD09) - joystick right
        "SWITCH_U",  # Pin(116) or Pin(PD20) - joystick up
        "SWITCH_X",  # Pin(104) or Pin(PD08) - joystick down
        "SWITCH_Z",  # Pin(106) or Pin(PD10) - joystick button
    ]
    
    pins = [None] * len(pin_names)
    for i, name in enumerate(pin_names):
        pins[i] = Signal(Pin(name, Pin.IN), invert=True)
    
    while True:
        for i in range(len(pin_names)):
            print(pins[i].value(), end="")
        print()
        sleep_ms(100)
    

    Buzzer

    A very quiet little PWM speaker.

    • MicroPython interfaces: machine.PWM
    • Control pin: Pin(“BUZZER”) or Pin(107) or Pin(“PD11”)

    Example: Wio-Terminal-Buzzer.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-Buzzer.py - play a scale on the buzzer with PWM
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    from time import sleep_ms
    from machine import Pin, PWM
    
    pwm = PWM(Pin("BUZZER", Pin.OUT))  # or Pin(107) or Pin("PD11")
    cmaj = [262, 294, 330, 349, 392, 440, 494, 523]  # C Major Scale frequencies
    
    for note in cmaj:
        print(note, "Hz")
        pwm.duty_u16(32767)  # 50% duty
        pwm.freq(note)
        sleep_ms(225)
        pwm.duty_u16(0)  # 0% duty - silent
        sleep_ms(25)
    

    Light Sensor

    This is a simple photo diode. It doesn’t seem to return any kind of calibrated value. Reads through the back of the case.

    • MicroPython interfaces: machine.ADC
    • Control pin: machine.ADC(“PD01”)

    Example code: Wio-Terminal-LightSensor.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-LightSensor.py - print values from the light sensor
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    from time import sleep_ms
    from machine import ADC
    
    # PD15-22C/TR8 photodiode
    light_sensor = ADC("PD01")
    
    while True:
        print([light_sensor.read_u16()])
        sleep_ms(50)
    

    Microphone

    Again, a simple analogue sensor:

    • MicroPython interfaces: machine.ADC
    • Control pin: machine.ADC(“MIC”)

    Example: Wio-Terminal-Microphone.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-Microphone.py - print values from the microphone
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    from time import sleep_ms
    from machine import ADC
    
    mic = ADC("MIC")
    
    while True:
        print([mic.read_u16()])
        sleep_ms(5)
    

    Grove I²C Port

    The Wio Terminal has two Grove ports: the one on the left (under the speaker port) is an I²C port. As I don’t know what you’ll be plugging in there, this example does a simple bus scan. You could make a, appalling typewriter if you really wanted.

    • MicroPython interfaces: machine.I2C (channel 3), machine. Pin
    • Control pins: scl=Pin(“SCL1”), sda=Pin(“SDA1”)

    Example: Wio-Terminal-Grove-I2C.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-Grove-I2C.py - show how to connect on Grove I2C
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    from machine import Pin, I2C
    
    # NB: This doesn't do much of anything except list what's
    # connected to the left (I²C) Grove connector on the Wio Terminal
    
    i2c = I2C(3, scl=Pin("SCL1"), sda=Pin("SDA1"))
    devices = i2c.scan()
    
    if len(devices) == 0:
        print("No I²C devices connected to Grove port.")
    else:
        print("Found these I²C devices on the Grove port:")
        for n, id in enumerate(devices):
            print(" device", n, ": ID", id, "(hex:", hex(id) + ")")
    

    LIS3DH Accelerometer

    This is also an I²C device, but connected to a different port (both logically and physically) than the Grove one.

    • MicroPython interfaces: machine.I2C (channel 4), machine. Pin
    • Control pins: scl=Pin(“SCL0”), sda=Pin(“SDA0”)
    • Library: from MicroPython-LIS3DH, copy lis3dh.py to the Wio Terminal’s small file system. Better yet, compile it to mpy using mpy-cross to save even more space before you copy it across

    Example: Wio-Terminal-Accelerometer.py (based on tinypico-micropython/lis3dh library/example.py)

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-Accelerometer.py - test out accelerometer
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    # based on
    #  https://github.com/tinypico/tinypico-micropython/tree/master/lis3dh%20library
    
    import lis3dh, time, math
    from machine import Pin, I2C
    
    i2c = I2C(4, scl=Pin("SCL0"), sda=Pin("SDA0"))
    imu = lis3dh.LIS3DH_I2C(i2c)
    
    last_convert_time = 0
    convert_interval = 100  # ms
    pitch = 0
    roll = 0
    
    # Convert acceleration to Pitch and Roll
    def convert_accell_rotation(vec):
        x_Buff = vec[0]  # x
        y_Buff = vec[1]  # y
        z_Buff = vec[2]  # z
    
        global last_convert_time, convert_interval, roll, pitch
    
        # We only want to re-process the values every 100 ms
        if last_convert_time < time.ticks_ms():
            last_convert_time = time.ticks_ms() + convert_interval
    
            roll = math.atan2(y_Buff, z_Buff) * 57.3
            pitch = (
                math.atan2((-x_Buff), math.sqrt(y_Buff * y_Buff + z_Buff * z_Buff)) * 57.3
            )
    
        # Return the current values in roll and pitch
        return (roll, pitch)
    
    
    # If we have found the LIS3DH
    if imu.device_check():
        # Set range of accelerometer (can be RANGE_2_G, RANGE_4_G, RANGE_8_G or RANGE_16_G).
        imu.range = lis3dh.RANGE_2_G
    
        # Loop forever printing values
        while True:
            # Read accelerometer values (in m / s ^ 2).  Returns a 3-tuple of x, y,
            # z axis values.  Divide them by 9.806 to convert to Gs.
            x, y, z = [value / lis3dh.STANDARD_GRAVITY for value in imu.acceleration]
            print("x = %0.3f G, y = %0.3f G, z = %0.3f G" % (x, y, z))
    
            # Convert acceleration to Pitch and Roll and print values
            p, r = convert_accell_rotation(imu.acceleration)
            print("pitch = %0.2f, roll = %0.2f" % (p, r))
    
            # Small delay to keep things responsive but give time for interrupt processing.
            time.sleep(0.1)
    

    SD Card

    • MicroPython interfaces: machine.SPI (channel 6), machine.Pin, machine.Signal
    • Control Pins: Pin(“SD_SCK”), Pin(“SD_MOSI”), Pin(“SD_MISO”) for SD access. Pin(“SD_DET”) is low if an SD card is inserted, otherwise high
    • Library: copy sdcard.py from micropython-lib to the Wio Terminal’s file system.

    Rather than provide a small canned example (there’s one here, if you must: Wio-Terminal-SDCard.py) here’s my boot.py startup file, showing how I safely mount an SD card if there’s one inserted, but keep booting even if it’s missing:

    # boot.py - MicroPython / Seeed Wio Terminal / SAMD51
    
    import sys
    
    sys.path.append("/lib")
    
    import machine
    import gc
    import os
    import sdcard
    
    machine.freq(160000000)  # fast but slightly jittery clock
    gc.enable()
    
    # mount SD card if there's one inserted
    try:
        sd_detected = machine.Signal(
            machine.Pin("SD_DET", machine.Pin.IN),
            invert=True,
        )
        sd_spi = machine.SPI(
            6,
            sck=machine.Pin("SD_SCK"),
            mosi=machine.Pin("SD_MOSI"),
            miso=machine.Pin("SD_MISO"),
            baudrate=40000000,
        )
        sd = sdcard.SDCard(sd_spi, machine.Pin("SD_CS"))
        if sd_detected.value() == True:
            os.mount(sd, "/SD")
            print("SD card mounted on /SD")
        else:
            raise Exception("SD card not inserted, can't mount /SD")
    except:
        print("SD card not found")
    
    
    

    ILI9341 Display

    I’m going to use the library rdagger/micropython-ili9341: MicroPython ILI9341Display & XPT2046 Touch Screen Driver because it’s reliable, and since it’s written entirely in MicroPython, it’s easy to install. It’s not particularly fast, though.

    The Wio Terminal may have an XPT2046 resistive touch controller installed, but I haven’t been able to test it. There are LCD_XL, LCD_YU, LCD_XR and LCD_YD lines on the schematic that might indicate it’s there, though.

    • MicroPython interfaces: machine.SPI (channel 7), machine.Pin.
    • Control Pins: Pin(“LCD_SCK”), Pin(“LCD_MOSI”), Pin(“LCD_MISO”). Pin(“LED_LCD”) is the backlight control
    • Library: copy ili9341.py from rdagger /micropython-ili9341 to the Wio Terminal’s file system.

    This demo draws rainbow-coloured diamond shapes that change continuously.

    Example: Wio-Terminal-Screen.py

    # MicroPython / Seeed Wio Terminal / SAMD51
    # Wio-Terminal-Screen.py - output something on the ILI9341 screen
    # scruss, 2022-10
    # -*- coding: utf-8 -*-
    
    
    from time import sleep
    from ili9341 import Display, color565
    from machine import Pin, SPI
    
    
    def wheel565(pos):
        # Input a value 0 to 255 to get a colour value.
        # The colours are a transition r - g - b - back to r.
        # modified to return RGB565 value for ili9341 - scruss
        (r, g, b) = (0, 0, 0)
        if (pos < 0) or (pos > 255):
            (r, g, b) = (0, 0, 0)
        if pos < 85:
            (r, g, b) = (int(pos * 3), int(255 - (pos * 3)), 0)
        elif pos < 170:
            pos -= 85
            (r, g, b) = (int(255 - pos * 3), 0, int(pos * 3))
        else:
            pos -= 170
            (r, g, b) = (0, int(pos * 3), int(255 - pos * 3))
        return (r & 0xF8) << 8 | (g & 0xFC) << 3 | b >> 3
    
    
    # screen can be a little slow to turn on, so use built-in
    # LED to signal all is well
    led = Pin("LED_BLUE", Pin.OUT)
    
    backlight = Pin("LED_LCD", Pin.OUT)  # backlight is not a PWM pin
    spi = SPI(
        7, sck=Pin("LCD_SCK"), mosi=Pin("LCD_MOSI"), miso=Pin("LCD_MISO"), baudrate=4000000
    )
    display = Display(spi, dc=Pin("LCD_D/C"), cs=Pin("LCD_CS"), rst=Pin("LCD_RESET"))
    display.display_on()
    display.clear()
    led.on()  # shotgun debugging, embedded style
    backlight.on()
    
    # use default portrait settings: x = 0..239, y = 0..319
    dx = 3
    dy = 4
    x = 3
    y = 4
    i = 0
    
    try:
        while True:
            # display.draw_pixel(x, y, wheel565(i))
            display.fill_hrect(x, y, 3, 4, wheel565(i))
            i = (i + 1) % 256
            x = x + dx
            y = y + dy
            if x <= 4:
                dx = -dx
            if x >= 234:
                dx = -dx
            if y <= 5:
                dy = -dy
            if y >= 313:
                dy = -dy
    except:
        backlight.off()
        led.off()
        display.display_off()
    
  • MicroPython MIDI mayhem (kinda)

    It pleased me to learn about umidiparser – MIDI file parser for Micropython. Could I use my previous adventures in beepy nonsense to turn a simple MIDI file into a terrible squeaky rendition of same? You betcha!

    MIDI seems to be absurdly complex. In all the files I looked at, there didn’t seem to be much of a standard in encoding whether the note duration was in the NOTE_ON event or the NOTE_OFF event. Eventually, I managed to fudge a tiny single channel file that had acceptable note durations in the NOTE_OFF events. Here is the file:

    I used the same setup as before:

    Raspberry Pi Pico with small piezo speaker connected to pins 23 and 26
    piezo between pins 26 and 23

    With this code:

    # extremely crude MicroPython MIDI demo
    # MicroPython / Raspberry Pi Pico - scruss, 2022-08
    # see https://github.com/bixb922/umidiparser
    
    import umidiparser
    from time import sleep_us
    from machine import Pin, PWM
    
    # 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('LED', Pin.OUT)
    
    
    def play_tone(freq, usec):
        # play RTTL/midi notes, also flash onboard LED
        # original idea thanks to
        #   https://github.com/dhylands/upy-rtttl
        print('freq = {:6.1f} usec = {:6.1f}'.format(freq, usec))
        if freq > 0:
            pwm.freq(int(freq))       # Set frequency
            pwm.duty_u16(32767)       # 50% duty cycle
        led.on()
        sleep_us(int(0.9 * usec))     # Play for a number of usec
        pwm.duty_u16(0)               # Stop playing for gap between notes
        led.off()
        sleep_us(int(0.1 * usec))     # Pause for a number of usec
    
    
    # map MIDI notes (0-127) to frequencies. Note 69 is 440 Hz ('A4')
    freqs = [440 * 2**((float(x) - 69) / 12) for x in range(128)]
    
    for event in umidiparser.MidiFile("lg2.mid", reuse_event_object=True):
        if event.status == umidiparser.NOTE_OFF and event.channel == 0:
            play_tone(freqs[event.note], event.delta_us)
    
    

    This isn’t be any means a general MIDI parser, but is rather specialized to play monophonic tunes on channel 0. The result is gloriously awful:

    apologies to LG
  • INA219 Current Sensor and MicroPython

    More Micropython programmers — and especially beginners — should know about Awesome MicroPython. It’s a community-curated list of remarkably decent MicroPython libraries, frameworks, software and resources. If you need to interface to a sensor, look there first.

    For example, take the INA219 High Side DC Current Sensor. It’s an I²C sensor able to measure up to 26 V, ±3.2 A. It does this by measuring the voltage across a 0.1 ohm precision shunt resistor with its built-in 12-bit ADC. I got a customer return from the store that was cosmetically damaged but still usable, so I thought I’d try it with the simplest module I could find in Awesome MicroPython and see how well it worked.

    I guess I needed a test circuit too. Using all of what was immediately handy — a resistor I found on the bench and measured at 150.2 ohm — I came up with this barely useful circuit:

    simple circle with 3.3 V DC supply ad two resistors of 150.2 ohms and 0.1 ohms in series
    Should indicate a current of 3.3 / (150.2 + 0.1) = 21.96 mA

    The INA219 would be happier with a much higher current to measure, but I didn’t have anything handy that could do that.

    Looking in Awesome MicroPython’s Current section, I found robert-hh/INA219: INA219 Micropython driver. It doesn’t have much (okay, any) documentation, but it’s a very small module and the code is easy enough to follow. I put the ina219.py module file into the /lib folder of a WeAct Studio RP2040 board, and wrote the following code:

    # INA219 demo - uses https://github.com/robert-hh/INA219
    
    from machine import Pin, I2C
    import ina219
    
    i = I2C(0, scl=Pin(5), sda=Pin(4))
    print("I2C Bus Scan: ", i.scan(), "\n")
    
    sensor = ina219.INA219(i)
    sensor.set_calibration_16V_400mA()
    
    # my test circuit is 3V3 supply through 150.2 ohm resistor
    r_1 = 150.2
    r_s = 0.1  # shunt resistor on INA219 board
    
    # current is returned in milliamps
    print("Current       / mA: %8.3f" % (sensor.current))
    # shunt_voltage is returned in volts
    print("Shunt voltage / mV: %8.3f" % (sensor.shunt_voltage * 1000))
    # estimate supply voltage from known resistance * sensed current
    print("3V3 (sensed)  / mV: %8.3f" % ((r_1 + r_s) * sensor.current))
    
    

    with everything wired up like this (Blue = SDA, Yellow = SCL):

    breadboard with RP2040 pico board and INA219 sensor board benath it, and the 150 ohm wired as a circuit on the side
    all of the wires

    Running it produced this:

    I2C Bus Scan:  [64] 
    
    Current       / mA:   22.100
    Shunt voltage / mV:    2.210
    3V3 (sensed)  / mV: 3321.630
    

    So it’s showing just over 22 mA: pretty close to what I calculated!

  • It works! It works!

    on a messy desk, a small USB midi keyboard is connected to a Korg NTS-1 mini synthesizer via a small micro-controller board that acts as a USB host for the Akai keyboard, converting USB MIDI to traditional MIDI for the Korg
    Akai LPK25 keyboard has USB MIDI out, but the Korg NTS-1 only has regular MIDI in. The little board in the middle acts as a USB host for the Akai and MIDI source for the Korg

    This is great: gdsports/midiuartusbh: MIDI DIN to MIDI USB Host Converter allows your USB MIDI instruments to act as traditional MIDI controllers. It uses a Adafruit Trinket M0 to act as the USB host and MIDI output.

    I modified gdsports’ design very slightly:

    1. Instead of using a 74AHCT125 Logic level converter and driver, I used a FET-based SparkFun Logic Level Converter
    2. Instead of a 5-pin DIN socket, I used a 3.5 mm stereo socket.

    And it works!

    breadboard showing Trinket M0 microcontroller board, logic level shifter, audio socket breakout and two resistors
    Breadboard layout for MIDI-standard 3.5 mm output (Korg). The resistors are both 220 ohm, and the boards need 5 V power
  • Mystery lockdown phone message

    “Just a test call. Time to stay home. Stay safe and stay home.”

    This message from an unknown caller has sat on our landline answering machine since 2020 or 2021. No idea who or what sent it. All I know is it came in just before noon on a Tuesday morning. The entirely synthesized voice makes me think it’s a junk call, but there’s no scam attached. Just this message, slightly eerie, quite inexplicable.

  • The Quite Rubbish Clock, mk.2

    this is bad and I should feel bad

    In early 2013, I must’ve been left unsupervised for too long since I made The Quite Rubbish Clock:

    It still isn’t human readable …

    Written in (Owen Wilson voice) kind of an obsolete vernacular and running on hardware that’s now best described as “quaint”, it was still absurdly popular at the time. Raspberry Pis were still pretty new, and people were looking for different things to do with them.

    I happened across the JASchilz/uQR: QR Code Generator for MicroPython the other day, and remembered I had some tiny OLED screens that were about the same resolution as the old Nokia I’d used in 2013. I wondered: could I …?

    small microcontroller board with USB C cable attached and an OLED screen on top. The OLED is displaying a QR code which reads '172731'
    OLED Shield on a LOLIN S2 Mini: very smol indeed

    The board is a LOLIN S2 Mini with a OLED 0.66 Inch Shield on top, all running MicroPython. One limitation I found in the MicroPython QR library was that it was very picky about input formats, so it only displays the time as HHMMSS with no separators.

    Source, of course:

    # -*- coding: utf-8 -*-
    # yes, the Quite Rubbish Clock rides again ...
    # scruss, 2022-06-30
    # MicroPython on Lolin S2 Mini with 64 x 48 OLED display
    # uses uQR from https://github.com/JASchilz/uQR
    # - which has problems detecting times with colons
    
    from machine import Pin, I2C, RTC
    import s2mini  # on Lolin ESP32-S2 Mini
    import ssd1306
    from uQR import QRCode
    
    WIDTH = 64  # screen size
    HEIGHT = 48
    SIZE = 8  # text size
    r = RTC()
    
    # set up and clear screen
    i2c = I2C(0, scl=Pin(s2mini.I2C_SCL), sda=Pin(s2mini.I2C_SDA))
    oled = ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c)
    oled.fill(0)
    
    
    def snazz():
        marquee = [
            "   **",
            "   **",
            "   **",
            "   **",
            "   **",
            "********",
            " ******",
            "  ****",
            "   **",
            " quite",
            "rubbish",
            " clock",
            "  mk.2",
            "<scruss>",
            " >2022<"
        ]
        for s in marquee:
            oled.scroll(0, -SIZE)  # scroll up one text line
            oled.fill_rect(0, HEIGHT-SIZE, WIDTH,
                           SIZE, 0)  # blank last line
            oled.text("%-8s" % s, 0, HEIGHT-SIZE)  # write text
            oled.show()
            time.sleep(0.25)
        time.sleep(5)
        oled.fill(1)
        oled.show()
    
    
    snazz()  # tedious crowd-pleasing intro
    
    qr = QRCode()
    while True:
        qr.add_data("%02d%02d%02d" % r.datetime()[4:7])
        qr.border = 1  # default border too big to fit small screen
        m = qr.get_matrix()
        oled.fill(1)
        for y in range(len(m)):
            for x in range(len(m[0])):
                # plot a double-sized QR code, centred, inverted
                oled.fill_rect(9 + 2*x, 1 + 2*y, 2, 2, not m[y][x])
        oled.show()
        time.sleep(0.05)
        qr.clear()
    
    

    If your output is glitchy, you might need to put the following in boot.py:

    import machine
    machine.freq(240000000)
    

    This increases the ESP32-S2’s frequency from 160 to 240 MHz.

    Update: there’s a fork of uQR that provides better character support, particularly those required for sending Wi-Fi Network config.

  • Hydraulic Tiles

    (decorative pattern)
    recreated from the Threlkeld Granite Co Ltd’s Album: Ornamental Granitic Tiles (1898), sheet 8

    The Internet Archive has Threlkeld Granite Co Ltd’s Album of ornamental granitic tiles online, and I’m really digging the patterns of the hydraulic tiles they made. I’ve recreated some of their patterns in InkScape, and made this small demo by tiling bitmapped renderings.